拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

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

自动标注与数据飞轮:Grounded-SAM、autodistill、X-AnyLabeling 实战

自动标注与数据飞轮:Grounded-SAM、autodistill、X-AnyLabeling 实战

1. 自动标注这条链路,到底解决了什么痛点

做过视觉项目的人都有一个共识:模型效果的上限,往往不是被网络结构卡住的,而是被标注数据卡住的。一个目标检测或者分割任务,前期花在拉框、描边、分类上的时间,经常占到整个项目周期的六七成。更难受的是,当你辛辛苦苦标完一批数据、训练完一版模型,发现漏检的类别还得补标,于是又回到标注工具里重复劳动。这个循环一旦转起来,人就变成了流水线上的一颗螺丝。

自动标注要解决的就是这个循环。它的核心思路是:用已有的基础模型先跑一遍推理,把结果作为"预标注"导入标注工具,人工只做修正和确认,而不是从零开始画。这样一来,单张图的标注时间可以从几分钟压缩到几十秒甚至几秒。而X-AnyLabeling、autodistill、Grounded-SAM这三个东西组合起来,恰好能覆盖"预标注生成—人工精修—模型迭代"的完整闭环,也就是大家常说的数据飞轮。

我先把这三个角色说清楚,不然后面容易混。X-AnyLabeling是一个带 AI 辅助能力的标注客户端,它本身集成了 SAM、YOLO 等模型,可以在标注界面里一键出框、一键分割,支持导出多种格式。Grounded-SAM是 GroundingDINO 加 SAM 的组合,前者负责"用文字描述找到目标框",后者负责"把框变成精细掩码",也就是说你输入"cat"它就能把图里所有猫框出来并分割。autodistill则是一个编排框架,它把"用大模型给无标注数据打伪标签、再训练小模型"这件事标准化了,你只要指定基础模型和目标模型,它帮你把中间流程串起来。

这三者拼在一起的价值在于:Grounded-SAM 负责零样本的粗标注,autodistill 负责把粗标注转成可训练的数据集并蒸馏出轻量模型,X-AnyLabeling 负责人工介入的精修和格式导出。适合谁来参考?如果你手头有一批没标注的图、想快速起一个检测或分割任务,或者你已经在用 LabelImg、Labelme 觉得效率太低,这套流程值得花一个下午搭起来。哪怕你只是想了解自动标注的原理,跟着走一遍也能把概念落地。

2. 三个工具的分工与选型逻辑

2.1 为什么不是随便挑一个用

很多人第一反应是"我直接用 Grounded-SAM 跑完不就行了,为什么还要 autodistill 和 X-AnyLabeling"。这个问题我踩过。Grounded-SAM 的输出是零样本的,它对常见类别效果不错,但对细分类别、密集小目标、遮挡严重的场景,召回和精度都不稳定。你直接拿它的结果当训练集,训出来的模型会继承它的偏差,越训越偏。所以它只能当"第一遍粗筛",不能当"最终答案"。

autodistill 存在的意义是把"粗筛结果"变成"可迭代的数据资产"。它的工作方式是:用 Grounded-SAM 这类基础模型对无标注图片生成伪标签,然后拿这些伪标签去训练一个 YOLO 或者 DETR 之类的小模型。小模型在特定数据分布上微调后,精度往往能超过基础模型本身,而且推理速度快几十倍。这就是知识蒸馏在标注场景的落地。

X-AnyLabeling 则是"人机协同"的入口。自动标注不可能 100% 正确,人工必须能高效地检查和修正。X-AnyLabeling 的优势是它把模型推理内置到了标注界面里,你按一个快捷键就能让当前图重新推理,或者对某个框做 SAM 精修,不用在命令行和标注工具之间来回切。这个体验差异在实际标注几千张图的时候非常明显。

2.2 三者的能力对照

工具核心能力输入输出在流程中的位置
Grounded-SAM文本提示零样本检测+分割图片+文字描述框+掩码伪标签生成
autodistill伪标签编排与模型蒸馏无标注图片集训练好的小模型数据飞轮引擎
X-AnyLabelingAI辅助标注与格式导出图片+预标注精修后的标注文件人工精修与交付

这张表建议存下来,后面配置环境的时候对照着看,能少走很多弯路。选型上我的建议是:如果你的类别是 COCO 里常见的八十类,Grounded-SAM 直接跑效果就够用;如果是工业缺陷、医学影像这种专有类别,Grounded-SAM 的文本提示要写得非常具体,而且必须经过 autodistill 蒸馏才能用;如果团队里有人工标注资源,X-AnyLabeling 一定要配上,它是保证数据质量的下限。

2.3 环境依赖的坑要先说

这三个工具对环境的敏感度不一样。Grounded-SAM 依赖 PyTorch、GroundingDINO 的自定义 CUDA 算子、segment-anything 的权重,安装过程最容易出问题。autodistill 相对干净,它本质是 Python 包编排,但会拉一堆依赖。X-AnyLabeling 是带 GUI 的,它对 PyQt 版本、显卡驱动、CUDA 版本都有要求,而且不同版本对 Python 版本的支持不一样。

我实测下来比较稳的组合是:Python 3.10、PyTorch 2.1 配 CUDA 11.8、GroundingDINO 用官方仓库编译、segment-anything 用 pip 装、X-AnyLabeling 用官方 release 的打包版本而不是源码跑。为什么 X-AnyLabeling 建议用打包版?因为源码跑的时候 PyQt 和 onnxruntime 的版本冲突非常常见,打包版已经把这些依赖锁死了,省心。如果你非要用源码跑(比如要改界面),那一定要单独建一个虚拟环境,别和 Grounded-SAM 的环境混用。

注意:Grounded-SAM 和 X-AnyLabeling 不要装在同一个 conda 环境里。前者要编译 CUDA 算子,后者要特定版本的 PyQt,两者对 numpy、protobuf 的版本要求经常打架,混装的结果就是两个都跑不起来。

3. Grounded-SAM 伪标签生成实操

3.1 环境搭建的关键步骤

先建一个独立环境,我习惯用 conda:

conda create -n grounded-sam python=3.10 -y conda activate grounded-sam pip install torch==2.1.0 torchvision==0.16.0 --index-url https://download.pytorch.org/whl/cu118

PyTorch 装完先验证一下 CUDA 是否可用,这一步别跳过:

import torch print(torch.cuda.is_available()) print(torch.version.cuda)

如果输出 False,后面所有推理都会退到 CPU,速度慢到没法用。确认可用之后再装 GroundingDINO。GroundingDINO 需要编译一个 CUDA 扩展,所以要先确认本机有 nvcc:

nvcc --version

没有的话要装对应版本的 CUDA Toolkit。编译命令是:

git clone https://github.com/IDEA-Research/GroundingDINO.git cd GroundingDINO pip install -e .

编译过程如果报 "no kernel image is available",基本是 CUDA 架构不匹配,需要在 setup.py 里把-gencode参数改成你显卡对应的算力,比如 3090 是 8.6,4090 是 8.9。这个坑我遇到过两次,第一次折腾了一晚上才定位到。

segment-anything 就简单多了:

pip install git+https://github.com/facebookresearch/segment-anything.git

然后下载三个权重文件:GroundingDINO 的groundingdino_swint_ogc.pth、SAM 的sam_vit_h_4b8939.pth。权重文件加起来好几个 G,建议提前下好放本地,别在代码里现下。

3.2 文本提示怎么写才准

Grounded-SAM 的效果高度依赖文本提示(prompt)的写法。这里有个反直觉的点:不是写得越详细越好,而是要贴近模型训练时的语言分布。GroundingDINO 训练数据里的类别描述大多是简短名词短语,所以你写 "person" 比写 "a human being standing in the scene" 效果好得多。

几个实操经验:

  • 类别之间用英文句点分隔,比如"person . car . dog .",末尾的点别漏,它起到分隔符作用。
  • 同义词可以都写上,比如"bottle . flask .",能提升召回。
  • 不要写颜色、位置、状态这类修饰,模型对这类词不敏感,反而干扰。
  • 如果某个类别总是漏检,试试换更通用的上位词,比如 "sedan" 换成 "car"。

阈值方面,box_threshold控制框的置信度,text_threshold控制文本匹配的置信度。默认 0.3 和 0.25 对大多数场景够用。如果发现框太多太杂,把 box_threshold 提到 0.4;如果漏检严重,降到 0.2 试试。这两个参数没有万能值,要拿几张典型图试出来。

3.3 批量推理脚本

单张图跑通之后,写个批量脚本处理整个文件夹。核心逻辑是遍历图片、调用 Grounded-SAM、把结果存成 COCO 或者 YOLO 格式。下面是我常用的一个精简版:

import os import cv2 import torch import numpy as np from groundingdino.util.inference import load_model, load_image, predict from segment_anything import sam_model_registry, SamPredictor TEXT_PROMPT = "person . car . dog ." BOX_THRESHOLD = 0.3 TEXT_THRESHOLD = 0.25 model = load_model("GroundingDINO/groundingdino/config/GroundingDINO_SwinT_OGC.py", "weights/groundingdino_swint_ogc.pth") sam = sam_model_registry["vit_h"](checkpoint="weights/sam_vit_h_4b8939.pth") sam.to("cuda") predictor = SamPredictor(sam) img_dir = "images" out_dir = "pseudo_labels" os.makedirs(out_dir, exist_ok=True) for name in os.listdir(img_dir): path = os.path.join(img_dir, name) image_source, image = load_image(path) boxes, logits, phrases = predict( model=model, image=image, caption=TEXT_PROMPT, box_threshold=BOX_THRESHOLD, text_threshold=TEXT_THRESHOLD) predictor.set_image(image_source) h, w, _ = image_source.shape for box, phrase in zip(boxes, phrases): xyxy = box * torch.Tensor([w, h, w, h]) masks, _, _ = predictor.predict(box=xyxy.numpy()[None, :], multimask_output=False) # 这里把 masks 和 phrase 存成你要的格式 print(f"done: {name}")

这段代码里有个细节值得说:box * torch.Tensor([w, h, w, h])是把归一化坐标还原成像素坐标,GroundingDINO 输出的是归一化的 cxcywh,用之前一定要转。我第一次写的时候忘了转,框全挤在左上角,排查了半天。

3.4 伪标签的质量控制

伪标签不是生成完就能用的,必须做一轮质量过滤。我的做法是:

  • 按置信度排序,把低于阈值的框直接丢掉。
  • 对面积过小的框(比如小于 20x20 像素)做过滤,这类框大多是噪声。
  • 对重叠度极高的同类框做 NMS,避免一个目标出多个框。
  • 抽样人工看 50 到 100 张,统计漏检和误检的比例,决定要不要调 prompt 或阈值。

这一步花的时间不多,但能避免把垃圾数据喂给下游。我见过有人跳过这步,结果蒸馏出来的模型精度还不如直接用 Grounded-SAM,白忙一场。

4. autodistill 把伪标签变成可用模型

4.1 autodistill 的工作机制

autodistill 的设计很巧妙,它把"基础模型"和"目标模型"解耦了。基础模型负责打标签,目标模型负责学习。你只需要写几行代码指定两者,中间的格式转换、数据集划分、训练循环它都帮你处理了。

它的核心抽象是BaseModel和TargetModel。比如用 GroundingDINO 当基础模型、YOLOv8 当目标模型:

from autodistill_grounding_dino import GroundingDINO from autodistill_yolov8 import YOLOv8 base_model = GroundingDINO(ontology=ontology) target_model = YOLOv8("yolov8n.pt") target_model.train("./dataset", epochs=50)

这里的ontology是本体定义,说白了就是类别列表和它们的文本描述。autodistill 用这个 ontology 去调基础模型,把每个类别的伪标签生成出来。

4.2 ontology 的定义技巧

ontology 是 autodistill 里最需要花心思的地方。它决定了基础模型用什么词去找目标。定义方式有两种,简单的是直接给类别名列表,复杂的是用Ontology类做层级定义。

from autodistill.ontology import Ontology ontology = Ontology( images="./unlabeled_images", detections_ontology=[ "person", "car", "traffic light" ] )

如果类别之间有包含关系,比如"车辆"下面有"轿车""卡车",可以用层级结构,这样基础模型先找大类再细分,召回会更好。但层级太深也会让 prompt 变复杂,一般两层就够了。

我踩过的一个坑是:ontology 里的类别名要和后续 X-AnyLabeling 里的标签名完全一致,包括大小写和空格。不一致的话,导入预标注的时候标签会对不上,得手动映射,很烦。所以定义 ontology 的时候就把最终标签体系定死。

4.3 蒸馏训练的参数选择

autodistill 默认用 YOLOv8 当目标模型,这个选择是合理的,因为 YOLOv8 训练快、部署方便、社区支持好。模型大小从 n 到 x 有五个档,选哪个取决于你的精度要求和部署环境。

模型参数量推理速度适用场景
yolov8n3.2M最快边缘设备、实时检测
yolov8s11.2M快一般服务器部署
yolov8m25.9M中精度要求较高
yolov8l43.7M慢离线高精度
yolov8x68.2M最慢精度优先

我的建议是先用 yolov8n 跑一轮,看精度能不能接受。如果差得多,再往上换。因为伪标签本身有噪声,用太大的模型反而容易过拟合噪声。epochs 一般设 50 到 100,配合早停。学习率用默认的 0.01 起步,如果 loss 震荡就降到 0.001。

训练完之后一定要在留出的验证集上评估,别只看训练 loss。伪标签训练的模型,验证集精度和训练集精度差距往往比较大,这个差距就是伪标签噪声的体现。

4.4 数据飞轮的迭代节奏

autodistill 真正的价值在于迭代。第一轮用 Grounded-SAM 生成伪标签、训练 YOLOv8,然后用这个 YOLOv8 去推理新的无标注数据,把高置信度的结果加入训练集,再训一轮。每轮迭代,模型对专有类别的识别能力都会提升,因为它在逐步拟合你的数据分布。

这个循环跑三轮左右,精度通常能接近全人工标注的水平。但要注意,每轮加入的新数据要控制比例,别一次性加太多低质量样本。我的做法是每轮只加入置信度高于 0.7 的预测,且人工抽检确认无误后才入库。

5. X-AnyLabeling 人工精修与导出

5.1 安装与模型配置

X-AnyLabeling 官方提供了各平台的打包版本,直接下载解压就能用。如果你要用源码跑,环境是:

conda create -n xanylabeling python=3.10 -y conda activate xanylabeling pip install -r requirements.txt python anylabeling/app.py

源码跑的时候最容易出问题的是 onnxruntime 和 PyQt5 的版本。requirements 里锁的版本不一定适配你的系统,如果启动报 DLL 加载失败,多半是 onnxruntime 的问题,换成onnxruntime-gpu或者降版本试试。

模型配置在设置里的"模型"选项卡。X-AnyLabeling 支持导入自定义的 ONNX 模型,你可以把 autodistill 训出来的 YOLOv8 导出成 ONNX 放进去。导出命令:

yolo export model=runs/detect/train/weights/best.pt format=onnx opset=12

opset 用 12 兼容性最好。导出后在 X-AnyLabeling 里新建一个模型配置,指定模型路径、输入尺寸、类别列表,就能在标注界面里用这个模型做预标注了。

5.2 快捷键与高效标注流程

X-AnyLabeling 的快捷键设计得挺顺手,熟练之后效率提升明显。常用的几个:

  • D/A:下一张 / 上一张
  • Ctrl+I:用当前模型对整图推理
  • Ctrl+J:对选中的框做 SAM 精修
  • Ctrl+S:保存
  • Delete:删除选中的标注

高效流程是这样的:打开一张图,先Ctrl+I让模型出预标注,然后快速扫一遍,框对的留着,框错的删掉或调整,漏的补上。对于分割任务,选中框按Ctrl+J,SAM 会把框变成精细掩码,比手描快太多。

有个技巧是批量推理。X-AnyLabeling 支持对整个文件夹做批量预标注,你晚上挂机跑,第二天来精修,能省不少时间。批量推理的入口在"AI 标注"菜单里,选好模型和文件夹就行。

5.3 导出格式与下游对接

X-AnyLabeling 支持导出 COCO、YOLO、VOC、Labelme 等多种格式。导出前要确认类别映射正确,尤其是从预标注导入的标签,有时候会带上模型输出的原始类别名,需要统一成你的标签体系。

导出 YOLO 格式的时候注意,它生成的是images和labels两个文件夹加一个classes.txt。labels 里是归一化的class_id cx cy w h。如果你要做分割,导出的是多边形点集,格式和检测不一样,下游训练脚本要对应改。

注意:导出前先备份原始工程文件。X-AnyLabeling 的工程文件是 json 格式,导出操作有时会覆盖,我吃过一次亏,几百张图的标注差点丢了。

6. 常见问题与排查速查

6.1 环境类问题

现象可能原因解决方向
torch.cuda.is_available() 为 False驱动或 CUDA 版本不匹配重装对应 CUDA 版本的 torch
GroundingDINO 编译报错缺 nvcc 或算力不匹配装 CUDA Toolkit,改 gencode
X-AnyLabeling 启动闪退PyQt 或 onnxruntime 冲突用打包版,或单独建环境
SAM 推理 OOM显存不足换 vit_b 权重或缩小输入尺寸

6.2 效果类问题

伪标签漏检严重怎么办?先检查 prompt 写法,换成更通用的词。再降 box_threshold 到 0.2。如果还不行,说明基础模型对你的类别确实不熟,这时候要么换基础模型,要么老老实实人工标一批种子数据,用种子数据微调基础模型再生成伪标签。

蒸馏后模型精度反而下降?大概率是伪标签噪声太大。回去做质量过滤,把低置信度、小面积、重叠的框清掉。另外检查训练集和验证集有没有数据泄漏,伪标签生成和评估用了同一批图会导致虚高。

X-AnyLabeling 导入预标注后标签错乱?检查类别名是否完全一致,包括大小写。X-AnyLabeling 是按字符串匹配的,差一个字符就匹配不上。

6.3 几个独家避坑经验

第一,Grounded-SAM 的推理结果一定要存原始输出,别只存可视化图。后面调阈值、换格式都要用到原始框和掩码,重新跑一遍很费时间。

第二,autodistill 训练时把workers设小一点,设太大在有些系统上会卡死。我一般设 4。

第三,X-AnyLabeling 的自动保存间隔默认比较长,建议在设置里改成 1 分钟,防止意外崩溃丢数据。

第四,整个流程的中间产物(伪标签、ONNX 模型、工程文件)按日期分文件夹存,迭代几轮之后你会感谢自己做了这件事。

7. 关于数据飞轮的一点个人体会

这套流程我前后搭过三次,每次都有新的坑,但每次跑通之后的收益都很实在。最开始我执着于把 Grounded-SAM 的阈值调到完美,后来发现没必要,因为下游还有 autodistill 和人工精修两道关,粗标注的容错空间比想象中大。真正决定最终质量的是人工精修那一步,以及迭代的轮数。

另外,别指望一次就把类别体系定死。实际项目里类别经常要增删,所以 ontology 和标签体系要设计得容易扩展。我现在的习惯是留一个"其他"类兜底,遇到暂时归不了类的目标先扔进去,后面再细分。

最后分享一个小技巧:如果你手头有少量已标注数据,别浪费,用它们去评估每一轮蒸馏模型的精度,画出精度随迭代轮数的曲线。这条曲线能告诉你什么时候该停止迭代,也能在汇报的时候拿出有说服力的证据。数据飞轮转起来之后,你会发现标注这件事从"体力活"变成了"调参活",这才是它最大的价值。

返回列表