YOLO安全帽反光衣工作服数据集:标注训练到端侧部署
简介面向施工工地、工厂等安全生产场景的YOLO目标检测数据集聚焦人员安全帽、反光衣与工作服的穿戴状态识别可供算法工程师、计算机视觉学习者及安全信息化项目开发者用于模型训练、微调与效果验证。数据集共2000个文件以1999个txt标注文件为主体逐条记录图像中人员头部与身体的类别及边界框坐标另附1个yaml配置文件用于声明训练集、验证集路径与类别名称。压缩包约952.16MB采用zip打包解压后可直接接入YOLO系列训练流程。样本覆盖不同光照与作业环境下的工人着装情况可用于构建违规穿戴检测、实时告警与抓拍联动等功能支撑事前预防、事中检测、事后追溯的安全管理闭环。目前已有1614人学习下载能够帮助读者省去人工标注成本快速完成从数据准备到模型落地的验证。1. 从一次 0.11 置信度的漏报说起这套三类别数据集解决什么某园区的施工区域出过一次漏报画面里一名工人从背光侧走进来模型给出的置信度只有 0.11没触发任何告警。回看训练样本才发现逆光、侧身、半遮挡这几类情况在数据里几乎是空白——模型不笨是喂进去的东西不全。这套基于 YOLO 的安全帽/反光衣/工作服自动识别数据集价值就在这里三类目标放在同一套标注体系里标注格式直接对齐 YOLO 的 txt 规范拿到手不用自己贴几千张图可以直接进入训练、调阈值、上端侧的环节。它适合三类人做工地或园区安全视觉的算法同学、想完整跑通一次 YOLO 检测流程的在校同学、以及需要快速拿出可演示原型的外包团队。下面按「读标注 → 训基线 → 验指标 → 避坑 → 闭环」的顺序拆开讲。2. 拆开标注目录YOLO txt 五个字段与三类别的边界怎么读数据集拿到手第一件事不是急着敲训练命令而是把目录和标注文件翻开看一遍。YOLO 格式的检测数据集通常是images/与labels/平行存放图片和同名 txt 一一对应train 与 val 各一份。这个结构看着简单真正决定模型上限的是 txt 里那五个字段代表什么、三类别之间怎么划界。这一章先把标注读明白后面所有的训练参数才有意义。2.1 三类标签的互斥边界helmet、vest、workwear 谁该框在哪YOLO 的每一行标注是五个值class_id x_center y_center width height后四个都是相对图像宽高归一化到 01 的浮点数。很多人第一次看会以为是像素坐标直接乘回去就错了。三类别数据集的 class_id 一般固定为 0安全帽、1反光衣、2工作服具体顺序以数据集自带的说明或data.yaml里的names为准动手前必须核对一遍因为这个顺序一旦和 txt 里的 id 对不上训练照样能跑完只是模型学到的全是错的东西。三类的框选边界需要说清楚否则自己做增量标注时一定会标歪class_id名称框住的对象常见误标0helmet头顶佩戴的安全帽本体不含头部裸露区域把人头整个框进去帽檐被裁切时直接丢弃1vest躯干部分的反光背心/反光条服装区域把整条人框进去把橙色普通工装当反光衣2workwear非反光类的工装上衣通常覆盖躯干与 vest 混标只框袖子或局部同一个工人同时戴帽、穿反光衣会出现两个独立框这是正常的不要当成重复标注删掉。真正需要警惕的是「类别互斥」被破坏同一个人上半身既标 vest 又标 workwear模型会在两个类之间反复摇摆表现出来就是两个类的置信度都上不去各自卡在 0.4 附近。我一般会在标注复核阶段用脚本扫一遍同一张图里框的 IoU超过 0.7 又属于不同类别的全部拉出来人工看一遍这一步花十分钟能省掉后面两天调参的折磨。还有一点容易忽略部分公开数据集会用person类代替工作服类。如果手里的数据混了这两种体系先做类别映射统一别想着让模型自己区分。数据集内部一致比类别定义「更合理」重要得多。2.2 用一段脚本反推数据分布类别计数、每图目标数与框面积在开始训练前用一段脚本把数据集的真实分布摸清楚比看任何说明文档都靠谱。重点看三个数每类有多少个框、每张图平均几个目标、框的相对面积集中在哪个区间。安全帽这类目标在远景监控画面里往往只占图像的 0.5%2%属于典型小目标这个数字直接决定后面 imgsz 要设多大。from pathlib import Path from collections import Counter, defaultdict label_dir Path(datasets/ppe/labels/train) # 换成实际标签目录 cnt Counter() # 每类框数量 per_img [] # 每图目标数 area_bin defaultdict(int) # 框面积分布 for txt in label_dir.rglob(*.txt): lines [l.split() for l in txt.read_text(encodingutf-8).strip().splitlines() if l.strip()] if not lines: continue per_img.append(len(lines)) for p in lines: cid int(p[0]) w, h float(p[3]), float(p[4]) # 归一化宽高都合法才算有效框越界说明标注写坏了 if not (0 w 1 and 0 h 1): print(异常框:, txt.name, p) continue cnt[cid] 1 area w * h if area 0.01: area_bin[1%] 1 elif area 0.05: area_bin[1-5%] 1 elif area 0.15: area_bin[5-15%] 1 else: area_bin[15%] 1 total sum(cnt.values()) print(类别计数:, dict(sorted(cnt.items()))) for k, v in sorted(cnt.items()): print(f class {k}: {v} 框, 占比 {v/total:.1%}) print(f每图目标数 平均 {sum(per_img)/len(per_img):.2f}, 最大 {max(per_img)}) print(框面积分布:, dict(area_bin))这段脚本做了三件事。第一遍历目录下所有 txt用rglob保证子目录也能扫到避免漏掉分批存放的标签。第二对每个框做合法性校验归一化宽高越界基本可以断定是标注工具写坏了坐标直接打印文件名方便回查比训练时报一个莫名其妙的断言错误好定位。第三把框面积按图像占比分桶1%和1-5%两桶加起来如果超过一半说明这是个小目标主导的场景后面imgsz至少要从 960 起步640 大概率不够。输出结果里类别计数那一行最值得盯。安全帽、反光衣、工作服的框数如果差到 5 倍以上就属于明显不平衡训练时要么调类别权重要么对少数类做重采样。每图平均目标数超过 8 的多半是密集人群场景NMS 的 iou 阈值要往下调一点否则重叠框会被大量误抑制。2.3 划分与 data.yaml路径写错时训练为什么照跑不误data.yaml是整个训练入口写错一个字母都可能让训练「看起来在跑、实际在空转」。常见结构是这样path: /data/ppe # 数据集根目录绝对路径最稳 train: images/train # 相对 path 的路径或直接写 txt 列表文件 val: images/val names: 0: helmet 1: vest 2: workwear关键是path和train的组合规则train写成目录时框架会去找path/train并把路径里的images自动替换成labels去找标签写成.txt文件时则逐行读取图片绝对路径。前者方便后者可控。我一般用前者但会把path写成绝对路径避免训练脚本从别的目录启动时相对路径解析到奇怪的地方。划分训练验证集有一条硬规则同一个视频抽出来的相邻帧不能跨集。相邻帧之间差异极小随机划分会让验证集里充斥着和训练集几乎一样的画面mAP 漂亮得不像话一上现场就露馅。正确做法是先按视频源或时间段分组再在组级别上做 8:2 划分import random from pathlib import Path random.seed(42) src_img Path(raw/images) # 按文件名前缀分组前缀相同的视为同一个视频源 groups {} for p in sorted(src_img.glob(*.jpg)): groups.setdefault(p.stem.split(_)[0], []).append(p.stem) keys list(groups.keys()) random.shuffle(keys) n_val max(1, int(len(keys) * 0.2)) val_keys set(keys[:n_val]) val_list [s for k in val_keys for s in groups[k]] train_list [s for k in keys if k not in val_keys for s in groups[k]] Path(split/train.txt).write_text(\n.join(train_list), encodingutf-8) Path(split/val.txt).write_text(\n.join(val_list), encodingutf-8) print(ftrain {len(train_list)} 张 / val {len(val_list)} 张)random.seed(42)固定随机种子保证每次划分结果一致方便复现按_前缀分组是个偷懒但有效的办法只要原始文件名保留了来源信息就能用正式项目里还是建议维护一张「图片 → 视频源」的映射表。n_val至少保留 1 组防止数据源太少时验证集为空。有一类翻车特别隐蔽val路径写的是一个空目录训练照样开始前几个 epoch 的 loss 正常下降但验证指标一直是 0日志里也不报错。所以训练启动后的第一件事是去日志里确认val的图片数量和你脚本统计出来的一致这一步十秒钟能挡掉一半的低级问题。3. 训出第一个可用基线环境、超参与曲线读法数据和标注都核对过了接下来要的是一个「能跑起来、指标可解释」的基线模型。基线的意义不是刷最高分而是建立一个参照系后面换 imgsz、换权重、加数据增强效果好不好都有个对比对象。这一章把环境、命令、参数和曲线读法一次讲透跟着敲完就能拿到第一个可用的权重文件。3.1 依赖与权重版本对齐比装最新更重要环境这块最容易踩的坑是装了一堆最新版本结果框架、PyTorch、CUDA 三方互相不认。稳妥的做法是先确定显卡驱动支持的 CUDA 上限再选对应的 PyTorch 版本最后装检测框架。Python 建议 3.10 或 3.11太新的版本有些依赖轮子还没跟上。# 建独立环境别装在系统 python 里 conda create -n ppe python3.10 -y conda activate ppe # 先装与驱动匹配的 torchcu121 只是示例按 nvidia-smi 的结果选 pip install torch torchvision --index-url https://download.pytorch.org/whl/cu121 # 再装检测框架本体 pip install ultralytics # 验证三件事torch 能否看到卡、cuda 版本、框架版本 python -c import torch; print(torch.__version__, torch.cuda.is_available(), torch.version.cuda)torch.cuda.is_available()返回False就说明版本没对上这时候继续训练会用 CPU 硬扛一个 epoch 跑半小时很容易被误判成「训练太慢」。装完先跑一个 5 epoch 的小实验确认链路通再上正式训练。预训练权重的作用是给模型一个合理的初始特征nano 级别的权重在小目标场景上未必最优但足够快适合用来验证数据和配置是否正确。等基线跑通了再换 s/m 级别权重对比这个顺序别反。3.2 训练命令与超参imgsz960 换来的召回值不值先把完整命令摆出来再逐条解释yolo detect train \ modelyolov8n.pt \ data/data/ppe/data.yaml \ epochs120 \ imgsz960 \ batch16 \ workers8 \ patience30 \ cacheram \ cos_lrTrue \ close_mosaic15 \ projectruns/ppe \ namebaseline_v1imgsz960是这份数据最值得调的一个参数。640 是默认值速度快但安全帽在远景画面里本身只有几十个像素缩到 640 后可能只剩十几个像素特征提取网络根本抓不住。960 相当于把小目标放大 1.5 倍召回通常能涨 510 个百分点代价是显存占用涨约 2.2 倍、单 epoch 时间涨 1.8 倍左右。如果 960 显存吃紧可以先降到 800或者把batch从 16 降到 8 再试。判断标准很简单验证集上安全帽类别的召回率是否明显低于精确率低就说明小目标没学进去往上加分辨率。batch16在 12G 显存上配合 960 尺寸基本能跑显存更大的可以提到 24 或 32batch 大一点 BN 统计更稳但小数据集上并不总是越大越好。workers8是数据加载进程数CPU 核少就降到 4设太高反而会因为进程调度拖慢速度甚至触发共享内存不足。cacheram把图片缓存进内存数据量在几万张以内收益明显第二次 epoch 开始几乎不耗 IO 时间。但如果机器内存只有 16G、数据又有十几万张开这个选项会直接把内存吃满导致进程被杀这时候改成cachedisk或者关掉。close_mosaic15表示最后 15 个 epoch 关闭马赛克增强。马赛克会把四张图拼成一张对小目标学习有帮助但收尾阶段开着会让模型对目标的实际尺寸分布判断失准关掉后指标通常还能再涨一点。cos_lrTrue用余弦退火学习率比阶梯下降平滑小数据集上比较稳。patience30配合epochs120意味着连续 30 轮验证指标没提升就提前停避免无效算力消耗。数据增强方面这个场景有两类增强要慎用大幅度的上下翻转工人不会倒着走路翻了反而制造噪声、过强的颜色抖动反光衣的辨识很大程度依赖那几条高亮反光带HSV 的 h 通道抖动幅度太大会把这层特征抹掉。角度旋转建议控制在 ±10 度以内degrees10.0足够。3.3 三条曲线与一张混淆矩阵什么时候该停训练跑起来后runs/ppe/baseline_v1/下面会自动生成结果曲线。有三个地方必须盯第一是box_loss和cls_loss训练集和验证集的这两条线应该同步下降如果训练集一路降到 0.3 而验证集在 1.5 附近翘头就是过拟合考虑加数据或加正则别继续耗着。第二是mAP50和mAP50-95前者看「大致能不能找到」后者看「框得准不准」两者差距过大比如 0.85 对 0.45通常说明定位精度不够多半是标注框本身松紧不一致或者小目标太多。第三是precision和recall的平衡安全帽场景里漏检代价远高于误检所以 recall 的权重应该更高如果 recall 长期低于 precision 十个百分点以上就该往提高召回的方向调。混淆矩阵在val阶段生成非对角线的数字最说明问题。常见两种形态一是「背景」那一列误检多说明模型把安全帽的黄色边缘、反光条误判成了目标属于典型的颜色域过拟合补负样本二是 vest 和 workwear 之间互相串说明两类边界本身不清晰需要回头复核标注。训练什么时候停我的经验是看 mAP50-95 连续 20 轮波动在 ±0.005 以内且 recall 不再上涨就可以收了。硬等满 120 轮没有意义patience提前停的权重往往就是最好的那一版。4. 把 mAP 翻译成现场语言阈值扫描、混淆矩阵与端侧测速验证集上的 mAP 再高也只是一个平均数它掩盖了「在某个置信度下到底漏多少、误多少」。真正决定这套模型能不能上的是置信度阈值怎么定、反光衣漏检的根因在哪、端侧跑不跑得动。这一章把这三件事分别落到脚本和数字上。4.1 推理脚本与置信度扫描0.25 不是万能值默认置信度阈值 0.25 是个通用起点放到具体场景里往往不合适。安全帽漏检的代价高阈值就该往下压到 0.15 甚至更低用误报换漏报如果只是做统计报表误报多了影响观感就往上抬。扫描的办法是固定 IoU 阈值让 conf 从低到高跑一遍把每档的 precision、recall 拉出来对比from ultralytics import YOLO model YOLO(runs/ppe/baseline_v1/weights/best.pt) rows [] for conf in [0.10, 0.15, 0.20, 0.25, 0.35, 0.50]: m model.val( data/data/ppe/data.yaml, splitval, confconf, # 只影响指标计算时的框筛选 iou0.6, # NMS 的 IoU 阈值密集场景可降到 0.5 plotsFalse, verboseFalse, ) rows.append((conf, m.box.mp, m.box.mr, m.box.map50, m.box.map)) print(f{conf:6} {P:7} {R:7} {mAP50:8} {mAP50-95:9}) for r in rows: print(f{r[0]:6.2f} {r[1]:7.3f} {r[2]:7.3f} {r[3]:8.3f} {r[4]:9.3f})m.box.mp是平均精确率mr是平均召回率map50和map分别对应 IoU0.5 和 0.5:0.95 的平均精度。这段脚本的价值在于把「阈值」这个抽象参数和「漏多少」这个具体业务指标连起来。实际选点的时候我会画一条 conf-recall 曲线找到召回还能保持在 0.9 以上的最低 conf作为告警阈值再取一个高 conf 作为「确定事件」的阈值两级输出既保证不漏也避免刷屏。iou0.6是针对这张表的设置人群密集、框重叠严重的场景建议降到 0.5否则相邻工人的反光衣框会被 NMS 吃掉一个。这个参数和推理时的实际值必须一致训练验证用一套、部署用另一套是最常见的「离线好好的、上线就崩」的原因。4.2 混淆矩阵定位「反光衣漏检」是特征问题还是标注问题反光衣这一类特别容易出现「mAP 单看还行实际场景漏得厉害」的情况原因通常分三种靠混淆矩阵和单图可视化就能区分开。第一种是类别不平衡。如果 vest 的框数只有 helmet 的三分之一模型天然会倾向于把模棱两可的区域判成 helmet 或背景表现为混淆矩阵里 vest 行大量落到 background 列。解决办法是重采样把含 vest 的图片复制一份参与训练或者调高该类的损失权重注意别复制太多导致过拟合。第二种是标注边界不一致。反光衣的框到底框到肩膀还是框到腰不同标注员理解不同。训练集里两种画法混杂模型学到的框会偏大或偏小IoU 卡在 0.40.5 之间上不去表现是 mAP50 尚可但 mAP50-95 很低。这类问题只能靠复核标注解决调参没用。第三种是外观域偏移。训练集里的反光衣以荧光黄为主现场出现了荧光橙、带灰条的新款式模型没见过就直接漏。判断方法是把漏检的图裁出来看颜色分布如果集中在某一两个色调上基本可以确认。对策不是重训而是在推理前做一次简单的颜色增强或者补几百张新款式样本做增量训练。from ultralytics import YOLO import cv2 model YOLO(runs/ppe/baseline_v1/weights/best.pt) img cv2.imread(case/backlight_017.jpg) # 低阈值全量输出看模型到底「看到了什么」而不是「报了什麼」 res model.predict(img, conf0.05, iou0.5, verboseFalse)[0] for box in res.boxes: cid int(box.cls.item()) score float(box.conf.item()) x1, y1, x2, y2 [int(v) for v in box.xyxy[0].tolist()] print(f{model.names[cid]:10} {score:.3f} ({x1},{y1})-({x2},{y2}))conf0.05是故意设得很低目的是看见 0.10.2 区间的「弱响应」。如果反光衣在 0.12 附近有一堆框说明模型其实识别到了只是置信度不够属于阈值问题如果一个框都没有那是特征层面就没学到得回到数据和分辨率上找原因。这两种情况处理方式完全不同别一上来就重训。4.3 导出 ONNX 与端侧帧率别拿显卡成绩骗自己训练机上跑出来的 FPS 参考价值有限真正要上的是边缘盒子或者工控机算力可能只有显卡的十分之一。导出前先确认目标设备的推理后端支持哪些算子ONNX 是最通用的中间格式。# 导出为 ONNXopset 12 兼容性最好dynamicFalse 固定尺寸跑得更快 yolo export modelruns/ppe/baseline_v1/weights/best.pt \ formatonnx opset12 imgsz960 simplifyTrue dynamicFalseimgsz960必须和训练时一致导出尺寸和训练尺寸不同会带来明显的精度损失尤其是小目标。simplifyTrue会做一轮图优化去掉冗余节点通常能提速 10% 左右。dynamicFalse表示输入尺寸固定很多端侧推理库对动态尺寸支持不好固定下来更稳。如果业务上需要处理不同分辨率的输入就在送入模型前统一做 letterbox 缩放到 960×960别指望模型自适应。导出后在目标设备上跑一次纯推理测速只统计前向时间不包含读图和预处理import time, numpy as np, onnxruntime as ort sess ort.InferenceSession(best.onnx, providers[CPUExecutionProvider]) name sess.get_inputs()[0].name x np.random.rand(1, 3, 960, 960).astype(np.float32) for _ in range(10): # 预热前几次包含内存分配不计入 sess.run(None, {name: x}) t0 time.perf_counter() N 50 for _ in range(N): sess.run(None, {name: x}) dt (time.perf_counter() - t0) / N print(f单帧前向 {dt*1000:.1f} ms, 理论上限 {1/dt:.1f} FPS)预热这一步不能省ONNX Runtime 第一次推理会做算子融合和内存分配直接计时会得到一个虚高的数字。测出来的前向时间只是下限实际链路还要加上解码、缩放、NMS 后处理经验上后处理会再吃掉 20%40% 的时间。如果单帧前向已经超过 80ms1 FPS 都不到那就得考虑换更小的输入尺寸或者量化别指望优化后处理能救回来。5. 避坑与排查六个让指标虚高的隐蔽问题跑通过一次不代表跑对了。下面这几条都是我在不同项目里真实撞过的共同特征是「训练过程毫无异常、指标好看、上线就废」每条按现象、原因、解决三段拆开。5.1 训练 loss 正常下降验证 mAP 一直是 0现象训练日志里 box_loss 从 2.1 稳步降到 0.8看起来一切正常但每个 epoch 结束后的验证指标全是 0混淆矩阵一片空白。原因验证集的标签路径没对上。框架根据val路径把images替换成labels去找标签如果验证图片放在images/val但标签放到了labels/val_set这种自定义目录就找不到任何标签文件验证时全部按背景处理。也有一种情况是names里定义的类别数和 txt 里的最大 class_id 不匹配越界的标注被静默丢弃。解决训练启动后第一件事是看日志里的验证集图片数和标签数和你脚本统计的结果对一遍。对不上就手动检查一个验证样本用Path拼出对应的标签路径print出来看是否存在。这一步花十秒能省掉一整轮无效训练。5.2 mAP50 冲到 0.92现场漏检率却接近三成现象验证集指标非常漂亮交付演示时也顺但换了一个摄像头点位漏检肉眼可见地多。原因训练集和验证集来自同一批视频的相邻帧随机划分后两边的画面高度相似验证集本质上是「训练集的近邻」指标虚高。这种情况在数据量小的时候特别容易发生看起来有几千张图实际可能只来自十几个场景。解决按视频源或拍摄时段做组级别划分强制同一来源的帧只出现在训练集或验证集一侧。划分完再统计一次两边的场景数量如果验证集只覆盖三五个场景说明数据多样性本身就不够得补数据而不是调参。5.3 反光衣 AP 只有 0.3另外两类都在 0.85 以上现象三类的指标严重撕裂安全帽和工作服都正常唯独反光衣怎么调都上不去换权重、改学习率都没用。原因两个可能一是类别数量差太大导致模型偏向多数类二是反光衣在画面里普遍偏小且经常被遮挡特征提取不充分。判断方法是分别统计三类的框面积中位数如果反光衣明显更小那就是分辨率问题。解决先把imgsz从 640 提到 960 或 1280看 AP 有没有明显变化如果涨幅有限再对含反光衣的图片做重采样让该类样本量拉到其他类的 0.6 倍以上。两个手段叠加通常能把 AP 拉回 0.7 附近。都不行的话就是标注边界本身有问题回到 2.1 节复核。5.4 导出 ONNX 后精度明显掉档现象PyTorch 权重验证 mAP50 有 0.86导出 ONNX 后同一批图跑出来只有 0.7 出头框的位置也偏。原因预处理不一致。训练时框架对输入做了 letterbox 缩放加 padding导出后如果用 OpenCV 直接 resize 到 960×960会改变目标的宽高比模型见到的分布和训练时不同。另一个常见原因是归一化参数写错训练是除以 255推理时忘了除或者用了 ImageNet 的均值方差。解决把训练框架的预处理逻辑原样搬到推理侧letterbox 的缩放比例、padding 值、通道顺序BGR 还是 RGB、归一化方式四项必须逐一对齐。核对办法是拿同一张图分别走 PyTorch 和 ONNX 两条路比较输出的原始张量差异应该在 1e-3 以内对不上就是预处理的问题。5.5 训练跑到一半进程被杀或者速度突然变慢现象前十几个 epoch 正常之后速度断崖式变慢或者直接报内存错误退出。原因开了cacheram数据集又比较大内存被逐轮填满触发系统 OOM。另一种是workers设得比 CPU 物理核数还多进程之间抢资源反而拖慢数据加载表现出来是 GPU 利用率忽高忽低。解决数据量超过内存容量一半时把cache改成disk或直接关掉。workers设成物理核数的 0.7 倍左右比较合适8 核机器用 6 就行别设 16。判断是不是 IO 瓶颈看训练日志里每轮的耗时波动如果忽快忽慢超过 30%基本可以确定是数据加载跟不上。5.6 模型把橙色工装识别成反光衣现象现场有一种橙色普通工装外套模型给出反光衣 0.6 以上的置信度误报频繁。原因训练集里反光衣以荧光黄为主样本颜色分布单一模型学到的判别特征实际是「高饱和度的亮色」而不是反光条本身的纹理。这是典型的颜色捷径学习。解决在训练增强里适当加大 HSV 的 s 和 v 抖动逼模型去关注反光条的几何纹理同时补一批橙色普通工装的负样本直接从数据层面切断这条捷径。增强参数别一次加太猛hsv_s0.7、hsv_v0.4是比较温和的起点加完重新看混淆矩阵里 vest 行落到 background 的比例有没有下降。6. 用半自动标注把新场景喂进去一个可复用的闭环数据集的真正价值不在「能训出一个模型」而在于它给了你一个可复用的起点换一个工地、换一种工装样式不用从零标起走一遍半自动标注就能把新场景接进来。这套闭环我用了两年多新点位从拿到视频到模型可用通常压在三小时以内。核心思路是用当前最好的权重做预标注人工只做删改不做画框。预标注的置信度要设得比平时高宁缺毋滥保证自动生成的框绝大多数是对的人工复核的负担才降得下来from ultralytics import YOLO from pathlib import Path model YOLO(runs/ppe/baseline_v1/weights/best.pt) src Path(new_site/images) out Path(new_site/labels_auto) out.mkdir(parentsTrue, exist_okTrue) for img_path in src.glob(*.jpg): res model.predict(str(img_path), conf0.55, iou0.5, verboseFalse)[0] h, w res.orig_shape lines [] for box in res.boxes: cid int(box.cls.item()) x1, y1, x2, y2 box.xyxy[0].tolist() # 转成 YOLO 的归一化中心点格式 cx, cy (x1 x2) / 2 / w, (y1 y2) / 2 / h bw, bh (x2 - x1) / w, (y2 - y1) / h lines.append(f{cid} {cx:.6f} {cy:.6f} {bw:.6f} {bh:.6f}) (out / (img_path.stem .txt)).write_text(\n.join(lines), encodingutf-8)conf0.55是预标注专用的高阈值比业务推理阈值高得多目的是让自动框的准确率达到 95% 以上。iou0.5压低一些避免密集人群里相邻工人被合并。输出格式直接按 YOLO 规范写成归一化的中心点加宽高人工复核工具可以直接打开改完就能进训练。人工复核阶段只做三件事删掉误检的框、补上漏检的尤其是小目标和遮挡目标、修正画歪的边界。实测下来一张中等复杂度的图平均改动 12 个框比从零标注快 5 倍以上。增量训练时有个取舍要拿捏新数据全量替换旧数据模型会遗忘旧场景只用旧数据新场景又学不进去。我的做法是新旧按 1:2 混合且新数据里的难样本预标注置信度在 0.550.7 之间的额外复制一份。混合比例可以通过对比「新场景验证集」和「旧场景验证集」两组指标来微调两个都不掉才算稳。阶段输入关键参数输出耗时经验抽帧新点位视频每 30 帧取 1300500 张图10 分钟预标注上表图片 现有权重conf0.55, iou0.5自动 txt5 分钟人工复核自动 txt只删改不画修正后 txt1.5 小时增量训练新 1 : 旧 2imgsz 与原训练一致新权重40 分钟双集验证新旧两个验证集conf 阈值扫描对比表15 分钟这套流程跑顺之后最大的风险反而变成「太顺了」。有一次我图省事预标注阈值设到 0.3自动框里混了一堆低置信度的噪声框复核的同学改到崩溃最后还是推倒重来。从那以后我每次做预标注都强制走一遍小样本抽检随机抽 20 张逐张核对自动框的准确率和召回率两个数都达标才批量跑。这一步不超过五分钟但它是整个闭环里唯一的后悔药。希望帮到你。本文还有配套的精品资源点击获取