引言
在 CV 项目里,数据决定了上限,模型只是逼近上限的工具。标注是把原始像素变成监督信号的环节,它的质量直接决定模型能走多远。一个常见现象是:团队花两周换更强的模型提升 1 个点,却因为标注规范不清、类别定义含糊损失 5 个点。
本文按「数据集 → 格式 → 工具 → 类型 → 质量 → 划分 → 半自动 → 合成」的顺序讲清数据集工程,并给出可直接运行的格式转换与统计代码。
目录
- 主流数据集
- 标注格式
- 标注工具
- 标注类型
- 标注质量保障
- 数据划分与防泄漏
- 半自动标注与主动学习
- 合成数据与域差距
- 代码:COCO 统计
- 代码:VOC 转 COCO
- 数据版本管理与流水线
- 标注团队与流程管理
- 格式对比表
- 权衡取舍
- 常见坑清单
- 小结
1. 主流数据集
| 数据集 | 规模 | 类别数 | 任务 | 用途 |
|---|---|---|---|---|
| ImageNet-1K | 128 万训练图 | 1000 | 分类 | 预训练骨干 |
| COCO | 33 万图 / 150 万实例 | 80 | 检测分割关键点 | 检测分割基准 |
| Pascal VOC | 约 1.1 万图 | 20 | 检测分割 | 轻量实验 |
| Open Images | 900 万图 | 600 | 检测 | 大规模预训练 |
| Objects365 | 200 万图 / 3000 万框 | 365 | 检测 | 大规模检测预训练 |
公开数据集的价值有两个:一是直接用作预训练,二是作为标注规范的参考模板。使用前务必确认许可协议,商用场景要留意限制。
2. 标注格式
三种主流格式的差异:
- Pascal VOC:每张图一个 XML 文件,记录绝对像素坐标的 xmin、ymin、xmax、ymax。
- COCO:所有标注集中在一个 JSON 里,bbox 是左上角加宽高的绝对像素,还支持分割多边形与关键点。
- YOLO:每张图一个 txt,每行
class cx cy w h,坐标是相对图像宽高的归一化值。
选择建议:训练 YOLO 直接用 txt;需要分割与关键点用 COCO JSON;老项目遗留的 VOC XML 需要转换。
3. 标注工具
| 工具 | 部署 | 支持任务 | 特点 |
|---|---|---|---|
| Label Studio | 自建服务 | 分类框分割关键点 | 灵活,支持多模态 |
| CVAT | 自建服务 | 框分割关键点跟踪 | 视频标注强 |
| LabelImg | 桌面 | 框 | 轻量,导出 VOC/YOLO |
| Roboflow | SaaS | 全类型 | 托管,含增强与导出 |
| Labelme | 桌面 | 多边形分割 | 分割标注常用 |
团队协作优先选自建服务(Label Studio、CVAT),能统一规范、分配任务、统计进度;个人快速标注用桌面工具。
4. 标注类型
- 分类标签:整图一个或多个标签,成本最低。
- 边界框(bbox):检测用,成本中等。
- 多边形 / 掩码:分割用,成本最高,边界越精细越贵。
- 关键点:姿态用,需要定义点的顺序与可见性。
- 视频跟踪:给每帧目标分配一致 ID,成本高但支撑跟踪任务。
标注成本的经验值:分类小于检测小于分割,分割的标注时间常常是检测的三到五倍。
5. 标注质量保障
质量保障的核心是「可度量的规范」:
- 标注规范文档:明确定义每个类别的边界、遮挡、截断、最小可标尺寸,配正反例图。
- 一致性度量:用 IAA(标注者间一致性)量化分歧,常用 Cohen’s Kappa 或 IoU 匹配率。
- 双盲标注与仲裁:关键样本由两人独立标注,分歧交由第三人裁决。
- 抽检审核:按比例抽查,不合格的批次返工。
- 迭代校准:定期对齐标注员的理解,修正规范文档。
import numpy as np
def cohen_kappa(a, b):
# a, b 为两位标注者的标签序列
a, b = np.asarray(a), np.asarray(b)
po = (a == b).mean()
classes = np.unique(np.concatenate([a, b]))
pe = sum((a == c).mean() * (b == c).mean() for c in classes)
return (po - pe) / (1 - pe + 1e-12)
labels_a = [0, 1, 1, 0, 1, 1, 0]
labels_b = [0, 1, 0, 0, 1, 1, 1]
print("kappa:", round(cohen_kappa(labels_a, labels_b), 4))
Kappa 大于 0.8 通常表示高度一致,低于 0.6 说明规范需要重新对齐。
6. 数据划分与防泄漏
划分看似简单,实则最容易出错:
- 训练 / 验证 / 测试按约 8:1:1 或 7:1.5:1.5,测试集只在最后用一次。
- 同一来源不跨集:同一段视频的相邻帧、同一病人的切片、同一场景的连拍,必须整组划分。
- 时间切分:涉及时间演进的任务(如销量、路况),用过去训练、未来验证。
- 类别均衡:每类在三个集里都要有足够样本,稀有类可适当加权。
- 划分固定:划分结果存盘复用,避免每次重划分导致指标不可比。
7. 半自动标注与主动学习
降低标注成本的三条路径:
- 模型预标注:用 SAM 或已有检测器生成候选,人工只做修正,效率可提升数倍。
- 主动学习:挑模型最不确定的样本优先标注,用最少标注换最大提升。
- 弱监督:用图像级标签训练检测(如 CAM 类方法),省去框级标注。
import numpy as np
def select_uncertain(confidences, k=10):
# 置信度越接近 0.5 越不确定,优先标注
uncertainty = 1 - np.abs(np.asarray(confidences) - 0.5) * 2
order = np.argsort(-uncertainty)
return order[:k].tolist()
conf = [0.9, 0.51, 0.99, 0.48, 0.75, 0.5]
print("to label:", select_uncertain(conf, k=3))
主动学习的关键是「采样策略」与「批量大小」:一次标注太多会浪费,太少则迭代慢。经验上每轮选几百张,训练后重新评估不确定性。
8. 合成数据与域差距
合成数据用图形引擎或扩散模型生成带标注的图,能低成本造出罕见场景(如事故、缺陷)。难点是域差距(domain gap):合成图与真实图在纹理、光照、噪声上分布不同,直接训练会掉点。缩小差距的手段有域随机化、风格迁移与少量真实数据微调。
合成数据的定位是「补充稀有场景」,不能完全替代真实数据。
9. 代码:COCO 统计
拿到一个 COCO 数据集,先统计类别分布与框尺寸,判断是否长尾。
import json
from collections import Counter
import numpy as np
from pathlib import Path
def analyze_coco(coco_json):
data = json.loads(Path(coco_json).read_text())
cats = {c["id"]: c["name"] for c in data["categories"]}
counts = Counter(a["category_id"] for a in data["annotations"])
areas = np.array([a["area"] for a in data["annotations"]])
print("images:", len(data["images"]), "annotations:", len(data["annotations"]))
for cid, n in counts.most_common():
print(f"{cats[cid]:<20s} {n}")
print("area p50/p90/max:",
round(float(np.percentile(areas, 50)), 1),
round(float(np.percentile(areas, 90)), 1),
round(float(areas.max()), 1))
small = (areas < 32 ** 2).mean()
print("small object ratio:", round(float(small), 4))
analyze_coco("annotations/instances_train2017.json")
小目标占比高时,要提前规划输入分辨率与增强策略,否则训练完才发现小目标几乎检测不到。
10. 代码:VOC 转 COCO
老项目常需要把 VOC 转成 COCO 以复用现代训练框架。
import json
import xml.etree.ElementTree as ET
from pathlib import Path
def voc_to_coco(xml_dir, out_json, class_names):
images, annotations, categories = [], [], []
cat_id = {name: i + 1 for i, name in enumerate(class_names)}
for i, name in enumerate(class_names):
categories.append({"id": i + 1, "name": name})
ann_id = 1
for img_id, xml_path in enumerate(sorted(Path(xml_dir).glob("*.xml")), start=1):
root = ET.parse(xml_path).getroot()
w = int(root.find("size/width").text)
h = int(root.find("size/height").text)
images.append({"id": img_id, "file_name": root.find("filename").text,
"width": w, "height": h})
for obj in root.findall("object"):
name = obj.find("name").text
box = obj.find("bndbox")
x1 = float(box.find("xmin").text)
y1 = float(box.find("ymin").text)
x2 = float(box.find("xmax").text)
y2 = float(box.find("ymax").text)
annotations.append({
"id": ann_id, "image_id": img_id, "category_id": cat_id[name],
"bbox": [x1, y1, x2 - x1, y2 - y1],
"area": (x2 - x1) * (y2 - y1), "iscrowd": 0,
})
ann_id += 1
coco = {"images": images, "annotations": annotations, "categories": categories}
Path(out_json).write_text(json.dumps(coco))
print("images:", len(images), "annotations:", len(annotations))
voc_to_coco("VOCdevkit/Annotations", "coco.json", ["person", "car", "dog"])
注意 COCO 的 bbox 是左上角加宽高,VOC 是左上角与右下角,转换时要相减。
11. 数据版本管理与流水线
数据集会不断迭代,必须有版本管理,否则「上次那个效果好的版本」找不回来。
- 版本工具:DVC、Git LFS、或自建对象存储加清单文件,记录每版数据的哈希与变更。
- 数据卡(dataset card):记录数据来源、采集条件、类别分布、已知偏差与许可,方便交接。
- 流水线:采集 → 清洗 → 去重 → 标注 → 划分 → 版本固化,每一步可复现。
- 与实验追踪联动:训练时记录所用的数据集版本,保证实验可复现。
import hashlib
from pathlib import Path
def file_hash(path, chunk=1 << 20):
h = hashlib.md5()
with open(path, "rb") as f:
while chunk_data := f.read(chunk):
h.update(chunk_data)
return h.hexdigest()
def find_duplicates(image_dir):
seen, dups = {}, []
for p in Path(image_dir).rglob("*"):
if p.suffix.lower() in {".jpg", ".png", ".jpeg"}:
h = file_hash(p)
if h in seen:
dups.append((str(seen[h]), str(p)))
else:
seen[h] = p
return dups
print("duplicates:", find_duplicates("images/train")[:5])
去重能防止同一张图同时出现在训练与测试集,是数据泄漏最常见的原因之一。内容近似(裁剪、压缩)的重复可用感知哈希进一步检测。
12. 标注团队与流程管理
标注是「人」的工程,流程管理决定了效率与质量。
- 任务拆分:按类别或场景分派,让标注员专注相似任务,减少切换成本。
- 规范培训:先用几十张样本做培训与试标,达标后才进入正式标注。
- 质量指标:跟踪返工率、抽检合格率、每人日产量,用数据管理而非感觉。
- 抽检比例:新标注员高比例抽检,熟练后降低,关键类别始终高抽检。
- 成本估算:按框数或按工时计价,提前估算预算与工期。
- 反馈闭环:把模型在验证集上的错误按类别汇总,反馈给标注团队修正规范。
一个实用做法是「金标样本」:在标注任务里混入已知答案的样本,用来持续监控标注员准确率,无需逐条人工复核。
13. 格式对比表
| 维度 | VOC | COCO | YOLO |
|---|---|---|---|
| 载体 | 每图一个 XML | 单个 JSON | 每图一个 txt |
| 坐标 | 绝对像素 | 绝对像素 | 归一化 |
| 框表示 | 左上右下 | 左上加宽高 | 中心加宽高 |
| 分割 | 无 | 多边形 | 多边形 |
| 关键点 | 无 | 有 | 有 |
| 训练直接可用 | 需转换 | 需转换 | 是 |
14. 权衡取舍
- 标注成本与质量:精细标注提升上限但成本高,可先用粗标验证可行性再细化。
- 自建工具与 SaaS:自建可控但要维护,SaaS 省事但有数据合规顾虑。
- 主动学习与全量标注:主动学习省标注但工程复杂,数据量小时收益明显。
- 合成与真实:合成补稀有场景,但域差距需要额外手段弥补。
- 格式选择:跟随训练框架,避免不必要的来回转换。
15. 常见坑清单
- 类别定义含糊:不同标注员对「遮挡算不算」理解不同,标签噪声大。
- 坐标越界:框超出图像边界未裁剪,训练时报错或静默丢弃。
- 坐标系混淆:xywh 与 xyxy 混用,框整体错位。
- 数据泄漏:同视频相邻帧跨集,验证指标虚高。
- 类别不平衡:长尾类别样本极少,模型几乎学不会。
- 重复图片:同一图出现在训练与测试,指标失真。
- 标注顺序不一致:类别 id 从 0 还是 1 开始不统一,训练全乱。
- 忽略 IAA:不度量一致性,标注质量无法量化。
- 预标注不校验:直接信任模型预标注,错误被固化进数据集。
- 测试集反复使用:调参用测试集,最终指标不可信。
- 无数据卡:接手的人不了解数据来源与偏差,重复踩坑。
- 忽视近似重复:裁剪或压缩后的同源图未被识别,造成泄漏。
16. 小结
数据集工程的主线是:定义清晰的标注规范 → 选合适的格式与工具 → 用 IAA 度量一致性 → 整组划分防泄漏 → 用预标注与主动学习降本 → 用合成数据补稀有场景。记住三条原则:规范先于标注、划分先于训练、测试集只用一次。数据质量的收益通常大于换模型的收益,把精力优先投在数据上。
延伸阅读
- 目标检测与 YOLO 系列工程落地 — 检测标注格式的实战
- 图像分割与实例分割 — 分割标注的成本与半自动方案
- 数据标注与合成数据 — 标注与合成的更广视角
- 计算机视觉任务全景与工程链路 — 数据在链路中的位置
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。