对漏检和未检后备箱的帧做时间回溯

This commit is contained in:
2026-03-09 13:47:54 +08:00
parent 365dcc94e5
commit 969922df54

View File

@@ -1,9 +1,10 @@
import cv2 import cv2
import numpy as np import numpy as np
from typing import Dict, Any from typing import Dict, Any
import threading import threading
import queue import queue
from collections import deque
from biz.base_frame_processor import BaseFrameProcessorWorker from biz.base_frame_processor import BaseFrameProcessorWorker
@@ -13,10 +14,13 @@ from algorithm.common.npu_yolo_pose_onnx import YOLOv8_Pose_ONNX
from yolox.tracker.byte_tracker import BYTETracker from yolox.tracker.byte_tracker import BYTETracker
from utils.logger import get_logger from utils.logger import get_logger
from common.constants import MODEL_ROOT_PATH
logger = get_logger(__name__) logger = get_logger(__name__)
DETECT_MODEL_PATH = 'YOLO_Weight/Kadian.onnx' # ========================= 配置区 =========================
# Kadian 模型路径与ROI可根据实际情况修改
#DETECT_MODEL_PATH = 'YOLO_Weight/Kadian.onnx'
DETECT_MODEL_PATH = r'D:\Python_Save\PoliceProject\Yolo_Weight\Kadian\Kadian_wanjia_xinkailing.onnx'
#POSE_MODEL_PATH = 'YOLO_Weight/yolov8l-pose.onnx'
# 默认相对ROI与原文件一致 # 默认相对ROI与原文件一致
#ROI_RELATIVE = np.array([ #ROI_RELATIVE = np.array([
@@ -39,7 +43,6 @@ ROI_RELATIVE=np.array([
[0.15,0.15], [0.15,0.15],
[0.37,0.15], [0.37,0.15],
[0.55,0.2], [0.55,0.2],
[0.9,0.85], [0.9,0.85],
[0.35,0.85] [0.35,0.85]
]) ])
@@ -60,17 +63,9 @@ class KadianDetector:
# 摄像头额外参数 # 摄像头额外参数
self.params = params if params is not None else {} self.params = params if params is not None else {}
# 模型路径:从 params 读取,未配置则使用默认值 DETECT_MODEL_PATH
model_path = self.params.get('model_path')
if model_path:
full_model_path = f"{MODEL_ROOT_PATH}/{model_path}"
else:
full_model_path = DETECT_MODEL_PATH
logger.info(f"Loading model from: {full_model_path}")
# 模型加载 # 模型加载
self.detector = YOLOv8_ONNX( self.detector = YOLOv8_ONNX(
full_model_path, DETECT_MODEL_PATH,
conf_threshold=0.6, conf_threshold=0.6,
iou_threshold=0.65, iou_threshold=0.65,
input_size=PERSON_CAR_INPUT_SIZE input_size=PERSON_CAR_INPUT_SIZE
@@ -125,6 +120,8 @@ class KadianDetector:
self.height = 0 self.height = 0
# 车辆注册表 (字典) # 车辆注册表 (字典)
self.roi_car_registry = {} self.roi_car_registry = {}
# 违规车辆记录 # 违规车辆记录
@@ -140,6 +137,12 @@ class KadianDetector:
self.nobody_frames = 0 # 累计无人在场帧数 self.nobody_frames = 0 # 累计无人在场帧数
self.only_one_frames = 0 # 累计单人在场帧数 self.only_one_frames = 0 # 累计单人在场帧数
self.max_car_frames = int((3.0 + self.TIME_TOLERANCE_CAR) * self.fps) # 50帧
self.frame_buffer_ignore_untrunk = deque(maxlen=self.max_car_frames)
self.untrunk_rollback_time = 3.0 # 未检查后备箱需要回溯的时间
self.ignored_rollback_time = 1.0 # 漏检需要回溯的时间
def _get_roi_points(self, frame_width: int, frame_height: int): def _get_roi_points(self, frame_width: int, frame_height: int):
""" """
@@ -206,6 +209,21 @@ class KadianDetector:
x1, y1, x2, y2 = box x1, y1, x2, y2 = box
return x1 < px < x2 and y1 < py < y2 return x1 < px < x2 and y1 < py < y2
def find_target_frame(self, target_time_sec):
target_frame = None
min_time_diff = float('inf')
for buffered in self.frame_buffer_ignore_untrunk:
time_diff = abs(buffered['frame_idx'] - target_time_sec)
if time_diff < min_time_diff:
min_time_diff = time_diff
target_frame = buffered['frame']
# 如果没找到,返回最早的帧
if target_frame is None and len(self.frame_buffer_ignore_untrunk) > 0:
target_frame = self.frame_buffer_ignore_untrunk[0]['frame']
return target_frame
def process_frame(self, frame, camera_id: int, timestamp: float) -> Dict[str, Any]: def process_frame(self, frame, camera_id: int, timestamp: float) -> Dict[str, Any]:
h, w = frame.shape[:2] h, w = frame.shape[:2]
self.width, self.height = w, h self.width, self.height = w, h
@@ -217,6 +235,8 @@ class KadianDetector:
current_time_sec = timestamp current_time_sec = timestamp
# ========= 主检测删除pose检测========= # ========= 主检测删除pose检测=========
detections = self.detector(frame) detections = self.detector(frame)
@@ -267,7 +287,8 @@ class KadianDetector:
# ========= 处理跟踪结果 ========= # ========= 处理跟踪结果 =========
for t in tracks: for t in tracks:
tid = t.track_id tid = t.track_id
REVALIDATE_FRAME_INTERVAL = 10
REVALIDATE_FRAME_INTERVAL = 2
# 定期重新匹配跟踪ID的类别 # 定期重新匹配跟踪ID的类别
if (self.current_frame_idx % REVALIDATE_FRAME_INTERVAL == 0) or (tid not in self.track_role): if (self.current_frame_idx % REVALIDATE_FRAME_INTERVAL == 0) or (tid not in self.track_role):
@@ -299,13 +320,17 @@ class KadianDetector:
# 车辆注册表初始化 # 车辆注册表初始化
if tid not in self.roi_car_registry: if tid not in self.roi_car_registry:
self.roi_car_registry[tid] = { self.roi_car_registry[tid] = {
'first_seen': self.current_frame_idx, # 'first_seen': self.current_frame_idx,
'last_seen': self.current_frame_idx, # 'last_seen': self.current_frame_idx,
'first_seen': current_time_sec,
'last_seen': current_time_sec,
'trunk_frames': 0, 'trunk_frames': 0,
'is_checked': False, 'is_checked': False,
#'frame_buffer': deque(maxlen=self.max_car_frames), # 新增
} }
else: else:
self.roi_car_registry[tid]['last_seen'] = self.current_frame_idx #self.roi_car_registry[tid]['last_seen'] = self.current_frame_idx
self.roi_car_registry[tid]['last_seen'] = current_time_sec
label += " IN" label += " IN"
elif role == "opentrunk": elif role == "opentrunk":
color = (255, 165, 0) # 橙色 color = (255, 165, 0) # 橙色
@@ -325,11 +350,13 @@ class KadianDetector:
# 警察注册表初始化 # 警察注册表初始化
if tid not in self.roi_police_registry: if tid not in self.roi_police_registry:
self.roi_police_registry[tid] = { self.roi_police_registry[tid] = {
'first_seen': self.current_frame_idx, # 'first_seen': self.current_frame_idx,
'last_seen': self.current_frame_idx, # 'last_seen': self.current_frame_idx,
'first_seen': current_time_sec,
'last_seen': current_time_sec,
} }
else: else:
self.roi_police_registry[tid]['last_seen'] = self.current_frame_idx self.roi_police_registry[tid]['last_seen'] = current_time_sec
label += " IN" label += " IN"
else: else:
color = (255, 255, 255) # 白色 color = (255, 255, 255) # 白色
@@ -339,6 +366,16 @@ class KadianDetector:
cv2.rectangle(frame, (x1, y1), (x2, y2), color, 2) cv2.rectangle(frame, (x1, y1), (x2, y2), color, 2)
cv2.putText(frame, label, (x1, y1 - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.6, color, 2) cv2.putText(frame, label, (x1, y1 - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.6, color, 2)
# 每帧保存到缓存
# self.roi_car_registry[tid]['frame_buffer'].append({
# 'frame_idx': current_time_sec,
# 'frame': frame.copy(),
# })
self.frame_buffer_ignore_untrunk.append({
'frame_idx': current_time_sec,
'frame': frame.copy(),
})
# ========================================== # ==========================================
# 关联分析: 哪个后备箱属于哪辆车? # 关联分析: 哪个后备箱属于哪辆车?
# ========================================== # ==========================================
@@ -365,11 +402,16 @@ class KadianDetector:
for car_id, info in self.roi_car_registry.items(): for car_id, info in self.roi_car_registry.items():
last_seen = info['last_seen'] last_seen = info['last_seen']
if (self.current_frame_idx - last_seen) <= self.frame_buffer_limit_car: # if (self.current_frame_idx - last_seen) <= self.frame_buffer_limit_car:
if (current_time_sec - last_seen) <= self.frame_buffer_limit_car:
active_car_ids.append(car_id) active_car_ids.append(car_id)
else: else:
cars_to_remove.append(car_id) cars_to_remove.append(car_id)
unchecked_trunk_frame = None # 保存未检查后备箱的帧
ignored_trunk_frame = None # 保存漏检的帧
# 处理离场车辆,生成违规告警 # 处理离场车辆,生成违规告警
for car_id in cars_to_remove: for car_id in cars_to_remove:
car_info = self.roi_car_registry[car_id] car_info = self.roi_car_registry[car_id]
@@ -378,13 +420,24 @@ class KadianDetector:
# 情况1通过时间太短 -> Ignore (Too Fast) # 情况1通过时间太短 -> Ignore (Too Fast)
if duration_frames < self.frame_thresh_car_min_duration: if duration_frames < self.frame_thresh_car_min_duration:
logger.info(f"ALARM: Car {car_id} passed too fast -> Regarded as Ignore Checked!") logger.info(f"ALARM: Car {car_id} passed too fast -> Regarded as Ignore Checked!")
self.fast_pass_alerts[car_id] = self.current_frame_idx + int(self.ignore_show_seconds * self.fps) #self.fast_pass_alerts[car_id] = self.current_frame_idx + int(self.ignore_show_seconds * self.fps)
self.fast_pass_alerts[car_id] = current_time_sec + int(self.ignore_show_seconds * self.fps)
target_time_sec = car_info['last_seen'] - self.ignored_rollback_time
ignored_trunk_frame = self.find_target_frame(target_time_sec)
# 情况2时间够长但没检查后备箱 -> Unchecked Trunk # 情况2时间够长但没检查后备箱 -> Unchecked Trunk
elif not car_info['is_checked']: elif not car_info['is_checked']:
logger.info(f"ALARM: Car {car_id} left without checking trunk!") logger.info(f"ALARM: Car {car_id} left without checking trunk!")
self.unchecked_trunk_alerts[car_id] = self.current_frame_idx + int( #self.unchecked_trunk_alerts[car_id] = self.current_frame_idx + int(self.openTrunk_show_seconds * self.fps)
self.openTrunk_show_seconds * self.fps) self.unchecked_trunk_alerts[car_id] = current_time_sec + int(self.openTrunk_show_seconds * self.fps)
target_time_sec = car_info['last_seen'] - self.untrunk_rollback_time
unchecked_trunk_frame = self.find_target_frame(target_time_sec)
del self.roi_car_registry[car_id] del self.roi_car_registry[car_id]
@@ -398,7 +451,8 @@ class KadianDetector:
for police_id, info in self.roi_police_registry.items(): for police_id, info in self.roi_police_registry.items():
last_seen = info['last_seen'] last_seen = info['last_seen']
if (self.current_frame_idx - last_seen) <= self.frame_buffer_limit_police: #if (self.current_frame_idx - last_seen) <= self.frame_buffer_limit_police:
if (current_time_sec - last_seen) <= self.frame_buffer_limit_police:
active_police_ids.append(police_id) active_police_ids.append(police_id)
else: else:
polices_to_remove.append(police_id) polices_to_remove.append(police_id)
@@ -430,8 +484,10 @@ class KadianDetector:
# break # 只显示一次 # break # 只显示一次
# B. 显示 Unchecked Trunk (离场未检查后备箱) # B. 显示 Unchecked Trunk (离场未检查后备箱)
expired_alerts = [cid for cid, end_frame in self.unchecked_trunk_alerts.items() if #expired_alerts = [cid for cid, end_frame in self.unchecked_trunk_alerts.items() if self.current_frame_idx > end_frame]
self.current_frame_idx > end_frame] expired_alerts = [cid for cid, end_frame in self.unchecked_trunk_alerts.items() if current_time_sec > end_frame]
for cid in expired_alerts: for cid in expired_alerts:
del self.unchecked_trunk_alerts[cid] del self.unchecked_trunk_alerts[cid]
@@ -440,13 +496,16 @@ class KadianDetector:
current_frame_alerts.append({ current_frame_alerts.append({
'time': current_time_sec, 'time': current_time_sec,
'action': "Unchecked Trunk", 'action': "Unchecked Trunk",
'image': unchecked_trunk_frame
}) })
#self.draw_alert(frame, alert_text, (0, 0, 255), offset_y=alert_offset) #self.draw_alert(frame, alert_text, (0, 0, 255), offset_y=alert_offset)
alert_offset += 100 alert_offset += 100
# C. 显示 Ignore (通过过快) # C. 显示 Ignore (通过过快)
expired_fast_alerts = [cid for cid, end_frame in self.fast_pass_alerts.items() if #expired_fast_alerts = [cid for cid, end_frame in self.fast_pass_alerts.items() if self.current_frame_idx > end_frame]
self.current_frame_idx > end_frame] expired_fast_alerts = [cid for cid, end_frame in self.fast_pass_alerts.items() if current_time_sec > end_frame]
for cid in expired_fast_alerts: for cid in expired_fast_alerts:
del self.fast_pass_alerts[cid] del self.fast_pass_alerts[cid]
@@ -455,18 +514,25 @@ class KadianDetector:
current_frame_alerts.append({ current_frame_alerts.append({
'time': current_time_sec, 'time': current_time_sec,
'action': "Ignore", 'action': "Ignore",
'image': ignored_trunk_frame
}) })
#self.draw_alert(frame, alert_text, (0, 0, 255), offset_y=alert_offset) #self.draw_alert(frame, alert_text, (0, 0, 255), offset_y=alert_offset)
alert_offset += 100 alert_offset += 100
# D. 显示警察在场状态 (Nobody/Only One) # D. 显示警察在场状态 (Nobody/Only One)
# 清理过期的 Nobody 告警 # 清理过期的 Nobody 告警
expired_nobody = [k for k, v in self.nobody_alerts.items() if self.current_frame_idx > v] #expired_nobody = [k for k, v in self.nobody_alerts.items() if self.current_frame_idx > v]
expired_nobody = [k for k, v in self.nobody_alerts.items() if current_time_sec > v]
for k in expired_nobody: for k in expired_nobody:
del self.nobody_alerts[k] del self.nobody_alerts[k]
# 清理过期的 Only One 告警 # 清理过期的 Only One 告警
expired_only_one = [k for k, v in self.only_one_alerts.items() if self.current_frame_idx > v] # expired_only_one = [k for k, v in self.only_one_alerts.items() if self.current_frame_idx > v]
expired_only_one = [k for k, v in self.only_one_alerts.items() if current_time_sec > v]
for k in expired_only_one: for k in expired_only_one:
del self.only_one_alerts[k] del self.only_one_alerts[k]
@@ -488,7 +554,8 @@ class KadianDetector:
if effective_police_count == 0 and self.nobody_frames >= self.frame_thresh_nobody: if effective_police_count == 0 and self.nobody_frames >= self.frame_thresh_nobody:
alert_text = "Nobody" alert_text = "Nobody"
if "Nobody" not in self.nobody_alerts: if "Nobody" not in self.nobody_alerts:
self.nobody_alerts["Nobody"] = self.current_frame_idx + int(self.police_show_seconds * self.fps) # self.nobody_alerts["Nobody"] = self.current_frame_idx + int(self.police_show_seconds * self.fps)
self.nobody_alerts["Nobody"] = current_time_sec + int(self.police_show_seconds * self.fps)
current_frame_alerts.append({ current_frame_alerts.append({
'time': current_time_sec, 'time': current_time_sec,
'action': "Nobody", 'action': "Nobody",
@@ -498,7 +565,8 @@ class KadianDetector:
elif effective_police_count == 1 and self.only_one_frames >= self.frame_thresh_only_one: elif effective_police_count == 1 and self.only_one_frames >= self.frame_thresh_only_one:
alert_text = "Only One" alert_text = "Only One"
if "Only One" not in self.only_one_alerts: if "Only One" not in self.only_one_alerts:
self.only_one_alerts["Only One"] = self.current_frame_idx + int(self.police_show_seconds * self.fps) # self.only_one_alerts["Only One"] = self.current_frame_idx + int(self.police_show_seconds * self.fps)
self.only_one_alerts["Only One"] = current_time_sec + int(self.police_show_seconds * self.fps)
current_frame_alerts.append({ current_frame_alerts.append({
'time': current_time_sec, 'time': current_time_sec,
'action': "Only One", 'action': "Only One",