【工业级落地算法之打架斗殴检测算法详解】

打架斗殴检测算法详解

目录


概述

这是一套基于深度学习的实时打架斗殴检测算法 ,应用于地铁、学校、商场等公共场所的安防监控场景。系统采用多模型级联 + 时序分析的方案,能够有效识别挥拳、踢腿、推搡等暴力行为,同时过滤摔倒、拥抱、搀扶等误报场景。

核心特性

  • 多模型协同: YOLOv8 检测 + YOLOv8 Pose 姿态估计 + TSM 时序动作分类
  • 时序分析: 基于 16 帧连续视频片段进行行为判断,避免单帧误判
  • 目标跟踪: 使用 BYTETracker 实现跨帧目标追踪,保证行为分析的连续性
  • 姿态校验: 基于人体关键点检测挥拳、踢腿等攻击性动作
  • 智能过滤: 自动过滤搭肩、摸头、摔倒等非打架场景
  • 滑动窗口: 8 帧滑动重叠,保证时序分析连续性
  • 命中帧机制: YOLO 命中率校验,过滤误检测

系统架构

复制代码
┌─────────────────────────────────────────────────────────────────┐
│                      打架斗殴检测流水线                           │
└─────────────────────────────────────────────────────────────────┘

  输入视频流
       │
       ▼
┌──────────────────┐
│  1. YOLOv8 检测   │  ← 检测画面中所有人员 (打架检测模型)
│  2. YOLOv8-Pose  │  ← 提取每个人体关键点 (17 个点)
│     (并行推理)    │
└──────────────────┘
       │
       ▼
┌──────────────────┐
│ 3. 姿态规则过滤   │  ← 基于角度/距离校验攻击姿态 (挥拳/踢腿)
│                   │     过滤搭肩/摸头/摔倒等误报
└──────────────────┘
       │
       ▼
┌──────────────────┐
│ 4. BYTETracker   │  ← 跨帧跟踪目标,维持 ID 一致性
│                   │     卡尔曼滤波预测丢失目标
└──────────────────┘
       │
       ▼
┌──────────────────┐
│ 5. 16 帧采集缓冲   │  ← 滑动窗口收集连续帧
│   + 命中帧记录     │     记录检测每帧命中情况
└──────────────────┘
       │
       ▼
┌──────────────────┐
│ 6. 命中率校验     │  ← 命中率≥45% 才触发 TSM
│                   │     命中率<25% 停止采帧
└──────────────────┘
       │
       ▼
┌──────────────────┐
│ 7. TSM 分类器     │  ← 时序动作分类 (正常/打架)
└──────────────────┘
       │
       ▼
┌──────────────────┐
│ 8. 时序阈值判断   │  ← 持续时间超过配置阈值则报警
└──────────────────┘
       │
       ▼
    报警输出

模型介绍

1. YOLOv8 检测模型

用途: 检测画面中可能存在打架行为的人员区域,为后续模型排除噪声,锁定感兴趣区域。

模型配置
参数
模型版本 YOLOv8s (Small)
输入尺寸 640×384
输出 (4, 5, 5040) - 4 坐标 + 1 置信度
检测类别 单分类 (fight)
置信度阈值 0.7
IoU 阈值 0.5
训练数据
  • 数据集规模: 8000 张标注图片
  • 数据场景: 地铁、车站等公共场所的监控视频截图
  • 标注内容: 打架斗殴人员检测框
  • 数据增强: Mosaic、Mixup、随机翻转、色彩抖动等
版本选择说明

选择 YOLOv8s 而非更大版本的原因:

  • 精度平衡: s 版本在精度和速度之间取得良好平衡
  • 实时性要求: 监控场景需要高帧率处理,s 版本推理速度更快
  • 场景特点: 打架检测对极致精度要求不高,后续有 TSM 二次校验

2. YOLOv8-Pose 姿态估计模型

用途: 提取每个人体的 17 个关键点,用于后续姿态分析

模型配置
参数
模型版本 YOLOv8m-pose (Medium)
权重来源 YOLOv8 官方预训练权重
输入尺寸 640×384
输出 (4, 56, 5040) - 4 坐标 + 52 关键点 (17 点×3 坐标)
置信度阈值 0.25
IoU 阈值 0.45
关键点定义
复制代码
0:鼻子,1:左眼,2:右眼,3:左耳,4:右耳
5:左肩,6:右肩,7:左肘,8:右肘,9:左手腕
10:右手腕,11:左髋,12:右髋,13:左膝,14:右膝
15:左脚踝,16:右脚踝
版本选择说明

选择 YOLOv8m-pose 并使用官方预训练权重的原因:

  • 关键点精度: m 版本在关键点检测精度上明显优于 s 版本
  • 迁移学习: 使用 COCO 数据集预训练权重,无需重新训练即可获得良好效果
  • 通用性强: 人体关键点在不同场景下具有较好的泛化能力
  • 算力可接受: pose 模型与检测模型并行推理,m 版本延迟可接受

3. TSM 时序分类模型

用途: 对 16 帧连续视频片段进行时序分析,判断是否为打架行为

模型配置
参数
模型架构 TSM (Temporal Shift Module)
输入格式 N×3×224×224 (N 为帧数)
采样帧数 16 帧
采样方式 均匀分段采样 (16 段每段取中心帧)
输出 (2,) - [正常,打架] 的概率分布
类别 0:正常行走,1:打架斗殴
训练数据
  • 数据集规模: 2100 个精选打架视频片段,包括双人对打和多人混打
  • 数据来源: 真实监控录像剪辑
  • 数据清洗: 人工筛选高质量、清晰度的打架视频
  • 视频裁剪: 对原始视频进行裁剪,聚焦于打架区域
实验对比

在训练过程中,测试了两种输入配置:

配置 输入形状 效果
方案 A 8×3×224×224 8 帧输入,时序信息不足
方案 B 16×3×224×224 16 帧输入,效果明显提升

结论 : 16 帧输入相比 8 帧在打架检测任务上具有明显提升,原因:

  • 时序信息更丰富: 能够捕捉更完整的动作过程
  • 动作趋势更清晰: 对抗性行为的连续性更容易识别
  • 误报率更低: 瞬时动作不易被误判为打架
TSM 原理简述

TSM (Temporal Shift Module) 是一种高效的时间序列建模方法:

复制代码
┌─────────────────────────────────────────────────────┐
│                  TSM 核心思想                        │
└─────────────────────────────────────────────────────┘

  输入:[16 帧 × 3 通道] → 合并为 [48 通道]
              │
              ▼
  普通 CNN 卷积 (2D)
              │
              ▼
  在通道维度上进行时间移位操作
  (让相邻帧的信息能够交互)
              │
              ▼
  输出:分类结果 [正常,打架]

优势:

  • 无需额外的时序建模模块 (如 LSTM、Transformer)
  • 可以直接使用 2D CNN 骨干网络
  • 推理效率高,适合实时场景

核心算法流程

第一阶段:检测与姿态估计 (并行推理)

两模型并行推理,使用线程池加速:

python 复制代码
def __call__(self, image: np.ndarray):
    """
    并行执行检测和姿态估计,提高推理效率
    
    关键优化:
    - 检测和 Pose 模型独立推理,互不依赖
    - 使用线程池并行提交任务
    - 等待两个结果都返回后再继续
    """
    detection_future = executor.submit(detect_model, image)
    pose_future = executor.submit(pose_model, image)
    detection_result = detection_future.result()  # 检测结果
    pose_result = pose_future.result()  # 姿态结果
    return list(zip(detection_result, pose_result))

并行推理效果:

  • 串行执行:检测耗时 + Pose 耗时 ≈ 60ms + 100ms = 160ms
  • 并行执行:max(检测耗时,Pose 耗时) ≈ 100ms
  • 速度提升约 37%

第二阶段:姿态规则过滤

基于人体关键点检测攻击性动作,核心逻辑如下:

2.1 区域过滤
  • 每个检测框内必须包含 ≥2 人 (通过 Pose 框中心点判断)
  • 单框人数不足直接跳过
2.2 攻击姿态识别

挥拳检测:

python 复制代码
# 计算肘关节角度 (肩 - 肘-腕)
angle = calculate_angle(shoulder, elbow, wrist)

# 判定条件:
# 1. 肘部角度 < 150° (手臂弯曲成攻击姿态)
# 2. 手腕高度 < 肩膀高度 × 0.85 (手臂向前伸出)
# 3. 手腕未搭在肩膀上 (排除搭肩)
# 4. 手腕未接触自己头部 (排除摸头/打电话)
if angle < PUNCH_ANGLE_THRESH and height_diff < limit_h:
    if not is_rest and not is_self_touch:
        has_attack_action = True

踢腿检测:

python 复制代码
# 计算膝关节角度 (髋 - 膝-踝)
angle = calculate_angle(hip, knee, ankle)

# 判定条件:
# 1. 膝关节角度 < 135° (腿部弯曲)
# 2. 踝部高于膝部 (抬腿动作)
# 3. 踝部高度差 < 身体比例 × 0.45 (踢击高度合理)
# 4. 检测到踢向他人 (踝部靠近他人髋部)
if angle < KICK_ANGLE_THRESH and is_high and is_hit:
    has_attack_action = True
2.3 过滤规则
场景 过滤逻辑
搭肩 检测手腕是否 resting 在他人肩膀上
摸头/打电话 检测手腕是否接触自己头部关键点
摔倒 单人剧烈动作,无对抗目标
搀扶/拥抱 肢体接触但无攻击角度

第三阶段:目标跟踪与"捞回"机制

使用 BYTETracker 进行目标跟踪,核心功能:

python 复制代码
class ActionTracker:
    def __init__(self, tlwh, score, class_id):
        super().__init__(tlwh, score, class_id)
        self.capture_mode = False       # 采帧模式开关
        self.frame_buffer = deque(16)   # 16 帧裁剪缓冲
        self.hit_history = deque(16)    # 检测命中历史
        self.is_fighting = False        # 是否正在打架
        self.alarm_flag = False         # 报警标志

"捞回"机制:

python 复制代码
# 1. 存入当前帧成功匹配的活跃目标
for tracker in tracked_results:
    valid_tracks[tracker.id] = tracker

# 2. 捞回那些正在采帧,但当前帧被检测漏检的目标
for tid, tracker in active_captures.items():
    if tid not in valid_tracks:
        # 只要还没被彻底移除,卡尔曼滤波就在盲推坐标,我们必须继续采帧!
        if tracker.state != REMOVED:
            valid_tracks[tid] = tracker

# 更新字典
active_captures = valid_tracks

为什么需要"捞回":

  • 检测存在漏检可能 (遮挡、快速运动等)
  • Tracker 的卡尔曼滤波可以预测丢失目标的位置
  • 保持采帧连续性,避免因单帧漏检中断分析

第四阶段:16 帧采集与滑动窗口

4.1 动态裁剪框选择
python 复制代码
# 检查检测当前帧是否在该目标区域有检测结果 (IoU > 0.35)
is_detected = False
best_box = None
best_iou = 0

for det_box in detection_results:
    iou = compute_iou(tracker_box, det_box)
    if iou > 0.35 and iou > best_iou:
        is_detected = True
        best_iou = iou
        best_box = det_box

# --- 核心优化:动态决定用于 TSM 裁剪的基准框 ---
# 如果检测命中了,说明检测框更贴合真实人体边缘,优先使用
# 如果检测漏检了,使用 Tracker 的预测框维持画面连续性
crop_base_box = best_box if is_detected else tracker_box
4.2 滑动窗口机制
python 复制代码
# 满 16 帧进行分析
if len(tracker.frame_buffer) == 16:
    # 计算命中比例
    hit_ratio = sum(tracker.hit_history) / 16.0
    
    # 判断比例是否达标 (按规定设置 45%,即 0.45)
    if hit_ratio >= 0.45:
        # 触发 TSM 分析
        tsm_result = model_tsm.predict(list(tracker.frame_buffer))
        # ... 处理结果
    else:
        # 命中率太低,跳过 TSM 节省算力
        tracker.is_fighting = False

# 控制后续行为
# 如果目标已经完全不再被检出 (比例很低),彻底结束采集模式节约算力
if hit_ratio < 0.25:
    tracker.capture_mode = False
    tracker.frame_buffer.clear()
    tracker.hit_history.clear()
else:
    # --- 滑动窗口:弹出最旧 8 帧,保持采帧模式进行下一轮推理 ---
    for _ in range(8):
        tracker.frame_buffer.popleft()
        tracker.hit_history.popleft()

滑动窗口详解:

复制代码
┌─────────────────────────────────────────────────────────────────┐
│                     滑动窗口工作机制                              │
└─────────────────────────────────────────────────────────────────┘

  第 1 轮分析 (帧 1-16):
  ┌───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┐
  │ 1 │ 2 │ 3 │ 4 │ 5 │ 6 │ 7 │ 8 │ 9 │10 │11 │12 │13 │14 │15 │16 │
  └───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┘
                              ↓ TSM 分析
                              ↓ 弹出 8 帧 (1-8)

  第 2 轮分析 (帧 9-24):
              ┌───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┐
              │ 9 │10 │11 │12 │13 │14 │15 │16 │17 │18 │19 │20 │21 │22 │23 │24 │
              └───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┘
                              ↓ TSM 分析
                              ↓ 弹出 8 帧 (9-16)

  第 3 轮分析 (帧 17-32):
                      ┌───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┐
                      │17 │18 │19 │20 │21 │22 │23 │24 │25 │26 │27 │28 │29 │30 │31 │32 │
                      └───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┘

  窗口大小:16 帧
  滑动步长:8 帧
  重叠比例:50%

滑动窗口优势:

  1. 时序连续性: 50% 重叠保证动作不会在窗口边界被截断
  2. 实时响应: 每 8 帧进行一次分析,响应延迟更低
  3. 计算效率: 复用上一轮的部分帧,减少重复计算

第五阶段:命中帧机制与校验

5.1 检测命中率计算
python 复制代码
# 记录每一帧检测是否命中该目标
tracker.hit_history.append(is_detected)

# 满 16 帧后计算命中率
hit_ratio = sum(tracker.hit_history) / 16.0
5.2 命中率阈值策略
命中率范围 处理方式 说明
≥ 45% 触发 TSM 分析 检测稳定,可信度高
25% ~ 45% 不分析,继续采帧 检测不稳定,等待更多数据
< 25% 停止采帧,重置状态 目标已消失或严重遮挡

为什么需要命中率校验:

复制代码
场景 1: 检测误检
- 单帧检测到"打架",但后续帧无对应目标
- 命中率会很低 (<25%)
- 直接跳过 TSM,节省算力

场景 2: 目标部分遮挡
- 目标在画面中时断时续
- 命中率中等 (25%~45%)
- 继续采帧,等待更完整数据

场景 3: 真实打架持续
- 目标持续在画面中
- 命中率高 (≥45%)
- 触发 TSM 分析

第六阶段:时序分类与报警

python 复制代码
# TSM 预测为打架且置信度达标
if tsm_result['class_id'] == 1 and tsm_result['confidence'] >= action_confidence:
    if not tracker.is_fighting:
        tracker.is_fighting = True
        tracker.fight_start_time = current_time
    
    # 持续时间超过阈值才报警
    duration = current_time - tracker.fight_start_time
    if duration >= fight_duration_threshold:
        tracker.alarm_flag = True  # 触发报警
else:
    # 不是打架或置信度低,重置状态
    tracker.is_fighting = False
    tracker.fight_start_time = 0.0
    tracker.alarm_flag = False

关键代码解析

核心函数:detect_attack_actions 姿态过滤

位置:核心姿态校验逻辑

python 复制代码
def detect_attack_actions(detection_boxes, pose_boxes, pose_keypoints):
    """
    逐框校验检测结果:筛选出"框内≥2 人 + 至少 1 人有打架动作"的检测框
    
    参数:
        detection_boxes: 人员检测框 [x1, y1, x2, y2, conf, cls]
        pose_boxes: 关键点框 [x1, y1, x2, y2, conf, cls]
        pose_keypoints: 每个人体的 17 个关键点坐标 [17, 3] (x, y, confidence)
    
    返回:
        valid_boxes: 通过校验的检测框
    
    优化技巧:
        1. 预计算所有 Pose 框中心点,避免重复计算
        2. 提前返回:Pose 框少于 2 直接返回空
        3. 提前 break: 发现攻击动作立即跳出循环
    """
    
    # ========== 基础校验:Pose 框少于 2 人直接返回 ==========
    if len(pose_boxes) < 2:
        return []  # 不足 2 人不可能打架
    
    # ========== 预计算优化:所有 Pose 框中心点 ==========
    pose_centers = []
    for pose_box in pose_boxes:
        cx = (pose_box[0] + pose_box[2]) / 2
        cy = (pose_box[1] + pose_box[3]) / 2
        pose_centers.append((cx, cy))
    
    # 配置阈值
    KEYPOINT_CONF_THRESH = 0.30
    PUNCH_ANGLE_THRESH = 150
    KICK_ANGLE_THRESH = 135
    
    # ========== 逐个分析每个检测框 ==========
    valid_boxes = []
    for det_idx, det_box in enumerate(detection_boxes):
        
        # 步骤 1: 找出当前检测框内包含的所有 Pose 关键点框
        pose_indices_in_box = []
        for pose_idx, (cx, cy) in enumerate(pose_centers):
            if point_in_box(cx, cy, det_box):
                pose_indices_in_box.append(pose_idx)
        
        # 步骤 2: 当前检测框内 Pose 人数不足 2,跳过
        if len(pose_indices_in_box) < 2:
            continue
        
        # 收集区域内所有人的关键部位 (肩膀、髋部)
        all_shoulders = []
        all_hips = []
        for idx in pose_indices_in_box:
            kp = pose_keypoints[idx]
            if kp[5][2] > KEYPOINT_CONF_THRESH: all_shoulders.append(kp[5][:2])
            if kp[6][2] > KEYPOINT_CONF_THRESH: all_shoulders.append(kp[6][:2])
            if kp[11][2] > KEYPOINT_CONF_THRESH: all_hips.append(kp[11][:2])
            if kp[12][2] > KEYPOINT_CONF_THRESH: all_hips.append(kp[12][:2])
        
        # 步骤 3: 检查该检测框内是否有至少 1 人具备打架动作
        has_attack_action = False
        
        for pose_idx in pose_indices_in_box:
            kps = pose_keypoints[pose_idx]
            
            # 关键点有效性检查
            def is_valid(kp_idx):
                return kps[kp_idx][2] > KEYPOINT_CONF_THRESH
            
            # 动态比例尺:根据肩到髋的距离自适应
            body_scale = 50.0
            if is_valid(5) and is_valid(11):
                body_scale = max(30.0, calculate_distance(kps[5][:2], kps[11][:2]))
            
            # 辅助函数:判断手腕是否搭在他人肩膀上
            def is_resting_on_shoulder(wrist_pt):
                for sh_pt in all_shoulders:
                    if calculate_distance(wrist_pt, sh_pt) < body_scale * 0.5:
                        return True
                return False
            
            # 辅助函数:判断是否踢向他人目标
            def is_kick_hitting_target(ankle_pt, own_hip_pt):
                for hip_pt in all_hips:
                    if calculate_distance(hip_pt, own_hip_pt) < body_scale * 0.3:
                        continue  # 跳过自己的髋部
                    if calculate_distance(ankle_pt, hip_pt) < body_scale * 1.2:
                        return True
                return False
            
            # 辅助函数:判断是否触摸自己头部
            def is_touching_own_head(wrist_pt):
                HEAD_KPS = [0, 1, 2, 3, 4]
                head_touch_thresh = body_scale * 0.6
                for head_idx in HEAD_KPS:
                    if is_valid(head_idx):
                        head_pt = kps[head_idx][:2]
                        if calculate_distance(wrist_pt, head_pt) < head_touch_thresh:
                            return True
                return False
            
            # ---------- 右手挥拳检测 ----------
            if is_valid(6) and is_valid(8) and is_valid(10):  # 右肩 - 右肘 - 右手腕
                angle = calculate_angle(kps[6][:2], kps[8][:2], kps[10][:2])
                height_diff = kps[10][1] - kps[6][1]
                is_rest = is_resting_on_shoulder(kps[10][:2])
                is_self_touch = is_touching_own_head(kps[10][:2])
                limit_h = body_scale * 0.85
                
                if angle < PUNCH_ANGLE_THRESH:  # 肘部角度小于阈值
                    if height_diff < limit_h:   # 手腕低于肩膀
                        if not is_rest:         # 不是搭肩
                            has_attack_action = True
                            break  # 提前退出,无需继续检查
                        elif is_self_touch:
                            pass  # 摸头,过滤
                        else:
                            pass  # 搭肩,过滤
            
            # ---------- 左手挥拳检测 (逻辑同右手) ----------
            # ---------- 右腿踢击检测 ----------
            # ---------- 左腿踢击检测 ----------
        
        # 步骤 4: 判断结果
        if has_attack_action:
            valid_boxes.append(det_box)
    
    return valid_boxes

关键点提取说明:

python 复制代码
# COCO 17 点关键点索引
# 0:鼻子,1:左眼,2:右眼,3:左耳,4:右耳
# 5:左肩,6:右肩,7:左肘,8:右肘
# 9:左手腕,10:右手腕,11:左髋,12:右髋
# 13:左膝,14:右膝,15:左脚踝,16:右脚踝

# 示例:获取右手腕坐标 (置信度>0.30 才有效)
if kps[10][2] > KEYPOINT_CONF_THRESH:
    right_wrist = kps[10][:2]  # (x, y)

角度计算函数:

python 复制代码
def calculate_angle(a, b, c):
    """
    计算角 abc 的角度 (三点坐标)
    
    示例:calculate_angle(肩,肘,腕) -> 肘部角度
    
    数值稳定性处理:
    - 使用 np.clip 防止 cos 值越界
    - 检查模长避免除零错误
    """
    a = np.array(a)
    b = np.array(b)
    c = np.array(c)
    
    ba = a - b
    bc = c - b
    
    # 余弦定理
    norm_ba = np.linalg.norm(ba)
    norm_bc = np.linalg.norm(bc)
    
    if norm_ba == 0 or norm_bc == 0:
        return 180.0  # 返回平角,表示无效
    
    cosine_angle = np.dot(ba, bc) / (norm_ba * norm_bc)
    cosine_angle = np.clip(cosine_angle, -1.0, 1.0)  # 防止数值溢出
    angle = np.degrees(np.arccos(cosine_angle))
    return angle

核心函数:main_processing_loop 主处理循环

python 复制代码
def main_processing_loop():
    """
    主处理循环,串联整个检测流水线
    
    流程概览:
    1. 检测结果坐标还原
    2. 多边形区域过滤
    3. 姿态规则初步过滤
    4. BYTETracker 跟踪更新
    5. "捞回"丢失目标
    6. 16 帧采集 + 滑动窗口
    7. 命中率校验
    8. TSM 时序分类
    9. 报警触发
    """
    # 初始化跟踪器
    byte_tracker = ByteTracker(
        frame_rate=fps,
        image_shape=(width, height),
        match_thresh=0.7
    )
    active_captures = {}  # 活跃目标字典
    
    while not stop_event.is_set():
        # 1. 获取检测结果
        results = get_detection_results()
        results_pose = get_pose_results()
        
        # 2. 坐标还原到原图尺寸
        results = map_to_original(results)
        results_pose = map_to_original(results_pose)
        
        # 3. 多边形区域过滤 (只分析关注区域)
        results = filter_by_area(results)
        
        # 4. 姿态简单过滤
        if len(results) > 0:
            results = detect_attack_actions(results, results_pose, pose_keypoints)
        
        # 5. 更新跟踪器
        track_results = byte_tracker.update(results)
        
        # 6. "捞回"机制:维持丢失但正在采帧的目标
        current_valid_tracks = {}
        for trk in track_results:
            current_valid_tracks[trk.id] = trk
        
        for tid, trk in active_captures.items():
            if tid not in current_valid_tracks:
                if trk.state != REMOVED:
                    current_valid_tracks[tid] = trk
        active_captures = current_valid_tracks
        
        # 7. 遍历所有有效追踪区域
        for trk in active_captures.values():
            # 7.1 检查检测是否命中该目标区域
            is_detected = compute_iou(trk.box, detection_boxes) > 0.35
            
            # 7.2 动态裁剪框选择
            crop_base_box = best_det_box if is_detected else trk.box
            
            # 7.3 状态机:开始采帧
            if not trk.capture_mode and is_detected:
                trk.capture_mode = True
                trk.frame_buffer.clear()
            
            # 7.4 正在采帧,收集画面
            if trk.capture_mode:
                crop_img = crop_region(frame, crop_base_box, expand_ratio=1.3)
                trk.frame_buffer.append(crop_img)
                trk.hit_history.append(is_detected)
                
                # 7.5 满 16 帧进行分析
                if len(trk.frame_buffer) == 16:
                    hit_ratio = sum(trk.hit_history) / 16.0
                    
                    if hit_ratio >= 0.45:  # 命中率达标
                        # TSM 时序分类
                        tsm_result = model_tsm.predict(list(trk.frame_buffer))
                        
                        # 判断是否为打架
                        if tsm_result['class_id'] == 1 and \
                           tsm_result['confidence'] >= action_confidence:
                            trk.is_fighting = True
                            
                            # 检查持续时间
                            duration = current_time - trk.fight_start_time
                            if duration >= fight_duration_threshold:
                                trk.alarm_flag = True  # 触发报警
                        else:
                            # 重置状态
                            trk.is_fighting = False
                            trk.fight_start_time = 0
                        
                        # 7.6 滑动窗口
                        if hit_ratio < 0.25:
                            trk.capture_mode = False
                            trk.frame_buffer.clear()
                        else:
                            for _ in range(8):
                                trk.frame_buffer.popleft()
        
        # 8. 触发报警
        if alarm_flag:
            generate_alarm_output()

算法技巧与优化

1. 并行推理优化

python 复制代码
# 检测和 Pose 模型并行执行
detection_future = executor.submit(detect_model, image)
pose_future = executor.submit(pose_model, image)
detection_result = detection_future.result()
pose_result = pose_future.result()

效果: 速度提升约 37%


2. "捞回"机制

python 复制代码
# 捞回检测漏检但正在采帧的目标
for tid, tracker in active_captures.items():
    if tid not in valid_tracks:
        if tracker.state != REMOVED:  # 未被彻底移除
            valid_tracks[tid] = tracker

效果: 避免因单帧漏检中断分析


3. 滑动窗口 (8 帧重叠)

python 复制代码
# 弹出最旧 8 帧,保留最新 8 帧
for _ in range(8):
    tracker.frame_buffer.popleft()
# 继续采 8 帧,下一轮分析时仍有 16 帧

效果:

  • 50% 重叠保证时序连续性
  • 每 8 帧分析一次,响应更快

4. 命中帧机制

python 复制代码
# 记录每一帧检测是否命中
tracker.hit_history.append(is_detected)

# 满 16 帧后计算命中率
hit_ratio = sum(tracker.hit_history) / 16.0

# 命中率阈值策略
if hit_ratio >= 0.45:
    # 触发 TSM 分析
elif hit_ratio < 0.25:
    # 停止采帧,重置状态

效果:

  • 过滤检测误检
  • 节省无效 TSM 计算

5. 动态裁剪框选择

python 复制代码
# 检测命中时用检测框 (更精准)
# 检测漏检时用 Tracker 预测框 (维持连续性)
crop_base_box = best_det_box if is_detected else tracker_box

效果: 裁剪区域更精准,同时保证连续性


6. 预计算优化

python 复制代码
# 预计算所有 Pose 框中心点,避免重复计算
pose_centers = []
for pose_box in pose_boxes:
    cx = (pose_box[0] + pose_box[2]) / 2
    cy = (pose_box[1] + pose_box[3]) / 2
    pose_centers.append((cx, cy))

效果: 减少重复计算,提升效率


7. 提前退出优化

python 复制代码
# 发现攻击动作立即跳出循环
if has_attack_action:
    break

效果: 避免不必要的后续检查


8. 动态比例尺

python 复制代码
# 根据肩到髋的距离自适应阈值
body_scale = max(30.0, calculate_distance(kps[5][:2], kps[11][:2]))
rest_thresh = body_scale * 0.5
kick_target_thresh = body_scale * 1.2

效果: 适应不同距离、不同身高的人物


配置说明

主要配置参数:

参数 默认值 说明
fight_duration_threshold 0 打架持续时间阈值 (秒),超过此时长才报警
action_confidence 0.85 动作置信度阈值,TSM 预测置信度需超过此值
resample 1 采样频率,1 表示每帧处理
stop_alarm True 是否夜间停止报警
alarm_interval_time 30 报警间隔 (秒),防止重复报警
iou_threshold 0.5 检测框 IoU 阈值

姿态校验阈值 (代码内固定):

参数 默认值 说明
KEYPOINT_CONF_THRESH 0.30 关键点置信度阈值
PUNCH_ANGLE_THRESH 150° 挥拳角度阈值
KICK_ANGLE_THRESH 135° 踢腿角度阈值

命中率阈值 (代码内固定):

参数 默认值 说明
触发 TSM ≥ 45% 检测命中率达标才分析
停止采帧 < 25% 命中率太低,目标可能消失
滑动窗口 8 帧 每轮弹出 8 帧,保留 8 帧

常见问题

Q1: 为什么需要 16 帧时序分析?

A: 单帧姿态检测容易产生误报。例如:

  • 一个人挥手打招呼可能被误判为挥拳
  • 摔倒时的肢体伸展可能被误判为踢腿

通过 16 帧时序分析,TSM 模型可以:

  • 观察动作的连续性和趋势
  • 区分瞬时动作和持续对抗
  • 结合多人互动关系判断

Q2: 滑动窗口为什么选择 8 帧重叠?

A: 这是一个平衡选择:

  • 重叠太多 (如 12 帧): 计算冗余大,响应慢
  • 重叠太少 (如 4 帧): 可能截断连续动作
  • 8 帧重叠: 50% 重叠,既保证连续性又有较好响应

Q3: 命中率阈值为什么是 45% 和 25%?

A: 基于实验经验:

  • ≥45%: 表示目标稳定存在,可信度高
  • 25%~45%: 目标时断时续,继续观察
  • <25%: 目标基本消失,停止分析

由于打架行为复杂多变,比如挥拳动作就包含:抬手 - 蓄力 - 出拳 - 收拳,一套动作中检测出的有效帧实际可能只有出拳的那几帧,再加上抽帧推理等设计,45% 的命中阈值较为合理。

Q4: 如何过滤摔倒场景?

A: 摔倒过滤通过多层机制实现:

  1. 姿态规则层: 摔倒通常是单人动作,不满足"框内≥2 人"条件
  2. 对抗检测层 : 踢腿检测需要 is_hit - 检测到踢向他人目标
  3. 时序分析层: TSM 模型训练时包含摔倒样本,能够识别并分类为"正常"

Q5: 检测漏检怎么办?

A: 系统使用多重机制应对:

  1. "捞回"机制: 维持 Lost 状态目标的跟踪
  2. 动态裁剪框: 使用 Tracker 预测框代替检测框
  3. 命中帧记录: 不依赖单帧检测结果

Q6: 如何调整灵敏度?

A: 可通过以下配置调整:

参数 调高 调低
action_confidence 减少误报 提高召回
fight_duration_threshold 过滤短暂冲突 快速报警
Pose 角度阈值 更严格 更宽松
命中率阈值 更严格 更宽松

总结

本算法采用检测→姿态→跟踪→时序分类的级联方案,通过多层校验有效平衡准确率与误报率。核心设计思想:

  1. 规则 + 学习: 姿态规则快速过滤明显非打架场景,TSM 模型处理复杂时序判断
  2. 时序连贯: 16 帧缓冲 + 8 帧滑动窗口保证行为分析的连续性
  3. 鲁棒性设计: 支持检测漏检,使用 Tracker 预测维持分析
  4. 命中校验: 检测命中率机制过滤误检,节省算力
  5. 可配置: 通过配置文件灵活调整报警阈值和策略

核心算法技巧:

  • 并行推理 (检测+Pose) - 速度提升 37%
  • "捞回"机制 - 应对检测漏检
  • 滑动窗口 (8 帧重叠) - 保证时序连续性
  • 命中帧机制 - 过滤误检,节省算力
  • 动态裁剪框 - 精准 + 连续
  • 预计算优化 - 减少重复计算
  • 提前退出 - 发现即停止

本算法适合地铁、学校、商场、办公室等公共场所的打架斗殴行为实时监测场景。

更多技术细节与模型详情欢迎探讨,可以评论或者私信我 (爱睡懒觉的焦糖玛奇朵)。

相关推荐
programhelp_2 小时前
IBM OA 高频真题分享|2026最新-Programhelp 独家整理
人工智能·机器学习·面试·职场和发展·数据分析
nFBD29OFC2 小时前
pillow - 图像处理的瑞士军刀
图像处理·人工智能·pillow
深挖派2 小时前
PyCharm 2026.1 全版本安装配置与全功能环境搭建 (保姆级图文教程)
ide·python·pycharm
好家伙VCC2 小时前
**发散创新:基于算子融合的深度学习推理优化实战**在现代AI部署场景
java·人工智能·python·深度学习
Ofm1z1Q9R2 小时前
python-langchain框架(3-5-pdf文件load_and_split()加载 )
python·langchain·pdf
小码吃趴菜2 小时前
Transformer 视频学习笔记
人工智能·深度学习·transformer
练习时长一年2 小时前
后端开发常用的skill推荐
人工智能·算法·职场和发展
漂流瓶jz2 小时前
UVA-10384 推门游戏 题解答案代码 算法竞赛入门经典第二版
数据结构·算法·深度优先·题解·aoapc·算法竞赛入门经典·uva
心.c2 小时前
嵌入式 AI 助手的三层意图识别架构:如何在“快、准、稳“之间取得平衡
人工智能·ai·架构