Merging for deployment update with service #2
No files matched your search
@@ -0,0 +1,239 @@
|
|||||||
|
# ZenAI KPC Counter — Edge Deployment Guide
|
||||||
|
|
||||||
|
Production deployment for **RK3588** (or compatible RKNN NPU) edge devices running:
|
||||||
|
|
||||||
|
| Component | Script | systemd unit |
|
||||||
|
|-----------|--------|--------------|
|
||||||
|
| RTSP counter (RKNN + ByteTrack) | `counter_live_rknn.py` | `zenai-kpc-counter.service` |
|
||||||
|
| Web dashboard (Flask) | `counter_dashboard.py` | `zenai-kpc-dashboard.service` |
|
||||||
|
|
||||||
|
Both processes share a single `.env` file and read/write the same SQLite database and state JSON.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Prerequisites
|
||||||
|
|
||||||
|
### Hardware & OS
|
||||||
|
|
||||||
|
- RK3588 board (or Jetson/RK device with RKNN Lite runtime)
|
||||||
|
- Linux with systemd
|
||||||
|
- Network access to the RTSP camera stream
|
||||||
|
|
||||||
|
### System packages
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo apt update
|
||||||
|
sudo apt install -y python3 python3-venv python3-pip ffmpeg libgl1
|
||||||
|
```
|
||||||
|
|
||||||
|
`ffmpeg` is required for low-latency RTSP capture via OpenCV. `libgl1` is often needed for `opencv-python` on headless systems.
|
||||||
|
|
||||||
|
### RKNN model
|
||||||
|
|
||||||
|
Export or copy your `.rknn` model to the device, e.g.:
|
||||||
|
|
||||||
|
```text
|
||||||
|
/opt/models/your_model.rknn
|
||||||
|
```
|
||||||
|
|
||||||
|
Set `MODEL_PATH` in `.env` to match. The model class count must match `NUM_CLASSES`, and `OBJECT_CLASS_ID` must point at the class you count.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Directory layout
|
||||||
|
|
||||||
|
Default paths used by the service files and `env.example`:
|
||||||
|
|
||||||
|
```text
|
||||||
|
/opt/zenai-kpc-python/ # application code (this repo)
|
||||||
|
├── counter_live_rknn.py
|
||||||
|
├── counter_dashboard.py
|
||||||
|
├── counter_store.py
|
||||||
|
├── templates/
|
||||||
|
├── venv/ # Python virtual environment (created during install)
|
||||||
|
├── .env # runtime config (not in git)
|
||||||
|
├── env.example # template — copy to .env
|
||||||
|
└── DEPLOY.md
|
||||||
|
|
||||||
|
/opt/zenai-kpc-counter/ # persistent runtime data (created automatically)
|
||||||
|
├── counter.db # SQLite daily records
|
||||||
|
├── current_counter.json # live counting-day state
|
||||||
|
├── snapshots/ # crossing/detect JPEGs (if enabled)
|
||||||
|
└── crossings.csv # optional per-event CSV
|
||||||
|
|
||||||
|
/opt/models/ # RKNN models (deploy separately)
|
||||||
|
/dev/shm/zenai-kpc-counter/ # live JPEG frame for dashboard video (tmpfs)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Install application
|
||||||
|
|
||||||
|
### 3.1 Copy code to the device
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo mkdir -p /opt/zenai-kpc-python
|
||||||
|
sudo rsync -av --exclude venv --exclude .env --exclude __pycache__ \
|
||||||
|
./ /opt/zenai-kpc-python/
|
||||||
|
# Or: sudo git clone <repo-url> /opt/zenai-kpc-python
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.2 Create virtual environment and install dependencies
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd /opt/zenai-kpc-python
|
||||||
|
sudo python3 -m venv venv
|
||||||
|
sudo ./venv/bin/pip install --upgrade pip
|
||||||
|
sudo ./venv/bin/pip install -r requirements.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
> `rknn-toolkit-lite2` is platform-specific. Install on the target ARM device, not on a Windows dev machine.
|
||||||
|
|
||||||
|
### 3.3 Create runtime config
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd /opt/zenai-kpc-python
|
||||||
|
sudo cp env.example .env
|
||||||
|
sudo nano .env
|
||||||
|
```
|
||||||
|
|
||||||
|
**Minimum values to edit before starting:**
|
||||||
|
|
||||||
|
| Variable | Description |
|
||||||
|
|----------|-------------|
|
||||||
|
| `SOURCE` | RTSP URL or local video file path |
|
||||||
|
| `MODEL_PATH` | Path to your `.rknn` model on device |
|
||||||
|
| `NUM_CLASSES` | Must match the exported model |
|
||||||
|
| `OBJECT_CLASS_ID` | Class index of the object being counted |
|
||||||
|
| `CLASS_OBJECT` / `OBJECT_LABEL` | Labels stored in DB (e.g. `karung`) |
|
||||||
|
| `LINE_Y1_FRAC` / `LINE_Y2_FRAC` | Counting line positions (tune per camera) |
|
||||||
|
| `SECRET_KEY` | Random string for Flask sessions |
|
||||||
|
|
||||||
|
Ensure `STATE_FILE` and `DB_PATH` both live under `/opt/zenai-kpc-counter/` so data survives reboots (avoid `/tmp` in production).
|
||||||
|
|
||||||
|
### 3.4 Create data directories (optional — app creates most paths automatically)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo mkdir -p /opt/zenai-kpc-counter /opt/models /dev/shm/zenai-kpc-counter
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Install systemd services
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd /opt/zenai-kpc-python
|
||||||
|
sudo cp zenai-kpc-counter.service zenai-kpc-dashboard.service /etc/systemd/system/
|
||||||
|
sudo systemctl daemon-reload
|
||||||
|
sudo systemctl enable zenai-kpc-counter zenai-kpc-dashboard
|
||||||
|
sudo systemctl start zenai-kpc-counter
|
||||||
|
sudo systemctl start zenai-kpc-dashboard
|
||||||
|
```
|
||||||
|
|
||||||
|
The dashboard unit starts **after** the counter unit (`After=zenai-kpc-counter.service`).
|
||||||
|
|
||||||
|
### Verify
|
||||||
|
|
||||||
|
```bash
|
||||||
|
systemctl status zenai-kpc-counter
|
||||||
|
systemctl status zenai-kpc-dashboard
|
||||||
|
journalctl -u zenai-kpc-counter -f
|
||||||
|
```
|
||||||
|
|
||||||
|
Open the dashboard in a browser:
|
||||||
|
|
||||||
|
```text
|
||||||
|
http://<device-ip>:5000
|
||||||
|
```
|
||||||
|
|
||||||
|
(Port is set by `DASHBOARD_PORT` in `.env`, default `5000`.)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Operations
|
||||||
|
|
||||||
|
### Restart after config change
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo systemctl restart zenai-kpc-counter
|
||||||
|
sudo systemctl restart zenai-kpc-dashboard
|
||||||
|
```
|
||||||
|
|
||||||
|
### View logs
|
||||||
|
|
||||||
|
```bash
|
||||||
|
journalctl -u zenai-kpc-counter -n 100 --no-pager
|
||||||
|
journalctl -u zenai-kpc-dashboard -n 100 --no-pager
|
||||||
|
```
|
||||||
|
|
||||||
|
### Stop services
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo systemctl stop zenai-kpc-dashboard zenai-kpc-counter
|
||||||
|
```
|
||||||
|
|
||||||
|
The counter handles `SIGTERM` gracefully — it finishes the current frame, persists state to SQLite, then exits.
|
||||||
|
|
||||||
|
### Update application code
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd /opt/zenai-kpc-python
|
||||||
|
sudo systemctl stop zenai-kpc-dashboard zenai-kpc-counter
|
||||||
|
# rsync or git pull new code
|
||||||
|
sudo ./venv/bin/pip install -r requirements.txt # if dependencies changed
|
||||||
|
sudo systemctl start zenai-kpc-counter zenai-kpc-dashboard
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Troubleshooting
|
||||||
|
|
||||||
|
| Symptom | Things to check |
|
||||||
|
|---------|-----------------|
|
||||||
|
| Counter won't start | `journalctl -u zenai-kpc-counter`; verify `MODEL_PATH` exists; RKNN drivers installed |
|
||||||
|
| No RTSP frames | Ping camera; test with `ffplay <SOURCE>`; check `OPENCV_FFMPEG_CAPTURE_OPTIONS` |
|
||||||
|
| Dashboard shows 0 count | `STATE_FILE` in `.env` must match between counter and dashboard; check file exists |
|
||||||
|
| Live video blank | `LIVE_STREAM_ENABLED=true`; path matches `LIVE_STREAM_FRAME_PATH` in both processes |
|
||||||
|
| Wrong counts | Tune `LINE_Y1_FRAC`/`LINE_Y2_FRAC`, `CONF`, ByteTrack thresholds; enable `DEBUG_TRACKING=true` temporarily |
|
||||||
|
| Service keeps restarting | `journalctl -u zenai-kpc-counter -e`; often missing model, bad RTSP URL, or venv not created |
|
||||||
|
|
||||||
|
### Manual test (without systemd)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd /opt/zenai-kpc-python
|
||||||
|
source venv/bin/activate
|
||||||
|
python counter_live_rknn.py # terminal 1
|
||||||
|
python counter_dashboard.py # terminal 2
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. Optional: reverse proxy
|
||||||
|
|
||||||
|
For HTTPS or port 80 access, put nginx in front of the dashboard:
|
||||||
|
|
||||||
|
```nginx
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
server_name counter.example.com;
|
||||||
|
|
||||||
|
location / {
|
||||||
|
proxy_pass http://127.0.0.1:5000;
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_buffering off; # needed for /api/live-video MJPEG stream
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The live JPEG at `LIVE_STREAM_FRAME_PATH` can also be served statically by nginx if you prefer not to use the Flask MJPEG endpoint.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. Security notes
|
||||||
|
|
||||||
|
- Change `SECRET_KEY` from the default before exposing the dashboard on a network.
|
||||||
|
- Services currently run as `root` for simplicity on edge devices. For hardened deployments, create a dedicated user, chown `/opt/zenai-kpc-counter`, and update the `User=` / `Group=` lines in the service files.
|
||||||
|
- Do not commit `.env` — it may contain RTSP credentials.
|
||||||
|
- Set `FLASK_DEBUG=false` in production.
|
||||||
+29
-119
@@ -1,195 +1,105 @@
|
|||||||
# =============================================================================
|
# =============================================================================
|
||||||
# Edge RK3588 production counter + dashboard
|
# DEPRECATED — use env.example instead.
|
||||||
# Shared config for: counter_live_rknn_bytetrack.py + counter_dashboard.py
|
#
|
||||||
# Copy to .env on device: cp config.env.example .env && nano .env
|
# cp env.example .env && nano .env
|
||||||
|
#
|
||||||
|
# This file is kept for backward compatibility only. All new deployments should
|
||||||
|
# use env.example as the single canonical template. See DEPLOY.md.
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
|
|
||||||
# --- Core paths ---
|
# --- Core paths ---
|
||||||
# Root output directory (logs, DB, video, CSV)
|
OUTPUT_DIR=/opt/zenai-kpc-counter
|
||||||
OUTPUT_DIR=/opt/bytetrack-counter
|
DB_PATH=/opt/zenai-kpc-counter/counter.db
|
||||||
# SQLite database path for daily counter records & crossing logs
|
STATE_FILE=/opt/zenai-kpc-counter/current_counter.json
|
||||||
DB_PATH=/opt/bytetrack-counter/bytetrack_counter.db
|
|
||||||
# JSON file persisting the current active counting day state
|
|
||||||
STATE_FILE=/tmp/bytetrack_current_counter.json
|
|
||||||
|
|
||||||
# --- Input source ---
|
# --- Input source ---
|
||||||
# RTSP / HTTP live stream, or a local video file path
|
|
||||||
SOURCE=rtsp://user:pass@192.168.0.100:554/stream1
|
SOURCE=rtsp://user:pass@192.168.0.100:554/stream1
|
||||||
# FFmpeg capture options passed to cv2.VideoCapture (RTSP low-latency flags)
|
|
||||||
OPENCV_FFMPEG_CAPTURE_OPTIONS=rtsp_transport;tcp|fflags;nobuffer|flags;low_delay
|
OPENCV_FFMPEG_CAPTURE_OPTIONS=rtsp_transport;tcp|fflags;nobuffer|flags;low_delay
|
||||||
|
|
||||||
# --- RKNN model ---
|
# --- RKNN model ---
|
||||||
# Path to exported .rknn model (YOLO format, e.g. yolo11n.rknn)
|
MODEL_PATH=/opt/models/your_model.rknn
|
||||||
MODEL_PATH=/opt/models/yolo9t.rknn
|
|
||||||
# Input image size for the model (square, e.g. 320 → 320×320)
|
|
||||||
IMGSZ=320
|
IMGSZ=320
|
||||||
# Use FP16 inference on NPU (true/false); currently unused in ByteTrack variant
|
|
||||||
HALF=false
|
HALF=false
|
||||||
# NPU core mask: 1=core0, 2=core1, 3=core0+core1, 7=all three
|
CORE_MASK=1
|
||||||
CORE_MASK=7
|
|
||||||
# Compute device index (reserved; not used at runtime)
|
|
||||||
DEVICE=0
|
DEVICE=0
|
||||||
|
|
||||||
# --- YOLO decoder ---
|
# --- YOLO decoder ---
|
||||||
# Number of object classes the model outputs
|
NUM_CLASSES=4
|
||||||
NUM_CLASSES=2
|
|
||||||
# Apply sigmoid to raw class scores (true/false); set true if model head uses BCE logits
|
|
||||||
SCORE_SIGMOID=false
|
SCORE_SIGMOID=false
|
||||||
|
|
||||||
# --- Detection ---
|
# --- Detection ---
|
||||||
# Confidence threshold – detections below this are discarded before NMS
|
CONF=0.5
|
||||||
CONF=0.3
|
|
||||||
|
|
||||||
# --- ByteTrack tracking ---
|
# --- ByteTrack tracking ---
|
||||||
# Detections with score >= this get priority matching in the first association stage
|
|
||||||
TRACK_HIGH_THRESH=0.5
|
TRACK_HIGH_THRESH=0.5
|
||||||
# Detections with score between this and TRACK_HIGH_THRESH are matched in the second stage
|
TRACK_LOW_THRESH=0.3
|
||||||
TRACK_LOW_THRESH=0.1
|
TRACK_MATCH_THRESH=0.7
|
||||||
# IoU threshold for the first-stage association (0–1). Higher = stricter overlap required
|
TRACK_BUFFER=60
|
||||||
TRACK_MATCH_THRESH=0.8
|
|
||||||
# Frames a track survives without a match before being permanently removed
|
|
||||||
TRACK_BUFFER=30
|
|
||||||
# Minimum consecutive (or total) hits needed before a track is considered confirmed
|
|
||||||
TRACK_MIN_HITS=3
|
TRACK_MIN_HITS=3
|
||||||
|
|
||||||
# --- ID-switch counting guards ---
|
# --- ID-switch counting guards ---
|
||||||
# When a track's ID changes right at the counting line, one physical object can be
|
|
||||||
# counted twice (two IDs cross) or missed (neither ID sees the full transition).
|
|
||||||
# These two guards correct for that.
|
|
||||||
#
|
|
||||||
# Dedup guard (prevents double counting): after a crossing, a second crossing in
|
|
||||||
# the SAME direction within DEDUP_FRAMES frames and DEDUP_PX horizontal pixels is
|
|
||||||
# ignored (treated as the same object under a new ID).
|
|
||||||
DEDUP_FRAMES=15
|
DEDUP_FRAMES=15
|
||||||
DEDUP_PX=60
|
DEDUP_PX=60
|
||||||
# To DISABLE the dedup guard, set DEDUP_PX=-1 (distance check can never match).
|
|
||||||
#
|
|
||||||
# Inheritance guard (prevents missed counting): when a brand-new track appears, it
|
|
||||||
# inherits the last position of a recently-seen nearby track (within INHERIT_SEC
|
|
||||||
# seconds and INHERIT_PX horizontal pixels) so the crossing is still detected
|
|
||||||
# across the ID switch.
|
|
||||||
INHERIT_SEC=1.0
|
INHERIT_SEC=1.0
|
||||||
INHERIT_PX=60
|
INHERIT_PX=60
|
||||||
# To DISABLE the inheritance guard, set INHERIT_PX=-1 (distance check can never match).
|
|
||||||
|
|
||||||
# --- Display ---
|
# --- Display ---
|
||||||
# Site name shown on the dashboard header (top-right)
|
|
||||||
SITE_NAME=ZenAi
|
SITE_NAME=ZenAi
|
||||||
|
|
||||||
# --- Object class names ---
|
# --- Object class names ---
|
||||||
# Camera / location identifier shown in HUD and stored in DB
|
|
||||||
CAMERA_NAME=ZenAi
|
CAMERA_NAME=ZenAi
|
||||||
# Label used for batch grouping in the database
|
OBJECT_LABEL=karung
|
||||||
OBJECT_LABEL=object
|
CLASS_OBJECT=karung
|
||||||
# Class name for the counted object (must match model class order)
|
|
||||||
CLASS_OBJECT=object
|
|
||||||
# Model class ID for the object being counted (default 0)
|
|
||||||
OBJECT_CLASS_ID=0
|
OBJECT_CLASS_ID=0
|
||||||
|
|
||||||
# --- Line crossing ---
|
# --- Line crossing ---
|
||||||
# Two horizontal counting lines:
|
|
||||||
# Line 1 (default ~33%): counts top-to-down (IN)
|
|
||||||
# Line 2 (default ~66%): counts bottom-to-up (OUT)
|
|
||||||
# Fixed y-coordinate for line 1/IN (overrides LINE_Y1_FRAC if set)
|
|
||||||
LINE_Y1=
|
LINE_Y1=
|
||||||
# Fraction of frame height for line 1 (default 0.33)
|
LINE_Y1_FRAC=0.70
|
||||||
LINE_Y1_FRAC=0.33
|
|
||||||
# Fixed y-coordinate for line 2 (overrides LINE_Y2_FRAC if set)
|
|
||||||
LINE_Y2=
|
LINE_Y2=
|
||||||
# Fraction of frame height for line 2 (default 0.66)
|
LINE_Y2_FRAC=0.30
|
||||||
LINE_Y2_FRAC=0.66
|
|
||||||
|
|
||||||
# --- Counting day management ---
|
# --- Counting day management ---
|
||||||
# Daily cutoff time (HH:MM) – a new counting day starts after this time and the
|
|
||||||
# previous day's counter_in / counter_out totals are finalized in the database.
|
|
||||||
# CUTOFF_TIME is an alias used by the dashboard; DAILY_CUTOFF_TIME takes priority in counter_live_rknn.py.
|
|
||||||
DAILY_CUTOFF_TIME=20:00
|
DAILY_CUTOFF_TIME=20:00
|
||||||
CUTOFF_TIME=20:00
|
CUTOFF_TIME=20:00
|
||||||
|
|
||||||
# --- CSV export ---
|
# --- CSV export ---
|
||||||
# Write per-crossing events to a CSV file (true/false)
|
EXPORT_CSV=false
|
||||||
EXPORT_CSV=true
|
CROSS_CSV=/opt/zenai-kpc-counter/crossings.csv
|
||||||
# Path where the crossing CSV is written
|
|
||||||
CROSS_CSV=/opt/batch-counter/crossings.csv
|
|
||||||
|
|
||||||
# --- Crossing snapshots ---
|
# --- Crossing snapshots ---
|
||||||
# Save an annotated frame image every time an object crosses a line and the
|
SAVE_CROSS_SNAPSHOT=true
|
||||||
# counter increases (true/false, default: false). Written to <DIR>/cross/
|
|
||||||
# (filename: <YYYYmmdd_HHMMSS_mmm>_<in|out>_id<track>_f<frame>.jpg)
|
|
||||||
SAVE_CROSS_SNAPSHOT=false
|
|
||||||
# Also save one snapshot the first time each object is detected, before it crosses
|
|
||||||
# (true/false, default: false). Written to <DIR>/detect/ with the same track id so
|
|
||||||
# it can be correlated with the crossing snapshot
|
|
||||||
# (filename: <YYYYmmdd_HHMMSS_mmm>_detect_id<track>_f<frame>.jpg)
|
|
||||||
SAVE_DETECT_SNAPSHOT=false
|
SAVE_DETECT_SNAPSHOT=false
|
||||||
# Base directory for snapshots (detect/ and cross/ subfolders are created inside).
|
CROSS_SNAPSHOT_DIR=/opt/zenai-kpc-counter/snapshots
|
||||||
# The dashboard reads this same path to display the snapshot gallery, so keep it
|
|
||||||
# identical for both the counter and the dashboard.
|
|
||||||
CROSS_SNAPSHOT_DIR=/opt/batch-counter/snapshots
|
|
||||||
# JPEG quality for snapshots (1-100)
|
|
||||||
CROSS_SNAPSHOT_QUALITY=85
|
CROSS_SNAPSHOT_QUALITY=85
|
||||||
# Retention: keep at most this many snapshot files (detect + cross combined);
|
|
||||||
# oldest are deleted first (0 = unlimited)
|
|
||||||
CROSS_SNAPSHOT_MAX_FILES=1000
|
CROSS_SNAPSHOT_MAX_FILES=1000
|
||||||
# Retention: delete snapshots older than this many days (0 = never by age)
|
CROSS_SNAPSHOT_MAX_AGE_DAYS=3
|
||||||
CROSS_SNAPSHOT_MAX_AGE_DAYS=7
|
CROSS_SNAPSHOT_CLEANUP_SEC=3600
|
||||||
# Run the cleanup sweep at most once every N seconds
|
|
||||||
CROSS_SNAPSHOT_CLEANUP_SEC=60
|
|
||||||
|
|
||||||
# --- Rate / performance ---
|
# --- Rate / performance ---
|
||||||
# Enable motion detection pre-filter: skip inference on frames with no movement
|
MOTION_DETECTION_ENABLED=true
|
||||||
# (true/false, default: false), saving NPU/CPU load. Motion is measured by the
|
|
||||||
# fraction of pixels that changed (localized-motion aware), NOT the whole-frame
|
|
||||||
# average, so an object entering the edge of the frame is detected immediately.
|
|
||||||
MOTION_DETECTION_ENABLED=false
|
|
||||||
# Per-pixel intensity change (0-255) for a pixel to count as "moved". Lower = more
|
|
||||||
# sensitive to subtle movement. Default 25.
|
|
||||||
MOTION_PIXEL_DELTA=25
|
MOTION_PIXEL_DELTA=25
|
||||||
# Fraction of frame pixels (0-1) that must change to trigger inference. Lower =
|
|
||||||
# more sensitive / detects smaller or farther objects sooner. Default 0.002 (0.2%).
|
|
||||||
MOTION_MIN_AREA_FRAC=0.002
|
MOTION_MIN_AREA_FRAC=0.002
|
||||||
# Heartbeat: always run inference at least every N frames even with no detected
|
|
||||||
# motion, so a slow or barely-moving object is never missed for long. Default 15.
|
|
||||||
MOTION_HEARTBEAT_FRAMES=15
|
MOTION_HEARTBEAT_FRAMES=15
|
||||||
# (Deprecated) old whole-frame mean-difference threshold; no longer used.
|
|
||||||
MOTION_THRESHOLD=5.0
|
MOTION_THRESHOLD=5.0
|
||||||
# Sliding window in seconds for computing the crossing rate (objects/minute)
|
|
||||||
RATE_WINDOW_SEC=60
|
RATE_WINDOW_SEC=60
|
||||||
# Number of frames to discard at startup to let the stream buffer stabilise
|
|
||||||
WARMUP_FRAMES=30
|
WARMUP_FRAMES=30
|
||||||
# Delay in seconds between stream reconnection attempts
|
|
||||||
RECONNECT_DELAY_SEC=3
|
RECONNECT_DELAY_SEC=3
|
||||||
# Maximum reconnection attempts (0 = infinite)
|
|
||||||
MAX_RECONNECT_ATTEMPTS=0
|
MAX_RECONNECT_ATTEMPTS=0
|
||||||
# Seconds after which a tracked but unseen object is pruned from the active set
|
|
||||||
TRACKED_PRUNE_SEC=300
|
TRACKED_PRUNE_SEC=300
|
||||||
|
|
||||||
# --- Video recording ---
|
# --- Video recording ---
|
||||||
# Save annotated frames to segmented MP4 files (true/false)
|
|
||||||
RECORD_VIDEO=false
|
RECORD_VIDEO=false
|
||||||
# Duration in seconds of each video segment file
|
|
||||||
VIDEO_SEGMENT_SEC=3600
|
VIDEO_SEGMENT_SEC=3600
|
||||||
# Output video FPS (fallback if source FPS is unknown or ≤ 1)
|
|
||||||
OUTPUT_FPS=15
|
OUTPUT_FPS=15
|
||||||
|
|
||||||
# --- Live stream snapshot ---
|
# --- Live stream snapshot ---
|
||||||
# Periodically write the latest annotated frame as JPEG for an external web server
|
LIVE_STREAM_ENABLED=true
|
||||||
LIVE_STREAM_ENABLED=false
|
LIVE_STREAM_FRAME_PATH=/dev/shm/zenai-kpc-counter/live_frame.jpg
|
||||||
# Path to the shared-memory snapshot file (served by nginx / lighttpd)
|
|
||||||
LIVE_STREAM_FRAME_PATH=/dev/shm/byetrack-counter/live_frame.jpg
|
|
||||||
# JPEG quality (1–100)
|
|
||||||
LIVE_STREAM_QUALITY=75
|
LIVE_STREAM_QUALITY=75
|
||||||
# Write the snapshot every N frames (lower = more frequent updates)
|
|
||||||
LIVE_STREAM_EVERY_N=2
|
LIVE_STREAM_EVERY_N=2
|
||||||
|
|
||||||
# --- Dashboard (counter_dashboard.py) ---
|
# --- Dashboard (counter_dashboard.py) ---
|
||||||
# Flask secret key for session/cookie signing — change in production!
|
|
||||||
SECRET_KEY=change-me-in-production
|
SECRET_KEY=change-me-in-production
|
||||||
# Bind address for the Flask web server
|
|
||||||
DASHBOARD_HOST=0.0.0.0
|
DASHBOARD_HOST=0.0.0.0
|
||||||
# Listen port for the dashboard web UI
|
|
||||||
DASHBOARD_PORT=5000
|
DASHBOARD_PORT=5000
|
||||||
# Enable Flask debug mode (true/false) — auto-reloads on code changes; disable in production
|
|
||||||
FLASK_DEBUG=false
|
FLASK_DEBUG=false
|
||||||
# Fallback name for the active counting-day JSON state file used by the dashboard
|
|
||||||
CURRENT_COUNTER_PATH=/tmp/bytetrack_current_counter.json
|
|
||||||
+49
-58
@@ -1,27 +1,32 @@
|
|||||||
# =============================================================================
|
# =============================================================================
|
||||||
# Edge RK3588 production counter + dashboard
|
# ZenAI KPC edge counter + dashboard
|
||||||
# Shared config for: counter_live_rknn_bytetrack.py + counter_dashboard.py
|
# Shared config for: counter_live_rknn.py + counter_dashboard.py
|
||||||
# Copy to .env on device: cp config.env.example .env && nano .env
|
#
|
||||||
|
# On device:
|
||||||
|
# cp env.example .env && nano .env
|
||||||
|
#
|
||||||
|
# Install path (systemd): /opt/zenai-kpc-python
|
||||||
|
# Data path: /opt/zenai-kpc-counter
|
||||||
|
# See DEPLOY.md for full setup instructions.
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
|
|
||||||
# --- Core paths ---
|
# --- Core paths ---
|
||||||
# Root output directory (logs, DB, video, CSV)
|
# Root output directory (video segments, default snapshot/CSV paths)
|
||||||
OUTPUT_DIR=/opt/zenai-kpc-bt-counter
|
OUTPUT_DIR=/opt/zenai-kpc-counter
|
||||||
# SQLite database path for daily counter records & crossing logs
|
# SQLite database path for daily counter records
|
||||||
DB_PATH=/tmp/counter.db
|
DB_PATH=/opt/zenai-kpc-counter/counter.db
|
||||||
# JSON file persisting the current active counting day state
|
# JSON file for the active counting-day state (counter writes, dashboard reads)
|
||||||
STATE_FILE=/tmp/current_counter.json
|
STATE_FILE=/opt/zenai-kpc-counter/current_counter.json
|
||||||
|
|
||||||
# --- Input source ---
|
# --- Input source ---
|
||||||
# RTSP / HTTP live stream, or a local video file path
|
# RTSP / HTTP live stream, or a local video file path
|
||||||
#SOURCE=rtsp://user:pass@192.168.0.100:554/stream1
|
SOURCE=rtsp://user:pass@192.168.0.100:554/stream1
|
||||||
SOURCE=rtsp://10.38.30.64:8554/my_stream
|
|
||||||
# FFmpeg capture options passed to cv2.VideoCapture (RTSP low-latency flags)
|
# FFmpeg capture options passed to cv2.VideoCapture (RTSP low-latency flags)
|
||||||
OPENCV_FFMPEG_CAPTURE_OPTIONS=rtsp_transport;tcp|fflags;nobuffer|flags;low_delay
|
OPENCV_FFMPEG_CAPTURE_OPTIONS=rtsp_transport;tcp|fflags;nobuffer|flags;low_delay
|
||||||
|
|
||||||
# --- RKNN model ---
|
# --- RKNN model ---
|
||||||
# Path to exported .rknn model (YOLO format, e.g. yolo11n.rknn)
|
# Path to exported .rknn model (YOLO format)
|
||||||
MODEL_PATH=/opt/models/zenai_kac_sukawarna_20260702.rknn
|
MODEL_PATH=/opt/models/your_model.rknn
|
||||||
# Input image size for the model (square, e.g. 320 → 320×320)
|
# Input image size for the model (square, e.g. 320 → 320×320)
|
||||||
IMGSZ=320
|
IMGSZ=320
|
||||||
# Use FP16 inference on NPU (true/false); currently unused in ByteTrack variant
|
# Use FP16 inference on NPU (true/false); currently unused in ByteTrack variant
|
||||||
@@ -53,14 +58,24 @@ TRACK_BUFFER=60
|
|||||||
# Minimum consecutive (or total) hits needed before a track is considered confirmed
|
# Minimum consecutive (or total) hits needed before a track is considered confirmed
|
||||||
TRACK_MIN_HITS=3
|
TRACK_MIN_HITS=3
|
||||||
|
|
||||||
|
# --- ID-switch counting guards ---
|
||||||
|
# Dedup guard: ignore a second crossing in the same direction within DEDUP_FRAMES
|
||||||
|
# frames and DEDUP_PX horizontal pixels (set DEDUP_PX=-1 to disable).
|
||||||
|
DEDUP_FRAMES=15
|
||||||
|
DEDUP_PX=60
|
||||||
|
# Inheritance guard: new tracks inherit position from a recently-seen nearby track
|
||||||
|
# (set INHERIT_PX=-1 to disable).
|
||||||
|
INHERIT_SEC=1.0
|
||||||
|
INHERIT_PX=60
|
||||||
|
|
||||||
# --- Display ---
|
# --- Display ---
|
||||||
# Site name shown on the dashboard header (top-right)
|
# Site name shown on the dashboard header
|
||||||
SITE_NAME=ZenAi
|
SITE_NAME=ZenAi
|
||||||
|
|
||||||
# --- Object class names ---
|
# --- Object class names ---
|
||||||
# Camera / location identifier shown in HUD and stored in DB
|
# Camera / location identifier shown in HUD and stored in DB
|
||||||
CAMERA_NAME=ZenAi
|
CAMERA_NAME=ZenAi
|
||||||
# Label used for batch grouping in the database
|
# Label used for grouping in the database
|
||||||
OBJECT_LABEL=karung
|
OBJECT_LABEL=karung
|
||||||
# Class name for the counted object (must match model class order)
|
# Class name for the counted object (must match model class order)
|
||||||
CLASS_OBJECT=karung
|
CLASS_OBJECT=karung
|
||||||
@@ -68,83 +83,59 @@ CLASS_OBJECT=karung
|
|||||||
OBJECT_CLASS_ID=0
|
OBJECT_CLASS_ID=0
|
||||||
|
|
||||||
# --- Line crossing ---
|
# --- Line crossing ---
|
||||||
# Two horizontal counting lines:
|
# Line 1 (~upper): counts top-to-down (IN). Line 2 (~lower): bottom-to-up (OUT).
|
||||||
# Line 1 (default ~33%): counts top-to-down (IN)
|
# Fixed y-coordinate overrides the fraction if set.
|
||||||
# Line 2 (default ~66%): counts bottom-to-up (OUT)
|
|
||||||
# Fixed y-coordinate for line 1/IN (overrides LINE_Y1_FRAC if set)
|
|
||||||
LINE_Y1=
|
LINE_Y1=
|
||||||
# Fraction of frame height for line 1 (default 0.33)
|
|
||||||
LINE_Y1_FRAC=0.70
|
LINE_Y1_FRAC=0.70
|
||||||
# Fixed y-coordinate for line 2 (overrides LINE_Y2_FRAC if set)
|
|
||||||
LINE_Y2=
|
LINE_Y2=
|
||||||
# Fraction of frame height for line 2 (default 0.66)
|
|
||||||
LINE_Y2_FRAC=0.30
|
LINE_Y2_FRAC=0.30
|
||||||
|
|
||||||
# --- Counting day management ---
|
# --- Counting day management ---
|
||||||
# Daily cutoff time (HH:MM) – a new counting day starts after this time and the
|
# New counting day starts after this time (HH:MM).
|
||||||
# previous day's counter_in / counter_out totals are finalized in the database.
|
|
||||||
# CUTOFF_TIME is an alias used by the dashboard; DAILY_CUTOFF_TIME takes priority in counter_live_rknn.py.
|
|
||||||
DAILY_CUTOFF_TIME=20:00
|
DAILY_CUTOFF_TIME=20:00
|
||||||
CUTOFF_TIME=20:00
|
CUTOFF_TIME=20:00
|
||||||
|
|
||||||
# --- CSV export ---
|
# --- CSV export ---
|
||||||
# Write per-crossing events to a CSV file (true/false)
|
|
||||||
EXPORT_CSV=false
|
EXPORT_CSV=false
|
||||||
# Path where the crossing CSV is written
|
CROSS_CSV=/opt/zenai-kpc-counter/crossings.csv
|
||||||
CROSS_CSV=/tmp/crossings.csv
|
|
||||||
|
# --- Crossing snapshots ---
|
||||||
|
SAVE_CROSS_SNAPSHOT=true
|
||||||
|
SAVE_DETECT_SNAPSHOT=false
|
||||||
|
CROSS_SNAPSHOT_DIR=/opt/zenai-kpc-counter/snapshots
|
||||||
|
CROSS_SNAPSHOT_QUALITY=85
|
||||||
|
CROSS_SNAPSHOT_MAX_FILES=1000
|
||||||
|
CROSS_SNAPSHOT_MAX_AGE_DAYS=3
|
||||||
|
CROSS_SNAPSHOT_CLEANUP_SEC=3600
|
||||||
|
|
||||||
# --- Rate / performance ---
|
# --- Rate / performance ---
|
||||||
# Enable motion detection pre-filter: skip inference on frames with no movement
|
|
||||||
# (true/false, default: false). When enabled, frames below MOTION_THRESHOLD are
|
|
||||||
# skipped, saving NPU/CPU load.
|
|
||||||
MOTION_DETECTION_ENABLED=true
|
MOTION_DETECTION_ENABLED=true
|
||||||
# Mean absolute pixel difference threshold (0–255) to consider a frame as having
|
MOTION_PIXEL_DELTA=25
|
||||||
# motion. Lower = more sensitive. Default 5.0.
|
MOTION_MIN_AREA_FRAC=0.002
|
||||||
|
MOTION_HEARTBEAT_FRAMES=15
|
||||||
MOTION_THRESHOLD=5.0
|
MOTION_THRESHOLD=5.0
|
||||||
# Sliding window in seconds for computing the crossing rate (objects/minute)
|
|
||||||
RATE_WINDOW_SEC=60
|
RATE_WINDOW_SEC=60
|
||||||
# Number of frames to discard at startup to let the stream buffer stabilise
|
|
||||||
WARMUP_FRAMES=30
|
WARMUP_FRAMES=30
|
||||||
# Delay in seconds between stream reconnection attempts
|
|
||||||
RECONNECT_DELAY_SEC=3
|
RECONNECT_DELAY_SEC=3
|
||||||
# Maximum reconnection attempts (0 = infinite)
|
|
||||||
MAX_RECONNECT_ATTEMPTS=0
|
MAX_RECONNECT_ATTEMPTS=0
|
||||||
# Seconds after which a tracked but unseen object is pruned from the active set
|
|
||||||
TRACKED_PRUNE_SEC=300
|
TRACKED_PRUNE_SEC=300
|
||||||
|
|
||||||
# --- Video recording ---
|
# --- Video recording ---
|
||||||
# Save annotated frames to segmented MP4 files (true/false)
|
|
||||||
RECORD_VIDEO=false
|
RECORD_VIDEO=false
|
||||||
# Duration in seconds of each video segment file
|
|
||||||
VIDEO_SEGMENT_SEC=3600
|
VIDEO_SEGMENT_SEC=3600
|
||||||
# Output video FPS (fallback if source FPS is unknown or ≤ 1)
|
|
||||||
OUTPUT_FPS=15
|
OUTPUT_FPS=15
|
||||||
|
|
||||||
# --- Live stream snapshot ---
|
# --- Live stream snapshot ---
|
||||||
# Periodically write the latest annotated frame as JPEG for an external web server
|
|
||||||
LIVE_STREAM_ENABLED=true
|
LIVE_STREAM_ENABLED=true
|
||||||
# Path to the shared-memory snapshot file (served by nginx / lighttpd)
|
LIVE_STREAM_FRAME_PATH=/dev/shm/zenai-kpc-counter/live_frame.jpg
|
||||||
LIVE_STREAM_FRAME_PATH=/dev/shm/byetrack-counter/live_frame.jpg
|
|
||||||
# JPEG quality (1–100)
|
|
||||||
LIVE_STREAM_QUALITY=75
|
LIVE_STREAM_QUALITY=75
|
||||||
# Write the snapshot every N frames (lower = more frequent updates)
|
|
||||||
LIVE_STREAM_EVERY_N=2
|
LIVE_STREAM_EVERY_N=2
|
||||||
|
|
||||||
# --- Dashboard (counter_dashboard.py) ---
|
# --- Dashboard (counter_dashboard.py) ---
|
||||||
# Flask secret key for session/cookie signing — change in production!
|
|
||||||
SECRET_KEY=change-me-in-production
|
SECRET_KEY=change-me-in-production
|
||||||
# Bind address for the Flask web server
|
|
||||||
DASHBOARD_HOST=0.0.0.0
|
DASHBOARD_HOST=0.0.0.0
|
||||||
# Listen port for the dashboard web UI
|
|
||||||
DASHBOARD_PORT=5000
|
DASHBOARD_PORT=5000
|
||||||
# Enable Flask debug mode (true/false) — auto-reloads on code changes; disable in production
|
|
||||||
FLASK_DEBUG=false
|
FLASK_DEBUG=false
|
||||||
# Fallback name for the active counting-day JSON state file used by the dashboard
|
|
||||||
CURRENT_COUNTER_PATH=/tmp/bytetrack_current_counter.json
|
|
||||||
|
|
||||||
SAVE_CROSS_SNAPSHOT=true
|
# Debug: set DEBUG_TRACKING=true to log per-frame tracking details to stdout
|
||||||
CROSS_SNAPSHOT_DIR=/opt/zenai-kpc-snaps/snapshots
|
# DEBUG_TRACKING=false
|
||||||
CROSS_SNAPSHOT_CLEANUP_SEC=3600
|
|
||||||
CROSS_SNAPSHOT_MAX_AGE_DAYS=3
|
|
||||||
|
|
||||||
DEDUP_PX=-1
|
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=ZenAI KPC Edge Counter (RTSP + RKNN)
|
||||||
|
Documentation=file:///opt/zenai-kpc-python/DEPLOY.md
|
||||||
|
After=network-online.target
|
||||||
|
Wants=network-online.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
User=root
|
||||||
|
Group=root
|
||||||
|
|
||||||
|
WorkingDirectory=/opt/zenai-kpc-python
|
||||||
|
EnvironmentFile=/opt/zenai-kpc-python/.env
|
||||||
|
Environment=PYTHONNOUSERSITE=1
|
||||||
|
Environment=PATH=/opt/zenai-kpc-python/venv/bin:/usr/local/bin:/usr/bin:/bin
|
||||||
|
|
||||||
|
ExecStart=/opt/zenai-kpc-python/venv/bin/python counter_live_rknn.py
|
||||||
|
|
||||||
|
TimeoutStopSec=30
|
||||||
|
KillSignal=SIGTERM
|
||||||
|
|
||||||
|
Restart=always
|
||||||
|
RestartSec=10
|
||||||
|
StartLimitInterval=120s
|
||||||
|
StartLimitBurst=5
|
||||||
|
|
||||||
|
NoNewPrivileges=true
|
||||||
|
ProtectHome=true
|
||||||
|
PrivateTmp=false
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=ZenAI KPC Dashboard (Flask)
|
||||||
|
Documentation=file:///opt/zenai-kpc-python/DEPLOY.md
|
||||||
|
After=network-online.target zenai-kpc-counter.service
|
||||||
|
Wants=network-online.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
User=root
|
||||||
|
Group=root
|
||||||
|
|
||||||
|
WorkingDirectory=/opt/zenai-kpc-python
|
||||||
|
EnvironmentFile=/opt/zenai-kpc-python/.env
|
||||||
|
Environment=PATH=/opt/zenai-kpc-python/venv/bin:/usr/local/bin:/usr/bin:/bin
|
||||||
|
Environment=FLASK_DEBUG=false
|
||||||
|
|
||||||
|
ExecStart=/opt/zenai-kpc-python/venv/bin/python counter_dashboard.py
|
||||||
|
|
||||||
|
TimeoutStopSec=15
|
||||||
|
KillSignal=SIGTERM
|
||||||
|
|
||||||
|
Restart=always
|
||||||
|
RestartSec=5
|
||||||
|
StartLimitInterval=60s
|
||||||
|
StartLimitBurst=3
|
||||||
|
|
||||||
|
NoNewPrivileges=true
|
||||||
|
ProtectHome=true
|
||||||
|
PrivateTmp=false
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
Reference in new issue
Block a user