diff --git a/chicken-detection-model-v26n-300e-best-2026-05-02-NEW.engine.cache b/chicken-detection-model-v26n-300e-best-2026-05-02-NEW.engine.cache index 8f08090..0a67a8e 100644 Binary files a/chicken-detection-model-v26n-300e-best-2026-05-02-NEW.engine.cache and b/chicken-detection-model-v26n-300e-best-2026-05-02-NEW.engine.cache differ diff --git a/chicken-detection-model-v26n-300e-best-2026-05-02-NEW.onnx b/chicken-detection-model-v26n-300e-best-2026-05-02-NEW.onnx index 55acf3a..e3fdf00 100755 Binary files a/chicken-detection-model-v26n-300e-best-2026-05-02-NEW.onnx and b/chicken-detection-model-v26n-300e-best-2026-05-02-NEW.onnx differ diff --git a/configs/cycle7_batch.yaml b/configs/cycle7_batch.yaml index 9a7acbc..4bc1bfe 100755 --- a/configs/cycle7_batch.yaml +++ b/configs/cycle7_batch.yaml @@ -11,11 +11,11 @@ defaults: model_path: /media/jetson/DATA/.Codes/chicken-counting-sukawarna-det/chicken-detection-model-v26n-300e-best-2026-05-02-NEW.engine classes: [0] ignored_classes: [1, 2] - conf: 0.45 + conf: 0.55 iou: 0.55 imgsz: 640 device: "0" - min_box_area_px: 3000 + min_box_area_px: 5000 validate_while_inside: true detection_zone: enabled: true @@ -53,6 +53,7 @@ defaults: show_track_ring: false count_anchor: [780, 120] inside_box_only: true + validated_only: true pending_blink: true pending_colors: - [255, 255, 0] @@ -67,7 +68,7 @@ defaults: overlay_buffer_reuse: true inference_stride: 2 stream: - enabled: false + enabled: true shm_dir: /dev/shm interval_frames: 5 feedback: @@ -80,7 +81,7 @@ defaults: inset_right_px: 60 inset_top_px: 0 inset_bottom_px: 0 - min_overlap_ratio: 0.30 + min_overlap_ratio: 0.35 cameras: CC1: @@ -95,6 +96,8 @@ cameras: CC2: camera_num: 2 count_anchor: [900, 120] + detection: + min_box_area_px: 3000 roi: points: - [20, 380] diff --git a/configs/cycle7_batch.yaml.1 b/configs/cycle7_batch.yaml.1 new file mode 100644 index 0000000..d50284f --- /dev/null +++ b/configs/cycle7_batch.yaml.1 @@ -0,0 +1,121 @@ +batch: + root_dir: /media/jetson/DATA/.Codes/VIDEOS/cycle7/kandang-atas + camera_glob: "kandang_*_camera_{num}_*.mp4" + output_subdir: output + compress_max_mb: 200 + delete_intermediate: false + checkpoint_every_n_frames: 3000 + +defaults: + detection: + model_path: /media/jetson/DATA/.Codes/chicken-counting-sukawarna-det/chicken-detection-model-v26n-300e-best-2026-05-02-NEW.engine + classes: [0] + ignored_classes: [1, 2] + conf: 0.35 + iou: 0.55 + imgsz: 640 + device: "0" + min_box_area_px: 3000 + validate_while_inside: true + detection_zone: + enabled: true + buffer_above_px: 250 + buffer_below_px: 250 + show_in_overlay: true + tracker: + tracker_config_path: /media/jetson/DATA/.Codes/chicken-counting-sukawarna-det/configs/trackers/botsort_chicken.yaml + persist: true + track_buffer: 75 + gate: + mode: two_line + lines_y: [420, 730] + direction: bottom_to_up + motion: + enabled: true + axis: vertical + forward_sign: 1.0 + ema_alpha: 0.2 + reverse_enter_threshold: -1.5 + reverse_exit_threshold: -0.5 + debounce_frames: 12 + min_features: 60 + max_corners: 80 + stride_frames: 2 + flow_scale: 0.5 + quality_level: 0.01 + min_distance: 8 + block_radius: 6 + overlay: + show_boxes: true + show_track_trails: false + trail_length: 20 + show_center_marker: true + show_track_ring: false + count_anchor: [780, 120] + inside_box_only: true + pending_blink: true + pending_colors: + - [255, 255, 0] + - [0, 255, 255] + display: + show_window: false + encoder: auto + output_bitrate_kbps: 4000 + codec_preference: [avc1, mp4v, H264] + performance: + half: false + overlay_buffer_reuse: true + inference_stride: 2 + stream: + enabled: true + shm_dir: /dev/shm + interval_frames: 5 + feedback: + enabled: true + every_n_frames: 3000 + save_images: true + log_to_terminal: true + roi: + inset_left_px: 60 + inset_right_px: 60 + inset_top_px: 0 + inset_bottom_px: 0 + min_overlap_ratio: 0.30 + +cameras: + CC1: + camera_num: 1 + count_anchor: [780, 120] + roi: + points: + - [250, 330] + - [1650, 330] + - [1650, 720] + - [250, 720] + CC2: + camera_num: 2 + count_anchor: [900, 120] + roi: + points: + - [20, 380] + - [1880, 380] + - [1880, 720] + - [20, 720] + CC3: + camera_num: 3 + count_anchor: [900, 120] + roi: + points: + - [20, 330] + - [1880, 330] + - [1880, 720] + - [20, 720] + CC4: + camera_num: 4 + count_anchor: [700, 120] + roi: + points: + - [50, 330] + - [1450, 330] + - [1450, 720] + - [50, 720] diff --git a/configs/trackers/botsort_chicken.yaml b/configs/trackers/botsort_chicken.yaml index 14bb22d..e90b83c 100755 --- a/configs/trackers/botsort_chicken.yaml +++ b/configs/trackers/botsort_chicken.yaml @@ -1,6 +1,6 @@ tracker_type: botsort -track_high_thresh: 0.55 -track_low_thresh: 0.1 +track_high_thresh: 0.65 +track_low_thresh: 0.3 new_track_thresh: 0.7 track_buffer: 75 match_thresh: 0.9 diff --git a/cpp/CMakeLists.txt b/cpp/CMakeLists.txt index af8e8ba..1ba09fa 100644 --- a/cpp/CMakeLists.txt +++ b/cpp/CMakeLists.txt @@ -5,6 +5,11 @@ set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_EXTENSIONS OFF) +if(NOT CMAKE_BUILD_TYPE) + set(CMAKE_BUILD_TYPE Release) +endif() +set(CMAKE_INTERPROCEDURAL_OPTIMIZATION_RELEASE TRUE) + find_package(OpenCV 4.0 REQUIRED COMPONENTS core imgproc video videoio highgui imgcodecs dnn) find_package(nlohmann_json 3.0 REQUIRED) find_package(yaml-cpp REQUIRED) @@ -31,11 +36,13 @@ target_include_directories(chicken_counter_lib PUBLIC /usr/include/aarch64-linux-gnu ) target_link_libraries(chicken_counter_lib PUBLIC ${COMMON_LIBS}) +target_compile_options(chicken_counter_lib PRIVATE -O3 -march=armv8.2-a+fp16+dotprod -flto -DNDEBUG) add_executable(chicken_counter_cli src/main.cpp ) target_link_libraries(chicken_counter_cli PRIVATE chicken_counter_lib) +target_link_options(chicken_counter_cli PRIVATE -Wl,--strip-all) add_executable(test_config tests/test_config.cpp diff --git a/cpp/include/chicken_counter/config.hpp b/cpp/include/chicken_counter/config.hpp index 5316a1b..b7cb9cd 100644 --- a/cpp/include/chicken_counter/config.hpp +++ b/cpp/include/chicken_counter/config.hpp @@ -284,6 +284,7 @@ struct OverlayConfig { bool show_track_ring = false; cv::Point2i count_anchor = {900, 120}; bool inside_box_only = true; + bool validated_only = false; bool pending_blink = true; std::vector pending_colors = {cv::Scalar(255, 255, 0), cv::Scalar(0, 255, 255)}; }; @@ -291,7 +292,8 @@ inline void to_json(nlohmann::json& j, const OverlayConfig& c) { j = {{"show_boxes", c.show_boxes}, {"show_track_trails", c.show_track_trails}, {"trail_length", c.trail_length}, {"show_center_marker", c.show_center_marker}, {"show_track_ring", c.show_track_ring}, {"count_anchor", c.count_anchor}, - {"inside_box_only", c.inside_box_only}, {"pending_blink", c.pending_blink}, + {"inside_box_only", c.inside_box_only}, {"validated_only", c.validated_only}, + {"pending_blink", c.pending_blink}, {"pending_colors", c.pending_colors}}; } inline void from_json(const nlohmann::json& j, OverlayConfig& c) { @@ -302,6 +304,7 @@ inline void from_json(const nlohmann::json& j, OverlayConfig& c) { c.show_track_ring = j.value("show_track_ring", false); c.count_anchor = j.value("count_anchor", cv::Point2i{900, 120}); c.inside_box_only = j.value("inside_box_only", true); + c.validated_only = j.value("validated_only", false); c.pending_blink = j.value("pending_blink", true); c.pending_colors = j.value("pending_colors", std::vector{cv::Scalar(255, 255, 0), cv::Scalar(0, 255, 255)}); @@ -328,7 +331,8 @@ inline void to_json(nlohmann::json& j, const DisplayConfig& c) { inline void from_json(const nlohmann::json& j, DisplayConfig& c) { c.window_name = j.value("window_name", "Chicken Counter"); c.show_window = j.value("show_window", true); - c.output_path = j.value("output_path", ""); + if (j.contains("output_path") && !j["output_path"].is_null()) + c.output_path = j["output_path"].get(); c.write_fps = j.value("write_fps", -1.0f); c.max_frames = j.value("max_frames", -1); c.encoder = j.value("encoder", "auto"); @@ -455,9 +459,10 @@ struct CameraPreset { std::string camera_id; int camera_num; RoiConfig roi; - cv::Point2i count_anchor = {-1, -1}; // (-1,-1) = not set + cv::Point2i count_anchor = {-1, -1}; GateConfig gate; MotionConfig motion; + nlohmann::json detection_overrides; bool has_gate = false; bool has_motion = false; }; @@ -536,6 +541,9 @@ inline BatchSettings load_batch_config(const std::string& path) { preset.motion = cam["motion"].get(); preset.has_motion = true; } + if (cam.contains("detection")) { + preset.detection_overrides = cam["detection"]; + } settings.cameras[id] = std::move(preset); } @@ -561,6 +569,10 @@ inline CameraConfig build_camera_config_from_batch( if (!raw.contains("overlay")) raw["overlay"] = nlohmann::json::object(); raw["overlay"]["count_anchor"] = preset.count_anchor; } + if (!preset.detection_overrides.empty()) { + if (!raw.contains("detection")) raw["detection"] = nlohmann::json::object(); + raw["detection"].update(preset.detection_overrides); + } if (!raw.contains("roi")) raw["roi"] = nlohmann::json::object(); raw["roi"]["points"] = preset.roi.points; @@ -589,7 +601,7 @@ inline CameraConfig build_camera_config_from_batch( } if (!raw.contains("display")) raw["display"] = nlohmann::json::object(); - raw["display"]["output_path"] = output_path.empty() ? nlohmann::json(nullptr) : nlohmann::json(output_path); + if (!output_path.empty()) raw["display"]["output_path"] = output_path; raw["display"]["show_window"] = false; if (!raw.contains("feedback")) raw["feedback"] = nlohmann::json::object(); diff --git a/cpp/include/chicken_counter/overlay.hpp b/cpp/include/chicken_counter/overlay.hpp index cdc8eeb..90435a3 100644 --- a/cpp/include/chicken_counter/overlay.hpp +++ b/cpp/include/chicken_counter/overlay.hpp @@ -108,6 +108,7 @@ inline cv::Mat draw_overlay( if (config.overlay.inside_box_only && !inside_box) continue; bool validated = counting_zone.is_validated(track.track_id); + if (config.overlay.validated_only && !validated) continue; int x1 = track.bbox_x1, y1 = track.bbox_y1, x2 = track.bbox_x2, y2 = track.bbox_y2; int cx = track.centroid_x, cy = track.centroid_y; int seq = static_cast(counting_zone.sequence_number_for(track.track_id)); diff --git a/cpp/include/chicken_counter/tracking.hpp b/cpp/include/chicken_counter/tracking.hpp index 057bb35..6efa0dd 100644 --- a/cpp/include/chicken_counter/tracking.hpp +++ b/cpp/include/chicken_counter/tracking.hpp @@ -119,8 +119,6 @@ public: std::string input_name, output_name; size_t input_bytes = 0, output_bytes = 0; - int output_num_classes = 1, output_num_cells = 8400; - bool output_layout_packed = true; // true = [1,N,8400], false = [1,8400,N] private: struct Logger : nvinfer1::ILogger { @@ -158,25 +156,12 @@ private: input_name = name; input_bytes = bytes; } else { output_name = name; output_bytes = bytes; - // cache output layout once - int d1 = (shape.nbDims >= 2) ? shape.d[1] : 1; - int d2 = (shape.nbDims >= 3) ? shape.d[2] : 1; - if (d2 > d1) { - output_layout_packed = true; // [1, N, cells] - output_num_classes = std::max(1, d1 - 4); - output_num_cells = d2; - } else { - output_layout_packed = false; // [1, cells, N] - output_num_classes = std::max(1, d2 - 4); - output_num_cells = d1; - } } } stream.reset(new cudaStream_t{}); trt_check(cudaStreamCreate(stream.get())); std::cerr << "[trt] ready: in=" << input_name << " (" << input_bytes - << "B) out=" << output_name << " (" << output_bytes - << "B) cls=" << output_num_classes << " cells=" << output_num_cells << "\n"; + << "B) out=" << output_name << " (" << output_bytes << "B)\n"; } }; @@ -311,52 +296,37 @@ private: return cv::dnn::blobFromImage(p, 1.0/255.0, {imgsz, imgsz}, cv::Scalar(), true, false); } - // --- decode (uses cache layout) --- + // --- decode: model outputs (1, 300, 6) = [x1,y1,x2,y2,conf,cls] in letterbox space --- std::vector decode(float scale, int pad_x, int pad_y, int ow, int oh) { std::vector iboxes; iboxes.reserve(32); std::vector scores; scores.reserve(32); - int nc = trt->output_num_classes; - int cells = trt->output_num_cells; - int stride = nc + 4; // total channels per cell const float* d = output_buf; + int stride = 6; // (x1,y1,x2,y2,conf,cls) per detection - if (trt->output_layout_packed) { - // layout [1, N, cells] — each channel is contiguous stride apart - for (int i = 0; i < cells; ++i) { - float maxc = 0; - for (int c = 0; c < nc; ++c) { - float v = d[(4 + c) * cells + i]; - if (v > maxc) maxc = v; - } - if (maxc < conf_thresh) continue; - float cx = d[i], cy = d[cells + i], w = d[2*cells + i], h = d[3*cells + i]; - float x = (cx - pad_x) / scale, y = (cy - pad_y) / scale; - float bw = w / scale, bh = h / scale; - int x1 = std::max(0, std::min(static_cast(x - bw/2), ow)); - int y1 = std::max(0, std::min(static_cast(y - bh/2), oh)); - int x2 = std::max(0, std::min(static_cast(x + bw/2), ow)); - int y2 = std::max(0, std::min(static_cast(y + bh/2), oh)); - if (x2 > x1 && y2 > y1) { iboxes.push_back({x1, y1, x2 - x1, y2 - y1}); scores.push_back(maxc); } - } - } else { - // layout [1, cells, N] — contiguous per row - for (int i = 0; i < cells; ++i) { - const float* row = d + i * stride; - float maxc = 0; - for (int c = 0; c < nc; ++c) { float v = row[4 + c]; if (v > maxc) maxc = v; } - if (maxc < conf_thresh) continue; - float cx = row[0], cy = row[1], w = row[2], h = row[3]; - float x = (cx - pad_x) / scale, y = (cy - pad_y) / scale; - int x1 = std::max(0, std::min(static_cast(x - w/scale/2), ow)); - int y1 = std::max(0, std::min(static_cast(y - h/scale/2), oh)); - int x2 = std::max(0, std::min(static_cast(x + w/scale/2), ow)); - int y2 = std::max(0, std::min(static_cast(y + h/scale/2), oh)); - if (x2 > x1 && y2 > y1) { iboxes.push_back({x1, y1, x2 - x1, y2 - y1}); scores.push_back(maxc); } + for (int i = 0; i < output_buf_size / stride; ++i) { + const float* row = d + i * stride; + float conf = row[4]; + if (conf < conf_thresh) continue; + + // boxes are in letterbox coordinates, scale back + float x1 = (row[0] - pad_x) / scale; + float y1 = (row[1] - pad_y) / scale; + float x2 = (row[2] - pad_x) / scale; + float y2 = (row[3] - pad_y) / scale; + + int ix1 = std::max(0, std::min(static_cast(x1), ow)); + int iy1 = std::max(0, std::min(static_cast(y1), oh)); + int ix2 = std::max(0, std::min(static_cast(x2), ow)); + int iy2 = std::max(0, std::min(static_cast(y2), oh)); + if (ix2 > ix1 && iy2 > iy1) { + iboxes.push_back({ix1, iy1, ix2 - ix1, iy2 - iy1}); + scores.push_back(conf); } } - std::vector idx; cv::dnn::NMSBoxes(iboxes, scores, conf_thresh, iou_thresh, idx); + std::vector idx; + cv::dnn::NMSBoxes(iboxes, scores, conf_thresh, iou_thresh, idx); std::vector out; out.reserve(idx.size()); for (int i : idx) out.push_back(iboxes[i]); return out; diff --git a/src/chicken_counter/config.py b/src/chicken_counter/config.py index 5d01be9..c9c897d 100755 --- a/src/chicken_counter/config.py +++ b/src/chicken_counter/config.py @@ -145,6 +145,7 @@ class OverlayConfig: show_track_ring: bool = False count_anchor: Point = (900, 120) inside_box_only: bool = True + validated_only: bool = False pending_blink: bool = True pending_colors: list[Color] = field( default_factory=lambda: [(255, 255, 0), (0, 255, 255)] @@ -222,6 +223,7 @@ class CameraPreset: count_anchor: Point | None = None gate: GateConfig | None = None motion: MotionConfig | None = None + detection_overrides: dict[str, Any] | None = None @dataclass @@ -330,6 +332,7 @@ def load_batch_config(path: str | Path) -> BatchSettings: gate = GateConfig(**camera_raw["gate"]) if "gate" in camera_raw else None motion = MotionConfig(**camera_raw["motion"]) if "motion" in camera_raw else None + detection_overrides = camera_raw.get("detection", None) cameras[camera_id] = CameraPreset( camera_id=camera_id, @@ -338,6 +341,7 @@ def load_batch_config(path: str | Path) -> BatchSettings: count_anchor=count_anchor, gate=gate, motion=motion, + detection_overrides=detection_overrides, ) return BatchSettings(batch=batch, defaults=defaults, cameras=cameras) @@ -387,6 +391,10 @@ def build_camera_config_from_batch( raw.setdefault("overlay", {}) raw["overlay"]["count_anchor"] = list(preset.count_anchor) + if preset.detection_overrides is not None: + raw.setdefault("detection", {}) + raw["detection"].update(preset.detection_overrides) + raw.setdefault("display", {}) raw["display"]["output_path"] = str(output_path) if output_path is not None else None raw["display"]["show_window"] = False diff --git a/src/chicken_counter/overlay.py b/src/chicken_counter/overlay.py index 2e4998d..788ff0c 100755 --- a/src/chicken_counter/overlay.py +++ b/src/chicken_counter/overlay.py @@ -52,6 +52,8 @@ def draw_overlay( continue validated = counting_zone.is_validated(track.track_id) + if config.overlay.validated_only and not validated: + continue x1, y1, x2, y2 = track.bbox_xyxy cx, cy = track.centroid sequence_number = counting_zone.sequence_number_for(track.track_id)