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

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 numpy as np
from typing import Dict, Any
import threading
import queue
from collections import deque
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 utils.logger import get_logger
from common.constants import MODEL_ROOT_PATH
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_RELATIVE = np.array([
@@ -39,7 +43,6 @@ ROI_RELATIVE=np.array([
[0.15,0.15],
[0.37,0.15],
[0.55,0.2],
[0.9,0.85],
[0.35,0.85]
])
@@ -60,17 +63,9 @@ class KadianDetector:
# 摄像头额外参数
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(
full_model_path,
DETECT_MODEL_PATH,
conf_threshold=0.6,
iou_threshold=0.65,
input_size=PERSON_CAR_INPUT_SIZE
@@ -125,6 +120,8 @@ class KadianDetector:
self.height = 0
# 车辆注册表 (字典)
self.roi_car_registry = {}
# 违规车辆记录
@@ -140,6 +137,12 @@ class KadianDetector:
self.nobody_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):
"""
@@ -206,6 +209,21 @@ class KadianDetector:
x1, y1, x2, y2 = box
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]:
h, w = frame.shape[:2]
self.width, self.height = w, h
@@ -217,6 +235,8 @@ class KadianDetector:
current_time_sec = timestamp
# ========= 主检测删除pose检测=========
detections = self.detector(frame)
@@ -267,7 +287,8 @@ class KadianDetector:
# ========= 处理跟踪结果 =========
for t in tracks:
tid = t.track_id
REVALIDATE_FRAME_INTERVAL = 10
REVALIDATE_FRAME_INTERVAL = 2
# 定期重新匹配跟踪ID的类别
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:
self.roi_car_registry[tid] = {
'first_seen': self.current_frame_idx,
'last_seen': self.current_frame_idx,
# 'first_seen': self.current_frame_idx,
# 'last_seen': self.current_frame_idx,
'first_seen': current_time_sec,
'last_seen': current_time_sec,
'trunk_frames': 0,
'is_checked': False,
#'frame_buffer': deque(maxlen=self.max_car_frames), # 新增
}
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"
elif role == "opentrunk":
color = (255, 165, 0) # 橙色
@@ -325,11 +350,13 @@ class KadianDetector:
# 警察注册表初始化
if tid not in self.roi_police_registry:
self.roi_police_registry[tid] = {
'first_seen': self.current_frame_idx,
'last_seen': self.current_frame_idx,
# 'first_seen': self.current_frame_idx,
# 'last_seen': self.current_frame_idx,
'first_seen': current_time_sec,
'last_seen': current_time_sec,
}
else:
self.roi_police_registry[tid]['last_seen'] = self.current_frame_idx
self.roi_police_registry[tid]['last_seen'] = current_time_sec
label += " IN"
else:
color = (255, 255, 255) # 白色
@@ -339,6 +366,16 @@ class KadianDetector:
cv2.rectangle(frame, (x1, y1), (x2, y2), 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():
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)
else:
cars_to_remove.append(car_id)
unchecked_trunk_frame = None # 保存未检查后备箱的帧
ignored_trunk_frame = None # 保存漏检的帧
# 处理离场车辆,生成违规告警
for car_id in cars_to_remove:
car_info = self.roi_car_registry[car_id]
@@ -378,13 +420,24 @@ class KadianDetector:
# 情况1通过时间太短 -> Ignore (Too Fast)
if duration_frames < self.frame_thresh_car_min_duration:
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
elif not car_info['is_checked']:
logger.info(f"ALARM: Car {car_id} left without checking trunk!")
self.unchecked_trunk_alerts[car_id] = self.current_frame_idx + int(
self.openTrunk_show_seconds * self.fps)
#self.unchecked_trunk_alerts[car_id] = self.current_frame_idx + int(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]
@@ -398,7 +451,8 @@ class KadianDetector:
for police_id, info in self.roi_police_registry.items():
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)
else:
polices_to_remove.append(police_id)
@@ -430,8 +484,10 @@ class KadianDetector:
# break # 只显示一次
# B. 显示 Unchecked Trunk (离场未检查后备箱)
expired_alerts = [cid for cid, end_frame in self.unchecked_trunk_alerts.items() if
self.current_frame_idx > end_frame]
#expired_alerts = [cid for cid, end_frame in self.unchecked_trunk_alerts.items() if 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:
del self.unchecked_trunk_alerts[cid]
@@ -440,13 +496,16 @@ class KadianDetector:
current_frame_alerts.append({
'time': current_time_sec,
'action': "Unchecked Trunk",
'image': unchecked_trunk_frame
})
#self.draw_alert(frame, alert_text, (0, 0, 255), offset_y=alert_offset)
alert_offset += 100
# C. 显示 Ignore (通过过快)
expired_fast_alerts = [cid for cid, end_frame in self.fast_pass_alerts.items() if
self.current_frame_idx > end_frame]
#expired_fast_alerts = [cid for cid, end_frame in self.fast_pass_alerts.items() if 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:
del self.fast_pass_alerts[cid]
@@ -455,18 +514,25 @@ class KadianDetector:
current_frame_alerts.append({
'time': current_time_sec,
'action': "Ignore",
'image': ignored_trunk_frame
})
#self.draw_alert(frame, alert_text, (0, 0, 255), offset_y=alert_offset)
alert_offset += 100
# D. 显示警察在场状态 (Nobody/Only One)
# 清理过期的 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:
del self.nobody_alerts[k]
# 清理过期的 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:
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:
alert_text = "Nobody"
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({
'time': current_time_sec,
'action': "Nobody",
@@ -498,7 +565,8 @@ class KadianDetector:
elif effective_police_count == 1 and self.only_one_frames >= self.frame_thresh_only_one:
alert_text = "Only One"
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({
'time': current_time_sec,
'action': "Only One",