diff --git a/biz/checkpoint/checkpoint_biz.py b/biz/checkpoint/checkpoint_biz.py index 965df49..0aa4d93 100644 --- a/biz/checkpoint/checkpoint_biz.py +++ b/biz/checkpoint/checkpoint_biz.py @@ -79,7 +79,7 @@ class KadianDetector: # 1. 业务判定时间阈值 self.TIME_THRESHOLD_ONLY_ONE = 3.0 # 单人单检判定时长 - self.TIME_THRESHOLD_NOBODY = 2.0 # 无人检查判定时长 + self.TIME_THRESHOLD_NOBODY = 3.0 # 无人检查判定时长 # 后备箱检查判定阈值 self.TIME_THRESHOLD_TRUNK_OPEN = 0.5 @@ -129,6 +129,15 @@ class KadianDetector: logger.info(f" 制服合规恢复缓冲帧数: {self.frame_buffer_uniform} 帧") logger.info(f" 判定 'Too Fast' (视为Nobody) 最小停留: {self.frame_thresh_car_min_duration} 帧") + self.onlyone_counter = 0 + self.onlyone_lost_counter = 0 + self.onlyone_buffer_limit = self.frame_buffer_limit_person # 10帧(1秒) + self.onlyone_thresh = self.frame_thresh_one # 30帧(3秒) + + self.nobody_counter = 0 + self.nobody_present_counter = 0 + self.nobody_buffer_limit = 10 # 10帧(1秒) + self.nobody_thresh = self.frame_thresh_nobody # 20帧(2秒) self.current_frame_idx = 0 self.cnt_frame_one_person = 0 @@ -161,21 +170,14 @@ class KadianDetector: 用于 pointPolygonTest 和 polylines """ if self.roi_points is None: - # 使用默认相对坐标 - default_rel = np.array([ - [0.15, 0.01], - [0.45, 0.01], - [0.95, 0.95], - [0.35, 0.95] - ], dtype=np.float64) - roi_abs = default_rel * np.array([frame_width, frame_height]) + raise ValueError("ROI points must be provided; cannot be None.") + + if self.roi_points.max() <= 1.0: + # 相对坐标 → 转换为绝对 + roi_abs = self.roi_points * np.array([frame_width, frame_height]) else: - if self.roi_points.max() <= 1.0: - # 相对坐标 → 转换为绝对 - roi_abs = self.roi_points * np.array([frame_width, frame_height]) - else: - # 绝对坐标,直接使用 - roi_abs = self.roi_points.copy() + # 绝对坐标,直接使用 + roi_abs = self.roi_points.copy() # 强制转为 int32(关键!解决 OpenCV 断言错误) return roi_abs.astype(np.int32) @@ -529,34 +531,87 @@ class KadianDetector: # ========================================== # 9. 业务逻辑判定 (Only One / Nobody) # ========================================== - status_text = "" + # status_text = "" + # + # if effective_car_count > 0: + # # --- Only One --- + # if current_roi_person_count == 1: + # self.cnt_frame_one_person += 1 + # self.cnt_missing_buffer_person = 0 + # self.cnt_frame_nobody = 0 + # + # # --- Nobody --- + # elif current_roi_person_count == 0: + # if self.cnt_frame_one_person > 0 and self.cnt_missing_buffer_person < self.frame_buffer_limit_person: + # self.cnt_frame_one_person += 1 + # self.cnt_missing_buffer_person += 1 + # self.cnt_frame_nobody = 0 + # status_text = f"Person Buffer ({self.cnt_missing_buffer_person}/{self.frame_buffer_limit_person})" + # else: + # self.cnt_frame_one_person = 0 + # self.cnt_missing_buffer_person = 0 + # self.cnt_frame_nobody += 1 + # else: + # self.cnt_frame_one_person = 0 + # self.cnt_missing_buffer_person = 0 + # self.cnt_frame_nobody = 0 + # else: + # self.cnt_frame_one_person = 0 + # self.cnt_missing_buffer_person = 0 + # self.cnt_frame_nobody = 0 - if effective_car_count > 0: - # --- Only One --- - if current_roi_person_count == 1: - self.cnt_frame_one_person += 1 - self.cnt_missing_buffer_person = 0 - self.cnt_frame_nobody = 0 + # ========================================== + # 9. 业务逻辑判定 (Only One / Nobody) - 重构版 + # ========================================== + if effective_car_count >= 0: # 只要没人就检测,不用等到来了车再检测 + # ----- 定义条件 ----- + onlyone_condition = (current_roi_person_count == 1 and self.pose_person_count == 1) + nobody_condition = (current_roi_person_count == 0 and self.pose_person_count == 0) - # --- Nobody --- - elif current_roi_person_count == 0: - if self.cnt_frame_one_person > 0 and self.cnt_missing_buffer_person < self.frame_buffer_limit_person: - self.cnt_frame_one_person += 1 - self.cnt_missing_buffer_person += 1 - self.cnt_frame_nobody = 0 - status_text = f"Person Buffer ({self.cnt_missing_buffer_person}/{self.frame_buffer_limit_person})" - else: - self.cnt_frame_one_person = 0 - self.cnt_missing_buffer_person = 0 - self.cnt_frame_nobody += 1 + # ----- Onlyone 计数器更新 ----- + if onlyone_condition: + self.onlyone_counter += 1 + self.onlyone_lost_counter = 0 else: - self.cnt_frame_one_person = 0 - self.cnt_missing_buffer_person = 0 - self.cnt_frame_nobody = 0 + if self.onlyone_counter > 0: + self.onlyone_lost_counter += 1 + if self.onlyone_lost_counter > self.onlyone_buffer_limit: + self.onlyone_counter = 0 + self.onlyone_lost_counter = 0 + # 没有累积的 Onlyone 则不做任何事 + + # ----- Nobody 计数器更新 ----- + if nobody_condition: + self.nobody_counter += 1 + self.nobody_present_counter = 0 + else: + if self.nobody_counter > 0: + self.nobody_present_counter += 1 + if self.nobody_present_counter > self.nobody_buffer_limit: + self.nobody_counter = 0 + self.nobody_present_counter = 0 + # 没有累积的 Nobody 则不做任何事 + + # ----- 准备显示状态文字(可选)----- + if self.onlyone_counter > 0: + if onlyone_condition: + status_text = f"OnlyOne: {self.onlyone_counter}/{self.onlyone_thresh}" + else: + status_text = f"OnlyOne Lost: {self.onlyone_lost_counter}/{self.onlyone_buffer_limit}" + elif self.nobody_counter > 0: + if nobody_condition: + status_text = f"Nobody: {self.nobody_counter}/{self.nobody_thresh}" + else: + status_text = f"Nobody Interrupted: {self.nobody_present_counter}/{self.nobody_buffer_limit}" + else: + status_text = "" + else: - self.cnt_frame_one_person = 0 - self.cnt_missing_buffer_person = 0 - self.cnt_frame_nobody = 0 + # 无活跃车辆,清零所有计数器 + self.onlyone_counter = 0 + self.onlyone_lost_counter = 0 + self.nobody_counter = 0 + self.nobody_present_counter = 0 # ========================================== # 10. 显示报警 (UI分层优化) @@ -581,25 +636,37 @@ class KadianDetector: # 第一层:实时状态 (Real-time Status) # ------------------------------------------------ # A. 显示 Only One - if self.cnt_frame_one_person >= self.frame_thresh_one: - current_frame_alerts.append( - { - 'time': current_time_sec, - 'action': "Only One", - } - ) - self.draw_alert(frame, "Only One", (0, 255, 255), status_text, offset_y=alert_offset) + # if self.cnt_frame_one_person >= self.frame_thresh_one: + # current_frame_alerts.append( + # { + # 'time': current_time_sec, + # 'action': "Only One", + # } + # ) + # self.draw_alert(frame, "Only One", (0, 255, 255), status_text, offset_y=alert_offset) + # alert_offset += 100 + # + # # B. 显示 Nobody (实时状态) + # elif self.cnt_frame_nobody >= self.frame_thresh_nobody: + # current_frame_alerts.append( + # { + # 'time': current_time_sec, + # 'action': "Nobody", + # } + # ) + # self.draw_alert(frame, "Nobody", (0, 0, 255), offset_y=alert_offset) + # alert_offset += 100 + + # A. 显示 Only One(当累积帧数达到阈值时) + if self.onlyone_counter >= self.onlyone_thresh: + current_frame_alerts.append({'time': current_time_sec, 'action': "Only One"}) + self.draw_alert(frame, "Only One", (0, 255, 255), None, offset_y=alert_offset) alert_offset += 100 - # B. 显示 Nobody (实时状态) - elif self.cnt_frame_nobody >= self.frame_thresh_nobody: - current_frame_alerts.append( - { - 'time': current_time_sec, - 'action': "Nobody", - } - ) - self.draw_alert(frame, "Nobody", (0, 0, 255), offset_y=alert_offset) + # B. 显示 Nobody(当累积帧数达到阈值时) + elif self.nobody_counter >= self.nobody_thresh: + current_frame_alerts.append({'time': current_time_sec, 'action': "Nobody"}) + self.draw_alert(frame, "Nobody", (0, 0, 255), None, offset_y=alert_offset) alert_offset += 100 # C. 显示 Trunk Checked (在车辆存活期间) @@ -619,28 +686,28 @@ class KadianDetector: if self.phone_alert_active: # 可以显示检测的持续时间 duration_seconds = self.phone_detection_frames / self.fps - sub_text = f"Detected for {duration_seconds:.1f}s" + #sub_text = f"Detected for {duration_seconds:.1f}s" current_frame_alerts.append( { 'time': current_time_sec, 'action': "Playing Phone", } ) - self.draw_alert(frame, "Playing Phone", (255, 0, 0), sub_text, offset_y=alert_offset) + self.draw_alert(frame, "Playing Phone", (255, 0, 0), None, offset_y=alert_offset) alert_offset += 100 # E. 新增:显示 Unvaild Uniform!! if self.uniform_alert_active: # 显示具体数量差异 diff = self.pose_person_count - current_roi_person_count - sub_text = f"Missing {diff} uniform(s)" + #sub_text = f"Missing {diff} uniform(s)" current_frame_alerts.append( { 'time': current_time_sec, 'action': "Unvaild Uniform!!", } ) - self.draw_alert(frame, "Unvaild Uniform!!", (255, 165, 0), sub_text, offset_y=alert_offset) + self.draw_alert(frame, "Unvaild Uniform!!", (255, 165, 0), None, offset_y=alert_offset) alert_offset += 100 # 第二层:离场违规 (Post-Event Alerts) @@ -663,18 +730,18 @@ class KadianDetector: self.draw_alert(frame, alert_text, (0, 0, 255), offset_y=alert_offset) alert_offset += 100 - # G. 显示 Nobody (离场结果) + # G. 显示 Ignore (离场结果) expired_fast_alerts = [cid for cid, end_frame in self.fast_pass_alerts.items() if self.current_frame_idx > end_frame] for cid in expired_fast_alerts: del self.fast_pass_alerts[cid] if len(self.fast_pass_alerts) > 0: - alert_text = f"Nobody (ID:{list(self.fast_pass_alerts.keys())})" + alert_text = f"Ignore: (ID:{list(self.fast_pass_alerts.keys())})" current_frame_alerts.append( { 'time': current_time_sec, - 'action': "Nobody", + 'action': "Ignore", } ) self.draw_alert(frame, alert_text, (0, 0, 255), offset_y=alert_offset)