首页 / 资讯中心 / 文章详情

YOLO半自动标注工具实战:从TXT格式到迭代回灌

YOLO半自动标注工具实战:从TXT格式到迭代回灌 ★ FEATURED ARTICLE
简介这是一套面向YOLO系列目标检测数据集制作的自动标注工具源码包适合需要快速完成图像标注、构建训练集的开发者与研究人员。压缩包共115个文件约6.3MB以Python脚本、png图标与界面资源为主同时包含shell脚本、配置文件、Markdown说明及YOLO格式样例文件可支撑工具的运行、二次开发与定制编译。工具代码内置多边形标注、快速编辑、类别管理、批量处理、预览校验、YOLO格式导出等常用功能并保留团队协作与自动化辅助的扩展空间有助于将繁琐的人工框选流程转化为更高效的半自动标注管线。labelImg-master目录结构完整便于按模块理解入口、配置与界面逻辑。已有382人学习下载适合正在训练YOLO模型并希望提升数据准备效率的算法工程师和计算机视觉初学者。1. 自动标注工具YOLO 系列数据集从「手动框框」到「半自动生产」做目标检测的人迟早会撞上同一个问题模型训练只花几小时标数据却要耗掉几周。YOLO 全系列v5、v8、v11 乃至更早的 v3都吃同一种 TXT 标注格式这既是好消息也是坏消息——格式统一意味着工具链能复用但也意味着一旦标注质量出问题后面所有训练迭代都在为脏数据买单。我最早用 LabelImg 一张张框图5000 张车流图框到手指发酸后来换上半自动标注工作流把「预训练模型出初标 人工修正 格式校验」串成流水线相同工作量能覆盖 2 到 3 倍的数据量。这篇笔记就把这套流程拆开讲格式规范、批量生成脚本、校验工具以及我踩过的几个坑。适合正准备训练 YOLO 自定义数据集、又不想在标注环节耗掉大量时间的开发者。2. 先把标注格式立住YOLO TXT 的规范与三条路线选型很多人翻车不是翻在模型而是翻在标注文件的第一行。YOLO 系列要求的不是 VOC 的 XML也不是 COCO 的 JSON而是每个图片对应一个同名 TXT每行一个目标格式为class x_center y_center width height四个坐标全部归一化到 0-1 区间。这个设计让不同分辨率的图片可以共用同一套标注文件但也带来两个隐蔽问题类别索引必须从 0 开始且和训练配置里的data.yaml严格对齐坐标必须除以图片宽高漏除一次就全废。2.1 坐标格式拆解归一化不是可选项以一帧 1920×1080 的图片为例假设检测框左上角是 (480, 270)右下角是 (960, 810)。框的绝对宽高是 480 和 540中心点绝对坐标是 (720, 540)。转成 YOLO 格式就是0 0.375 0.5 0.25 0.5其中0.375 720 / 19200.5 540 / 10800.25 480 / 19200.5 540 / 1080。这里有个新手容易忽略的点宽高也是归一化后的比例值不是像素值。有些自动标注工具导出时直接写绝对像素训练时 loss 直接起飞而且报错不明显loss 看起来在下降但 mAP 永远上不去。我一般会在工具链里加一道强制校验读取图片尺寸检查 TXT 里每个值是否都在 0-1 区间内超出就报警。这个习惯救过我很多次尤其是从网上找来的数据集很多是 VOC 或 COCO 格式转换脚本里一不留神就漏了归一化步骤。2.2 三条标注路线对比纯手动 / 预训练伪标签 / SAM 辅助市面上的标注方案大致分三类选哪条取决于你的数据场景和算力。纯手动用 LabelImg 或 LabelStudio胜在可控适合小样本冷启动但 5000 张图的工作量会让人怀疑人生。预训练伪标签方案是用现成 YOLO 权重跑一遍未标注图片把检测结果直接写成 TXT再人工修正这是目前性价比最高的路线前提是你的目标类别和预训练模型的类别有重叠。SAM 辅助方案用分割模型帮你框物体轮廓适合边界不规则的场景比如肺结节、遥感目标但 SAM 本身是大模型推理速度慢而且输出的是分割掩码转成检测框还要额外处理。就我自己的经验工业缺陷检测、车辆检测这类类别固定且外观相对规整的数据直接用 YOLOv8n 或 YOLOv8s 的预训练权重生成初标就够用。鸟类、船舶这类细长目标预训练模型可能漏检率高这时再用 SAM 补漏比较划算。简单说先伪标签后 SAM 补漏最后人工修正这套组合在成本和精度之间最平衡。2.3 我常用的标注工作流预标注 人工修正的配比具体执行时我不会把所有图片一次性丢给模型标注。更稳的做法是先挑 200 张代表性图片覆盖不同光线、角度、目标大小用预训练模型出初标后人工精修作为验证基准。然后跑批量自动标注每跑一批就抽样 10% 检查修正后再继续。这个「小批量迭代」策略能尽早发现类别映射错误或漏检系统性偏差避免 5000 张图全部标完才发现某个类别索引错位。标注完成后还有一步容易被跳过的操作把标注文件里的类别索引映射整理成清单和data.yaml里的类别列表做对比。自动标注工具如果是从 COCO 权重迁移过来的类别索引往往和你实际业务的类别顺序不一致比如 COCO 里0是 person你的数据集里0可能是车这个错位不检查的话训练出来的模型会永远学错东西。3. 批量生成初标用 YOLOv8n 推理脚本把图片变成 TXT半自动标注的核心引擎就是用现成权重跑推理然后把结果落成 TXT。我用的是 Ultralytics YOLOv8 的 Python API因为它在导出检测结果时能直接拿到归一化坐标省去手动转换。下面这套脚本是我实际在用的简化版适合批量处理一个文件夹里的所有图片。3.1 环境准备与模型选择推荐用 ultralytics 库装一次就能跑推理和训练。模型权重方面速度和精度的平衡点我选yolov8n.pt如果你的目标物体很小可以换yolov8s.pt或yolov8m.pt代价是推理时间翻倍。pip install ultralytics注意版本至少 8.0 以上早期版本的predict接口和现在的 API 有差异代码照着网上的旧教程抄容易跑不通。3.2 推理转标注脚本从检测框到 TXT核心逻辑遍历图片目录调用模型预测把结果的boxes数据取出来写入同名 TXT。坐标直接用.xywhn属性拿归一化值不用自己算。from ultralytics import YOLO from pathlib import Path model YOLO(yolov8n.pt) # 加载预训练权重 image_dir Path(raw_images) output_dir Path(labels) output_dir.mkdir(exist_okTrue) results model.predict( sourcestr(image_dir), conf0.25, # 置信度阈值低于此值的检测框丢弃 iou0.5, # NMS 的 IoU 阈值控制重叠框去重 imgsz640, # 推理分辨率 verboseFalse ) for res in results: image_path Path(res.path) txt_path output_dir / (image_path.stem .txt) lines [] for box in res.boxes: cls int(box.cls[0]) # 类别索引 x_center, y_center, w, h box.xywhn[0].tolist() lines.append(f{cls} {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f}) txt_path.write_text(\n.join(lines), encodingutf-8) # 同时复制原图到对应目录便于后续用标注工具打开检查 (output_dir.parent / images / image_path.name).write_bytes( image_path.read_bytes() )逻辑说明这是标准的「推理 落盘」两段式流程。先预测所有图片再把每张图的检测框逐行写入 TXT。用xywhn而不是xyxy是关键前者直接输出归一化中心点宽高后者是像素坐标还得额外转换一遍。参数说明conf0.25是置信度阈值初标阶段我建议调低到 0.2宁可多出一些假阳性框让后续人工删也不要漏检让后续人工加框加框比删框费时得多。iou0.5是 NMS 阈值目标密集的场景可以调到 0.4避免重叠目标被合并。imgsz640是推理尺寸小目标多的数据集建议用 1280但显存占用会显著上升。3.3 类别过滤与重映射拿 COCO 权重标自己的类如果你的分类不是 COCO 那 80 类比如你要标的是「车 行人 摩托车」三类就必须做类别重映射。COCO 里car是索引 2person是 0motorcycle是 3你的data.yaml里可能定义索引 0 是 car1 是 person2 是 motorcycle。如果不做映射标注文件里的类别索引就和业务对不上。# COCO 类别名到业务索引的映射 COCO_NAMES model.names # {0: person, 1: bicycle, 2: car, ...} TARGET_MAP {car: 0, person: 1, motorcycle: 2} for box in res.boxes: cls_id int(box.cls[0]) cls_name COCO_NAMES[cls_id] if cls_name not in TARGET_MAP: continue # 不感兴趣的类别直接跳过 target_id TARGET_MAP[cls_name] x_center, y_center, w, h box.xywhn[0].tolist() lines.append(f{target_id} {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f})逻辑说明先查 COCO 类别名再通过字典映射到业务索引不感兴趣的类别整行跳过。这种方法比硬编码索引数字安全得多因为 COCO 的索引顺序背不下来查名字不容易出错。这里有一个值得注意的细节初标阶段的类别映射表记得保存下来后面训练时的data.yaml里的names顺序必须和这里的TARGET_MAP值完全一致。我习惯把映射表直接写进data.yaml的注释里防止过两周自己忘了当初怎么标的。3.4 图片与标注文件的目录约定YOLO 训练时的数据集目录结构有约定images/train、images/val、labels/train、labels/val。自动标注脚本输出的标签应该按这个结构放置。我一般会先把所有图片放在一个临时目录跑批量标注然后用划分脚本按比例拆到 train 和 val再对应移动标注文件。需要特别注意的是图片和标注文件必须保持同名后缀不同.jpg对应.txt一旦有一张图漏了标注文件训练时会报No labels found的警告默认会跳过这张图你还不容易察觉。4. 人工修正与格式校验别把脏数据直接喂给训练自动标注完成后最危险的操作就是直接开始训练。预训练模型生成的伪标签包含三类典型问题误检框把背景当目标、漏检框该标没标、框不准贴合度差。这些问题不修正模型会在错误标注上继续学错误还会被放大。所以人工修正环节不是可选项而是必经流程。4.1 可视化检查把 TXT 画回图片靠肉眼在文件夹里翻 TXT 是不现实的正确做法是把标注画到图片上输出检查。用 OpenCV 写一个快速脚本批量生成带标注框的预览图import cv2 from pathlib import Path images_dir Path(images) labels_dir Path(labels) preview_dir Path(preview) preview_dir.mkdir(exist_okTrue) for img_path in images_dir.glob(*.jpg): label_path labels_dir / (img_path.stem .txt) if not label_path.exists(): print(f[跳过] 无标签文件: {img_path.name}) continue img cv2.imread(str(img_path)) h, w img.shape[:2] for line in label_path.read_text().strip().splitlines(): if not line.strip(): continue cls, xc, yc, bw, bh line.split() x1 int((float(xc) - float(bw) / 2) * w) y1 int((float(yc) - float(bh) / 2) * h) x2 int((float(xc) float(bw) / 2) * w) y2 int((float(yc) float(bh) / 2) * h) cv2.rectangle(img, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.putText(img, cls, (x1, max(0, y1 - 5)), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 0, 255), 1) cv2.imwrite(str(preview_dir / img_path.name), img)逻辑说明把归一化坐标换算回像素坐标画框并标注类别索引。这一步是「人眼过滤」的入口建议快速翻一遍预览图不需要每张细看主要是发现系统性误检比如某个类别总把路灯杆识别成人。参数说明h, w img.shape[:2]拿图片原始尺寸换算公式x1 int((xc - bw/2) * w)是归一化坐标还原像素的标准做法。cv2.rectangle的绿色框和红色类别文字是我习惯的配色实际用的时候按你喜好换即可。4.2 格式校验脚本归一化、越界、空文件批量检查标注文件的格式合法性比人工翻几十倍效率高。我写的校验脚本检查四件事每行是否恰好 5 个值、类别索引是否在合法范围内、坐标是否在 0-1 之间、目标宽高是否为正值。import sys from pathlib import Path labels_dir Path(labels) num_classes 3 # 改成你的数据集类别数 error_count 0 for txt_path in sorted(labels_dir.glob(*.txt)): for line_no, line in enumerate(txt_path.read_text().strip().splitlines(), 1): parts line.split() if len(parts) ! 5: print(f[格式错误] {txt_path.name}:{line_no} 期望 5 个字段实际 {len(parts)}) error_count 1 continue cls, xc, yc, bw, bh parts if not (0 int(cls) num_classes): print(f[类别越界] {txt_path.name}:{line_no} 类别 {cls} 超出 0-{num_classes-1}) error_count 1 for val in (xc, yc, bw, bh): if not (0 float(val) 1): print(f[坐标越界] {txt_path.name}:{line_no} 值 {val} 不在 0-1 区间) error_count 1 print(f校验完成共发现 {error_count} 个问题 if error_count else 校验通过所有标注格式合法)逻辑说明逐行解析 TXT按字段数和取值范围做合法性判断。这个脚本最直接的价值是把「看不见的脏数据」变成「报错清单」训练前必须跑一遍error_count 归零才开工。参数说明num_classes是数据集的类别总数要和data.yaml里的nc一致。坐标检查这里用的是0 x 1注意如果某个值是nan或inf这个判断也会通过所以更严谨的写法要加一个math.isfinite判断不过现实中很少遇到这里从简。4.3 类别映射表管理一份写进配置一份写进注释自动标注工具来自不同渠道时类别映射的混乱几乎是必然的。有的工具输出类别名而不是索引有的工具用 COCO 索引有的工具用自己训练时的业务索引。我吃过一次亏一个公开车辆数据集下载下来标注文件里类别 0 是轿车我自己数据集里类别 0 是卡车合并后没检查训练出来模型一塌糊涂。从那以后我给自己定了规矩无论标注工具来自哪里第一件事就是生成一份类别映射表格式如下业务索引: 类别名 0: car 1: person 2: motorcycle这份映射表放在数据集根目录命名为CLASSES.md同时写入data.yaml注释里。每次合并外部数据集或者换标注工具先过一遍映射表再合并。这个习惯帮我规避了大量「数据没问题就是类别对不上」的隐性 bug。4.4 修正阶段的效率技巧用 LabelImg 的自动保存模式人工修正推荐用 LabelImg它支持Auto Save mode切换标签或切换图片时自动保存。配合键盘快捷键修正一张图可以在 5-10 秒内完成。如果修正量确实太大比如误检框超过 30%这时候不该硬修而是回头调低置信度阈值或者换一个训练更充分的预训练权重。5. 避坑指南自动标注常见的五个翻车现场自动标注工具看起来简单实际跑起来坑不少。以下五条是我从多个项目里攒出来的血泪经验每一条都真实遇到过。5.1 现象训练时 loss 正常下降但验证集 mAP 几乎为 0原因标注文件与图片尺寸不匹配。我从一个二手数据集里拿到标注后没校验直接训练仔细排查发现它的坐标是按 416×416 归一化的但我训练时用了 640×640 的原图等于所有框的位置全部偏移。 解决训练前加入归一化基准检查确认推理尺寸和标注时的基准尺寸一致。标注产线里的imgsz参数固定成和训练时一致的值不要两头各设各的。5.2 现象目标检测框全部偏向图片左上角原因标注坐标没有归一化到 0-1而是用了绝对像素值。比如 1920×1080 的图中心点直接写了 960 而不是 0.5数值超过 1 后模型会认为目标在画面外。 解决写一个全局扫描脚本找出所有坐标值大于 1 的行然后批量除以对应的图片宽高。保险的做法是在标注生成脚本里就强制用xywhn属性不做任何手工坐标换算。5.3 现象目标数量没问题但类别全乱了原因COCO 预训练权重的类别索引和业务数据集的类别索引错位。COCO 里 0 是 person你数据集的 0 是 car模型输出的框里的类别索引 2 实际是 car但你按自己的索引训练后全部错位。 解决做好类别映射表后还要抽检 10% 的预览图看类别文字和框内物体是否一致。自动化映射和人工抽检双保险。5.4 现象训练时大量图片被跳过图片数量远小于实际文件数原因图片文件存在但同名 TXT 缺失或者 TXT 里是空文件、内容全被清理了。YOLO 训练遇到没有标注的图片会默认跳过不报错只警告你根本意识不到数据少了一截。 解决写一个比对脚本检查images/train和labels/train文件名集合是否完全一致两边各有多少文件所有名称一一对应才放行训练。5.5 现象使用某工具标注后框看起来正常但边缘物体总被框得夸张地大原因自动标注工具在输出检测框时做了「贴合目标」的后处理把本应只包围目标的框扩展到了整个连通区域。这在目标密集的场景里非常致命多个目标的框互相重叠训练时的 NMS 会把这些框当成一个目标抑制掉。 解决检查标注文件中width和height的分布如果存在大量超过 0.5 的框即目标占图片一半以上面积大概率是后处理过度。此时切换标注工具或者对输出框做一次clip限制把框尺寸限制在合理范围内。6. 进阶玩法自训练回灌让标注成本一次比一次低自动标注的终局形态是「迭代回灌」先用公开权重标一批数据人工修正后训练出你自己的模型再用这个模型去标新一批未标注数据。因为你的模型是在你的数据分布上训练的它比 COCO 通用权重漏检率低得多生成初标的置信度阈值也可以相应提高人工修正量逐轮递减。具体的闭环我这样跑第一轮用公开权重标 2000 张修正后训练出一个基础模型第二轮用基础模型标另外 2000 张置信度阈值从 0.25 提高到 0.4此时人工修正量通常能减少一半第三轮再用第二轮的模型标第三批数据。每轮训练的输入数据都在变多模型越来越贴合你的场景标注效率随之指数级上升。这个流程的成本主要在第二轮的人工精修之后边际成本快速下降。我实际项目中第三轮的修正量从第一轮的每图 5 分钟降到了每图 1 分钟整批数据从原本预估的三周压缩到了一周内完成。6.1 回灌训练时的两个关键参数回灌训练本质上就是普通 YOLO 训练但有两点差异。一是起始学习率不要用默认的 0.01我习惯调到 0.005因为新增的伪标签本身有噪声学习率过大容易让模型过拟合到这些噪声上二是训练轮数不用太多50 轮足够伪标签数据量通常较大全靠前 30 轮就能收敛后面只是微调。Ultralytics 命令行可以直接覆盖这两个参数yolo train modelyolov8n.pt datadataset.yaml epochs50 lr00.005参数说明lr00.005是初始学习率回灌训练用偏小的值更稳epochs50是训练轮数伪标签够多的话这个轮次基本够收敛。别在这里用resumeTrue回灌的场景每次都是新数据分布接着上一次的训练没有意义。6.2 置信度筛选与难例挖掘回灌时不是所有检测结果都值得写入标注。置信度低于阈值的框大概率是误检直接丢弃置信度处于中间区间的框比如 0.4-0.6往往是难例值得保留下来让模型在训练时「记住」它们。我一般用两个阈值做分段处理低于 0.4 的丢弃0.4-0.7 的保留高于 0.7 的直接采信。保留中间段是刻意的「难例挖掘」让模型在下一轮里见到更多真实分佈中容易出错的样本。还有一种更细粒度的做法是拿训练好的模型对未标注图片做 TTA测试时增强把水平翻转、多尺度推理的结果合并后取平均得到的框位置更稳定。代价是推理时间翻了三五倍但用于离线标注不心疼这点耗时。6.3 最后一道检查训练前的随机抽样比对所有标注流程跑完后我会做一次最终验证随机抽 50 张预览图逐个比对框和真实目标的位置。这不是系统性的全量检查而是一种「玄学」式的心理确认——当你连续几轮标注没有发现问题时模型的 mAP 已经给出正向反馈抽检只是走个形式图个安心。我坦白说这一眼有时候确实能发现意外比如某一类目标在夜间场景下大量漏检而这类问题在数据统计指标里完全看不出来。从那以后我每次新接一个 YOLO 数据集项目无论标注工具多方便都会强制走一遍「格式校验 → 类别映射核对 → 随机抽检 50 张」的流程不跳过不偷懒。这套流程救了我不少次也希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?
咨询建站