ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

基于YOLOv5的全自动标注工具:从伪标签到训练闭环的工程实践

基于YOLOv5的全自动标注工具:从伪标签到训练闭环的工程实践 简介这是一套基于YOLOv5的全自动标注工具面向目标检测开发者和标注人员只需要将自己训练的权重文件与yaml配置放入data文件夹并在detector_classes.txt中指定目标类别即可对images内的图片批量自动生成XML标注文件可有效替代手动框选大幅提升数据准备效率。压缩包大小约13.94MB共135个文件以Python脚本33个py、YAML配置41个yaml和依赖环境定义文件为主辅以少量pt权重、XML样例、Markdown说明及Dockerfile等方便一键调整跑通。已有3192人学习浏览。通过图形界面完成参数设置后执行自动标注可用CtrlC安全终止并用LabelImg查看结果整套工具包覆盖了YOLOv5工程、自动标注脚本、类别配置与目录模板入手即可结合自有数据使用适合需要快速构建训练集的中高级开发者。1. 全自动标注工具支持YOLOv5解放的是重复劳动不是标注责任第一次用全自动标注工具跑新项目我的日审核量从一千张掉到两百张但漏检率也比纯人工标注高了四个百分点。这套工具的原理并不复杂拿一个已经收敛的YOLOv5模型去推理未标注图片按置信度筛掉低质量预测框再把剩下的框直接写成YOLO格式的txt标签让图片到标注的转换不再需要人画框。对数据量几千张、场景分布稳定、类别数量别超过几十个的项目这套“预标注加人工复核”的弱监督流程能省掉七成重复劳动。它适合被标注疲劳折磨的CV工程师和快速验证目标检测原型的团队但别指望连冷启动都不做就解放双手。2. 为什么拿YOLOv5当标注引擎而不是SAM或人工框选2.1 三种标注管线的工程取舍全自动标注这个需求出现很久了现在市面上能落到实处的方案大体分三条路直接拿检测模型当预标注器拿SAM这类通用分割模型先生成掩码再转矩形框以及完全交给人工框选。三选一的时候我几乎总是先把宝压在“检测模型当预标注器”上因为全自动标注的命门是吞吐量而不只是框的质量。YOLOv5在推理速度上比其他方案有数量级优势。一张1080Ti上单帧推理三到五毫秒几千张图一个上午就能全部产出伪标签这个量级足够把人工从纯画框里捞出来。换成SAM的话掩码质量确实细但成本和格式转换问题很现实——掩码转出来的矩形框经常带大片背景直接喂给训练反而引入噪声还得额外写一套掩码转yolo标注的管线。人工框选当然是精度基准但一小时三百张就是这个工种的生理上限。全自动标注的真正价值是把人从“画框”改成“审框”压力从低效率的机械劳动转成抽查式的质量把关。所以选型结论按项目来我一般建议先上YOLOv5做第一轮预标注人工重点复核置信度中段也就是0.3到0.7之间的样本这个区间的质量直接决定后面模型迭代是正循环还是负循环。2.2 YOLOv5源码与网络结构在预标注任务里顺手的三个点YOLOv5源码本身就是一个能直接用的推理框架绝大多数情况下不需要自己写预处理和后处理。三个点对预标注任务特别对症第一detect.py里开一个--save-txt参数输出的就是和训练标注格式一致的同类标签第二网络结构图里的特征金字塔在P3、P4、P5三个尺度上都输出预测结果小目标召回率不至于太难看第三超参数配置在data/hyp.scratch.yaml里摊得很明白当你发现伪标签漏检集中在某个类别时按参数找问题比瞎猜模型靠谱得多。我拉源码做标注工具的工作流很固定只改数据集路径不动网络主体。有人喜欢在预标注阶段换backbone或者魔改检测头这属于给自己挖坑。一旦结构变了后面伪标签和真标签的混合训练会因为特征分布漂移直接翻车排错成本比省下来的那点精度收益高得多。稳定版本的YOLOv5源码里检测头输出和loss是解耦的作为标注引擎时黑匣子最少置信度、类别、坐标这些信息都能稳定拿到调起参来有底。还有一点容易被忽略标注用的模型和部署用的模型最好保持同一个框架。YOLOv5的export.py可以直接把训练好的权重导出成ONNX配合树莓派5上的onnxruntime可以把“自动标注”和“边缘侧推理验证”串成同一条技术栈后面换引擎等于推倒重来。这些细节在项目立项时看着不起眼到回灌训练时才发现是省时间的关键。2.3 伪标签质量的关键一定搞清楚自己在做弱监督不是全自动把自动标注当全自动是项目翻车的最大根源。伪标签的质量完全取决于teacher模型当时训练数据的分布。同一张图白天光线好的时候模型可能给出0.9的置信度到黄昏光线一变就掉到0.2。所以伪标签必须经过“置信度阈值加类别白名单加人工抽检”三层过滤否则模型会把自己学坏。这个流程本质上是Learning from Noisy Labels翻译成人话就是高置信度样本直接入库低置信度样本丢给人工复核。这个认知直接决定你的参数怎么设计——置信度阈值设太低噪声进训练集模型精准率崩阈值设太高大量样本没被自动标掉人工负但没真正降下来。我现在做新项目都会先拿验证集跑一轮置信度分布再决定阈值不再凭感觉拍脑袋。类别白名单也是必须搭的那层过滤。很多工具自动标注出的“人”的框其实是墙上的阴影检测“车”的模型会把路牌也框成车。这些误检置信度还不低不设类别过滤伪标签会把后面模型的精确率拉到没法看。常用做法是给过滤脚本传一个ignore_classes参数把已知易混类别排除让审核员只处理被过滤掉的样本而不是把整批结果从头看到尾。3. 搭建全自动标注工具从环境配置到批量推理3.1 环境配置YOLOv5源码与PyTorch版本的最小搭配全自动标注工具吃的就是YOLOv5源码那套运行环境。先创建一个干净的conda环境Python版本选3.9PyTorch版本跟着源码里requirements.txt锁定的一致就好。我的建议是PyTorch选1.12到2.x之间不要太激进推理路径上没什么差距但后续在树莓派5上做onnxruntime部署时ARM端的兼容矩阵对旧版本更友好。# 创建conda环境指定Python 3.9 conda create -n yolov5auto python3.9 -y conda activate yolov5auto # 把YOLOv5源码拉取到本地保持官方默认分支 git clone YOLOv5官方仓库地址 yolov5 cd yolov5 # 安装运行依赖首次建议直接全量装 pip install -r requirements.txt依赖装完先别急着跑数据验证一下CUDA是否可用很多标注工具跑不起来不是模型问题是环境里少了关键包。# check_cuda.py # 验证PyTorch能否正常调用GPU推理 import torch print(torch.__version__) print(torch.cuda.is_available())逻辑说明requirements.txt里的torch、opencv、pyyaml这几个依赖缺一不可opencv缺失时detect.py读取图片会直接崩pyyaml缺失则配置文件解析失败。CUDA打印True说明GPU可用打印False也别慌伪标签量不大时CPU推理也能过夜跑完只是慢。参数说明conda环境名随意但建议起成项目相关名防止多个环境混淆git clone保持默认分支即可预标注任务不需要锁特定版本稳定主分支跟着更新问题不大。3.2 用detect.py一次跑完三千张图save-txt与置信度参数组合环境配好把需要自动标注的图片丢进一个目录直接调YOLOv5自带的检测入口就行。我不推荐在这个阶段自己写推理循环detect.py在预标注场景下比手写脚本稳得多它的image resize、letterbox、NMS后处理都处理完了你只需要关注参数。# 批量推理把data/raw下的所有图片变成txt标签 python detect.py \ --weights weights/best_teacher.pt \ --source ../data/raw \ --save-txt \ --save-conf \ --project ../out/preds \ --name auto_labels \ --conf-thres 0.3 \ --iou-thres 0.5 \ --img-size 640 \ --agnostic-nms逻辑说明--save-txt是核心开关它把每个检测结果写到与图片同名的txt文件里--save-conf会在每行末尾追加置信度供后续过滤脚本使用--agnostic-nms让不同类别间的框也参与NMS抑制防止同一个目标同时被标成两个类。参数说明--conf-thres设0.3是保守起步值宁多勿缺过滤留在后面的脚本里做--iou-thres是NMS的IoU阈值设在0.5左右框更紧凑低于0.3容易把重叠目标合并且漏标--img-size务必和训练teacher模型时一致突然改分辨率会显著影响小目标标注质量。命令跑完以后去out/preds/auto_labels目录检查labels子目录下就是伪标注结果文件名和原图一一对应。如果图片里没有任何检测框对应txt不会生成这是正常现象后面漏检排查再处理。3.3 写一个过滤脚本把伪标签收编成能进训练的数据集detect.py输出的txt虽然格式正确但直接拿去训练会有问题——低置信度框混入太多噪声。我一般会在流水线里加一个独立过滤脚本把类别过滤、最小尺寸过滤、置信度过滤合并成同一个入口保证伪标签进入训练集前是干净的。# process_labels.py # 把detect.py输出的伪标签过滤成正式YOLO格式标签 from pathlib import Path def filter_labels( labels_dir: Path, out_dir: Path, min_conf: float, min_size_px: int, ignore_classes: set[int], img_height: int 640, ): out_dir.mkdir(parentsTrue, exist_okTrue) for txt in sorted(labels_dir.glob(*.txt)): keeps [] for line in txt.read_text().strip().splitlines(): parts line.split() cid, cx, cy, w, h map(float, parts[:5]) conf float(parts[5]) if len(parts) 5 else 0.0 if int(cid) in ignore_classes: continue if conf min_conf: continue # 把归一化宽高换算成像素尺寸过滤小目标噪声 if (w * img_height) min_size_px or (h * img_height) min_size_px: continue keeps.append(f{int(cid)} {cx:.6f} {cy:.6f} {w:.6f} {h:.6f}) if keeps: (out_dir / f{txt.stem}.txt).write_text(\n.join(keeps)) print(filtered labels:, len(list(labels_dir.glob(*.txt)))) if __name__ __main__: filter_labels( Path(../out/preds/auto_labels/labels), Path(../out/preds/auto_labels/filtered), min_conf0.45, min_size_px8, ignore_classes{0, 80}, # 0和80是当前场景最常见的阴影误检类别 )逻辑说明脚本逐行读入带置信度的txt每行是一个检测结果逐行做三类过滤过滤后的新文件写到filtered目录文件名保持和原图一致这样训练时可以直接把这张图的标签替换成过滤后的版本。图像分辨率不是640时必须把img_height改成实际值否则归一化换算失真小目标过滤就失效了。参数说明min_conf0.45是第4章里用PR曲线分析出来的拐点不是随便选的min_size_px8对应640分辨率下宽或高小于8像素的框这类框大多来自模型幻觉ignore_classes按实际误检查结果写不同项目的差异很大一定要自己统计。这个脚本是我做全自动标注工具时最舍不得删的一段。它算不上漂亮但成功把“全自动”和“能训练”之间那层不靠谱剥掉了。视频数据也一样处理先用ffmpeg按1fps抽帧再喂给detect.py抽帧频率影响的是相邻帧标签的连续性1fps对标注任务够用。4. 参数怎么调置信度、NMS和超参数的热平衡4.1 置信度阈值用PR曲线定别拿0.5当万金油全自动标注工具最常被问的参数是置信度阈值该设多少。我见过最普遍的翻车方式是默认0.5一把梭然后发现模型漏检严重。0.5对很多目标检测模型其实是比较高的阈值尤其在小目标或者遮挡场景下正确检测的置信度往往只有0.3到0.5之间。正确做法是拿验证集标签统计置信度分布找到精确率和召回率的平衡点。写一个小脚本就能解决# conf_dist.py # 统计验证集所有真标注框在teacher模型下的置信度分布 from pathlib import Path import numpy as np confs [] for txt in Path(../out/preds/valid_fake/labels).glob(*.txt): for line in txt.read_text().strip().splitlines(): confs.append(float(line.split()[-1])) # 每行最后一列是置信度 confs np.array(confs) for thr in (0.2, 0.3, 0.4, 0.5, 0.6, 0.7): print(thr, (confs thr).mean())逻辑说明这个脚本统计的是teacher模型在验证集伪标签上的置信度分布。输出结果是“阈值对应保留比例”保留比例掉得越快说明模型越自信可以适当提高阈值反之模型偏悲观阈值应当往下压。参数说明推荐阈值取PR曲线拐点附近的值。模型过自信时分布右移阈值可以设到0.6以上模型欠拟合时左移0.25到0.3才能保住召回率。标注工具里阈值宁可偏低因为后面还有人工抽检兜底漏标比错标配更致命。4.2 NMS阈值改的是“框打架”不是“有没有框”第二个高频参数是--iou-thres。很多人误以为调NMS能改善漏检这是个认知错误。NMS后处理只解决两个问题同一个目标上叠了多个框应该留哪个不同类别在同一区域预测冲突按什么顺序抑制。它不负责把一个没检测出来的目标变出来。在预标注场景里NMS最值得调的不是阈值大小而是要不要开--agnostic-nms。默认情况下NMS只在同类之间抑制当你标注的类别之间有高重叠关系比如“行人”和“骑车人”同一个目标经常被同时标成两个类审核时就会出现一个框是行人、另一个框是自行车的重叠标注。开掉agnostic之后会让class之间互相竞争只保留置信度更高的那个。NMS阈值本身我一般固定在0.45到0.5。设太低了比如0.3会让两个重叠的检测框互相抑制原本正确的边界框被扔掉设太高比如0.7密集场景下框叠着框过滤脚本反而不知道该留谁。从标注工具出发0.5是稳妥起点。4.3 类别过滤与小目标下限标注工具工作的最后一公里置信度阈值挡掉了模型不自信的样本但拦不住“模型自信地错”。比如我们项目里的阴影类别模型置信度拉到0.8以上漏进过滤脚本后直接污染训练集。类别过滤解决的就是这一类系统性误检它跟置信度是两个维度的问题。我建议把ignore_classes配置做成数据集级别的yaml而不是硬编码进脚本。因为随着迭代你会发现每个类别的误检率完全不一样——有的类别永远不需要过滤有的类别误检率稳定在15%这些规律只有统计完才清楚。把配置外部化之后每次迭代可以按上一轮的误检统计结果改配置避免动代码。小目标下限这参数设的是“保留多少像素以上的框”。在检测任务里小于8像素宽高的目标对640分辨率来说基本只有几个噪点大这两个框既难训练又难标注。我能给的建议是根据你的数据分布来调透明物体、远处车辆这类本来就小的目标别一刀切而典型的噪点框集中在个位数像素设8到16像素比较合理。设太高会导致尾部类别消失全自动标注变成自动删标注那就本末倒置了。5. 全自动标注常见问题与避坑五条血泪记录5.1 伪标签全是漏检审核员想砸键盘现象跑完批量推理以后人工审核时发现大量本来肉眼清晰的目标没有被标注。最夸张的一次一整屏的零售商品只被标出了三分之一审核员得把漏掉的框全补上工作量反而比全人工还高。原因teacher模型在该场景下的训练数据不足或者是目标尺度变化超出模型能力范围。伪标签的下限完全取决于模型上限模型在验证集上的漏检率会成倍放大到标注环节。解决先回验证集看PR曲线拐点把置信度阈值降0.1到0.15试试。阈值降完还是漏得离谱就别硬调参数了先补充该场景下的小批量真标注数据快速微调一轮teacher模型再回来重新跑。全自动标注不是一次就成的流程它依赖一个相对合格的teacher模型做支撑。5.2 自动生成的框有系统性偏移目标永远不在框中心现象伪标签里所有框都比目标实际位置偏右偏移二十个像素左右看起来整齐但不准。审核员起初以为是个别误检统计之后发现是系统性偏差。原因一个常见原因是teacher模型训练时数据增强设置太激进比如hsv随机增强让模型学到了一个整体偏向的色彩分布空间另一个常见原因是--img-size比训练尺寸小letterbox在缩放时产生的坐标映射偏差被放大了。解决先确认推理时的--img-size和训练时一致这是最容易被忽略的一步。然后打开训练时的data/hyps配置把hsv_h、hsv_s这类增强参数调到默认值范围重新训一版teacher。遇到坐标偏移不要手动加偏移量修正那是靠玄学打补丁真正问题在数据增强或尺寸不匹配。5.3 视频抽帧标注闪烁序列像抽风现象从视频抽帧自动标注时同一辆车的标签一会在框里一会在框外相邻帧的类别还被交替标成“车”和“货车”看序列集时跟故障一样闪烁。原因独立处理每一帧是标准做法但预测结果天然不稳定。相邻帧的遮挡、运动模糊、抽帧频率不连贯都会让单帧推理的置信度上下浮动。解决抽帧频率降到1fps以下让相邻帧的内容差异小一些做一次时序平滑把连续几帧的检测框做加权合并。我在生产里用的是最轻量的方案对同一视频源取三帧结果做投票保留至少出现两次的框类别取置信度最高的一类。时序平滑虽然朴素但能显著降低审核时的闪烁感。5.4 伪标签拿去训练反而掉点现象把过滤后的伪标签和真标注混在一起训练新模型验证集mAP反而比只用真标注训练的模型低了两个点。第一反应是疯狂调超参数结果越调越乱。原因伪标签的噪声分布和真标注不一样模型对干净标注的记忆会被噪声覆盖。最严重的是那些过滤时漏进来的低质量边缘框它们通常集中在目标边界附近会导致训练时边界回归反复横跳。解决不要拿所有伪标签直接灌进训练集。常见做法是分层采样置信度0.8以上的样本全量进训练集0.45到0.8之间的按二分之一比例随机抽入0.45以下直接丢弃。伪标签占比不超过全部标注的百分之五十一旦超过噪声开始压制真实信号mAP会系统性下沉。5.5 自动标注循环停不下来没有收敛条件现象全自动标注跑完一轮又一轮每轮都标注了新图片、重新训练了模型但验证集精度几乎不涨人力投入却在持续增加。项目里没有“什么时候停下来”的定义成了无底洞。原因没有设定“停止迭代”的客观标准。伪标签带来的信息增益会随着迭代逐渐减小当模型在新增样本上已经学不到新分布时继续跑只是自我重复。解决在项目开始时定义一个收敛指标连续两轮自动标注后的验证集新增标注收益低于百分之二或者新增伪标签中人工抽检的误检率下降到百分之三以下就停止是本轮标注并固化数据集。收敛条件写进项目文档谁都不许凭感觉决定加跑一轮。标注不是越多越好是越准越好。6. 验证你的标注管线检查清单加一次回灌训练6.1 五个问题的检查清单全自动标注管线搭完以后别急着铺开用拿下面的清单过一遍。这张表也是我在新项目里给数据团队列的最低验收标准验证项验证方法通过标准标签一致性随机抽100张图人工核对伪标签与真实标注框的重叠IoU均值大于0.7漏检率在固定验证集上统计漏检目标占比漏检率低于百分之五类别分布对比伪标签类别占比与人工标注类别占比每个类别偏差小于百分之三时间收益统计单人每小时审核伪标签张数比全人工标注快一点五倍以上训练收益用混合数据集和纯人工标注各训练一个模型混合训练mAP不低于人工标注版本漏检率是最关键的一项它反映了teacher模型的下限。时间收益决定了这套工具值不值得长期投入。训练收益则是最终说服团队继续用这套流程的定心丸。6.2 让标注与训练闭环伪标签回灌与边缘侧验证验证通过后要做的不是继续手动保证标签质量而是把标注和训练接成闭环。伪标签回灌是常见做法把过滤后的伪标签和真标注按比例混合重新训练一版student模型再拿这个模型去做新数据的预标注。注意保留上一版teacher模型的权重作为对照回灌后模型如果掉点还能回滚到上一版这就是伪标签方案的后悔药。# 将伪标签和真标注合并后直接训练新模型 python train.py \ --data mixed.yaml \ --weights weights/yolov5s.pt \ --img 640 \ --epochs 100逻辑说明mixed.yaml里的train路径同时指向真标注目录和伪标签目录两份数据在读取时会自动被混进同一个batch。跑完这轮训练模型导出best.pt作为新一轮预标注的teacher。参数说明--epochs按数据集规模设几千张图片一百轮足够--weights选yolov5s.pt效率比较高标注引擎不需要最大的模型。我这套流程还会把训练好的模型用export.py转成ONNX在树莓派5上跑一版实时推理对着现场视频验证新采集的样本能不能稳定出框。树莓派5上的部署主要是为了确认“标注管线的输出在真实边缘环境也可用”如果边缘端能稳定出框说明现在这版伪标签生成器具备实战价值。我现在的标注管线里人只负责看板和抽样复核不再对着画框工具熬夜。这套流程给出的不是一个永远正确的模型而是一套已经知道自己在哪、能改善的点在哪的迭代机制。认清这个边界才敢说画框这件事真的解放了双手希望帮到你。本文还有配套的精品资源点击获取
返回列表