ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

YOLOv5打架行为检测实战:从数据集标注到PyQt界面与边缘部署

YOLOv5打架行为检测实战:从数据集标注到PyQt界面与边缘部署 简介本资源面向计算机视觉学习者与安防场景开发者提供一套可直接落地的打架行为检测方案包含YOLOv5训练好的权重、一万余张标注数据集以及PyQt可视化界面适合课程设计、毕业设计或行为识别项目快速验证。压缩包共2000个文件以1994个XML标注文件为主另含3个Python脚本与3份环境配置及PyQt使用说明PDF整体约508MB数据集已按train、val、test划分并附data.yaml类别为normal与fight两类txt格式标签可直接供YOLOv5、YOLOv7、YOLOv8、YOLOv9等算法训练。已有160人学习下载。读者可借助预训练权重直接推理检测也可基于完整目录结构复现训练流程配合说明文档完成环境配置与界面调用省去数据采集与标注成本。1. 从一张监控截图说起YOLOv5打架行为检测到底在做什么凌晨两点值班室的屏幕上弹出一段自动截取的视频片段画面里两个人从推搡升级到挥拳系统在第三秒就框出了两个交叠的人体框并打上“fight”标签。这不是什么高精尖的实验室demo而是一套用 YOLOv5 训练出来的打架行为检测模型在跑。很多人第一次听到“打架行为检测”脑子里浮现的是动作识别、时序建模、光流法那一套重装备但真正落地到园区、工地、校园这些场景时最稳、最快、最容易维护的方案反而是把问题降维成目标检测——用 YOLOv5 直接检测画面里“正在打架的人”这个类别。它不追求理解整段动作的语义只回答一个问题这一帧里有没有人正在做出打架姿态。配合 PyQt 界面值班人员不需要懂命令行打开软件、选视频、点开始结果就实时画在画面上。这套组合适合谁适合手里有几百到几千张标注图、想在一周内跑出一个能演示能迭代的原型的工程师也适合已经用过 YOLOv5 做安全帽、烟火检测、想再扩一个行为类别的熟手。它不适合追求学术级动作识别精度的团队但对绝大多数工程落地场景够用而且快。2. 数据集怎么攒从公开数据到自标注的取舍2.1 打架行为检测的数据集长什么样打架检测的数据集和普通目标检测有个本质区别它的正样本极度依赖“姿态”而不是“物体”。一个人站着是 person挥拳也是 person区别在肢体关系。所以标注时不能只框人要框“正在发生冲突的两个人或一群人”通常做法是用一个较大的框把参与冲突的人整体包住或者对每个参与人单独框但统一打上 fight 标签。常见做法是后者因为 YOLOv5 本身是多框检测单框包两人容易在密集场景下漏检。数据集来源一般三条路一是公开的行为识别数据集里抽帧比如 RWF-2000、Movies Fight 这类抽帧后人工筛掉模糊和误标二是自己从监控录像里截取这是最贴合落地场景的但要注意隐私合规只保留人体区域、不涉及可识别身份信息三是用现有 person 检测模型先跑一遍把所有人框出来再人工把其中打架的框改标签这样能省一半标注时间。我一般会建议至少准备 1500 张图其中正样本含打架不低于 500 张负样本里要有大量“两个人靠得近但没打架”的干扰图比如握手、递东西、排队否则模型会把“靠近”学成“打架”。2.2 标注格式转换与目录结构YOLOv5 要的是 YOLO 格式的 txt每行class x_center y_center width height全部归一化到 0~1。如果你用 LabelImg 或 CVAT 标的是 VOC 的 xml需要转一道。下面这个脚本是我常用的转换逻辑处理了边界裁剪和空文件跳过import os import xml.etree.ElementTree as ET # 输入VOC xml目录输出YOLO txt目录 voc_dir annotations_xml out_dir labels_yolo classes [person, fight] # 类别顺序要和训练时一致 os.makedirs(out_dir, exist_okTrue) for xml_file in os.listdir(voc_dir): if not xml_file.endswith(.xml): continue tree ET.parse(os.path.join(voc_dir, xml_file)) root tree.getroot() size root.find(size) w int(size.find(width).text) h int(size.find(height).text) lines [] for obj in root.iter(object): cls_name obj.find(name).text if cls_name not in classes: continue cls_id classes.index(cls_name) bbox obj.find(bndbox) x1 float(bbox.find(xmin).text) y1 float(bbox.find(ymin).text) x2 float(bbox.find(xmax).text) y2 float(bbox.find(ymax).text) # 裁剪到图像边界内防止标注越界导致归一化出错 x1 max(0, min(x1, w)) y1 max(0, min(y1, h)) x2 max(0, min(x2, w)) y2 max(0, min(y2, h)) if x2 x1 or y2 y1: continue # 跳过无效框 xc (x1 x2) / 2.0 / w yc (y1 y2) / 2.0 / h bw (x2 - x1) / w bh (y2 - y1) / h lines.append(f{cls_id} {xc:.6f} {yc:.6f} {bw:.6f} {bh:.6f}) if lines: txt_name os.path.splitext(xml_file)[0] .txt with open(os.path.join(out_dir, txt_name), w) as f: f.write(\n.join(lines))逻辑说明先读图像宽高做归一化分母再对每个 object 取类别名映射到 id坐标裁剪防止越界最后按 YOLO 格式写入。参数上classes列表的顺序必须和后面训练时 data.yaml 里的 names 完全一致否则标签全错。空文件不写因为 YOLOv5 训练时遇到空 txt 会当作纯背景图这是有用的但如果你不希望背景图参与就在生成后手动删掉。目录结构按 YOLOv5 官方推荐dataset/ images/ train/ val/ labels/ train/ val/train 和 val 按 8:2 分注意同一个视频抽出来的帧不能同时出现在 train 和 val否则验证指标会虚高这是血泪经验。2.3 数据增强里哪些该开哪些不该开YOLOv5 自带 mosaic、mixup、HSV 增强、随机翻转。打架检测里mosaic 和 mixup 要谨慎mosaic 把四张图拼一起容易让两个不相关的人被拼成“近距离接触”模型可能学到假的冲突关系。我一般会把 mosaic 关掉或把概率降到 0.3 以下mixup 直接关。HSV 增强可以开因为监控画面色偏常见。随机翻转要分场景如果打架动作有方向性比如右勾拳水平翻转会造出不符合人体工学的样本但实际监控里左右都有所以水平翻转可以开垂直翻转绝对不要开人不会倒着打架。这些参数在hyp.scratch-low.yaml里改训练时用--hyp指定。3. 训练自己的打架检测模型超参数怎么设才不翻车3.1 从预训练权重起步还是从头训除非你有十万张以上的标注图否则一定从预训练权重起步。YOLOv5 在 COCO 上学到的人体特征对打架检测非常有用因为打架首先得是人。常见做法是下载yolov5s.pt或yolov5m.pts 版本推理快适合 PyQt 实时显示m 版本精度高一点但帧率会掉。我一般先用 s 跑通全流程如果验证集漏检严重再换 m。命令如下python train.py \ --img 640 \ --batch 16 \ --epochs 100 \ --data data/fight.yaml \ --weights yolov5s.pt \ --hyp data/hyp.fight.yaml \ --project runs/fight \ --name exp1参数说明--img 640是输入分辨率监控画面里人通常占画面比例不大640 够用如果小目标多可以上 1280 但显存翻倍--batch 16在 8G 显存上比较稳爆显存就降到 8--epochs 100配合早停实际可能 60 轮就收敛--data指向你的 yaml里面写 train/val 路径和类别名--hyp指向自定义增强配置。训练过程中重点看mAP0.5和val/box_loss如果 box_loss 震荡不降多半是学习率太大或标注框有大量越界。3.2 打架检测的锚框需不需要重新聚类YOLOv5 默认锚框是基于 COCO 的COCO 里人体框的宽高比和监控里打架的人体框有差异——打架时人可能弯腰、伸展框更扁或更方。所以建议用自己的数据跑一遍 k-means 聚类。YOLOv5 仓库里有utils/autoanchor.py训练时加--noautoanchor关闭自动锚框然后手动跑python utils/autoanchor.py --data data/fight.yaml --weights yolov5s.pt它会输出建议的 anchors 并计算 BPRbest possible recall。如果 BPR 低于 0.98说明默认锚框确实不匹配把输出的 anchors 填到模型 yaml 里。这一步很多人跳过结果就是小目标打架框回归不准mAP 卡在 0.5 上不去。注意聚类用的图应该是训练集子集别用验证集否则等于偷看答案。3.3 训练中必须盯的三个指标第一个是metrics/mAP_0.5这是最直观的但别只看它因为打架检测里负样本多mAP 高不代表误报低。第二个是val/obj_loss如果它一直降但 mAP 不涨说明模型在学背景可能是正样本太少。第三个是混淆矩阵训练完在runs/fight/exp1/下会生成confusion_matrix.png重点看 fight 被误判成 person 的比例如果很高说明模型分不清“人”和“打架的人”这时候要么加更多打架正样本要么把 person 类去掉只留 fight 和 background。我一般会保留 person 类因为 PyQt 界面上同时显示人和打架框值班人员能看清上下文。4. PyQt 界面怎么接从推理到可视化4.1 界面线程与推理线程必须分开PyQt 最大的坑就是卡界面。如果你在按钮点击槽函数里直接跑 YOLOv5 推理视频一播放主线程就冻结窗口直接“未响应”。正确做法是用 QThread 把推理循环扔到子线程通过信号槽把带框的图像传回主线程显示。下面是最小可用的结构from PyQt5.QtCore import QThread, pyqtSignal import cv2 import torch class DetectThread(QThread): frame_signal pyqtSignal(object) # 传numpy图像 def __init__(self, model, source): super().__init__() self.model model self.source source self.running True def run(self): cap cv2.VideoCapture(self.source) while self.running and cap.isOpened(): ret, frame cap.read() if not ret: break # 推理model是加载好的YOLOv5 results self.model(frame, size640) annotated results.render()[0] # 画框后的图 self.frame_signal.emit(annotated) cap.release() def stop(self): self.running False self.wait()逻辑说明frame_signal用 object 类型传 numpy 数组主线程收到后转成 QImage 再塞给 QLabel。self.running控制循环退出窗口关闭时调用stop()避免线程泄漏。参数上size640要和训练时一致不一致精度会掉。注意results.render()返回的是列表取第 0 个。这个结构跑 1080p 视频在普通 i5 上能到 15~20 FPS够值班看。4.2 检测结果怎么画才不糊YOLOv5 自带的 render 画的是细线框在监控大屏上远看还行但 PyQt 窗口里如果缩放显示线会糊。我一般自己写绘制用 OpenCV 的rectangle加putText框线宽按图像宽度动态算比如thickness max(2, int(frame.shape[1] / 640))。颜色上 fight 用红色person 用绿色这样值班人员一眼能区分。文字背景加个实心矩形不然白字在亮背景上看不清。还有一点如果连续多帧都检测到 fight不要每帧都弹报警加一个简单的计数器连续 5 帧命中才触发声音或日志否则单帧误检会把人烦死。4.3 模型加载与界面初始化的顺序PyQt 启动时加载 YOLOv5 模型要几百毫秒到几秒如果放在主窗口构造函数里界面会白屏一会儿。常见做法是先把界面显示出来再在子线程里加载模型加载完发信号启用“开始检测”按钮。模型加载用torch.hub.load或直接DetectMultiBackend注意指定device为cpu或cuda:0如果机器没显卡就老老实实 cpu别硬等 cuda 报错。还有模型路径别写死绝对路径用os.path.join(os.path.dirname(__file__), weights/fight.pt)不然换台机器就翻车。5. 避坑与排查打架检测落地时最容易翻车的五件事5.1 现象模型把两个人靠近就判成打架原因训练集里负样本缺少“近距离但非打架”的图模型把空间距离当成了冲突特征。解决专门收集握手、递烟、排队、搀扶这类图标成 person 或 background数量至少和打架正样本持平重新训练。如果已经训完不想重来可以在推理后加一个简单规则两个 fight 框的 IoU 超过阈值且持续多帧才报警但这只是补救。5.2 现象PyQt 界面播放视频越来越卡内存一直涨原因每帧都创建新的 QImage 和 QPixmap旧对象没释放Python 垃圾回收跟不上。解决在子线程里复用 numpy 缓冲主线程显示时用QLabel.setPixmap后手动del旧 pixmap或者用QImage直接构造不经过QPixmap。更彻底的做法是限制显示帧率比如每两帧显示一次人眼看起来一样流畅。5.3 现象训练 loss 正常下降但验证 mAP 一直是 0原因data.yaml 里的val路径写错或者 val 的 labels 目录为空YOLOv5 找不到验证集就跳过评估。解决检查data/fight.yaml里 train 和 val 的路径是相对于数据集根目录还是绝对路径建议统一用绝对路径。再确认 val 的 images 和 labels 文件名一一对应缺一个都会导致该图被忽略。5.4 现象换一台机器推理检测框全偏了原因训练时图像做了 letterbox 填充推理时如果直接 resize 不填充坐标映射会错。YOLOv5 的DetectMultiBackend默认会做 letterbox但如果你自己写预处理就容易漏。解决要么直接用官方推理接口要么自己写时严格按scale min(640/w, 640/h)算缩放和 padding再把框坐标反算回去。这个坑我踩过两次框整体偏移几十像素查了一晚上。5.5 现象PyQt 打包成 exe 后模型加载失败原因PyInstaller 没把weights和models目录打进去或者torch.hub.load在打包环境里找不到缓存路径。解决用--add-data把权重和模型配置目录加进去代码里用sys._MEIPASS拼路径。另外 torch 打包体积巨大可以用--exclude-module去掉不用的模块但别删 torch 核心否则推理直接崩。6. 进阶技巧把打架检测模型塞进 RK3568 或树莓派4B如果你已经跑通了 PC 上的 YOLOv5 PyQt下一步大概率是想把它挪到边缘设备。RK3568 和树莓派4B 是热搜里出现最多的两个平台但它们的算力差一个量级。RK3568 有 NPU支持 INT8 量化YOLOv5s 量化后能跑到 15 FPS 以上树莓派4B 只有 CPU跑原版 YOLOv5s 大概 2~3 FPS基本没法实时看视频只能做定时抓拍。所以选型上要实时就上 RK3568要低成本演示就树莓派加低分辨率。RK3568 的路径是PyTorch 训练 → 导出 ONNX → 用 RKNN-Toolkit2 转成 rknn 模型 → 板端用 rknn_api 推理。导出 ONNX 时注意把--img固定成 640动态轴关掉否则 RKNN 转换会报错。量化需要准备一批校准图一般从训练集里抽 200 张就够校准图要覆盖白天、夜晚、室内、室外不然量化后夜间误检会飙升。下面是一个导出 ONNX 的命令python export.py \ --weights runs/fight/exp1/weights/best.pt \ --include onnx \ --img 640 640 \ --batch 1 \ --simplify--simplify会调用 onnx-simplifier 去掉冗余节点RKNN 转换时更顺。转 RKNN 的脚本在 RKNN-Toolkit2 的 examples 里改重点配置mean_values和std_values要和训练时的归一化一致YOLOv5 默认是 0~1 归一化所以 mean 填 0,0,0std 填 255,255,255。板端推理后处理要自己写 NMS因为 RKNN 输出的是原始特征图别指望它帮你解码。树莓派4B 上如果非要跑建议用 YOLOv5n 或者把输入降到 320然后开 OpenVINO 或 NCNN 推理框架比原版 PyTorch 快 2~3 倍。但说实话树莓派做实时打架检测体验很差我一般只拿它做离线视频分析一晚上跑完一天的录像第二天看报警片段。最后说个习惯不管上哪个边缘设备先在 PC 上用同一段视频对比量化前后的检测结果如果量化后 mAP 掉超过 5 个点就回去检查校准集是不是太单一。这个对比步骤能帮你省下大量在板子上反复烧录的时间。希望帮到你。本文还有配套的精品资源点击获取
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进