forked from zakaria/chicken-counting-sukawarna-det
intial commit
This commit is contained in:
commit
a54a070ca9
49 files changed
+2960
No files matched your search
@@ -0,0 +1,400 @@
|
||||
# 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.
|
||||
Reference in new issue
Block a user