先说个容易混淆的点。如果你去搜“自动标注”,大概率会看到一堆AutoCAD自动标注外挂相关的东西——那是给图纸加尺寸、加引线的辅助工具,和我们计算机视觉圈子里说的“自动标注”完全不是一回事。我们要聊的自动标注,是用模型来给训练数据打标签:检测框、分割掩码、关键点,标完之后再去训练另一个模型。最近我刚好把 X-AnyLabeling、autodistill 与 Grounded-SAM 这三个东西组合起来,走完了一条从原始图片到 COCO 分割数据集的完整链路,中间踩了不少坑,也把工具之间的分工彻底摸清楚了。这篇文章就按我的实操顺序,把这套流程完整写下来。
我手头的场景是路面病害检测:需要标注裂缝、坑槽、修补带这三类目标。裂缝边缘不规则,坑槽大小差异巨大,传统的手动画框非常痛苦,画一个裂缝掩码有时候要花三五分钟。这个场景特别适合用模型辅助标注,因为视觉特征相对明确,但又不能全靠模型自己做——裂缝的边界、阴影导致的误检、新旧修补带的纹理差异,都需要人来兜底。于是我用 X-AnyLabeling 做第一轮人机协同标注,autodistill 搭批量伪标签流水线,Grounded-SAM 作为背后的定位与分割引擎,三轮迭代之后,数据集从 260 张人工标注扩展到了 3000 张可用图片。整个过程不算复杂,但每一步都有值得记录的选择和教训。
1. 先把三件工具的分工捋清楚:它们不是竞品,是流水线上的三个工位
很多刚接触自动标注的朋友容易陷入一个问题:这三个工具我都听说过,到底该用哪个?我先给一个结论:它们三个根本不是同一层的东西,硬要比“谁更强”没有意义,它们更像是装配线上的三个工位,各干各的活。
1.1 为什么自动标注不是“点一下自动”就完事
自动标注的本质是用一个已经能工作的模型(或者用文本提示驱动的开放词表模型)先给未标注数据出一个“初稿”,再由人来修正这个初稿。这个逻辑听起来简单,但它会带来一个根本性变化:标注工作的重心从“从零画框”变成了“检查、修正、确认”。前者考验耐心,后者考验眼光。所以效率提升的幅度,取决于初稿质量的上限,而不是工具本身花不花哨。
我实测下来,在路面病害这种纹理复杂、边界模糊的场景里,纯自动标注的初稿质量能到七八成可用,剩下两三成需要人工修。但即便是这样,整体效率也比纯手动画掩码提升了三倍以上。如果换成目标检测项目(比如检测行人、车辆这种矩形目标),自动标注的初稿质量可以到九成以上,人工只需处理遮挡和极小目标。
1.2 X-AnyLabeling 的定位与我的使用方式
X-AnyLabeling 是一个基于 PyQt5 的交互式标注工具,它最打动我的地方是:在同一个界面里既支持手动标注,又内置了多个深度学习模型做自动推理。你可以加载一个 YOLOv8 检测模型,让它在当前图片上跑一遍,生成检测框和类别,然后你只需要调整错框、补漏框、改类别就能完成标注。
它的核心思路是人机协同,不是全自动。这也意味着它是一个“前端工具”,你需要的是模型推理能力,模型文件可以在界面里直接导入。我自己的使用习惯是:先用已有的旧模型跑全图预测,再在界面上逐张检查,遇到新的裂缝类型就手动补标,保存之后把它纳入下一轮的训练集。
1.3 autodistill 的核心逻辑与使用方式
autodistill 是一个 Python 库,它的设计思路非常明确:用一个能力更强的“教师模型”给未标注数据打伪标签,然后用这些伪标签去训练一个更轻量的“学生模型”。教师模型可以是 Grounded-SAM 这类开放词表模型,也可以是任何你本地已经训练好的推理模型;学生模型可以是 YOLOv8、RT-DETR 这些适合部署的模型。
它的核心概念是 CaptionOntology,也就是一个“提示词到类别名”的映射字典。你告诉它“哪些描述对应哪个标签”,它就拿着这些描述去调用教师模型标注数据,然后把标注结果转成目标模型能读的格式。这种方式特别适合批量化处理,而且整个流程可以在脚本里跑,不用开图形界面。
1.4 Grounded-SAM 的组成部分
Grounded-SAM 是这套方案里最核心的标注引擎,名字来自两个模型的组合:Grounding-DINO 负责“文本检测”——输入一句“路面裂缝”,它在图中找出所有相关的目标框;Segment Anything Model(SAM)负责“框内分割”——把检测框给出的区域进一步细化成精确的像素级掩码。
我在这个项目里只画了一句提示词,就能让模型把裂缝和坑槽的掩码轮廓都抠出来。虽然分割结果不能直接作为最终标注,但作为“初稿”已经能省下大量时间。这里要强调,Grounded-SAM 不是独立开箱即用的版本,需要自行组合 GroundingDINO 与 SAM 的推理脚本。
1.5 一张表看懂三者的分工与数据流
| 工具 | 扮演的角色 | 输入 | 输出 | 谁说了算 |
|---|---|---|---|---|
| X-AnyLabeling | 人工协同前端 | 图片 + 本地模型 | VOC/COCO 格式标注 | 人做最终检查 |
| autodistill | 批处理编排层 | 未标注图片 + 教师模型 | 对齐目标模型的伪标签 | 模型出初稿,配置约束规则 |
| Grounded-SAM | 底层定位与分割引擎 | 图片 + 文本提示词 | 检测框 + 像素掩码 | 模型出结果,阈值负责过滤 |
三者之间的数据流向大致是:Grounded-SAM 负责把文本提示变成掩码,autodistill 负责把掩码组织成结构化的标签并喂给学生模型,X-AnyLabeling 则是人工修正这些结果时唯一的图形化入口。接下来我按实际使用顺序,把这套流程拆开来讲。
2. X-AnyLabeling:先用它把第一批数据“盘活”
项目一上来,我手里只有 200 多张已经画好框的图片,掩码只有几十张,根本不够训练分割模型。我的第一反应不是急着上复杂方案,而是先用 X-AnyLabeling 把现有的几张掩码图变成可用的初版训练数据,同时验证整个标注流程走不走得通。
2.1 部署与 Linux 踩坑记录
X-AnyLabeling 的部署不算难,但 Linux 下有几个坑比较稳定地出现。我在 Ubuntu 20.04 上克隆源码后直接装依赖,跑起来时报了一个很经典的错误:
libGL.so.1: cannot open shared object file: No such file or directory这是缺少 OpenGL 运行库导致的,PyQt 的图形栈依赖它。解决办法很直接:
sudo apt update sudo apt install libgl1 libglib2.0-0之后启动程序也有讲究。我从源码目录运行:
git clone https://github.com/CVHub520/X-AnyLabeling.git cd X-AnyLabeling pip install -r requirements.txt python app.py如果只是 Windows 上用,上述第三行改成pip install X-AnyLabeling基本就能跑,条件是你的环境有 Python 3.8 以上,且没有把 PyQt5 的依赖搞乱。
另外,Linux 下如果用到 GPU 推理,建议提前把 CUDA 的 PyTorch 装好,否则程序会自动退回 CPU 推理,一张 1200 万像素的图上跑一次 YOLOv8s 可能要等好几秒,标注节奏会非常难受。
2.2 加载模型跑自动预标注
启动界面之后,关键操作是加载模型。这个方法在“模型”菜单的“创建模型”里,可以手动输入模型配置的 JSON 路径,我直接填的是 X-AnyLabeling 官方提供的 YOLOv8-seg 配置,模型文件放到了model_data目录下。
加载之后,每打开一张图片,我按一下“自动标注”按钮,界面里就会叠出一层检测框和掩码。第一次跑出来的结果说实话有点粗糙——裂缝经常被断成好几段,坑槽的边界也偏大。但这时候你不要急着否定它,因为它的定位是“初稿”,你要做的是在初稿上改,而不是推倒重来。
我修正了一百多张图片之后,第一个版本的数据集就成形了。这一版不追求完美,追求的是能训练出一个“初始学生模型”。有了这个初始模型,后面的自动标注质量才会有质的飞跃,这是整个流程里最关键的一个飞轮起点。
2.3 快捷键与标注效率
X-AnyLabeling 提供了一组还算顺手的快捷键,我用得最勤的几张列在这里:
| 操作 | 快捷键 | 说明 |
|---|---|---|
| 保存当前图片标注 | Ctrl+S | 按完即保存,别一直攒着 |
| 撤销上一步 | Ctrl+Z | 修正误画时高频使用 |
| 取消当前编辑框 | Esc | 画一半不想要了 |
| 删除选中的标注 | Delete | 配合自动检测修正漏检最常用 |
| 放大当前区域 | 滚轮 | 标注裂缝边缘时必备 |
| 切换上一张/下一张 | 方向键或A/D | 看个人习惯 |
| 复制当前标注 | Ctrl+C / Ctrl+V | 同类目标密集时能省不少事 |
我的实际工作流是这样的:先批量打开一批图片,逐张点自动标注,快速浏览一遍,看到明显问题就键盘修正,标注状态靠谱的图直接 Ctrl+S。每天结束前统一检查导出的格式,再生成训练集。习惯了这套节奏之后,我一天大概可以完成 300 张以上图片的修正量,比纯手工标注快得多。
2.4 标签格式导出的注意事项
X-AnyLabeling 支持导出 VOC XML 和 COCO JSON,也能导出掩码 PNG。我最终选择的是 COCO JSON,因为后续 autodistill 和训练脚本对 COCO 格式的兼容性最好。导出之前有个容易忽略的点:类别名和 ID 的顺序必须和训练脚本里的配置文件一致,否则训练时会出现“标签错位”问题,看起来检测框位置对,但类别全都偏了。
我吃了一次这样的亏。第一版导出的 JSON 里,类别顺序是“裂缝、坑槽、修补带”,但训练脚本读到的顺序是“坑槽、裂缝、修补带”,结果模型的预测结果一路全错。从那次以后,我养成了一个习惯:每次导出之后先写一小段脚本检查 categories 列表,再开始训练。
3. autodistill:把“伪标签”当成正式工作流的一部分
第一轮用 X-AnyLabeling 迭代完之后,我手上有了一个性能还不错的学生模型,但数据量还不够大。这时候我引入 autodistill 来批量扩展数据。很多教程把 autodistill 包装成“全自动标注神器”,但我的使用体验是:它真正擅长的是承接流水线,而不是凭空创造可用数据。
3.1 先别误会它是自动标注的全部
autodistill 做的是三件事:调用教师模型给图片打标签,把标签写成目标模型需要的格式,然后启动目标模型的训练流程。它不负责评估伪标签质量,也不负责处理数据清洗,这些环节依然需要人工脚本和抽检。
所以我把它定位成一个“编曲工具”——它把 Grounded-SAM 这种底层的标注引擎和目标模型之间的接口理顺了,让整条流水线能以脚本方式反复运行。在路面病害这个项目里,我用它处理了 1200 张新采集的未标注图片,全程没有打开一次图形界面。
3.2 CaptionOntology 的设计与语义提示
autodistill 里最核心的概念是 CaptionOntology,它是一个字典,键是给教师模型看的描述,值是写入标注文件的类别名。我一开始写的是这样的:
from autodistill.detection import CaptionOntology ontology = CaptionOntology({ "a crack on the road": "裂缝", "a pothole on the road": "坑槽", "a repaired patch on the road": "修补带" })跑完一批之后我发现,提示词的写法对结果影响非常大。Grounded-SAM 对短语层面的描述非常敏感,“a crack on the road”这种笼统表达容易漏掉细裂缝。我后来把描述改成更具体、更贴近实际照片的写法,效果立刻不一样:
ontology = CaptionOntology({ "a thin dark crack on asphalt pavement": "裂缝", "a wide pothole with broken asphalt edge": "坑槽", "an asphalt patch with rectangular repair boundary": "修补带" })原因是 Grounding-DINO 这类模型在开放词表模式下,更像是在做“文本特征与视觉特征的匹配搜索”。描述词越贴近真实场景,特征空间里匹配到的区域就越准确。简单的类别名不是不能用,但通常只能给出一个比较宽松的候选集合,需要更长的时间在 NMS 里消耗。
3.3 最小可跑的示例代码
我建议第一次跑 autodistill 不要直接上大流程,先用 20 张图片跑通最小示例,确认标签格式没有问题,再放开全量数据。一个最小可跑的流程如下:
from autodistill.detection import CaptionOntology from autodistill_grounded_sam import GroundedSAM from autodistill_yolov8 import YOLOv8 # 1. 定义提示词到标签的映射 ontology = CaptionOntology({ "a thin dark crack on asphalt pavement": "裂缝", "a wide pothole with broken asphalt edge": "坑槽", "an asphalt patch with rectangular repair boundary": "修补带" }) # 2. 教师模型自动标注 base_model = GroundedSAM(ontology) base_model.label( input_folder="raw_images", output_folder="auto_labels" ) # 3. 学生模型用伪标签训练 target_model = YOLOv8("yolov8s.pt") target_model.train( "auto_labels/annotations.json", "auto_labels/images" )这个示例跑通之后,我就把它包装成一个循环执行的批处理脚本:每天晚上新图片进入raw_images,脚本自动标注、自动训练,第二天早上直接看训练日志。这才是 autodistill 真正省时间的地方。
3.4 为什么我会在两套流程里都用 Grounded-SAM
X-AnyLabeling 可以加载本地模型推理,autodistill 也可以把教师模型换成 Grounded-SAM。这里存在一个隐患:两套流程都在调用 Grounded-SAM,但它们的调用方式完全不同。
在 X-AnyLabeling 里,我是在图形界面上点击运行,模型推理只针对当前一张图,我可以立即看到结果并人工修正。在 autodistill 里,Grounded-SAM 是以批处理方式运行的,推理结果直接写成 JSON 文件,我只能在事后通过脚本检查。这意味着同样的“提示词”,在两套流程里需要维护两份配置。
我的做法是:把提示词和阈值参数统一放在一个 YAML 文件里,两个流程都去读它。这样在 X-AnyLabeling 里优化过的提示词,可以直接同步到 autodistill 的批处理任务中。否则你很容易出现手动标注效果不错、批处理结果却一言难尽的情况,最后排查半天发现只是提示词没有同步。
4. Grounded-SAM:从一句话到分割掩码的完整链路
如果说前面两个工具是生产线的框架,那 Grounded-SAM 就是真正的“标注机械臂”。它接受一句自然语言描述,输出检测框和分割掩码,是整条流程里最能提升标注效率、也最容易出幺蛾子的部分。
4.1 Grounding-DINO 做检测:把文本变成框
Grounding-DINO 的检测流程,本质上是在做两件事:先用文本编码器把提示词变成一组特征向量,再用视觉编码器从图片中提取候选区域特征,最后通过跨模态匹配决定“哪些区域与当前提示词的语义最相关”。
它输出的不只是一个类别名,还有一个置信度分数。这个分数就是后面我们要调的box_threshold。在我的场景里,裂缝的视觉特征变化非常大——有的裂缝细得像头发丝,有的裂缝宽到能塞进一只脚,所以检测分数的跨度也很宽。如果阈值设得太高,细裂缝就全漏了;设得太低,路面接缝、阴影边缘都会被误检成裂缝。
4.2 SAM 做分割:把框变成掩码
拿到 Grounding-DINO 的检测框之后,SAM 会以这个框为“提示”生成掩码。SAM 支持三种输入提示:点、框、掩码。Grounded-SAM 使用的是框提示,也就是把检测框编码成提示向量送入 SAM 的掩码解码器。
SAM 的输出往往比检测框要精细得多,因为它会尝试沿着目标的实际边缘游走。我在实测中发现,SAM 对裂缝的边缘处理意外地好——它能把一条弯弯曲曲的裂缝完整抠出来,而不是像传统分割模型那样只能给出一个大致的区域。这也是为什么我选择用 Grounded-SAM,而不是直接用一个分割模型来做自动标注:它同时兼顾了目标定位(Detection)和目标轮廓(Segmentation),两个环节可以互相补偿。
4.3 安装与模型权重下载
Grounded-SAM 的安装比前两个工具都要繁琐,因为它依赖两个独立的模型权重。我以一个干净的 conda 环境为例,把整个安装过程捋一遍:
git clone https://github.com/IDEA-Research/Grounded-Segment-Anything.git cd Grounded-Segment-Anything conda create -n gsa python=3.10 conda activate gsa pip install torch==2.0.1 torchvision==0.15.2 pip install segment-anything pip install opencv-python pycocotools matplotlibGroundingDINO 需要单独编译安装。这一步对新手来说最容易卡住,因为需要指定 GPU 算力:
cd Grounded-Segment-Anything python -m pip install -e GroundingDINO如果遇到CUDA_HOME相关的报错,要先把 CUDA 工具链装好,确认nvcc -V能正常输出。我见过很多人在这一步被卡了一两天,最后发现是系统中存在多个 CUDA 版本,导致编译时选错了环境变量。
下载权重也很关键,我用到的是这两个文件:
sam_vit_h_4b8939.pth:SAM 的最大权重,分割精度最高,但显存占用也最大。groundingdino_swint_ogc.pth:Grounding-DINO 的 Swin-T 权重,检测速度较快,适合批量推理。
如果你的显卡只有 8GB 显存,建议把 SAM 换成sam_vit_b_01ec64.pth,否则很容易在推理长图时直接显存溢出。我自己的 GPU 是 12GB,跑 640 分辨率上百张图偶尔也会崩,后来乖乖把分辨率降到 640 以下才算稳定。
4.4 实际推理中要调整的参数
Grounded-SAM 的官方推理脚本里,有三个参数值得细调:
| 参数 | 作用 | 经验值 |
|---|---|---|
box_threshold | 检测框的置信度阈值 | 0.25~0.35 |
text_threshold | 文本与视觉特征的匹配阈值 | 0.25~0.35 |
nms_threshold | 重叠框去重阈值 | 0.5~0.8 |
我在路面裂缝场景里,最终把box_threshold和text_threshold都设在 0.3。太高了漏检严重,太低了误检一堆,0.3 是一个相对平衡的点。用 NVIDIA 显卡推理时,显存占用会随阈值降低而上升,因为低阈值会保留更多候选框,后续 SAM 要处理的分割请求也更多。
一个经验是:在批量跑之前,先拿 20 张覆盖不同场景的图片试参,把误检和漏检都调到一个可接受的范围,再开始全量推理。不要指望一个阈值在所有场景下都完美,你只能找一个“最不坏”的平衡点。
4.5 从框到掩码后又该做什么
Grounded-SAM 的输出不只是掩码,它还会把检测框、类别名、置信度一并保存。我建议在流水线里保留这些原始输出,不要直接丢弃——置信度是后续人工抽检的重要依据。
实际写脚本时我一般这样组织输出:
output/ images/ 0001.jpg masks/ 0001.png boxes/ 0001.json0001.json里保存的是检测框坐标、类别名、置信度。这样,如果某个类别的置信度整体偏低,我可以单独筛出来重新检查,而不是重新跑一遍完整推理。类似这种“先保存原始输出,再派生格式”的组织方式,让我在后期的数据清洗阶段节省了大量时间。
5. 全流程串起来:从 3000 张原始图片到可用的 COCO 数据集
分开讲完三件工具之后,我把整条流水线再合拢到一起。很多人把自动标注理解成“图进去,标签出来,然后训练”,但真实项目远没有这么干净。你需要面对的是:图片质量参差不齐、提示词在部分场景下失效、伪标签中有一批需要人工修正、格式转换可能丢信息。所以我把整个流程设计成了三层:先人工把底子打好,再用自动标注大规模扩展,最后人工抽检收口。
5.1 整体作业顺序
我的完整流程分成七个步骤,每一步之间有清晰的交付物:
- 用 X-AnyLabeling 手工标注 200 张基础图,训练一个初始 YOLOv8-seg 模型。
- 用初始模型跑一批 600 张新图,在 X-AnyLabeling 里逐张修正,形成第二轮训练集。
- 在 autodistill 里配置好 CaptionOntology 和 Grounded-SAM 参数。
- 对大量未标注图片自动生成伪标签。
- 写脚本按置信度分层抽检伪标签,挑出低置信度样本人工修正。
- 把人工修正结果与自动标注结果合并为正式训练集,导出 COCO JSON。
- 训练正式版本的分割模型,回到第 2 步继续迭代。
我实际跑了三轮。第一轮后模型 mAP@0.5 大概在 0.35 左右,基本只能找出大坑槽。第二轮后到了 0.56,细裂缝的召回有明显改善。第三轮之后稳定在 0.63,人工抽检的工作量也开始显著下降。这个趋势说明,自动标注的收益在“数据飞轮”里是滚雪球式上涨的——一开始人工费劲,后面越来越轻松。
5.2 数据翻转:格式与文件夹结构怎么规范化
整个流程跑完了,数据格式问题会变成最大的隐形地雷。我踩过的最典型的坑是:autodistill 输出的 JSON 是它自己的格式,X-AnyLabeling 导出的是 COCO 格式,两者合并时键名不一致,导致训练脚本直接报错。
我的解决方案是写一个统一的数据集整理脚本,把一切最终输出都转成标准 COCO 格式,并统一图片和掩码的命名规则。图片一律按六位数字编号,掩码文件名与图片同名,JSON 里的categories固定为[{"id": 1, "name": "裂缝"}, {"id": 2, "name": "坑槽"}, {"id": 3, "name": "修补带"}]。所有后续训练代码都只认这个规范。
5.3 人工抽检的关键方法
自动标注永远需要人工抽检,但抽检不能凭感觉。我的做法是:按置信度给每个类别分层,从每一层里随机抽固定数量的样本,而不是简单地从全量里随机抽。低置信度层多抽一些,高置信度层少抽一些,这样能在有限的精力里覆盖最多的“危险样本”。
路面病害场景还有一个特殊问题:裂缝在阴影里、雨天积水反光时,即使是人工判断也很模糊。遇到这种情况,我会把图片单独放进一个uncertain文件夹,等训练到后续版本时再重新审视,而不是强行打一个不靠谱的标签。一个错误的标签会污染整个类别,风险远大于暂时不标。
5.4 迭代一次后的效果对比
拿第一轮和第二轮的模型做对比,差异非常直观:
| 指标 | 第一轮后 | 第三轮后 |
|---|---|---|
| 训练图片数 | 260 | 3000 |
| mAP@0.5 | 0.35 | 0.63 |
| 人工纠错耗时/百张 | 60分钟 | 15分钟 |
| 细裂缝召回率 | 40%左右 | 75%以上 |
这些数字谈不上顶尖,但对于一个路面病害检测的落地项目来说,已经具备工程可用性。更重要的是,整个流程后期的边际成本非常低——新增一批 500 张图片,从自动标注到抽检修正,半天就能完成,这在纯手工时代是不可想象的。
6. 这一轮下来我踩过的坑,提前帮你避掉
最后把我在实际运行中遇到的最典型的几个问题集中说一下。这些问题在官方文档里基本不会写,但在真实项目里几乎必然会遇到。
6.1 “漏检”不一定是模型弱,可能是提示词不对
我最开始写提示词用的是“a crack on the road”,跑出来的结果惨不忍睹,很多细裂缝完全没有被检测到。后来我分析了一下,发现 Grounding-DINO 在匹配时是拿整句描述的语义特征与图像区域特征做比对,“crack”这个词的视觉特征其实很模糊,它可以指冰裂纹、龟裂、毛发状裂缝,模型不知道你要的是哪一种。
把提示词改成更具体的行为描述之后,漏检率立刻降了下来。比如“a long continuous dark line on asphalt with small branches”比“a crack”要有效得多。多写一些同义词、近义词,或者用短语描述颜色和形状,都比单一类别名管用。
6.2 显存溢出与批处理崩溃
批量推理时最常见的杀手就是显存溢出。我一开始图省事,把所有图片设置为 1024 分辨率,跑了几百张就崩了。后来我把分辨率调到 768 或 640,并把批处理脚本里的一次性推理张数设为 1,稳定性立刻提高。如果一张大图上需要分割的目标特别多,SAM 的推理时间会显著上升,这时候可以考虑先只保留 Grounding-DINO 的检测框,对掩码做按需后处理,而不是对全图掩码同时生成。
6.3 文件名与标注名的错位
这个问题特别隐蔽。我在合并数据集时,用 Python 的os.listdir()读取文件列表,然后按顺序给图片分配标注。Windows 和 Linux 的文件排序规则不一样,同一个脚本在两个系统上跑出来的顺序可能不同。结果就是我第一批数据里有两张图片的标注和张冠李戴。
后来的解决方法是:训练脚本里完全用图片 ID 或显式的文件名映射来索引标注,绝不依赖文件系统的排序顺序。这一点对于任何做过自动化训练的人来说可能觉得很基础,但实际发生的时候却特别容易疏忽。
6.4 显而易见的边缘案例
自动标注模型对“常规场景”表现良好,但到了遮挡严重、强光、极暗环境、雨天反光这一类边界条件,可靠性会急剧下降。如果你手里有这类图片,我强烈建议不要直接进批处理,而是单独走人工流程。
原因在于,这些边界案例往往也是模型上线后最需要覆盖的情况——如果自动标注时丢掉它们,训练出来的模型在真实环境下大概率会出问题。宁可花一点时间手工标注这批图片,也不要让它们变成数据集里的噪声。
6.5 伪标签置信度的取舍
最后一个常见问题:伪标签的置信度阈值到底该设多少。设高了,漏检多,数据量上不去;设低了,误检多,训练噪音大。我的经验是,先用一个相对保守的值(比如 0.4)跑一批,检查误检率;如果误检率太高,就缓慢调低,同时加大抽检比例。
这个取舍没有标准答案,跟你的目标类别复杂度、数据场景、模型部署环境都有关。我通常的做法是:训练过程中同时输出一个“无人工修正”版本和“人工修正”版本的精度对比,如果差距小于两个点,说明伪标签质量够用,人工介入的比例可以降低。
整个项目做下来,我的最大感受是:自动标注不是“机器替代人”,而是把人的精力从“画框”转移到“审图”上。你省下来的时间不是用来休息的,而是用来把数据质量做得更好。如果你打算在自己的项目里复制这条流程,我的建议是先拿 100 张图小规模跑通,确认每一步的产物都正确,再放量。不要一上来就批量处理几千张图,因为一旦格式或提示词有问题,返工的成本远高于省下的时间。