forked from zakaria/chicken-counting-sukawarna-det
401 lines
13 KiB
Markdown
401 lines
13 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/
|
|
cameras/example_camera.yaml
|
|
cycle7_batch.yaml
|
|
trackers/botsort_chicken.yaml
|
|
src/chicken_counter/
|
|
batch_discovery.py
|
|
batch_runner.py
|
|
capture.py
|
|
cli.py
|
|
compress.py
|
|
config.py
|
|
counting.py
|
|
motion.py
|
|
overlay.py
|
|
pipeline.py
|
|
report.py
|
|
tracking.py
|
|
types.py
|
|
video_writer.py
|
|
```
|
|
|
|
## Install
|
|
|
|
```bash
|
|
python -m 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.
|
|
|
|
## 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` already uses these production defaults.
|
|
|
|
**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
|
|
|
|
## 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: /media/jetson/DATA/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 under:
|
|
|
|
```text
|
|
/media/jetson/DATA/chicken-sukawarna/cycle7/2026-07-09/
|
|
kandang_1_camera_1_2026-07-09_120056.mp4
|
|
kandang_1_camera_2_2026-07-09_120456.mp4
|
|
kandang_1_camera_3_2026-07-09_121012.mp4
|
|
kandang_1_camera_4_2026-07-09_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
|
|
chicken-counter batch --config configs/cycle7_batch.yaml
|
|
|
|
# Process a specific date
|
|
chicken-counter batch --config configs/cycle7_batch.yaml --date 2026-07-09
|
|
```
|
|
|
|
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
|
|
/media/jetson/DATA/chicken-sukawarna/cycle7/2026-07-09/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-07-09.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 /media/jetson/DATA/chicken-sukawarna && /usr/bin/chicken-counter batch --config configs/cycle7_batch.yaml >> logs/cycle7-batch.log 2>&1
|
|
```
|
|
|
|
Requires `ffmpeg` on the Jetson PATH for post-run compression.
|
|
|
|
## 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`), and long-run ETA logging.
|