diff --git a/.gitignore b/.gitignore index cf51c7b..8e805c5 100644 --- a/.gitignore +++ b/.gitignore @@ -16,4 +16,11 @@ feeder/ .vscode/ dist/ websocket_comm.log -temp_debug/ \ No newline at end of file +temp_debug/ +.claude + +# Video Test +video_rtsp/ +multi_stream_viewer.py +multi_camera_simulator.py +start_4_cameras.bat \ No newline at end of file diff --git a/app.py b/app.py index 4c15324..8f35512 100644 --- a/app.py +++ b/app.py @@ -1069,10 +1069,34 @@ async def detect(websocket: WebSocket): from siwatsystem.pympta import run_lightweight_detection basic_detection = run_lightweight_detection(cropped_frame, model_tree) - any_car_detected = basic_detection and basic_detection.get("car_detected", False) - logger.debug(f"🔍 Camera {camera_id}: LIGHTWEIGHT - simple car presence check: {any_car_detected}") + # Enhanced car detection: requires both confidence pass AND bbox >= 50% of frame + car_detected_confidence = basic_detection and basic_detection.get("car_detected", False) + car_detected_with_bbox_validation = False - if any_car_detected: + if car_detected_confidence: + # Car passed confidence - now check bbox area + best_detection = basic_detection.get("best_detection") + if best_detection and best_detection.get("bbox"): + bbox = best_detection["bbox"] + x1, y1, x2, y2 = bbox + bbox_area = (x2 - x1) * (y2 - y1) + frame_height, frame_width = cropped_frame.shape[:2] + frame_area = frame_height * frame_width + bbox_area_ratio = bbox_area / frame_area if frame_area > 0 else 0 + + min_area_ratio = 0.2 # 20% of frame + car_detected_with_bbox_validation = bbox_area_ratio >= min_area_ratio + + if not car_detected_with_bbox_validation: + logger.info(f"🚫 Camera {camera_id}: LIGHTWEIGHT - car detected but bbox {bbox_area_ratio:.1%} < {min_area_ratio:.0%} (too distant) - counting as absent") + else: + logger.debug(f"✅ Camera {camera_id}: LIGHTWEIGHT - car detected with valid bbox {bbox_area_ratio:.1%} >= {min_area_ratio:.0%}") + else: + logger.debug(f"⚠️ Camera {camera_id}: LIGHTWEIGHT - car detected but no bbox info available") + + logger.debug(f"🔍 Camera {camera_id}: LIGHTWEIGHT - enhanced car presence check: confidence={car_detected_confidence}, bbox_valid={car_detected_with_bbox_validation}") + + if car_detected_with_bbox_validation: # Car detected - reset absence counter, continue sending cached detection dict pipeline_state["absence_counter"] = 0 # Reset absence since cars are present diff --git a/siwatsystem/pympta.py b/siwatsystem/pympta.py index 975ee36..6ae5c72 100644 --- a/siwatsystem/pympta.py +++ b/siwatsystem/pympta.py @@ -733,12 +733,17 @@ def run_detection_with_tracking(frame, node, context=None): # Tracking zone validation removed - process all detections + # Calculate bbox area + x1, y1, x2, y2 = bbox + bbox_area = (x2 - x1) * (y2 - y1) + # Create detection object detection = { "class": class_name, "confidence": conf, "id": track_id, "bbox": bbox, + "bbox_area": bbox_area, "class_id": cls_id } @@ -927,6 +932,31 @@ def update_single_track_stability(node, detection, camera_id, frame_shape=None, current_track_id = detection.get("id") if detection else None + # ─── Bbox Area Validation for Stability ─── + # Only count detections where bbox area is >=20% of frame (close cars only) + if detection and frame_shape is not None: + bbox = detection.get("bbox", [0, 0, 0, 0]) + if bbox and len(bbox) >= 4: + x1, y1, x2, y2 = bbox + bbox_area = (x2 - x1) * (y2 - y1) + frame_height, frame_width = frame_shape[:2] + frame_area = frame_height * frame_width + bbox_area_ratio = bbox_area / frame_area if frame_area > 0 else 0 + + # Require bbox to be at least 20% of frame area + min_area_ratio = 0.2 + + if bbox_area_ratio < min_area_ratio: + logger.info(f"🚫 Camera {camera_id}: Track {current_track_id} REJECTED for stability - bbox area {bbox_area_ratio:.1%} < {min_area_ratio:.0%} (too small/distant)") + # Completely reset - remove track entirely (same as trackId change) + if current_track_id and current_track_id in track_counters: + old_count = track_counters.pop(current_track_id, 0) # Remove completely + stable_tracks.discard(current_track_id) # Remove from stable + logger.info(f"🔄 Camera {camera_id}: COMPLETELY RESET track {current_track_id} counter from {old_count} to 0 (reason: bbox too small)") + return {"validation_complete": False, "stable_tracks": list(stable_tracks), "current_tracks": [], "bbox_too_small": True} + else: + logger.debug(f"✅ Camera {camera_id}: Track {current_track_id} bbox area {bbox_area_ratio:.1%} >= {min_area_ratio:.0%} - acceptable for stability") + # ═══ MODE-AWARE TRACK VALIDATION ═══ logger.debug(f"📋 Camera {camera_id}: === TRACK VALIDATION ANALYSIS ===") logger.debug(f"📋 Camera {camera_id}: Current mode: {current_mode} (validation_mode={is_validation_mode})") @@ -1432,6 +1462,35 @@ def run_pipeline(frame, node: dict, return_bbox: bool=False, context=None, valid else: # Normal detection stage - Using structured detection function all_detections, regions_dict, track_validation_result = run_detection_with_tracking(frame, node, context) + + # ─── Apply largest bbox area selection for full pipeline mode ─── + # When we have stable tracks and multiple detections, select the largest bbox area one + stable_tracks = track_validation_result.get("stable_tracks", []) + if stable_tracks and len(all_detections) > 1: + logger.info(f"🔍 PIPELINE: Full pipeline mode - selecting largest bbox area from {len(all_detections)} detections") + + # Select detection with largest bbox area + largest_detection = max(all_detections, key=lambda x: x.get("bbox_area", 0)) + logger.info(f"🎯 PIPELINE: Selected largest bbox area detection: conf={largest_detection.get('confidence', 0):.3f}, area={largest_detection.get('bbox_area', 0):.0f}") + + # Update all_detections to only contain the largest bbox area detection + all_detections = [largest_detection] + + # Update regions_dict to reflect the selected detection + class_name = largest_detection.get("class", "car") + regions_dict = { + class_name: { + "confidence": largest_detection.get("confidence"), + "bbox": largest_detection.get("bbox", [0, 0, 0, 0]), + "detection": largest_detection + } + } + + logger.debug(f"🔄 PIPELINE: Updated regions_dict for largest bbox area selection: {list(regions_dict.keys())}") + elif stable_tracks: + logger.debug(f"🔄 PIPELINE: Full pipeline mode - single detection, no area selection needed") + else: + logger.debug(f"🔄 PIPELINE: No stable tracks yet, proceeding with confidence-based detection") # Debug: Save crops for debugging (disabled for production) # if regions_dict: