
简介面向智慧工地施工安全监测场景的 YOLOv8DeepSORT 目标检测与跟踪项目包可对施工人员安全帽、反光背心穿戴情况及人员位置进行实时检测与轨迹跟踪适合安全巡检系统开发者和目标检测初学者使用。包内共2000个文件包含997个txtYOLO格式标签、998个xmlVOC格式标签、Python运行脚本、Markdown说明文档及PDF运行步骤教程压缩包约356.67MB。数据集含1206张已标注图像并预先划分为train/val/test附带data.yaml可直接用于YOLOv5/v8/v9/v10/v11/v12等模型训练检测类别涵盖helmet、no-helmet、no-vest、person、vest等支撑安全隐患识别。此外集成DeepSORT跟踪算法配合脚本与教程可快速跑通检测加跟踪流程。已有93人浏览学习适合需要完整数据与代码结合实践的研究者。1. 施工安全隐患巡检为什么是 yolov8deepsort 而不是两套独立系统工地现场的安全帽、反光背心、人员站位是施工安全管理的三个高频检查项。单独用 yolov8 做检测只能拿到“这一帧有没有人没戴安全帽”一旦人走动、转身、被遮挡框就断断续续告警也会跟着抖加上 deepsort 之后检测结果会先被追踪器串成带 ID 的轨迹统计“谁进入了危险区域”“在卸料平台逗留了多久”这类问题才成立。对做智慧工地、安全巡检系统的工程师来说把 yolov8 和 deepsort 拼成一条检测加跟踪的流水线是这条业务最直接的落地路径。标题里的 site 就是施工场地目标是保障 construction workers 的安全数据方面常见做法是收集安全帽、反光背心、人员三类样本先用一段真实施工视频做验证再把训练好的检测模型接进 deepsort 跟踪算法。下面我从数据到部署按我实际搭建这套系统时会走的顺序把关键命令、参数和坑位讲清楚。2. 先搞清跟踪目标yolov8 的检测输出与 deepsort 的输入约定用 yolov8deepsort 不能直接当作两个黑盒拼接得先看接口长什么样。很多人第一步就把数据格式传错了后面怎么调参数都白搭。2.1 yolov8 返回的是“帧级目标”deepsort 要的是“轨迹状态”yolov8 的 detect 输出结构是(N, 6)x1, y1, x2, y2, confidence, class_id用 predict 模式得到的就是这种 xyxy 格式。这个结果只描述当前这一帧没有任何跨帧信息。deepsort 要做的是利用卡尔曼滤波预测每个目标下一帧的位置再和当前帧的检测框做级联匹配和 IOU 匹配所以它需要的外部输入也只是“这一帧的检测框”但要求框的格式稳定、置信度过滤逻辑统一。常见做法是先由 yolov8 做 conf 过滤和 NMS然后把剩余的框交给 deepsort。这里有一个关键点deepsort 会维护一个 tracker 数组每个 target 有 bbox、velocity、age、hits、time_since_update 这些字段。当某一帧检测丢失只要 time_since_update 没超过 max_age目标轨迹依然保留。这意味着“两个人重叠导致漏检了一帧”也能续上施工工人弯腰捡东西、被塔吊吊臂短暂遮挡这类动作不会立刻判为离开。2.1.1 deepsort 里两个最容易忽视的状态级联匹配与 IOU 匹配级联匹配优先处理那些连续被匹配、外观特征稳定的轨迹避免 ID 被新目标抢占IOU 匹配作为兜底处理被遮挡后重现的目标。施工场景里安全帽和反光背心的外观差异很大级联匹配表现稳定但如果两个工人并肩穿过IOU 一旦交叠超过阈值就会短暂串 ID。因此实际部署时常见做法是把外观特征提取网络换成更轻量的 ReID 模型并调低max_cosine_distance到 0.2 附近让外观特征更“挑剔”。2.2 先验证“检测框接进跟踪器”的最小协议下面这段代码演示 yolov8 的检测结果如何转换后送入 deepsort 的 update。注意我用的是 xyxy 格式deepsort 内部需要转成中心点加宽高from ultralytics import YOLO import numpy as np # 常见的 deepsort 实现都会提供这个接口 from deep_sort_pytracker import DeepSort # 仅示意实际以你的工程为准 detector YOLO(yolov8s.pt) # 先用预训练权重验证协议 tracker DeepSort( model_pathckpt/reid_ckpt.t7, # ReID 权重通常用行人重识别数据集训练 max_dist0.2, # 外观特征距离阈值 max_iou_distance0.7, # IOU 匹配阈值 max_age70, # 轨迹丢失保留帧数 n_init3, # 连续命中多少帧才确认新轨迹 nn_budget100 # 外观特征库上限 ) frame np.zeros((1080, 1920, 3), dtypenp.uint8) # 示意一帧 results detector.predict(frame, conf0.35, classes[0]) for r in results: arr r.boxes.data.cpu().numpy() # (N, 6): x1, y1, x2, y2, conf, cls bbox_xywh np.zeros((len(arr), 5)) if len(arr): bbox_xywh[:, 0] (arr[:, 0] arr[:, 2]) / 2 # cx bbox_xywh[:, 1] (arr[:, 1] arr[:, 3]) / 2 # cy bbox_xywh[:, 2] arr[:, 2] - arr[:, 0] # w bbox_xywh[:, 3] arr[:, 3] - arr[:, 1] # h bbox_xywh[:, 4] arr[:, 4] # conf outputs, _ tracker.update(bbox_xywh, frame)注意一个常见的错误deepsort 的 update 接收的第四列是置信度不是类别。如果手滑把 class id 传进去匹配结果会变得很怪。再有tracker.update内部会先对现有轨迹做卡尔曼预测再执行级联匹配和 IOU 匹配输出的 outputs 已经带上 track_id业务逻辑里直接读就行。字段映射关系可以用下面这张表来记阶段数据字段含义送入跟踪前要做的处理yolov8 predictx1, y1, x2, y2, conf, cls检测框、置信度、类别conf 和 NMS 过滤deepsort updatecx, cy, w, h, conf中心点、宽高、置信度把 xyxy 换成 cxcywhtracker 输出x1, y1, x2, y2, id, conf, cls轨迹框、身份、置信度、类别画框、记录轨迹、触发业务2.3 用完整视频验证接口是否接通最小协议验证通过后别急着上自己的施工数据集先找一段普通行人的视频跑通流程。看两点一是画面上每个行人是否有稳定 ID二是人走出画面再回来时 ID 是否变化。如果 ID 频繁切换说明检测抖动或者 ReID 特征质量不够如果完全不切换说明 max_age 设得太大要为后面的施工场景留调整空间。3. 用施工安全数据集把落点养熟标签清洗、数据划分与 yolov8 训练命令coco 预训练权重里根本没有“安全帽”“反光背心”这两个类直接拿来检测施工场景等于让模型盲猜。要落地必须用施工安全数据集重新训练 yolov8 检测模型。3.1 施工安全数据集的常见构成与标签口径常见做法是用公开的安全帽检测数据集再补充自采工地视频帧。字段一般有 helmet、person、vest、head_without_helmet 这几类。拿到数据后第一件事是统一标签口径并去重比如“worker”是否标注在没有戴安全帽的工人身上“反光背心”在夜间画面中颜色偏暗是否单独建类我一般把目标固定为四类类别序号标签名说明0person工人整体跟踪的主要对象1helmet戴在头部的安全帽2vest反光背心3head_without_helmet未戴安全帽的头部只参与检测不进跟踪注意 head_without_helmet 不要送给 deepsort否则会出现“同一个人的头部”和“整个身体”两条轨迹同时更新的情况告警会重复。这个类只做检测作为 person 轨迹下的一个子状态。3.2 把 VOC / Roboflow 标注转成 yolov8 的 yolo 格式yolov8 训练需要的数据目录结构是固定的dataset/ images/train/*.jpg images/val/*.jpg labels/train/*.txt labels/val/*.txt data.yamldata.yaml 是关键文件names 列表的顺序必须和 txt 文件里的类别序号严格一致。标注转换脚本的核心逻辑如下import xml.etree.ElementTree as ET def voc_to_yolo(xml_path, out_path, class_map, img_w, img_h): root ET.parse(xml_path).getroot() lines [] for obj in root.findall(object): cls_name obj.find(name).text if cls_name not in class_map: 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) dw, dh 1.0 / img_w, 1.0 / img_h xc, yc (x1 x2) / 2.0, (y1 y2) / 2.0 bw, bh x2 - x1, y2 - y1 lines.append( f{class_map[cls_name]} {xc*dw:.6f} {yc*dh:.6f} {bw*dw:.6f} {bh*dh:.6f} ) with open(out_path, w) as f: f.write(\n.join(lines))参数说明XML 里 xmin/ymin 是整数像素值除以图片宽高变成归一化坐标class_map 决定类别序号data.yaml 里的 names 顺序不能变。目标框不裁剪到图片边缘外yolov8 训练时会自动做 letterbox不需要自己 resize。3.3 用 yolov8 训练自己的数据集数据准备好后训练命令如下yolo detect train data/path/to/data.yaml \ modelyolov8s.pt \ imgsz1280 \ epochs100 \ batch16 \ device0 \ optimizerAdamW \ lr00.0005 \ patience15 \ mosaic0.5参数建议表参数推荐值理由modelyolov8s.pt中等骨架平衡速度和精度施工现场一般用 CPU 工控机或 Jetson 边缘盒子imgsz1280 或 960安全帽、反光背心属于小目标分辨率不够直接漏检mosaic0.5全开时训练易抖动施工数据量不足时更明显batch168GB 显存够用24GB 可以加到 32patience15早停防过拟合optimizerAdamW小数据集收敛比 SGD 稳补充一点安全帽在 1080p 视频里往往只有几十个像素imgsz640 时极易漏检。我一般先用 640 跑 20 轮看 loss 是否正常下降再切 1280 精调。mosaic 别全关设 0.5 就能兼顾小目标的上下文信息。3.4 训练产物与验证命令训练完会生成best.pt和last.pt验证时跑yolo val modelruns/detect/train/weights/best.pt \ data/path/to/data.yaml \ imgsz1280 \ conf0.25 \ iou0.45注意看 val 日志里的 mAP50 和 mAP50-95以及每个类别的 AP 值。如果 helmet 的 AP 明显低于 person说明安全帽小目标样本不足或标注框太大回数据集补样本比换模型更有效。反光背心的 mAP 在夜间场景通常偏低这是正常现象后面要配合图像增强或红外摄像头处理。4. 把 yolov8 检测器和 deepsort 跟踪器拼成一条流水线训练好的模型只是检测器没有跟踪的业务价值。施工 site 部署时流水线一般是读帧 → yolov8 检测 → 过滤目标类别 → deepsort 更新 → 业务判断禁区闯入、逗留超时→ 记录事件。4.1 常见做法用 boxmot 把 yolov8 和 deepsort 装进同一套框架boxmot 是一个集成多种跟踪器的工具库封装了 deepsort 的整个内部流程。这样就不用自己维护卡尔曼滤波和匈牙利匹配的代码专注业务层。from boxmot import DeepSort from pathlib import Path tracker DeepSort( model_weightsPath(best.pt), devicecuda:0, fp16True )注意 boxmot 不同版本的接口参数略有差异部署前先打印help(tracker)确认参数名避免版本不兼容。4.1.1 处理一次视频流的最小脚本import cv2 from pathlib import Path from ultralytics import YOLO from boxmot import DeepSort model YOLO(best.pt) tracker DeepSort(model_weightsPath(best.pt), devicecuda:0, fp16True) cap cv2.VideoCapture(site.mp4) while cap.isOpened(): ret, frame cap.read() if not ret: break # 只保留 person(0)、helmet(1)、vest(2) 三类送跟踪 results model(frame, classes[0, 1, 2], conf0.3, iou0.45) dets results[0].boxes.data.cpu().numpy() # (N, 6) tracked tracker.update(dets, frame) for track in tracked: x1, y1, x2, y2, track_id, conf, cls track color (0, 255, 0) if cls 0 else (0, 255, 255) cv2.rectangle(frame, (int(x1), int(y1)), (int(x2), int(y2)), color, 2) cv2.putText(frame, fID:{int(track_id)}, (int(x1), int(y1) - 8), cv2.FONT_HERSHEY_SIMPLEX, 0.8, color, 2) cv2.imshow(site, frame) if cv2.waitKey(1) 0xFF ord(q): break cap.release() cv2.destroyAllWindows()这段代码里需要注意deepseek 输出的 track 数量可能和检测结果数量不一致因为 tracker 里有历史轨迹的预测框。所以业务逻辑里读的是 tracked 而不是 dets。cls 类别信息会从检测结果透传过来可以直接画不同颜色区分工人和安全帽。4.2 加一个施工场景的基础业务判断误入危险区域检测跟踪只是手段真正的“安全隐患”往往是“人员进入禁入区域”。tracker 输出后做多边形包含判断是最常见的做法import numpy as np # 在图上手动标一个危险区域四个点顺时针或逆时针都行 region np.array([[100, 300], [600, 300], [600, 800], [100, 800]]) def point_in_polygon(x, y, poly): n len(poly) inside False for i in range(n): x1, y1 poly[i] x2, y2 poly[(i 1) % n] if (y1 y) ! (y2 y): x_cross x2 - (y2 - y) * (x2 - x1) / (y2 - y1) if x x_cross: inside not inside return inside for track in tracked: x1, y1, x2, y2, track_id, conf, cls track if cls 0 and point_in_polygon((x1 x2) / 2, (y1 y2) / 2, region): print(fworker {track_id} entered danger zone)这里判断用顶端中心点而不是矩形底部因为工人站立时矩形底点在脚部容易被地面警戒线误触发。危险区域多边形应该在调试界面里可视化成半透明区域方便现场调整坐标。4.3 当检测和跟踪结果重新对应时容易踩的坑安全帽和反光背心的检测框通常比 person 小如果让这两类也参与 deepsort 的独立跟踪同一时刻一个工人身上最多会出现 3 条轨迹严重消耗计算资源且 ID 混乱。我的做法是只把 person 类送入跟踪器helmet 和 vest 标记为 person 轨迹的子状态等 person 轨迹稳定后再去查当前帧该框内有没有 helmet 和 vest 的检测结果在轨迹层面维护“该工人是否戴帽穿反光衣”。这样告警只基于 person 轨迹不重复也不漏报。5. 参数怎么调才不飘安全帽检测与工人跟踪的调参和排错流水线能跑通后就该面对“检测好好的一接跟踪就漂”的现实了。施工场景的调参先调检测器再调跟踪器。5.1 先调检测器的 conf 和 iou再谈跟踪deepsort 对检测质量很敏感检测框抖动会导致级联匹配失败ID 立刻切换。我一般按这个顺序来yolo predict modelbest.pt imgsz1280 sourcesite.mp4 conf0.25 iou0.35先肉眼过一遍视频看安全帽小目标是否稳定。如果大量重叠导致漏检把 iou 调小到 0.2 左右如果检测框闪烁把 conf 降到 0.2但别低于 0.1否则大量误检会让深 sort 建一堆假轨迹。夜间施工、反光背心颜色偏暗时优先在数据集补夜间负样本而不是一味提 conf。5.2 deesort 在施工人员跟踪场景的参数推荐参数推荐值说明max_dist0.2 ~ 0.3大于 0.5 时容易把两个不同工人的外观特征搞混max_age50 ~ 70工人被塔吊吊臂、集装箱遮挡时保持轨迹不消失n_init3减少短暂误检导致的假轨迹启动max_iou_distance0.7工人密集通道可以调低到 0.5nn_budget100特征库过大在边缘设备上占用内存高如果摄像头是高空俯拍工人很小检测框里包含的 ReID 特征少还需要在送入 tracker 前对特征向量做 L2 归一化避免距离计算被特征尺度带偏。5.3 常见症状排查跟踪器日志里会有类似内容[TRACK] update #1024 unmatched_detections: 3 unmatched_tracks: 1 ID switches: 12如果每千帧 ID switches 超过 20说明跟踪质量明显不行。排查顺序是先看检测是否在目标转身或被反光背心遮挡时频繁断框再用固定帧间隔的标注视频去统计。下面几个是我踩过的具体坑两个工人交错时 ID 互换但检测框连续。问题在 ReID 特征换更稳的 ReID 权重或者调低 max_dist。跟踪框出现在没有人的位置。原因是 tracker 把低置信度检测也接进来了应该在送入 update 前过滤掉 conf 低于 0.25 的检测框。远景处的安全帽检测到了但跟踪不上。这是检测小目标不稳定导致的应先把 imgsz 提到 1280不要动跟踪参数。6. 用 MOTA 说话再上 TensorRT施工场景跟踪效果的验证与部署最后落地时不光看某个视频的主观效果要量化。跟踪质量的标准度量是 MOTA、MOTP 和 ID Switches。常见做法是用 motmetrics 库对一段 310 分钟的现场视频算指标。6.1 对一段打标视频计算跟踪指标import motmetrics as mm import numpy as np acc mm.MOTAccumulator(auto_idTrue) # 示意一帧真实框和预测框索引是轨迹 ID gt {1: np.array([100, 200, 50, 100]), 2: np.array([300, 300, 50, 100])} pred {1: np.array([105, 202, 50, 100]), 3: np.array([310, 305, 50, 100])} # distance 传 0.5 表示 IoU 阈值也可以用中心点距离 acc.update(gt, pred, distance0.5) mh mm.metrics.create() report mh.compute(acc, names[site_10min]) print(report[[mota, motp, num_switches]])施工场景建议用 IoU 大于 0.4 作为匹配阈值。MOTA 低于 0.6 时不要直接上线业务告警先把检测的召回率拉上来。6.2 上边缘设备时常见的加速手段模型导出为 FP16 TensorRT engineyolo export modelbest.pt formatengine device0 imgsz1280 halfTrue导出的 engine 在 Jetson Orin 或工控机上可以直接替换原来的 .pt 权重。注意 imgsz 必须是推理时实际使用的尺寸改小后要重新导出否则 letterbox 的填充比例变化会导致跟踪框整体偏移。6.3 多路摄像头的部署细节多路摄像头时每一路摄像头必须创建独立的 deepsort 实例不能共用一个 tracker否则不同画面的工人会互相争抢 ID。如果现场帧率只有 5fps可以把 max_age 调到 80 以上让工人短暂离开画面后仍能保持 ID。最后一个小技巧把 conf、max_age、危险区域多边形都做成热加载的配置文件改参数不用重启服务现场调试能省下大量时间。本文还有配套的精品资源点击获取