diff --git a/camera_stream.py b/camera_stream.py index b27dcee..8827de0 100644 --- a/camera_stream.py +++ b/camera_stream.py @@ -270,12 +270,34 @@ def _process_stream(): # --- Draw overlays --- display = frame.copy() - for x1, y1, x2, y2, conf in person_boxes: - cv2.rectangle(display, (int(x1), int(y1)), (int(x2), int(y2)), (0, 255, 0), 2) + pending_gid_set = {gid for gid, _, _ in pending_alerts} + alert_person_indices = set() + for gid, grp_indices, _gc in frame_group_data: + if gid in pending_gid_set: + alert_person_indices.update(grp_indices) + + # Live overlay: mark any person that belongs to any alerting group. + if pending_alerts: + max_people = max(people for _gid, people, _dur in pending_alerts) + max_dur = max(dur for _gid, _people, dur in pending_alerts) + cv2.putText( + display, + f"ALERT: {len(pending_alerts)} group(s) | {max_people} people | {int(max_dur)}s", + (12, 32), + cv2.FONT_HERSHEY_SIMPLEX, + 0.8, + (0, 0, 255), + 3, + ) + + for idx, (x1, y1, x2, y2, conf) in enumerate(person_boxes): + is_alert_person = idx in alert_person_indices + box_color = (0, 0, 255) if is_alert_person else (0, 255, 0) + cv2.rectangle(display, (int(x1), int(y1)), (int(x2), int(y2)), box_color, 2) cv2.putText( display, f"{conf:.0%}", (int(x1), int(y1) - 6), - cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 2, + cv2.FONT_HERSHEY_SIMPLEX, 0.5, box_color, 2, ) for gid, grp_indices, gc in frame_group_data: @@ -301,14 +323,72 @@ def _process_stream(): cv2.FONT_HERSHEY_SIMPLEX, 0.8, (0, 0, 255), 3, ) - # --- Save annotated alerts --- + # --- Save annotated alerts (separate per group) --- if pending_alerts: os.makedirs("alerts", exist_ok=True) ts = datetime.now().strftime("%Y%m%d_%H%M%S") + + frame_group_by_id = {gid: (grp_indices, gc) for gid, grp_indices, gc in frame_group_data} + for gid, people, duration in pending_alerts: + grp_indices, gc = frame_group_by_id.get(gid, (None, None)) + if grp_indices is None: + continue + + alert_display = frame.copy() + alert_person_set = set(grp_indices) + + # Header annotation for this specific alert group. + cv2.putText( + alert_display, + f"ALERT GROUP {gid} | {people} people | {int(duration)}s", + (12, 32), + cv2.FONT_HERSHEY_SIMPLEX, + 0.9, + (0, 0, 255), + 3, + ) + + # Draw only the person boxes relevant to this group. + for idx, (x1, y1, x2, y2, conf) in enumerate(person_boxes): + is_alert_person = idx in alert_person_set + box_color = (0, 0, 255) if is_alert_person else (0, 255, 0) + cv2.rectangle(alert_display, (int(x1), int(y1)), (int(x2), int(y2)), box_color, 2) + cv2.putText( + alert_display, + f"{conf:.0%}", + (int(x1), int(y1) - 6), + cv2.FONT_HERSHEY_SIMPLEX, + 0.5, + box_color, + 2, + ) + + # Draw only the circle/label for this group. + radius = int(PROXIMITY_PX * 0.6) + cv2.circle(alert_display, (int(gc[0]), int(gc[1])), radius, (0, 0, 255), 2) + cv2.putText( + alert_display, + f"Group: {len(grp_indices)} | {duration:.0f}s", + (int(gc[0]) - 70, int(gc[1]) - radius - 10), + cv2.FONT_HERSHEY_SIMPLEX, + 0.55, + (0, 0, 255), + 2, + ) + cv2.putText( + alert_display, + "ALERT", + (int(gc[0]) - 35, int(gc[1]) + radius + 25), + cv2.FONT_HERSHEY_SIMPLEX, + 0.8, + (0, 0, 255), + 3, + ) + # Include group id to avoid collisions when multiple groups alert in one second. alert_path = f"alerts/alert_{ts}_gid{gid}.jpg" - cv2.imwrite(alert_path, display) + cv2.imwrite(alert_path, alert_display) alert_info = { "time": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),