2026-07-21 16:33:16 +07:00

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

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

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:

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:

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.
  7. Verify that once backward motion is confirmed, the output video ends at that point and the MP4 is finalized cleanly.
  8. Verify that the highest displayed sequence number matches Total Entered.
  9. 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:

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:
    ./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/).

# 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:

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:

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:

[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):

../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

# 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:

chicken-counter --config configs/cameras/example_camera.yaml
chicken-counter run --config configs/cameras/example_camera.yaml

Output layout

../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:

{
  "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

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:

# 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.

S
Description
No description provided
Readme
121 MiB
0 Stars 1 Watchers 1 Forks
Languages
Python 86.8%
Shell 7%
HTML 6.2%