ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

YOLOv8+DeepSORT施工安全检测跟踪:数据集、训练调参与避坑指南

YOLOv8+DeepSORT施工安全检测跟踪:数据集、训练调参与避坑指南 简介一套面向施工现场安全管理与计算机视觉开发者的YOLOv8DeepSORT施工安全隐患检测与跟踪方案包含完整数据集、训练模型与跟踪算法。资源包共2000个文件约356.67MB以VOC格式xml与YOLO格式txt标签文件为主分别对应998个与997个并附有Python跟踪脚本、PDF运行步骤说明和Markdown文档便于上手。标注数据集包含1206张施工场景图像划分好train、val、test并提供data.yaml配置文件可直接用于YOLOv5/v8/v9等系列训练类别涵盖安全帽、无安全帽、背心、无背心、人员等5类聚焦施工人员安全装备佩戴检测。集成DeepSORT跟踪算法可对检测到的施工人员与隐患目标进行跨帧稳定跟踪结合训练好的模型适用于工地实时安全监控、人员轨迹分析等场景帮助识别未佩戴安全帽或反光背心的违规行为。已有94人学习/下载资源内附使用教程与运行步骤可降低上手门槛适合有YOLO基础的开发者用于项目落地或算法研究。1. yolov8deepsort做施工安全这套五类检测与跟踪资源能不能直接跑通施工安全管理里yolov8deepsort这套组合几乎是视频监控类需求的标准起手式——检测管“有什么”跟踪管“同一个目标在哪几帧出现过”。这份资源把两件事都备齐了1206张已标注图像、VOC/YOLO双格式标签、划分好的train/val/test、可直接训练的data.yaml外加集成了deepsort的track.py推理脚本。也就是说你拿到手不用从标数据开始直接能把“安全帽有没有戴、反光背心有没有穿”跑成实时检测加目标跟踪。适合三类人要做工地视觉安防落地的算法工程师、刚学yolov8想拿真实数据集练手的初级开发者、以及需要快速复现一版检测跟踪Demo去汇报的现场实施人员。我拆这份资源时最关心的是三个点数据集类别口径是否合理、标签格式能否直接喂给yolov8训练、deepsort参数在施工场景下要不要额外调。下面按这个顺序把每个细节过一遍。2. 数据集拆解1206张图像、五类标签与VOC/YOLO格式互转2.1 五类标签的业务口径为什么把helmet和no-helmet拆成两类这份数据集的类别名是construction-safety五个类分别是helmet安全帽、no-helmet无安全帽、no-vest无背心、person人员、vest背心。注意它没有把问题建模成二分类有没有戴帽子而是把“戴了”和“没戴”都作为独立类别输出。这样做的实际好处是你在后处理时可以直接针对no-helmet和no-vest两个负类做告警不用在helmet检测框上去反推“缺失”状态——因为反推逻辑遇到漏检就会误报独立建模等于把漏检风险转移给了模型本身。从标注统计角度看helmet、vest属于正样本类no-helmet、no-vest属于隐患类person作为兜底类存在。这个兜底类很重要因为施工画面里经常有人只露出半身或背影无法判断是否佩戴单独标成person可以避免模型强行往正反两个方向塞。如果你拿这份数据训练推导阶段建议把person类置信度阈值设低一点把它当辅助上下文用告警只取no-helmet和no-vest这样误报率可控。zip根目录里散落的file_202512_290.txt这类文本每一个对应一张图像的YOLO格式标注每行是“class_id x_center y_center width height”坐标已归一化到0-1区间。对应的xml文件则是VOC格式的原始标注保留的是像素级整数坐标和原始图像尺寸信息。2.2 VOC标签转YOLO标签我惯用的转换脚本与参数说明虽然资源里已经同时给了VOC和YOLO两种格式但你后续如果自己补标注很可能是用LabelImg产出VOC的xml再转成yolo训练要用的txt。这种情况我一般直接写脚本批量转不手工改。import os import glob import xml.etree.ElementTree as ET # 类别顺序必须和训练用的data.yaml里names顺序完全一致 class_names [helmet, no-helmet, no-vest, person, vest] def voc_to_yolo(xml_path, output_dir, class_names): tree ET.parse(xml_path) root tree.getroot() img_w int(root.find(size/width).text) img_h int(root.find(size/height).text) if img_w 0 or img_h 0: print(f跳过无效尺寸: {xml_path}) return out_lines [] for obj in root.iter(object): name obj.find(name).text if name not in class_names: print(f未知类别 {name} 在 {xml_path}已跳过) continue cls_id class_names.index(name) box obj.find(bndbox) xmin float(box.find(xmin).text) ymin float(box.find(ymin).text) xmax float(box.find(xmax).text) ymax float(box.find(ymax).text) # 夹紧边界防止标注越界导致训练时坐标超出图像范围 xmin max(0, min(xmin, img_w)) xmax max(0, min(xmax, img_w)) ymin max(0, min(ymin, img_h)) ymax max(0, min(ymax, img_h)) x_center ((xmin xmax) / 2) / img_w y_center ((ymin ymax) / 2) / img_h box_w (xmax - xmin) / img_w box_h (ymax - ymin) / img_h out_lines.append(f{cls_id} {x_center:.6f} {y_center:.6f} {box_w:.6f} {box_h:.6f}) if out_lines: base os.path.splitext(os.path.basename(xml_path))[0] out_path os.path.join(output_dir, base .txt) with open(out_path, w) as f: f.write(\n.join(out_lines)) print(f已转换: {xml_path} - {out_path}) # 用法把某个目录下所有xml转成yolo标签 xml_files glob.glob(annotations/voc/*.xml) os.makedirs(annotations/yolo, exist_okTrue) for xml_file in xml_files: voc_to_yolo(xml_file, annotations/yolo, class_names)脚本里三个关键点务注意第一class_names的顺序必须和训练用的data.yaml里names顺序完全一致否则训练出来的类别语义全错位第二坐标夹紧到图像边界内这步不能省很多标注工具在裁剪后容易留下越界框不处理的话训练会报box越界的警告第三最终输出归一化坐标时保留六位小数确保精度足够。转完顺手抽查三五个txt拿记事本打开看一眼第一列数字是不是对应预期的类别ID这个习惯能挡住大部分低级错误。2.3 data.yaml与目录划分检查训练前先验证路径和类别数资源里附带的data.yaml已经按yolov5/v8/v9/v10/v11/v12通用的格式写好训练前我建议不要急着跑先做一次静态检查。打开data.yaml重点确认三件事path字段指向的绝对路径或相对路径是否存在train、val、test三个子目录名是否和实际目录一致names列表顺序是否与你预想的类别顺序一致。# 检查yaml配置与目录是否匹配 cat data.yaml ls -l train/ val/ test/ 2/dev/null | head -20 # 统计train目录下的标注文件数量 find train/labels -name *.txt | wc -l常见坑是val目录里图片数太少导致验证集mAP波动大。这份资源的1206张图已经按比例划分好但如果你打算自己合并扩充数据建议保持train:val:test在8:1:1左右。验证一下train/labels里的txt总数和train/images里的图片数是否一致如果数量对不上多半是某张图的标注文件缺失——yolov8训练时会静默跳过无标注图片你很难发现但推理时那一张就会以零样本状态漏检。3. 训练配置与选型从yolov8n起步的参数表与类别不均衡处理3.1 基准模型选型为什么先跑yolov8n而不是直接上yolov8m施工安全检测有个特性目标通常不大安全帽在1080p画面里可能只有几十个像素同时部署环境往往是工地的边缘设备算力有限。所以我建议基准模型从yolov8n开始跑原因是n模型体积最小、训练速度快先用它把数据质量和训练参数链路验证通再考虑增大模型。如果你发现n模型在验证集上的mAP0.5超过0.85但mAP0.5:0.95偏低说明瓶颈在检测框精度而不是模型容量这时换yolo8s或m才有意义如果mAP0.5本身就上不去换大模型只会更慢且过拟合更快。这份资源里的工具包同时支持从yolov5到yolov12的训练你拿到手后第一版训练直接用yolov8n跑通即可不用一上来就追新版本。新版算法带来的涨点通常只有零点几个mAP而配置成本和踩坑成本却是实打实的。3.2 训练命令及关键参数epochs、imgsz、batch与数据增强的取舍在训练之前先确认一下这份数据集所有图片的尺寸分布。如果原始图像普遍大于1280x1280训练时统一缩放到640会丢失大量小目标细节如果原图普遍在640附近直接按默认imgsz跑就行。# 一份可直接执行的yolov8训练命令 yolo detect train \ datadata.yaml \ modelyolov8n.pt \ epochs100 \ patience15 \ imgsz640 \ batch16 \ workers4 \ seed0 \ projectruns/train_construction \ namebaseline \ pretrainedTrue \ optimizerauto \ lr00.01参数说明epochs设100配合patience15做早停即连续15个epoch验证集mAP不再提升就自动停止防止无效训练浪费算力batch16在8GB显存下比较稳如果你的显卡只有6GB降到8或4imgsz640是通用起点后面专门讲小目标处理时再说往上提的理由pretrainedTrue表示用yolov8n在COCO上的预训练权重初始化这对小数据集非常关键1206张图从头训练几乎不可能收敛靠预训练权重做迁移是必须的optimizerauto让yolov8自己按epoch动态切换优化器省去手动调warmup的时间。我一般训练时会额外开一个终端跑yolo val的实时验证或者观察runs/train_construction/baseline/目录下的results.png曲线。重点看val/box_loss和val/cls_loss是否同步下降如果box_loss降了但cls_loss不动说明类别特征不好学大概率是标注里类别混淆样本多。3.3 类别不均衡no-vest样本偏少时的处理策略施工场景数据集天然不平衡——person类基本占一半以上no-vest这类隐患样本可能只占百分之十几。直接拿原始分布训练模型会偏向把不确定的框猜成person。我建议先做两类处理不需要改数据集只改训练策略。第一类是按类别统计框数量确定不平衡程度。训练前用一条Python命令快速统计每个类别在train标注里出现的次数import os from collections import Counter label_dir train/labels counter Counter() for txt_file in os.listdir(label_dir): with open(os.path.join(label_dir, txt_file)) as f: for line in f: cls_id int(line.strip().split()[0]) counter[cls_id] 1 print(counter) # 输出示例: Counter({0: 665, 1: 120, 2: 88, 3: 1020, 4: 430})如果某一个类别框数量低于其他类别的30%我一般会把该类框损失权重调高。yolov8支持在训练参数里指定class_weights把数量少的类别权重设为1.5到2。另外yolov8自带的增强策略中mosaic和flip对类别不均衡影响不大真正有效的是把no-helmet、no-vest这类目标做copy-paste增强在训练数据里手动复制这些框区域贴到其他图上。资源里没附带这个增强脚本但如果你想提高隐患类的召回这是当前最值得花时间的改动。4. 部署跟踪track.py的检测-关联流程与调参边界4.1 track.py调用链路从yolo检测框到deepsort的track_id这份资源里集成的deepsort运行入口是track.py。它做的事情可以拆成三步加载训练好的yolov8检测权重、初始化deepsort的tracker、逐帧把yolo的输出喂给deepsort。我拿到代码后梳理的简化流程如下import cv2 import numpy as np from ultralytics import YOLO from deep_sort.deep_sort.tracker import Tracker from deep_sort.deep_sort import nn_matching from deep_sort.application_util.preprocessing import create_detections # 初始化deepsortmax_cosine_distance控制外观特征匹配的容忍度 metric nn_matching.NearestNeighborDistanceMetric( cosine, max_cosine_distance0.3, nn_budget100 ) tracker Tracker(metric) # 每个track_id需要维护一个历史框用于绘制轨迹 track_history {} def process_frame(frame, model, conf_thres0.25): # 1. yolo检测一次输入整帧返回多个框 results model(frame, confconf_thres, classes[0, 1, 2, 3, 4])[0] detections [] for box in results.boxes.data.cpu().numpy(): x1, y1, x2, y2, score, cls_id box[:6] # deepsort需要的是[x1, y1, x2, y2, score]格式类别信息单独保存 detections.append([x1, y1, x2, y2, score]) # 这里可以按类别分别处理比如给no-helmet单独标记颜色 if int(cls_id) in (1, 2): cv2.rectangle(frame, (int(x1), int(y1)), (int(x2), int(y2)), (0, 0, 255), 2) cv2.putText(frame, fWARNING:{int(cls_id)}, (int(x1), int(y1)-10), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 0, 255), 2) # 2. 用deepsort更新跟踪状态内部执行级联匹配IOU匹配 if detections: detections_array create_detections(detections) tracker.predict() tracker.update(detections_array) # 3. 从tracker取回带ID的跟踪框并记录轨迹 for track in tracker.tracks: if not track.is_confirmed() or track.time_since_update 1: continue track_id track.track_id x1, y1, x2, y2 track.to_tlbr() cv2.putText(frame, fID:{track_id}, (int(x1), int(y1)-25), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (255, 255, 0), 2) if track_id not in track_history: track_history[track_id] [] track_history[track_id].append((int((x1x2)/2), int((y1y2)/2))) return frame这段逻辑说明了几个容易被忽略的点。第一yolo检测返回的是全类别结果但你在告警阶段应该按类别分流只对no-helmet和no-vest画红色告警框对helmet和vest画正常框不然画面全是红色的框没法看。第二deepsort只吃检测框和置信度不吃类别信息所以你需要在检测阶段单独把cls_id传出来否则跟踪ID和类别对应不上。第三track_history按track_id存轨迹点这才是下一步做轨迹统计的基础数据。4.2 跟踪调参max_cosine_distance、conf_thres与nn_budget的边界deepsort的调参核心围绕三个参数max_cosine_distance、nn_budget、conf_thres。施工场景下我一般这么设参数默认值施工场景建议调整理由max_cosine_distance0.20.3工地工人穿同款工服外观特征高度相似太严格会导致频繁ID切换nn_budgetNone100限定外观特征样本库规模防止长时间视频后内存膨胀conf_thres0.250.35提高检测置信度过滤减少低置信度误检框对跟踪器的干扰max_age默认3030控制目标丢失后保留跟踪ID的最大帧数遮挡频繁时调大到50特别要说的是conf_thres和max_cosine_distance的联动。如果你把检测置信度阈值从0.25提到0.35进入deepsort的框数量会减少误检少、ID稳定但代价是低置信度的远距离小目标会丢。在我实际测试的工地视频里这个阈值一般设在0.3到0.4之间比较合适——太低会让柱子、阴影被当成person太高会让5米外的安全帽完全漏检。建议你先拿几段测试视频跑一遍观察no-helmet的持续帧数是否合理。4.3 场景扩展从单视频跑通到多路实时监控track.py默认读取单视频文件但实际工地往往是多路IPC摄像头。我一般把process_frame这个函数抽出来配合多线程或异步队列每路摄像头独立一个tracker实例。注意deepsort的tracker不是线程安全的多路并行时绝对不能共用一个tracker否则ID管理直接乱掉。另外还要处理摄像头掉线重连、断帧导致的track丢失问题。经验做法是检测到连续5帧无人像时把该tracker实例重置避免残留在内存里的旧ID和新人交错。我见过最典型的问题是摄像头卡了半分钟恢复后一堆人穿着同样的衣服走在一起tracker把ID全部分错。这种情况下与其指望deepsort自我修正不如直接重置。5. 避坑指南标注ID错位、小目标漏检与ID跳变的排查记录5.1 训练效果差且所有框都偏一个类别类别ID错位现象训练完跑验证发现helmet被识别成no-helmet且所有框集中在某个固定类别。原因几乎可以确定是data.yaml里names的顺序和标注txt第一列的数字对应关系不一致。最常见的情况是你从资源里拿了数据集后自己改过names顺序但没同步修改标注文件里的类别ID。解决打开三个地方对比确认——data.yaml的names列表、VOC转YOLO脚本里的class_names、以及任一txt标签文件的第一列数字。如果数据集内部同时有VOC的xml原始标注推荐直接按xml里的name字段重转一版YOLO标签不要手动改txt数字。改完后重新训练并在验证集上挑三张图逐框打印类别和置信度确认。5.2 验证集mAP很高实测工地视频漏检严重现象在数据集自带的test集上mAP0.5有0.9左右但换成现场手机拍的视频后远处安全帽基本检测不到。原因数据集里大部分标注目标的像素尺寸偏大模型学到了“大目标”的尺度特征而实际监控画面里安全帽可能只有32x32甚至更小。另一个叠加因素是训练时imgsz640小目标在缩放过程中特征被压缩。解决第一步把推理尺寸imgsz提高到960或1280检测头部小目标能力明显增强第二步在训练数据里用小目标复制粘贴增强——将已有标注框缩小后重复粘贴到图像不同位置第三步实在还不行就对视频帧做切片推理把一帧分成4块分别检测再合并结果代价是速度减半。我见过不少项目在提升imgsz之后小目标漏检率直接腰斩这一步最省力。5.3 跟踪ID频繁跳变一个人前后被编成3个ID现象工人从柱子后走过再出来track_id从7变成31再变成52。原因遮挡期间deepsort把目标判为丢失max_age到期后ID被回收等目标再次出现时外观特征匹配失败被当成新目标分配新ID。工地场景里工人穿同样的反光背心外观特征区分度极低这个问题几乎无法完全避免。解决把max_age调大到50让遮挡期间ID保留更久同时把max_cosine_distance从0.2放宽到0.3允许更大外观差异。如果换了这两参数还是频繁切换就要考虑在track.py里加一个视觉重识别模块替换deepsort默认的外观模型比如换用更强的osnet权重。但注意重识别模型一变整个deepsort的级联匹配逻辑都要跟着测一遍别指望调参能根治。5.4 运行track.py时deepsort报shape不匹配错误现象detections数组传入tracker.update时报类似ValueError: cannot reshape array of size 5 into shape (1, 4)的错误。原因create_detections内部期望的输入是二维数组每行是一个检测框但如果你传了一个numpy数组而它正好只有一行shape就会变成(5,)被reshape时直接报错。根源是检测结果里只有一个人时代码没做维度保护。解决在调用create_detections之前强制检查detections的shape如果只有一条数据手动reshape成(1, 5)。更稳的写法是detections_array np.array(detections, dtypefloat).reshape(-1, 5)顺带也检查所有从yolo拿到的boxes.data把它.cpu().numpy()之后如果为空要直接跳过tracker.update否则也会报类似错误。5.5 视频推理FPS只有个位数无法实时现象GTX 1660 Ti跑yolov8n加deepsortFPS在4到6之间线上没法用。原因检测和跟踪都在同一条主线程里逐帧串行执行加上deepsort的外观特征提取本身耗时不低而yolov8n虽然模型小但前处理、后处理和NMS在Python层有固定开销。解决第一把输入帧从1080p缩到960x540检测耗时立刻降一半第二打开yolov8的half精度推理即model.half()在支持fp16的GPU上能再提速30%左右第三把deepsort的特征提取放到一个独立线程用队列做生产者消费者模式检测线程只管检测跟踪线程拿最新的检测结果做关联。这三步做完正常能在1080p下到15-20 FPS虽然算不上高帧率但做安防预警够用了。6. 进阶验证用跟踪轨迹统计施工安全风险并用MOTA确认跟踪质量跑通检测和跟踪只是第一步真正能让这套系统有业务价值的是对跟踪结果做二次统计。比如一个人没戴安全帽连续出现在画面里多少秒才算有效告警如果单帧误检就发告警工地每天会响几百次根本没人看。我的做法是把track.py输出的轨迹数据落成CSV然后在后处理里按track_id聚合持续帧数。import csv from collections import defaultdict # 每次检测到no-helmet时把track_id和帧号写入csv risk_events defaultdict(list) # track_id - [frame_ids] with open(risk_log.csv, newline) as f: for row in csv.DictReader(f): risk_events[row[track_id]].append(int(row[frame_id])) # 按连续性分组成事件间隔超过10帧算两次事件 for tid, frames in risk_events.items(): frames.sort() start frames[0] prev frames[0] for f in frames[1:]: if f - prev 10: duration (prev - start) / 30 # 按30fps折算秒数 if duration 5: print(ftrack {tid}: 未戴安全帽持续 {duration:.1f} 秒) start f prev f这段逻辑的核心是帧间隔阈值和持续时间阈值。帧间隔阈值设为10帧是为了容忍deepsort偶尔丢一两帧导致的轨迹断裂持续时间阈值按5秒起步低于这个值的告警大概率是过路人员或误检。调这两个值时先拿一段10分钟测试视频人工标一遍真实违规片段再用脚本对比确定阈值合理性。跟踪质量的定量验证我推荐用MOTA多目标跟踪准确率和IDF1两个指标。MOTA综合反映漏检、误检和ID切换次数IDF1反映ID维持能力正好对应前面第5章调参关注的两个痛点。计算方式是把track.py导出的带ID结果按MOTChallenge格式整理成txt再用motmetrics库计算。注意GT真值轨迹需要你自己标一段视频我一般只标一段90秒左右的关键场景就能定位出跟踪参数合不合适——单独看FPS和可视化都容易自我感觉良好指标一算就知道当前配置在遮挡场景下到底损失了多少ID。回到这份资源本身我每次拿到类似的数据集和脚本都会强制走一遍“数据检查→小模型基线→检测调优→跟踪参数验证→轨迹统计”这个流程其中数据检查这一步最便宜但最容易被跳过。你要想让这套施工安全检测在自己的场景里站得住脚先照着第2章的目录检查做一遍再往第6章的轨迹统计方向扩展会比直接改模型结构有效得多。希望帮到你。本文还有配套的精品资源点击获取
返回列表