同步卡点算法更新

This commit is contained in:
zqc
2026-02-28 14:49:11 +08:00
parent f0ec2cb855
commit 85bad4aa78

View File

@@ -52,18 +52,19 @@ class KadianDetector:
self.params = params if params is not None else {}
# 模型加载
self.detector = YOLOv8_ONNX(DETECT_MODEL_PATH, conf_threshold=0.25, iou_threshold=0.45,
self.detector = YOLOv8_ONNX(DETECT_MODEL_PATH, conf_threshold=0.15, iou_threshold=0.65,
input_size=PERSON_CAR_INPUT_SIZE)
self.pose_detector = YOLOv8_Pose_ONNX(POSE_MODEL_PATH, conf_threshold=0.7, iou_threshold=0.6,
self.pose_detector = YOLOv8_Pose_ONNX(POSE_MODEL_PATH, conf_threshold=0.45, iou_threshold=0.6,
input_size=POSE_INPUT_SIZE)
# Tracker
class TrackerArgs:
track_thresh = 0.25
track_buffer = 30
match_thresh = 0.8
mot20 = False
self.tracker = BYTETracker(TrackerArgs(), frame_rate=10.0)
track_thresh = 0.2
track_buffer = 60
match_thresh = 0.9
mot20 = True
self.tracker = BYTETracker(TrackerArgs(), frame_rate=RTSP_TARGET_FPS)
self.track_role = {}
@@ -78,28 +79,31 @@ class KadianDetector:
# ==========================================
# 1. 业务判定时间阈值
self.TIME_THRESHOLD_ONLY_ONE = 3.0 # 单人单检判定时长
self.TIME_THRESHOLD_NOBODY = 3.0 # 无人检查判定时长
self.TIME_THRESHOLD_ONLY_ONE = 10.0 # 单人单检判定时长
self.TIME_THRESHOLD_NOBODY = 10.0 # 无人检查判定时长
# 后备箱检查判定阈值
self.TIME_THRESHOLD_TRUNK_OPEN = 0.5
self.TIME_THRESHOLD_TRUNK_OPEN = 0.1
# 新增:手机检测判定阈值
self.TIME_THRESHOLD_PHONE = 1.0 # 手机检测持续1秒30帧 @30fps
self.TIME_TOLERANCE_PHONE = 0.5 # 手机丢失缓冲时间(防抖动)
self.TIME_THRESHOLD_PHONE = 3.0 # 手机检测持续1秒30帧 @30fps
self.TIME_TOLERANCE_PHONE = 1.5 # 手机丢失缓冲时间(防抖动)
# 新增:制服检测判定阈值
self.TIME_THRESHOLD_UNIFORM = 1.0 # 制服不合规判定时长
self.TIME_TOLERANCE_UNIFORM = 0.5 # 制服合规恢复缓冲时间
# 车辆最小停留时间阈值 (小于此时间视为无人检查/直接通过)
self.TIME_THRESHOLD_CAR_MIN_DURATION = 3.0
self.TIME_THRESHOLD_UNIFORM = 2.0 # 制服不合规判定时长
self.TIME_TOLERANCE_UNIFORM = 1.0 # 制服合规恢复缓冲时间
# 2. Person 丢帧缓冲
self.TIME_TOLERANCE_PERSON = 1.0
self.TIME_TOLERANCE_PERSON = 3.0
# 车辆最小停留时间阈值 (小于此时间视为无人检查/直接通过)
self.TIME_THRESHOLD_CAR_MIN_DURATION = 10.0
# 3. Car 丢帧/ID维持缓冲
self.TIME_TOLERANCE_CAR = 0.5
self.TIME_TOLERANCE_CAR = 10.0
# 4 OnlyOne 丢帧缓冲
self.TIME_TOLERANCE_ONLY_ONE_DURATION = 3.0
# --- 计算对应的帧数阈值 ---
self.frame_thresh_one = int(self.TIME_THRESHOLD_ONLY_ONE * self.fps)
@@ -118,6 +122,7 @@ class KadianDetector:
self.frame_buffer_limit_person = int(self.TIME_TOLERANCE_PERSON * self.fps)
self.frame_buffer_limit_car = int(self.TIME_TOLERANCE_CAR * self.fps)
self.frame_buffer_limit_onlyOne = int(self.TIME_TOLERANCE_ONLY_ONE_DURATION * self.fps)
logger.info(f"\n超参数设置:")
logger.info(f" FPS: {self.fps:.2f}")
@@ -127,22 +132,22 @@ class KadianDetector:
logger.info(f" 手机丢失缓冲帧数: {self.frame_buffer_phone}")
logger.info(f" 判定 'Uniform Invalid' 需连续检测: {self.frame_thresh_uniform}")
logger.info(f" 制服合规恢复缓冲帧数: {self.frame_buffer_uniform}")
logger.info(f" 判定 'Too Fast' (视为Nobody) 最小停留: {self.frame_thresh_car_min_duration}")
logger.info(f" 判定 'Too Fast' 最小停留: {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_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_buffer_limit = self.frame_buffer_limit_onlyOne
self.nobody_thresh = self.frame_thresh_nobody # 20帧2秒
self.current_frame_idx = 0
self.cnt_frame_one_person = 0
self.cnt_frame_nobody = 0
self.cnt_missing_buffer_person = 0
self.ignore_show_seconds = 0.5 # 未检测的警告显示时长
self.openTrunk_show_seconds = 0.5 # 打开后备箱的警告显示时长
# 手机检测状态变量(独立于车辆)
self.phone_detection_frames = 0 # 连续检测到手机的帧数
@@ -161,7 +166,7 @@ class KadianDetector:
# 违规车辆记录 (后备箱未检)
self.unchecked_trunk_alerts = {}
# 违规车辆记录 (通过过快 -> 归类为 Nobody)
# 违规车辆记录 (通过过快 -> 归类为 Ignore)
self.fast_pass_alerts = {}
def _get_roi_points(self, frame_width: int, frame_height: int):
@@ -226,6 +231,53 @@ class KadianDetector:
x1, y1, x2, y2 = box
return x1 < px < x2 and y1 < py < y2
def is_pose_inside_detector_person(self, pose_bbox, dets_xyxy, dets_roles):
"""
判断一个 pose 人是否位于 detector 的 person 框内部(中心点匹配)
参数:
pose_bbox: [x1, y1, x2, y2]
dets_xyxy: detector 输出的所有 bbox 列表
dets_roles: 对应的类别列表(如 "person", "car"...
返回:
True -> 在某个人体框内部
False -> 不在任何人体框内部
"""
px1, py1, px2, py2 = pose_bbox
cx, cy = (px1 + px2) // 2, (py1 + py2) // 2
for box, role in zip(dets_xyxy, dets_roles):
if role != "person":
continue
dx1, dy1, dx2, dy2 = map(int, box)
# 判断中心点是否在 detector person 框内
if dx1 <= cx <= dx2 and dy1 <= cy <= dy2:
return True
return False
def count_pose_inside_detector_person(self, pose_results, dets_xyxy, dets_roles):
"""
统计有多少个pose框在detector person框内部
参数:
pose_results: pose检测结果列表每个元素为字典包含'bbox'键,值为[x1, y1, x2, y2]
dets_xyxy: detector输出的所有bbox列表
dets_roles: 对应的类别列表(如 "person", "car"...
返回:
int: 在detector person框内部的pose框数量
"""
count = 0
for pose in pose_results:
pose_bbox = pose['bbox'] # [x1, y1, x2, y2]
if self.is_pose_inside_detector_person(pose_bbox, dets_xyxy, dets_roles):
count += 1
return count
def process_frame(self, frame, camera_id: int, timestamp: float) -> Dict[str, Any]:
h, w = frame.shape[:2]
@@ -421,20 +473,23 @@ class KadianDetector:
if self.check_point_in_roi(roi_points_int32, (cx, cy)):
self.pose_person_count += 1
# 统计pose框在detector person框内部的数量
pose_inside_count = self.count_pose_inside_detector_person(pose_results, dets_xyxy, dets_roles)
# ==========================================
# 5. 关联分析: 哪个后备箱属于哪辆车?
# ==========================================
for car_info in current_cars:
c_id = car_info['id']
c_box = car_info['box']
c_id = car_info['id'] # 车的id
c_box = car_info['box'] # 车的框
trunk_found_for_this_car = False
trunk_found_for_this_car = False # 开后备箱标记
for t_pt in current_trunks:
if self.is_point_in_box(t_pt, c_box):
if self.is_point_in_box(t_pt, c_box): # 如果开后备箱的框在车的框内,就设置开后备箱标记为true
trunk_found_for_this_car = True
break
if trunk_found_for_this_car:
if trunk_found_for_this_car: # 如果当前车辆的开后备箱标记为true了,就设置开了后备箱的帧数+1,凑够了判断“开后备箱”这个动作的帧数之后,就设置该车"已检查"
self.roi_car_registry[c_id]['trunk_frames'] += 1
if self.roi_car_registry[c_id]['trunk_frames'] >= self.frame_thresh_trunk_valid:
self.roi_car_registry[c_id]['is_checked'] = True
@@ -501,6 +556,7 @@ class KadianDetector:
cars_to_remove = []
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:
@@ -510,19 +566,25 @@ class KadianDetector:
# 执行删除 并 检查违规
for car_id in cars_to_remove:
# 遍历所有移除列表中的车辆,
# 如果该车辆最后出现时间-最早出现时间的值小于车辆最小存在时间,则判断为ignore,
# 如果该车辆的“已检查”标记为true,则
# 最后在所有车辆列表中删除该车辆
car_info = self.roi_car_registry[car_id]
duration_frames = car_info['last_seen'] - car_info['first_seen']
# 情况1通过时间太短 -> 归类为 Nobody (Too Fast)
# 情况1通过时间太短 -> 归类为 Ignore (Too Fast)
if duration_frames < self.frame_thresh_car_min_duration:
logger.warning(f"ALARM: Car {car_id} passed too fast -> Regarded as Nobody Checked!")
self.fast_pass_alerts[car_id] = self.current_frame_idx + int(3.0 * self.fps)
logger.warning(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)
# 情况2时间够长但没检查后备箱 -> Unchecked Trunk
elif not car_info['is_checked']:
logger.warning(f"ALARM: Car {car_id} left without checking trunk!")
self.unchecked_trunk_alerts[car_id] = self.current_frame_idx + int(3.0 * self.fps)
self.unchecked_trunk_alerts[car_id] = self.current_frame_idx + int(
self.openTrunk_show_seconds * self.fps)
del self.roi_car_registry[car_id]
@@ -565,51 +627,37 @@ class KadianDetector:
# ==========================================
if effective_car_count >= 0: # 只要没人就检测,不用等到来了车再检测
# ----- 定义条件 -----
onlyone_condition = (current_roi_person_count == 1 and self.pose_person_count == 1)
onlyone_condition = (pose_inside_count == 1)
nobody_condition = (current_roi_person_count == 0 and self.pose_person_count == 0)
# ----- Onlyone 计数器更新 -----
if onlyone_condition:
if onlyone_condition: # 如果骨骼点和检测框都检测到了只有一个人时,onlyone+1,当onlyone累计够了之后触发报警
self.onlyone_counter += 1
self.onlyone_lost_counter = 0
else:
if self.onlyone_counter > 0:
self.onlyone_lost_counter += 1
if self.onlyone_lost_counter > self.onlyone_buffer_limit:
# self.onlyone_lost_counter = 0
elif current_roi_person_count > 1 or self.pose_person_count > 1:
self.onlyone_counter = 0
self.onlyone_lost_counter = 0
# 没有累积的 Onlyone 则不做任何事
# 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
# ----- 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_present_counter = 0
elif current_roi_person_count > 0 or self.pose_person_count > 0:
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 = ""
# 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
else:
# 无活跃车辆,清零所有计数器
self.onlyone_counter = 0
self.onlyone_lost_counter = 0
# self.onlyone_lost_counter = 0
self.nobody_counter = 0
self.nobody_present_counter = 0
@@ -699,7 +747,7 @@ class KadianDetector:
# E. 新增:显示 Unvaild Uniform!!
if self.uniform_alert_active:
# 显示具体数量差异
diff = self.pose_person_count - current_roi_person_count
# diff = self.pose_person_count - current_roi_person_count
#sub_text = f"Missing {diff} uniform(s)"
current_frame_alerts.append(
{
@@ -757,7 +805,7 @@ class KadianDetector:
return {
"image": frame,
"alerts":current_frame_alerts
"alerts": current_frame_alerts,
}