简介:这份资源面向目标检测方向的学习者与工程实践者,提供一套基于YOLO系列的半自动标注数据集代码,用于缓解大规模数据标注耗时耗力的问题。其核心思路是先人工标注少量样本训练出初版模型,再用该模型对剩余图像进行预标注,最后人工复核修正误检,从而显著降低标注成本。压缩包共5个文件,以2个Python脚本为主,另含1个yaml数据集配置、1个使用说明txt及1个系统隐藏文件,整体约7KB,体积轻量、便于快速部署。脚本中已预留图片路径、xml输出路径、模型权重与数据配置等可修改参数,读者只需按说明调整后运行即可完成批量预标注,同时附带仅推理检测的脚本,方便单独验证模型效果。目前已有523人学习下载,适合希望搭建半自动标注流程、提升数据集制作效率的开发者参考使用。
1. 半自动标注到底省了什么:从 auto_label.py 到人工复核的闭环
标注一百张图还能靠耐心,标注一万张就只能靠工程。目标检测项目里,真正拖慢进度的往往不是训练,而是把原始图片变成 YOLO 格式标签这一步。纯手工拉框,一天能出三百张就算手快;纯自动伪标签,又容易把漏检和误检直接固化进数据集,越训越偏。半自动标注的思路很直接:先用一个已经能跑的 YOLOv 系列模型对未标注图片做推理,把预测框写成 YOLO txt,再让人只做「删、改、补」三件事。它解决的是标注吞吐量问题,适合手上有少量已标注数据、想快速扩充数据集的目标检测从业者,也适合刚接触 YOLOv8 训练自己数据集、不想一上来就被标注劝退的新手。核心脚本通常叫 auto_label.py,它不神秘,本质就是推理加格式转换。
2. 半自动标注的工程链路:模型推理、阈值过滤与 YOLO txt 落盘
2.1 为什么选 YOLOv 系列做预标注模型
半自动标注对预标注模型的要求和最终上线模型不一样。上线模型追求精度极限,预标注模型追求「召回优先、速度够快、格式好接」。YOLOv 系列在这三点上比较均衡:推理速度快,单卡批量跑几千张图不需要等太久;输出直接是框加类别加置信度,转 YOLO txt 只涉及坐标归一化;生态成熟,YOLOv5、YOLOv8 到 YOLOv11 的推理接口基本一致,换版本成本低。常见做法是拿一个在 COCO 或自有小数据集上训过的权重当起点,如果目标类别和预训练类别重合,比如人、车、常见动物,直接零样本预标注就能用;如果类别是自定义的,比如鸟类目标检测的数据集里那些细分类别,就得先手工标几百张训一个初版模型,再用它去预标注剩下的。
这里有个选型判断:预标注模型的 mAP 不需要很高,但召回要尽量高。因为漏检的框人工补起来费时,误检的框人工删起来很快。所以推理时置信度阈值要压低,通常设 0.15 到 0.25,让模型多报一些,把筛选压力交给人工。这和最终评估模型时设 0.5 阈值的逻辑正好相反,很多人第一次做半自动标注会在这里翻车,直接沿用评估阈值,结果预标注框少得可怜,人工补框比从头标还累。
2.2 auto_label.py 的最小可运行实现
下面这段代码是半自动标注脚本的骨架,基于 ultralytics 的 YOLO 接口。它做四件事:加载模型、遍历图片、推理、把结果写成和图片同名的 txt。代码里保留了置信度和 IoU 两个关键参数,方便你按自己的数据调。
# auto_label.py # 依赖: pip install ultralytics opencv-python tqdm from pathlib import Path from ultralytics import YOLO import cv2 from tqdm import tqdm # 参数区:这三个值决定预标注框的多少 MODEL_PATH = "yolov8n.pt" # 预标注权重,可换成自己训练的 best.pt IMG_DIR = "datasets/raw/images" LABEL_DIR = "datasets/raw/labels" CONF_THRES = 0.20 # 预标注阶段压低,保召回 IOU_THRES = 0.45 # NMS 阈值,密集目标可调到 0.5~0.6 model = YOLO(MODEL_PATH) IMG_DIR = Path(IMG_DIR) LABEL_DIR = Path(LABEL_DIR) LABEL_DIR.mkdir(parents=True, exist_ok=True) img_paths = list(IMG_DIR.glob("*.jpg")) + list(IMG_DIR.glob("*.png")) for img_path in tqdm(img_paths): img = cv2.imread(str(img_path)) if img is None: continue h, w = img.shape[:2] results = model.predict(img, conf=CONF_THRES, iou=IOU_THRES, verbose=False) lines = [] for r in results: for box in r.boxes: cls_id = int(box.cls[0]) x1, y1, x2, y2 = box.xyxy[0].tolist() # YOLO 格式要求归一化中心点 + 宽高 cx = (x1 + x2) / 2.0 / w cy = (y1 + y2) / 2.0 / h bw = (x2 - x1) / w bh = (y2 - y1) / h # 过滤越界框,避免写入非法标签 if bw <= 0 or bh <= 0: continue lines.append(f"{cls_id} {cx:.6f} {cy:.6f} {bw:.6f} {bh:.6f}") txt_path = LABEL_DIR / (img_path.stem + ".txt") txt_path.write_text("\n".join(lines), encoding="utf-8")逻辑说明:model.predict返回的是像素坐标的 xyxy 框,YOLO 训练要的是归一化后的中心点加宽高,所以中间必须做一次坐标变换。cx、cy、bw、bh四个值都除以了图像宽高,范围落在 0 到 1 之间。参数方面,CONF_THRES控制预标注框数量,设低了框多但误检多,设高了框少但漏检多,建议从 0.2 起步,看一批结果再调。IOU_THRES影响重叠框的合并,密集小目标场景调高到 0.5 以上,避免相邻目标被 NMS 误删。MODEL_PATH换成自己训练的best.pt时,类别 id 会和训练时的data.yaml对齐,这点在后续人工复核时要特别注意,别把类别号对错了。
2.3 批量推理的显存与速度控制
几千张图一次性喂进去,显存容易爆。ultralytics 的predict支持stream=True,配合batch参数可以边推理边写盘,不用把所有结果堆在内存里。常见做法是把batch设成 8 或 16,具体看显卡显存,8G 显存跑 yolov8n 用 16 比较稳,跑 yolov8l 就降到 4。如果图片分辨率很高,比如遥感目标检测里常见的 4000 像素大图,建议先切图再预标注,否则模型输入被缩放到 640 后小目标基本消失,预标注框会漏掉一大片。切图可以用滑动窗口,重叠 20%,切完的图分别推理,再把框映射回原图坐标,这一步在 auto_label.py 里加一个坐标偏移量就能实现。
速度上,单张 1080p 图片在 3060 上跑 yolov8n 大约 10 毫秒,一万张图纯推理不到两分钟,瓶颈通常在读盘和写盘。把图片放在 SSD 上,标签写盘用追加模式,能再快一截。如果只是做预标注,不需要保存可视化结果,save=False默认就是关的,别手贱打开,否则每张图多写一个 jpg,磁盘很快就满。
3. 人工复核环节:把预标注框变成可用标签的三个动作
3.1 复核工具的选择与标签格式对齐
预标注生成的 txt 不能直接拿去训练,必须过一遍人工。复核工具常见的有 labelImg、Label Studio、CVAT,以及 ultralytics 自带的标注界面。选哪个主要看两点:能不能直接读 YOLO txt,能不能批量切换图片。labelImg 老牌但界面简陋,Label Studio 支持多人协作但部署重,CVAT 功能全但学习成本高。如果只是个人或小团队,labelImg 够用,打开labels目录对应的 txt,框会自动显示,人工只需要拖拽调整、删除误检、补画漏检。
这里有个格式坑:不同工具对 YOLO txt 的类别 id 起始值理解不一样。YOLO 官方是从 0 开始,但有些工具默认从 1 开始,导入导出时会整体偏移一位。复核前先拿一张有已知类别的图验证,确认框的类别名和你的data.yaml对得上,再批量开工。否则标了几百张才发现类别全错,那真是血泪经验。
3.2 复核时的优先级:先删误检还是先补漏检
人工复核的时间要花在刀刃上。我的习惯是分两遍:第一遍快速扫,只删明显误检,比如把背景纹理当成目标的框,这类框留着会教坏模型;第二遍再逐张补漏检,尤其是小目标和遮挡目标。为什么先删后补?因为误检框会干扰视线,让你误以为某个区域已经标过了,补漏检时容易跳过。先删干净,画面清爽,补漏检的效率会高不少。
补漏检时注意框的紧贴度。预标注框通常比较准,但漏检的目标需要手工拉,拉的时候别太松,背景留太多会让模型学到无关特征。对于鸟类目标检测这类细长目标,框的宽高比要尽量贴合身体,不要把翅膀外的天空也框进去。复核完一批,随机抽十张可视化检查,用下面的脚本把标签画回图上,肉眼确认没有越界框和类别错位。
# check_label.py 可视化复核结果 import cv2 from pathlib import Path IMG_DIR = Path("datasets/raw/images") LABEL_DIR = Path("datasets/raw/labels") CLASSES = ["bird", "person"] # 换成你的类别列表 for txt in LABEL_DIR.glob("*.txt"): img_path = IMG_DIR / (txt.stem + ".jpg") img = cv2.imread(str(img_path)) if img is None: continue h, w = img.shape[:2] for line in txt.read_text().strip().splitlines(): if not line: continue c, cx, cy, bw, bh = line.split() cx, cy, bw, bh = map(float, (cx, cy, bw, bh)) x1 = int((cx - bw / 2) * w) y1 = int((cy - bh / 2) * h) x2 = int((cx + bw / 2) * w) y2 = int((cy + bh / 2) * h) cv2.rectangle(img, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.putText(img, CLASSES[int(c)], (x1, y1 - 5), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 2) cv2.imwrite(f"check_vis/{txt.stem}.jpg", img)这段脚本把归一化坐标还原成像素坐标,画框并写上类别名。CLASSES列表的顺序必须和训练时data.yaml里的names完全一致,否则显示的类别名会张冠李戴。跑完抽几张看,重点检查有没有框跑到图像外面、有没有同一个目标被标了两次、有没有类别明显不对的。确认无误后,这批标签就可以和图片一起进训练流程了。
3.3 迭代预标注:用复核后的数据反哺模型
半自动标注不是一次性的。第一轮预标注加人工复核后,你得到了一批高质量标签,拿这批数据去微调预标注模型,第二轮预标注的质量会明显提升,人工复核的工作量随之下降。这个循环跑两到三轮,通常能把人工工作量压到纯手工的百分之二十到三十。具体做法是:把复核后的images和labels按 8:2 分训练验证,用 yolov8n 或 yolov8s 训 50 到 100 轮,得到新的best.pt,替换auto_label.py里的MODEL_PATH,对剩余未标注图片再跑一遍。注意每轮都要保留一份原始预标注结果,方便对比模型进步了多少,也方便回滚。
4. 半自动标注避坑:从坐标越界到类别错位的五条排查记录
4.1 现象:训练时报「Label class out of range」
原因:预标注模型输出的类别 id 超出了data.yaml里names的长度。常见于用 COCO 预训练权重直接跑自定义数据集,COCO 有 80 类,你的data.yaml只有 3 类,模型预测出 id 为 45 的框,写进 txt 后训练器直接报错。
解决:在auto_label.py里加一层类别过滤,只保留cls_id < len(CLASSES)的框。或者更彻底,用自己数据训练过的权重做预标注,类别自然对齐。过滤代码就一行:if cls_id >= len(CLASSES): continue。
4.2 现象:标签文件是空的,但图上明明有目标
原因:置信度阈值设太高,或者模型输入尺寸太小导致小目标没被检出。前者常见于直接沿用 0.5 的评估阈值,后者常见于高分辨率遥感图直接缩放推理。
解决:把CONF_THRES降到 0.15 到 0.2,同时把imgsz调大,比如从默认 640 调到 1280。如果显存不够,改用切图推理再映射回原图。切图时窗口重叠 20%,避免目标被切在边界上漏检。
4.3 现象:框的位置整体偏移或宽高反了
原因:坐标变换时把宽高和中心点算错,或者读图时用了 cv2 的 BGR 顺序但宽高取反。YOLO 格式是cx cy w h,不是x1 y1 x2 y2,顺序写错会导致框乱飞。
解决:用check_label.py可视化验证,肉眼确认框贴合目标。如果整体偏移,检查w和h有没有写反;如果框大小不对,检查有没有漏除图像宽高。归一化后的值必须在 0 到 1 之间,出现大于 1 的值说明坐标没除对。
4.4 现象:密集场景下相邻目标被合并成一个框
原因:NMS 的 IoU 阈值设太低,两个挨得近的目标框重叠度超过阈值,被当成重复框删掉一个。
解决:把IOU_THRES从 0.45 调到 0.55 甚至 0.6,让 NMS 更宽容。如果目标确实非常密集,比如人群检测,可以考虑用 soft-NMS,但 ultralytics 默认接口不支持,需要自己改后处理。更简单的办法是预标注阶段不依赖 NMS,把iou设成 0.7,让框多留一些,人工复核时再删。
4.5 现象:复核后的标签训练效果反而比预标注前差
原因:人工复核时引入了系统性偏差,比如补漏检时框拉得太松,或者删误检时把难样本也删了,导致训练集分布偏移。另一个可能是复核后的标签和图片没有一一对应,比如图片重命名后 txt 没跟着改。
解决:复核后随机抽 20 张做可视化检查,确认框的紧贴度和类别正确。训练前用脚本校验图片和标签文件名是否一一对应,数量是否一致。如果效果仍然差,回滚到上一版标签,对比两版差异,定位是哪一类操作引入的问题。
5. 把预标注阈值调成自适应:一个提升复核效率的小技巧
固定置信度阈值有个问题:不同图片的难度不一样,简单图片框少,复杂图片框多,人工复核的节奏被打乱。我后来改成一个自适应策略:先跑一遍低阈值推理,统计每张图的框数量,如果某张图框数超过 50,说明场景密集,自动把该图的阈值上调到 0.35,减少误检;如果框数少于 3,说明可能漏检,把阈值下调到 0.1,多报一些。这个逻辑不复杂,在auto_label.py里加一个两遍推理就能实现。
# 自适应阈值片段 def adaptive_conf(img, model, base_conf=0.2): # 第一遍低阈值探数量 r = model.predict(img, conf=0.1, iou=0.5, verbose=False)[0] n = len(r.boxes) if n > 50: return 0.35 # 密集场景,收紧 elif n < 3: return 0.10 # 疑似漏检,放宽 return base_conf这个技巧在鸟类目标检测数据集上效果比较明显,因为鸟群图片框数波动大,自适应阈值能让每张图的预标注框数量落在人工复核的舒适区间。参数上,50和3这两个分界值需要按你的数据分布调,可以先统计一批图的框数直方图,取 80 分位和 20 分位作为分界。跑完自适应预标注后,人工复核的时间大概能再省一成多,虽然不多,但积少成多。
另一个习惯是每轮预标注都保留一份auto_label的日志,记录每张图的框数和使用的阈值。这样当某张图复核时发现漏检严重,能回溯是阈值问题还是模型问题。日志用简单的 csv 就行,字段包括图片名、框数、阈值、耗时。别小看这个日志,模型迭代几轮后,它能帮你判断预标注质量是在提升还是停滞。
最后说个我自己的教训:半自动标注的瓶颈从来不在脚本,而在复核标准的一致性。同一个人上午和下午的标注尺度都可能不一样,更别说多人协作。所以复核前一定要定一份简短的标注规范,写清楚框的紧贴度、遮挡目标怎么标、最小目标尺寸是多少。规范不用长,一页纸就够,但能省下后面返工的大量时间。希望帮到你。
本文还有配套的精品资源,点击获取