同步卡点算法更新
This commit is contained in:
@@ -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):
|
||||
@@ -182,10 +187,10 @@ class KadianDetector:
|
||||
# 强制转为 int32(关键!解决 OpenCV 断言错误)
|
||||
return roi_abs.astype(np.int32)
|
||||
|
||||
def check_point_in_roi(self,roi_points, point):
|
||||
def check_point_in_roi(self, roi_points, point):
|
||||
return cv2.pointPolygonTest(roi_points, point, False) >= 0
|
||||
|
||||
def compute_iou(self,boxA, boxB):
|
||||
def compute_iou(self, boxA, boxB):
|
||||
# box = [x1, y1, x2, y2]
|
||||
xA = max(boxA[0], boxB[0])
|
||||
yA = max(boxA[1], boxB[1])
|
||||
@@ -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]
|
||||
@@ -310,7 +362,7 @@ class KadianDetector:
|
||||
# IoU 匹配角色
|
||||
# if tid not in track_role and dets_xyxy:
|
||||
REVALIDATE_FRAME_INTERVAL = 10
|
||||
#if tid not in self.track_role:
|
||||
# if tid not in self.track_role:
|
||||
if (self.current_frame_idx % REVALIDATE_FRAME_INTERVAL == 0) or (tid not in self.track_role):
|
||||
best_iou = 0
|
||||
best_role = "unknown"
|
||||
@@ -346,7 +398,7 @@ class KadianDetector:
|
||||
color = None
|
||||
label = None
|
||||
|
||||
if self.check_point_in_roi(roi_points_int32,(cx, cy)):
|
||||
if self.check_point_in_roi(roi_points_int32, (cx, cy)):
|
||||
if cls_id == 0: # Car
|
||||
color = (0, 255, 0)
|
||||
|
||||
@@ -418,23 +470,26 @@ class KadianDetector:
|
||||
x1, y1, x2, y2 = pose['bbox'][0], pose['bbox'][1], pose['bbox'][2], pose['bbox'][3]
|
||||
cx, cy = (x1 + x2) // 2, (y1 + y2) // 2
|
||||
# 判断中心点是否在ROI内
|
||||
if self.check_point_in_roi(roi_points_int32,(cx, cy)):
|
||||
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
|
||||
|
||||
@@ -686,7 +734,7 @@ 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,
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user