yolov8+deepsort智慧工地安全帽检测与多目标跟踪实战
简介这份资料面向施工安全领域的目标检测与多目标跟踪开发者整合了yolov8-deepsort检测跟踪流程与配套数据集可直接用于识别安全帽、背心、人员等施工隐患并通过deepsort算法持续跟踪违规目标。压缩包共包含2000个文件其中标注数据以txt和xml格式为主兼容YOLO与VOC标签体系另有训练好的检测模型、运行脚本及PDF使用说明包体约356.67MB。数据集含1206张已标注图像并划分好train/val/test附带data.yaml可无缝接入yolov5至v12系列训练类别覆盖helmet、no-helmet、no-vest、person、vest等满足施工现场安全监测场景。资源已有94人学习内置track.py、utils.py等可直接运行的跟踪代码结合报告式README与PDF教程便于读者复现端到端流程快速完成模型训练、验证与隐患目标跟踪部署。1. yolov8-deepsort 在工地场景到底解决什么从安全帽检测到轨迹级隐患判定工地上的人员安全管控最痛的不是“能不能检测到没戴安全帽”而是检测到了却没后续——框在画面上闪一下后台没有任何人跟进违规行为没法定位到具体时间点、具体区域。yolov8-deepsort 这套方案把问题拆成两段先用 yolov8 检测模型识别视频帧里的施工人员、安全帽、反光衣再用 deepsort 跟踪算法给每个目标分配独立 ID 并持续追踪这样不只能判断“此刻是否戴了安全帽”还能判定“这个人在危险区域滞留了多久”“是否翻越了围挡”。适合正在做智慧工地安全管理的开发者和安全负责人前者关心怎么把检测和跟踪串联起来后者关心这个方向到底能在什么场景落地、投入多少成本。2. 为什么是 yolov8deepsort检测与跟踪各管一段2.1 yolov8 出框deepsort 给 ID两个模型的接口关系先理清两个组件各自负责什么。yolov8 是单帧目标检测器输入一帧图像输出这一帧里所有目标的边界框、类别和置信度deepsort 是多目标跟踪算法它不自己做检测而是接收检测框序列通过卡尔曼滤波预测目标在下一帧的位置再结合目标的表观特征做匹配最终给每个目标分配一个稳定的 track_id。这个分工决定了你拿到“训练好的检测模型.zip”之后不能只跑检测就完事还要把 yolov8 的输出格式转成 deepsort 的输入格式。常见做法是统一成[x1, y1, x2, y2, conf, class]的 numpy 数组送入 tracker 前转成 float32避免类型不一致导致的匹配异常# 把 yolov8 的检测结果封装成 deepsort 需要的输入 import numpy as np from ultralytics import YOLO model YOLO(best.pt) frame ... # 来自视频或摄像头的一帧 BGR 图像 results model(frame, verboseFalse)[0] detections [] for box in results.boxes.data.cpu().numpy(): x1, y1, x2, y2, conf, cls box detections.append([float(x1), float(y1), float(x2), float(y2), float(conf), int(cls)]) detections np.array(detections, dtypenp.float32) # 之后交给 deepsort 的 update 方法得到带 track_id 的轨迹逻辑说明yolov8 输出的坐标是归一化前的像素坐标正好是 deepsort 需要的格式类别索引要保留因为跟踪结果里需要知道这是一个“person”还是“helmet”后续判定违规时要基于类别组合做推理。参数说明verboseFalse关闭逐帧打印避免推理日志刷屏拖慢视频处理conf阈值如果没有在推理时指定就保留模型默认的 0.25也可以在model(frame, conf0.3)里单独收严。2.2 单帧检测 vs 序列跟踪隐患判定靠轨迹而非单张图很多刚接触这个方向的人会问安全帽检测单帧就能做为什么非要加跟踪答案是工地上的隐患判定相当一部分依赖时序信息。未戴安全帽可以单帧判定但“人员进入塔吊作业半径后逗留超过 30 秒”“工人长时间蹲在基坑边缘不动”这类行为单帧根本无法判断必须把连续帧的检测结果串成轨迹再看轨迹与危险区域的空间关系。deepsort 跟踪算法里有几个关键参数直接决定轨迹质量拿到数据集和代码后第一个要改的就是这部分参数常见取值作用max_dist0.2特征匹配的最大余弦距离超过则认为不是同一人min_confidence0.3低于该置信度的检测框不进入跟踪器nms_max_overlap1.0检测框 NMS 的最大重叠率1.0 表示关闭max_iou_distance0.7卡尔曼预测框与检测框的 IOU 阈值max_age30目标丢失后最多保留多少帧再删除轨迹n_init3连续匹配成功多少帧才确认轨迹有效nn_budget100ReID 特征缓存的最大数量我一般会把min_confidence保持在 0.25 到 0.3max_age设在 30 帧左右。max_age太小检测漏一帧轨迹就断ID 会重新分配太大目标离开画面很久还给旧 ID 留位置内存占用涨上去。这两个参数是跟踪调优中最先动的。2.3 模型尺寸怎么选先看显卡再看部署目标yolov8 有 n、s、m、l、x 五个尺寸选型逻辑很直接训练时的显卡显存决定你能跑多大的模型部署时的硬件决定最终用哪个尺寸。如果手头是 gtx1660ti 这类 6GB 显卡训练阶段推荐 yolov8s 起步batch size 设 8imgsz 640能正常跑完 100 轮想上 m 或 lbatch 得降到 2 到 4训练时间会明显拉长。如果目标设备是 rk3588 这类边缘计算盒子部署阶段推荐 yolov8n 或 yolov8s 转 int8 量化检测速度才能到实时。不要看见 mAP 高就选 l部署时才发现在边缘设备上跑不动又要回头重新训练这是最常见的返工原因。3. 构建施工安全数据集四类目标与标注规范3.1 数据集从哪来自采视频抽帧配合公开数据集打底工地场景的数据集最理想是直接采集现场视频按帧抽取但多数项目初期没有这个条件。常见的做法是先用公开数据集打底做预训练再补一批自采数据做微调。crowdhuman 这样的大规模行人检测数据集提供人形目标的基础泛化能力bdd100 覆盖了不同天气和光照下的车辆与行人场景这些都能让模型在施工画面里的漏检率明显下降。但公开数据集解决不了工地特有的细粒度问题安全帽在画面里往往只有十几个像素反光衣在逆光下颜色失真这些要靠真实工地视频抽帧补齐。我的建议是类别不要设太多四类以内最稳person、helmet、vest、vehicle。注意“未戴安全帽”不要单独设一个类别因为正样本和负样本的边界不好画容易把“戴了但有阴影遮挡”的画面误标成负样本。更可靠的做法是模型只检测person和helmet后用几何规则判断如果 person 框内没有包含 helmet 框就判定为未佩戴。3.2 用 labelimg 标注并转成 yolov8 格式的代码标注工具用 labelimg 就能满足需求画框后保存为 VOC 格式的 XML 文件。yolov8 训练需要的是每个图片对应一个 txt 文件每行是类别编号 cx cy w h四个值都是归一化后的比例坐标。转换脚本是数据集构建里最值得写干净的一段代码# VOC 标注转 YOLO txt 格式 import os import xml.etree.ElementTree as ET classes [person, helmet, vest, vehicle] def convert_xml(xml_path, out_dir): tree ET.parse(xml_path) root tree.getroot() img_w int(root.find(size/width).text) img_h int(root.find(size/height).text) lines [] for obj in root.iter(object): name obj.find(name).text if name not in classes: continue box obj.find(bndbox) x1 float(box.find(xmin).text) y1 float(box.find(ymin).text) x2 float(box.find(xmax).text) y2 float(box.find(ymax).text) # 防止标注时手抖画反做一次修正 x1, x2 min(x1, x2), max(x1, x2) y1, y2 min(y1, y2), max(y1, y2) cx ((x1 x2) / 2) / img_w cy ((y1 y2) / 2) / img_h bw (x2 - x1) / img_w bh (y2 - y1) / img_h lines.append(f{classes.index(name)} {cx:.6f} {cy:.6f} {bw:.6f} {bh:.6f}) if lines: base os.path.splitext(os.path.basename(xml_path))[0] with open(os.path.join(out_dir, base .txt), w) as f: f.write(\n.join(lines))逻辑说明先解析 XML 里的图片宽高再遍历每个目标框做归一化。注意x1, x2 min(x1, x2), max(x1, x2)这一步很多人标注时从右下往左上拖框会产生 x2 小于 x1 的非法标注训练时 loss 直接变成 nan。参数说明归一化坐标保留 6 位小数足够过多小数据会无意义增大 txt 体积类别编号必须和下一步 data.yaml 里的类别顺序完全一致否则训练出来的类别张冠李戴。3.3 数据划分按视频切不能按图片随机切数据划分是这个数据集构建里最隐蔽的坑。如果直接把所有抽帧图片随机分成 train 和 val相邻帧几乎一样验证集的 mAP 会虚高到 0.9 以上一上真实视频就崩。正确做法是按视频片段划分整个视频的帧要么全进训练集要么全进验证集视频之间没有重叠。这样验证集才能代表“没见过的工地场景”。增强策略上yolov8 自带的 mosaic 和 mixup 能显著提升小目标检测能力但要注意训练最后 10 轮关闭 mosaic让模型在真实分布上收尾。光照增强对工地场景特别重要因为安全帽和反光衣的颜色在不同天气下变化极大建议把亮度扰动、对比度扰动打开hsv 增强保持默认即可。4. 训练 yolov8 检测模型配置、调参与损失曲线判断4.1 环境配置版本适配比安装本身更值得花时间训练环境用 conda 管理最省心Python 版本 3.9 或 3.10 都行ultralytics 包建议固定一个大版本避免后续接口变动导致代码失效。PyTorch 的安装要根据显卡驱动来gtx1660ti 建议 CUDA 11.8 对应的版本稳定性和兼容性都验证过conda create -n yolo python3.9 -y conda activate yolo pip install ultralytics8.2.0 pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118逻辑说明先创建独立环境再装包避免和已有项目的 numpy、opencv 版本打架。--index-url参数指定 PyTorch 官方源比默认源快而且能精确控制 CUDA 版本。参数说明ultralytics8.2.0这个版本号是相对稳定的一个训练命令、模型结构图和 loss 曲线输出都正常如果你电脑里已经有其他项目依赖了旧版 opencv建议在这个环境里重新装opencv 版本冲突是 yolov8 环境配置里出现频率最高的报错现象是cv2.error: Unknown C exception。4.2 data.yaml 与训练命令参数怎么调先看数据量数据集准备好之后写 data.yaml 指向图片目录和标注目录# data.yaml path: datasets/construction_safety # 数据集根目录 train: images/train val: images/val names: 0: person 1: helmet 2: vest 3: vehicle训练命令按下面的模板跑核心是 batch、imgsz、epochs 三个参数yolo detect train \ datadata.yaml \ modelyolov8s.pt \ epochs100 \ imgsz640 \ batch8 \ device0 \ patience20 \ close_mosaic10逻辑说明modelyolov8s.pt表示加载 COCO 预训练权重作为初始权重迁移学习能让模型快速收敛特别是 person 和 vehicle 这两类在 COCO 里已经有很好的基础。patience20表示连续 20 轮验证集 mAP 不提升就提前停止能省掉大量无效训练时间。参数说明close_mosaic10表示最后 10 轮关闭 mosaic 增强这一步对最终精度影响很大如果你发现训练结束后验证集 mAP 一直徘徊在 0.85 上不去多半是 mosaic 增强开到了最后一轮模型没见过干净的原始分布batch 大小取决于显存6GB 显存跑 yolov8s 只能设到 8强行设 16 会直接 OOM。4.3 损失函数曲线图别只看 mAP要看 loss 的下降形态训练完成后runs/detect/train目录下会生成results.png里面包含 box_loss、cls_loss、dfl_loss 三条训练曲线和对应的验证曲线。很多教程让你只看 mAP但 mAP 只告诉你最终结果loss 曲线能告诉你训练过程是否健康。如果训练集 loss 持续下降而验证集 loss 在某个 epoch 后反弹说明过拟合开始可以回调patience参数让早停生效。如果两条曲线都在高位震荡不下降先检查数据和标注常见原因是标注框类别编号和 data.yaml 不一致模型在硬学错误标签。自己画损失曲线也很简单训练过程会同步保存results.csvimport pandas as pd import matplotlib.pyplot as plt df pd.read_csv(runs/detect/train/results.csv) df.columns [c.strip() for c in df.columns] plt.figure(figsize(10, 5)) plt.plot(df[epoch], df[train/box_loss], labeltrain box_loss) plt.plot(df[epoch], df[val/box_loss], labelval box_loss) plt.xlabel(epoch) plt.ylabel(loss) plt.legend() plt.grid(True) plt.savefig(loss_curve.png, dpi150)逻辑说明读 CSV 时把列名首尾空格去掉是必须的ultralytics 生成的 CSV 列名带前导空格直接按列名取会 KeyError。参数说明dpi150保证图片放大后曲线依然清晰判断标准是验证集 box_loss 在训练后期稳定在 1.0 到 1.5 之间属于正常范围如果高于 2.0 且不再下降优先检查标注质量而不是盲目加训练轮数。5. 接入 deepsort 的避坑ID 跳变、漏检与目标丢失5.1 同一个工人 ID 来回变统计人数虚高现象画面里的同一个工人身上的 track_id 每隔十几帧就变一次后台统计的“在场人数”比实际多了好几倍。 原因检测框在目标身体轻微晃动时抖动deepsort 的特征匹配又过于严格旧轨迹和新检测框匹配不上系统认为是新目标。 解决先把max_dist从默认的 0.2 放宽到 0.3再看max_iou_distance是否小于 0.7。我踩坑时发现 ID 频繁跳变绝大多数是max_dist太紧ReID 特征提取在低分辨率施工画面上区分度本来就有限匹配阈值卡太死就是不断开新轨迹。如果放宽后还跳检查检测模型是否每一帧的框在 x、y 方向上有几个像素的抖动给检测框坐标做一次轻量平滑也能明显改善。5.2 工人被遮挡几帧后轨迹断裂重新分配 ID现象工人从脚手架立柱后面走过被遮挡 10 帧左右出来之后 ID 变了轨迹也不连续。 原因deepsort 的max_age默认只有几十帧目标在遮挡期间没有任何检测框输入超过max_age后轨迹被删除目标重现时只能分配新 ID。 解决把max_age调大到 50 到 70 帧让轨迹在目标短暂遮挡期间保留。注意max_age调大后目标真的离开画面时轨迹还会存活一段时间后台统计的离场时间会滞后这时要在业务逻辑里加一个“轨迹坐标长时间不在画面内就判定离场”的兜底。这个参数是轨道连续性和误报率之间的平衡点一定要根据实际视频里遮挡出现的频率来调。5.3 安全帽小目标漏检跟踪自然跟着失效现象距离摄像头 20 米外的工人安全帽在画面里只有 15×15 像素模型经常漏检安全帽漏了deepsort 只能跟踪到 person无法判断佩戴状态。 原因训练时 imgsz 用 640模型对密集小目标的特征提取不够推理时同样用 640小目标直接丢失。 解决训练和推理都尝试 imgsz 960尤其在标注数据里小目标占比高时。代价是显存占用和推理耗时上升gtx1660ti 上跑 960 推理帧率会降到 20 帧以下实际部署时可以用 640 和 960 各测一版对比 mAP。另外给安全帽类单独提高 loss 权重不一定有效更稳的做法是保证数据集里小目标框的数量足够用 mosaic 增强把不同尺寸的目标拼在一起训练。5.4 俯视摄像头下跟踪翻车ReID 特征失去区分度现象工地高位球机俯视画面里两个穿同样反光衣的工人站在一起ID 在两人之间反复横跳。 原因deepsort 的 ReID 特征在俯视角度下能提取到的表观信息很少两个工人服装颜色一致、体态特征不明显特征距离接近匹配时容易混淆。 解决一是针对俯视场景采集数据训练专门的 ReID 模型而不是直接用通用权重二是调小nn_budget让缓存的特征更贴近当前场景三是把检测框的面积、运动速度、运动方向加入匹配的辅助特征这在 deepsort 改进中很常见。最简单的临时方案是把max_dist收紧到 0.15宁可容忍部分 ID 切换也不要出现两个目标 ID 混淆的误判——前者影响统计精度后者直接影响安全隐患判定的准确性。5.5 gtx1660ti 跑跟踪掉帧特征提取成为瓶颈现象检测模型本身推理流畅接入 deepsort 后帧率从 25 掉到 12CPU 占用率打满。 原因deepsort 的 ReID 特征提取默认跑在 CPU 上每个检测框都要过一次特征网络目标一多 CPU 就成瓶颈。 解决把特征提取网络放到 GPU 上执行如果显存不够做隔帧跟踪检测帧全部跑跟踪每两帧执行一次中间帧的轨迹用卡尔曼预测补上。对于工地场景每秒 15 帧的跟踪输出已经足够后台隐患判定不依赖连续每一帧的精确位置。记住一个原则跟踪是连续的视频任务检测可以每帧跑但特征匹配没必要每帧跑这个取舍能省下一半算力。提示deepsort 的所有调参都会互相影响一次只改一个参数记录改前改后的 ID 切换次数和轨迹断裂次数不要同时动三四个参数然后靠感觉判断效果。6. 验证与部署从 mAP 到现场可用的最后一公里训练完成后先在验证视频上跑通最小流程确认检测框和跟踪 ID 都正常yolo predict modelruns/detect/train/weights/best.pt sourcedemo.mp4 saveTrue这条命令输出的视频只有检测框没有跟踪 ID。下一步才是把检测结果接入 deepsort跑出带 track_id 并标注“未戴安全帽”提示的视频。如果验证视频里能看到稳定 ID 和正确的违规提示再考虑部署到 rk3588 这类边缘设备。rk3588 部署时模型要先导出成 ONNX 再转 RKNN注意力主要放在 int8 量化这一环。我试过的经验是yolov8s 直接转 int8mAP 会掉 2 到 3 个点这在安全帽检测任务里一般可接受但反光衣类别的精度掉得比 helmet 明显因为反光衣颜色随光照变化大量化时更容易失真。如果 int8 后反光衣检测明显变差给这一类单独准备一批白天、逆光、阴天的校准图片跑一遍量化校准能找回大部分精度。模型部署后要做一次现场回归测试挑一段工地的日常监控视频统计三个数未戴安全帽的检出率、跟踪 ID 切换率、误报次数。这三项都稳定后这套方案才真正从“能跑”变成“能用”。我自己的习惯是先跑一个 24 小时连续视频看看夜间场景很多模型白天很好晚上一亮灯就全是误报夜里才是工地安全监控的主战场。这套方向值得投入吗如果你只是要验证工地安全帽检测的可行性公开数据集加预训练权重一周就能跑出能演示的结果。想真正部署给安全管理部门用工作量主要在数据采集和跟踪调优上检测模型本身反而不是最花时间的部分。希望这几年的踩坑经验能帮你少走一段弯路。本文还有配套的精品资源点击获取