forked from zakaria/chicken-counting-sukawarna-det
515 lines
19 KiB
Markdown
515 lines
19 KiB
Markdown
# Chicken Counter
|
||
|
||
First-pass Python pipeline for Jetson-style chicken counting using Ultralytics YOLO
|
||
tracking with BoT-SORT, ROI/gate-based counting, backward-motion detection from
|
||
background optical flow, and an OpenCV overlay that matches the provided reference
|
||
visual.
|
||
|
||
## What Is Included
|
||
|
||
- Modular runtime under `src/chicken_counter/`
|
||
- Sample camera config in `configs/cameras/example_camera.yaml`
|
||
- BoT-SORT tracker settings in `configs/trackers/botsort_chicken.yaml`
|
||
- CLI entrypoint: `chicken-counter`
|
||
|
||
## Pipeline Stages
|
||
|
||
1. Capture frames from a video file or camera stream
|
||
2. Run `model.track(..., persist=True)` with class filtering for chickens only
|
||
3. Maintain per-track state, track live occupancy inside the counting box, and count unique box entries
|
||
4. Estimate backward motion from sparse optical flow on background features
|
||
5. Render an OpenCV overlay with ROI, white gates, IDs, trails, and both live/cumulative counts
|
||
|
||
## Project Layout
|
||
|
||
```text
|
||
configs/
|
||
floor_config/ ← Lightweight floor configs (K1-L1 to K5-L2, kandang-atas)
|
||
K1-L1.yaml .. K5-L2.yaml ← Extends cycle7_batch_optimized.yaml
|
||
kandang-atas.yaml ← Extends cycle7_batch_optimized.yaml
|
||
cameras/example_camera.yaml
|
||
cycle7_batch.yaml
|
||
cycle7_batch_optimized.yaml ← Base batch processing config
|
||
mortality_config.yaml
|
||
trackers/botsort_chicken.yaml
|
||
src/chicken_counter/
|
||
batch_discovery.py
|
||
batch_runner.py
|
||
capture.py
|
||
cli.py
|
||
compress.py
|
||
config.py
|
||
counting.py
|
||
engine_utils.py
|
||
mortality.py
|
||
motion.py
|
||
overlay.py
|
||
pipeline.py
|
||
report.py
|
||
tracking.py
|
||
types.py
|
||
video_writer.py
|
||
dashboard.py
|
||
export_engine.py
|
||
export_excel_report.py
|
||
run_all_coops.sh ← Master multi-coop batch runner
|
||
start_dashboard.sh ← Live dashboard launcher
|
||
test_run_folder/ ← Archive of test run scripts and logs
|
||
```
|
||
|
||
## Install
|
||
|
||
```bash
|
||
python3 -m venv venv
|
||
venv/bin/pip install --upgrade pip
|
||
venv/bin/pip install -r requirements.txt
|
||
venv/bin/pip install -e .
|
||
```
|
||
|
||
For Jetson deployment you will usually want a Jetson-compatible OpenCV and PyTorch
|
||
stack already installed, then install the rest of the package around that environment.
|
||
|
||
> **Tip:** See `RUN.md` for a full step-by-step quickstart guide for new developers.
|
||
|
||
## Run
|
||
|
||
Update `configs/cameras/example_camera.yaml` with:
|
||
|
||
- `source`: your input video path, RTSP URL, or camera index
|
||
- `detection.model_path`: your TensorRT `.engine` or `.pt` checkpoint
|
||
- ROI coordinates and gate lines calibrated for the real camera
|
||
|
||
Then run:
|
||
|
||
```bash
|
||
chicken-counter --config configs/cameras/example_camera.yaml
|
||
```
|
||
|
||
Press `q` to quit the preview window.
|
||
|
||
For headless Jetson MP4 runs, set `display.show_window: false` and keep
|
||
`display.output_path` enabled so the annotated video is written without opening a GUI.
|
||
|
||
The video writer tries a Jetson GStreamer hardware encoder first when
|
||
`display.encoder: auto` or `gstreamer`, then falls back to OpenCV codecs in
|
||
`display.codec_preference` order (default: `avc1`, `mp4v`, `H264`).
|
||
|
||
## Config Notes
|
||
|
||
### Detection
|
||
|
||
The sample config restricts inference to class `0` and keeps ignored classes explicit:
|
||
|
||
- `classes: [0]`
|
||
- `ignored_classes: [1, 2]`
|
||
- `conf` and `iou` are exposed for real-footage tuning
|
||
- `min_box_area_px` can be used to reject very small partial detections from validation
|
||
- `device: "0"` should be set explicitly on Jetson CUDA
|
||
- `imgsz` must match the size used when a TensorRT `.engine` was exported
|
||
|
||
For TensorRT deployments, point `detection.model_path` at your `.engine` file and keep
|
||
`performance.half: false` (precision is already baked into the engine build).
|
||
|
||
### Tracking
|
||
|
||
The supplied tracker config enables:
|
||
|
||
- `tracker_type: botsort`
|
||
- `gmc_method: none` for fixed-camera MP4 runs (avoids duplicate optical flow)
|
||
- `with_reid: false`
|
||
|
||
Re-enable `gmc_method: sparseOptFlow` in `configs/trackers/botsort_chicken.yaml` only if
|
||
the camera mount moves or footage is shaky enough that track IDs drift without GMC.
|
||
|
||
Starting thresholds match the prompt defaults and can be tuned in
|
||
`configs/trackers/botsort_chicken.yaml`.
|
||
|
||
### Periodic Runtime Feedback
|
||
|
||
You can enable checkpoint-style progress feedback every `N` frames with the `feedback`
|
||
config block:
|
||
|
||
```yaml
|
||
feedback:
|
||
enabled: true
|
||
every_n_frames: 300
|
||
save_images: true
|
||
image_output_dir: output/checkpoints
|
||
log_to_terminal: true
|
||
```
|
||
|
||
When enabled, the pipeline will:
|
||
|
||
- print a periodic progress line with frame number, elapsed time, processing FPS, ETA, and total count
|
||
- save the current annotated frame as a checkpoint image (when `save_images: true`)
|
||
|
||
This is especially useful on Jetson when processing MP4 files headlessly, because you
|
||
can verify progress from the terminal and inspect saved snapshot images without needing
|
||
an on-device display.
|
||
|
||
### Counting ROI And Gates
|
||
|
||
The overlay is intended to resemble the reference image while staying easy to read:
|
||
|
||
- no outer green ROI outline
|
||
- one visible counting rectangle that is slightly smaller and cleaner than the previous broad region
|
||
- orange chicken bounding boxes that are visually distinct from the counting guides
|
||
- per-bird numeric labels based on count sequence, not raw tracker ID, using a non-white color
|
||
- short centroid trails
|
||
- one bold `TOTAL ENTERED` caption as the main count display, using a non-white color
|
||
|
||
The green ROI should be treated as the actual middle counting box. The current counting
|
||
semantics are:
|
||
|
||
- `Inside Box`: how many currently tracked chickens have their centroids inside the ROI
|
||
- `Total Entered`: how many unique tracked chickens have entered the ROI at least once
|
||
- a chicken is only valid for `Total Entered` if its bounding-box area meets `min_box_area_px`
|
||
- if backward motion is confirmed, the current frame is finalized and then the pipeline stops
|
||
- validated chickens receive a stable visible sequence number `1, 2, 3, ...` in entry order
|
||
- unvalidated chickens are tracked internally but do not show a visible sequence number yet
|
||
|
||
The implementation still assumes normal travel is `bottom_to_up`.
|
||
|
||
## Calibration Workflow
|
||
|
||
1. Start with a representative frame from the real camera.
|
||
2. Set `roi.points` so the counting rectangle spans the intended middle counting box only.
|
||
3. If the displayed rectangle feels too large or small, tighten or expand `roi.points` directly.
|
||
4. Run a short clip and compare `Inside Box` against the visible birds currently in that box.
|
||
5. Increase `min_box_area_px` if small partial chickens are being counted too early.
|
||
6. Verify `Total Entered` only increases when a new tracked bird enters the box during forward motion and is large enough to be valid.
|
||
6. Verify that once backward motion is confirmed, the output video ends at that point and the MP4 is finalized cleanly.
|
||
7. Verify that the highest displayed sequence number matches `Total Entered`.
|
||
8. Verify the final freeze frame stays on screen long enough to read the last total clearly.
|
||
|
||
## Backward-Motion Tuning
|
||
|
||
The stop trigger is separate from chicken tracks. It measures background motion while
|
||
masking detected chicken boxes.
|
||
|
||
Tune these values against real footage:
|
||
|
||
- `motion.forward_sign`
|
||
- `motion.ema_alpha`
|
||
- `motion.reverse_enter_threshold`
|
||
- `motion.reverse_exit_threshold`
|
||
- `motion.debounce_frames`
|
||
- `motion.min_features`
|
||
- `motion.stride_frames` (run flow every N frames; `2` is faster)
|
||
- `motion.flow_scale` (downscale ROI gray before flow; `0.5` is faster)
|
||
- `motion.max_corners` (fewer corners = faster; try `80`)
|
||
|
||
Important: confirm the actual sign convention from real cart footage before treating
|
||
the configured forward direction as final.
|
||
|
||
## Jetson Performance Speedups
|
||
|
||
For long batch runs, enable inference and motion stride in config:
|
||
|
||
```yaml
|
||
performance:
|
||
inference_stride: 2 # run YOLO+BoT-SORT every 2nd frame; reuse tracks in between
|
||
motion:
|
||
stride_frames: 2 # run optical flow every 2nd frame
|
||
flow_scale: 0.5 # half-resolution flow inside ROI crop
|
||
max_corners: 80
|
||
detection:
|
||
imgsz: 640 # keep 640 while using existing TensorRT .engine
|
||
```
|
||
|
||
`configs/cycle7_batch.yaml` and `configs/cycle7_batch_optimized.yaml` already use these production defaults.
|
||
|
||
`configs/cycle7_batch_optimized.yaml` adds per-camera parallelism, trimmed inference settings, and optimized YAML structure for the Sukawarna enclosure.
|
||
|
||
**Validation:** run a short clip with stride enabled, then compare `total_entered` against
|
||
`inference_stride: 1` and `motion.stride_frames: 1`. Watch checkpoint `fps=` logs for
|
||
speedup. Box positions may lag by up to one frame on skipped inference frames.
|
||
|
||
Set `inference_stride: 1` or `motion.stride_frames: 1` to restore full per-frame accuracy
|
||
for tuning.
|
||
|
||
## Known Limits In This First Pass
|
||
|
||
- No DeepStream integration yet
|
||
- No multi-process or multi-camera scheduler yet
|
||
- Counting currently assumes vertical motion and `bottom_to_up` travel
|
||
- The live box count depends on stable tracking centroids inside the ROI
|
||
- The optical-flow trigger is vision-first, though the config structure leaves room for
|
||
a future controller/encoder integration path
|
||
|
||
## Cross-Machine Portability & Self-Healing Engine Auto-Recompilation
|
||
|
||
TensorRT `.engine` files are compiled specifically for the host GPU architecture and TensorRT version. When copying the project to a different machine (e.g. from Jetson to NUC or across different RTX GPUs):
|
||
|
||
- **Automatic Compatibility Check**: `src/chicken_counter/engine_utils.py` runs a fast health check on the specified `.engine` before counting starts.
|
||
- **Self-Healing Recompilation**: If an incompatibility (e.g. platform tag mismatch or different compute capability) is detected:
|
||
1. The system automatically searches `models/` for the matching base `.pt` model weights (stripping hardware prefixes like `NUC5070_` or `jetson_`).
|
||
2. Automatically compiles a new optimized `.engine` on the host machine using FP16 precision.
|
||
3. Updates `defaults.detection.model_path` in `configs/cycle7_batch_optimized.yaml` automatically.
|
||
- **Manual Export Tool**: You can also compile engines manually anytime using `export_engine.py`:
|
||
```bash
|
||
./venv/bin/python export_engine.py models/chicken-detection-model-v26n-300e-best-2026-05-02-NEW.pt --half --workspace 4
|
||
```
|
||
|
||
## Multi-Stage Growth Cycles & Day 0 Configuration
|
||
|
||
The pipeline dynamically adjusts detection and ROI entry thresholds based on flock age (Days Old Chick / DOC vs Mid-Cycle):
|
||
|
||
- **Day 0 (`cycle_start_date`)**: Configured in `configs/cycle7_batch_optimized.yaml` (default: `"2026-05-22"`). Can be overridden via CLI (`--cycle-start-date YYYY-MM-DD`) or REST API (`/api/config/cycle_start_date`).
|
||
- **`early_cycle` (Days 0–15)**: Automatically applies high-sensitivity detection thresholds (`conf: 0.12`, `min_box_area_px: 200`, `min_overlap_ratio: 0.25`) for small fast-moving DOC chicks.
|
||
- **`mid_cycle` (Days 16+)**: Preserves standard tuned per-camera defaults (`conf: 0.35–0.50`, `min_box_area_px: 2500–3000`).
|
||
|
||
## Mortality Detection
|
||
|
||
A separate pipeline detects carcasses (dead birds) from still photos. Supports multi-image daily runs and date subfolders (`mortality/YYYY-MM-DD/`).
|
||
|
||
```bash
|
||
# Run on default mortality directory
|
||
./test_run_mortality.sh
|
||
|
||
# Run on a specific date (auto-creates/routes to mortality/2026-05-23/)
|
||
chicken-counter mortality --date 2026-05-23
|
||
```
|
||
|
||
Key features:
|
||
- **Multi-Image & Multi-Day Support**: Processes multiple images per day (e.g. morning/afternoon scans), aggregates the grand total carcass count (`total_mortality_count`), and saves outputs into date-isolated directories.
|
||
- **Direct High-Precision Segmentation (Default)**: Uses the **segmentation model** (`models/chicken-detection-model-v26n-seg-300e-best-2026-04-18.pt`) directly. 2-pass Detect & Refine (`two_pass: false`) is disabled by default because direct segmentation achieves higher accuracy and avoids false rejection on real farm photos.
|
||
- **Optional 2-Pass Refine (`--two-pass`)**: An optional mode combining initial segmentation candidate proposals with `cv2.matchTemplate` similarity refinement.
|
||
- **Containment filtering**: boxes where `IoA > 0.50` against a larger box are suppressed.
|
||
- **Centroid deduplication**: detections whose centroids are within `dedupe_radius_px` of each other are merged to prevent counting the same carcass twice.
|
||
|
||
Outputs for each daily run:
|
||
- `output_<name>.jpg` — annotated images with bounding boxes and carcass IDs
|
||
- `mortality_report.json` — full summary report with `total_mortality_count`, per-image breakdown, and detection coordinates
|
||
|
||
Config: `configs/mortality_config.yaml`
|
||
|
||
## Headless Jetson MP4 Example
|
||
|
||
For a headless run that saves both output video and periodic checkpoint images, use a
|
||
config shaped like this:
|
||
|
||
```yaml
|
||
display:
|
||
show_window: false
|
||
output_path: output/coop_cam_03_overlay.mp4
|
||
encoder: auto
|
||
output_bitrate_kbps: 4000
|
||
feedback:
|
||
enabled: true
|
||
every_n_frames: 300
|
||
save_images: true
|
||
image_output_dir: output/checkpoints
|
||
log_to_terminal: true
|
||
```
|
||
|
||
## 40-Minute Jetson Recipe
|
||
|
||
For long headless runs (~72,000 frames at 30 FPS), use the production-oriented settings
|
||
in `configs/cameras/example_camera.yaml`:
|
||
|
||
```yaml
|
||
detection:
|
||
device: "0"
|
||
imgsz: 640
|
||
model_path: /path/to/your-model.engine
|
||
overlay:
|
||
show_track_trails: false
|
||
show_track_ring: false
|
||
motion:
|
||
max_corners: 80
|
||
stride_frames: 2
|
||
flow_scale: 0.5
|
||
display:
|
||
show_window: false
|
||
output_path: /home/asus/.Codes/try-sukawarna-vis.mp4
|
||
encoder: auto
|
||
output_bitrate_kbps: 4000
|
||
performance:
|
||
half: false
|
||
overlay_buffer_reuse: true
|
||
inference_stride: 2
|
||
feedback:
|
||
enabled: true
|
||
every_n_frames: 900
|
||
log_to_terminal: true
|
||
save_images: false
|
||
```
|
||
|
||
Tracker YAML should use `gmc_method: none` for fixed-camera footage.
|
||
|
||
Lower `display.output_bitrate_kbps` produces smaller MP4 files with more compression
|
||
artifacts. Start at `4000` and adjust after inspecting output quality.
|
||
|
||
Checkpoint logs look like:
|
||
|
||
```text
|
||
[checkpoint] frame=9000/72000 elapsed=18m12s fps=8.2 total_entered=142 eta=2h05m status=running
|
||
```
|
||
|
||
When backward motion is confirmed, the pipeline now:
|
||
|
||
- finishes the current annotated frame
|
||
- writes that frame to the output video
|
||
- logs the backward-stop event
|
||
- appends a short freeze frame so the final total is readable
|
||
- exits immediately afterward, so the output MP4 ends there
|
||
|
||
## Daily Cycle7 Multi-Camera Batch
|
||
|
||
For everyday processing of 4 cameras, use `configs/cycle7_batch.yaml`.
|
||
|
||
### Input folder layout
|
||
|
||
Place today's videos in a `VIDEOS` folder **adjacent to the project directory** (i.e. `../VIDEOS` relative to the project root):
|
||
|
||
```text
|
||
../VIDEOS/cycle7/kandang-atas/
|
||
2026-06-18/
|
||
kandang_1_camera_1_2026-06-18_120056.mp4
|
||
kandang_1_camera_2_2026-06-18_120456.mp4
|
||
kandang_1_camera_3_2026-06-18_121012.mp4
|
||
kandang_1_camera_4_2026-06-18_121530.mp4
|
||
```
|
||
|
||
Date folders use `YYYY-MM-DD`. Camera files are matched by `camera_num` using the
|
||
pattern `kandang_*_camera_{num}_*.mp4`.
|
||
|
||
### Run commands
|
||
|
||
```bash
|
||
# Process today's folder using default parallel process mode
|
||
chicken-counter batch --config configs/cycle7_batch_optimized.yaml
|
||
|
||
# Process a specific date using Model-Level Tensor Batching mode
|
||
chicken-counter batch --config configs/cycle7_batch_optimized.yaml --date 2026-06-18 --mode tensor_batching
|
||
|
||
# Process a specific date using Hybrid mode (Threaded CPU + Batched GPU)
|
||
chicken-counter batch --config configs/cycle7_batch_optimized.yaml --date 2026-06-18 --mode hybrid
|
||
|
||
# Run automated batch script for Tensor Batching
|
||
./test_run_tensor_batch.sh # Runs all dates (2026-06-10 to 2026-06-19)
|
||
./test_run_tensor_batch.sh 2026-06-18 # Runs a specific date
|
||
|
||
# Run automated batch script for Hybrid execution mode
|
||
./test_run_hybrid.sh # Runs all dates (2026-06-10 to 2026-06-19)
|
||
./test_run_hybrid.sh 2026-06-18 # Runs a specific date
|
||
```
|
||
|
||
### Execution Modes (`execution_mode`)
|
||
|
||
Set `batch.execution_mode` in `configs/cycle7_batch_optimized.yaml` or override via `--mode`:
|
||
|
||
- `parallel_processes` (Default): Runs cameras in separate OS processes (e.g. via `test_run.sh`).
|
||
- `tensor_batching`: Synchronizes camera frame streams and executes a single batched GPU model inference pass across all cameras (`batch_size=N`).
|
||
- `hybrid`: Combines multi-threaded CPU frame capture, optical flow, and rendering across CPU cores with a single synchronized batched GPU forward pass (`batch_size=N`).
|
||
|
||
Single-camera mode still works:
|
||
|
||
```bash
|
||
chicken-counter --config configs/cameras/example_camera.yaml
|
||
chicken-counter run --config configs/cameras/example_camera.yaml
|
||
```
|
||
|
||
### Output layout
|
||
|
||
```text
|
||
../VIDEOS/cycle7/kandang-atas/2026-06-18/output/
|
||
CC1_vis.mp4
|
||
CC1_compressed.mp4
|
||
CC2_vis.mp4
|
||
CC2_compressed.mp4
|
||
...
|
||
checkpoints/CC1/frame_003000.jpg
|
||
checkpoints/CC2/frame_006000.jpg
|
||
counts_2026-06-18.json
|
||
```
|
||
|
||
After all 4 cameras finish counting, the batch runner compresses each annotated video
|
||
to under `batch.compress_max_mb` (default 200 MB) using `ffmpeg`.
|
||
|
||
### Per-camera counting boxes
|
||
|
||
| Camera | ROI points |
|
||
|--------|------------|
|
||
| CC1 | `[250,330], [1650,330], [1650,720], [250,720]` |
|
||
| CC2 | `[20,380], [1880,380], [1880,720], [20,720]` |
|
||
| CC3 | `[20,330], [1880,330], [1880,720], [20,720]` |
|
||
| CC4 | `[50,330], [1450,330], [1450,720], [50,720]` |
|
||
|
||
Tune these in `configs/cycle7_batch.yaml` if a lane drifts after camera maintenance.
|
||
|
||
### JSON report format
|
||
|
||
`counts_{date}.json` contains per-camera totals and the sum across all 4 cameras:
|
||
|
||
```json
|
||
{
|
||
"date": "2026-07-09",
|
||
"generated_at": "2026-07-09T11:45:00+00:00",
|
||
"cameras": {
|
||
"CC1": {
|
||
"total_entered": 142,
|
||
"source_video": "kandang_1_camera_1_2026-07-09_120056.mp4",
|
||
"vis_video": "CC1_vis.mp4",
|
||
"compressed_video": "CC1_compressed.mp4",
|
||
"compressed_size_mb": 187.4,
|
||
"frames_processed": 68432,
|
||
"stopped_reason": "backward",
|
||
"elapsed_seconds": 8234.5
|
||
}
|
||
},
|
||
"total_entered_sum": 580
|
||
}
|
||
```
|
||
|
||
### Checkpoint images
|
||
|
||
Batch mode saves review images every `checkpoint_every_n_frames` (default 3000) per camera.
|
||
For a ~72k frame run that is about 24 images per camera.
|
||
|
||
### Cron example
|
||
|
||
```cron
|
||
0 7 * * * cd /path/to/chicken-counting-sukawarna-det && ./test_run.sh >> logs/cycle7-batch.log 2>&1
|
||
```
|
||
|
||
Requires `ffmpeg` on the Jetson PATH for post-run compression.
|
||
|
||
## Dashboard & API
|
||
|
||
Start the live dashboard and API server:
|
||
|
||
```bash
|
||
# Portable launcher (recommended) — auto-discovers mortality dir
|
||
./start_dashboard.sh
|
||
|
||
# Or start manually
|
||
PYTHONPATH=src venv/bin/python dashboard.py \
|
||
--port 8080 \
|
||
--db db/chicken_counts.db \
|
||
--mortality-dir ../VIDEOS/cycle7/kandang-atas/mortality
|
||
```
|
||
|
||
Key API endpoints (see `API.md` for full schema):
|
||
|
||
| Endpoint | Description |
|
||
| :--- | :--- |
|
||
| `GET /api/db/summary` | Lifetime totals across all dates |
|
||
| `GET /api/db/history` | Per-date summary, newest first |
|
||
| `GET /api/db/date/<YYYY-MM-DD>` | Per-camera breakdown for a date |
|
||
| `GET /api/mortality/latest` | Latest carcass detection report |
|
||
| `GET /api/mortality/history` | All mortality reports, newest first |
|
||
| `GET /api/mortality/image/<name>` | Serve annotated output JPEG |
|
||
|
||
## Next Jetson-Focused Improvements
|
||
|
||
1. Add a hardware-aware video ingest path for CSI/GStreamer.
|
||
2. Export richer event logs for per-bird count timestamps.
|
||
3. Add a controller-signal adapter so encoder direction can override vision when available.
|
||
|
||
Recent work includes daily 4-camera batch processing, JSON count reports, post-run
|
||
compression under 200 MB, GStreamer hardware encoding, overlay buffer reuse,
|
||
duplicate optical-flow removal (`gmc_method: none`), long-run ETA logging, mortality
|
||
2-pass detection with feature similarity search, and a REST API via `dashboard.py`.
|