ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

从零制作VOC数据集:目标检测训练数据全流程指南

从零制作VOC数据集:目标检测训练数据全流程指南

不需要主标题,直接从二级标题开始。内容围绕项目标题展开,贴合领域专业调性。

1. 动手前先弄懂:VOC数据集到底长什么样

很多刚接触目标检测的朋友,第一次跑通训练脚本之后,都会遇到一个坎——想训练自己的模型,却不知道数据从哪来。直接下载现成数据集吧,类别对不上;自己标吧,又搞不清VOC格式到底要求什么。这个项目标题看起来简单,实际动手才发现坑不少。

先说结论:VOC数据集,全称是PASCAL VOC(Visual Object Classes),它不只是“一堆图片加一堆标注”,而是一套严格定义好的目录结构和文件格式约定。你最终要交付的,不是“看起来差不多”的文件,而是能让训练脚本直接读进去、不报错、能正常生成训练列表的东西。

一套标准的VOC数据集,目录结构是这样:

VOCdevkit/ ├── VOC2007/ │ ├── JPEGImages/ # 存放原始图像 │ ├── Annotations/ # 存放XML标注文件 │ ├── ImageSets/ │ │ └── Main/ # 存放训练/验证/测试划分文件 │ ├── SegmentationClass/ # 语义分割标注(可选) │ └── SegmentationObject/ # 实例分割标注(可选)

绝大多数目标检测模型(比如SSD、YOLO的VOC版本、Faster R-CNN的VOC支持)读数据时,核心依赖的是前三个目录:JPEGImages里的图片、Annotations里的XML标注文件、ImageSets/Main里的txt索引文件。

这里有个容易搞混的点:ImageSets/Main目录下放的不是图片,而是文本文件。每个txt文件里写的是一行一个图片文件名(不带扩展名),比如:

000001 000002 000003

训练脚本会先读train.txt,拿到所有训练图片的编号,再根据编号去JPEGImages找图、去Annotations找对应的XML标注。

理解了这一步,你就明白了:VOC格式的本质是“三分离”——图片、标注、索引各存各的,通过文件名关联起来。这种设计的好处是,当你需要调整训练集和验证集的划分时,不需要移动任何图片文件,只改txt内容就行。我一开始没想通这点,傻乎乎地复制了几百张图片到不同文件夹,浪费时间,后来才发现只要改txt就够了。

2. 图像采集这一步做不好,后面全都白费

很多教程上来就教你用标注工具,但忽略了图像本身的质量。我实际做项目时发现,图像采集阶段的错误,会在训练阶段被无限放大。

2.1 图像数量与类别的平衡策略

先说数量。作为一个经验值,每个类别至少准备300张包含该目标的图片。注意,是“包含该目标的图片”,不是“标注框的数量”。一张图里如果有5个目标,也只算1张图片。如果类别多,建议每个类别500张起步。

但光有数量还不够,类别平衡才是关键。我见过一个真实案例:某做安全帽检测的项目,训练集里有800张“戴安全帽”的图片,但只有120张“不戴安全帽”的图片。结果模型训练完后,检测精度在“不戴”这个类别上惨不忍睹。这类问题不是靠调参能解决的,根源在数据分布。

如果你发现某个类别图片确实难收集,有两个补救办法:

  • 数据增强:对已有图片做随机裁剪、翻转、亮度调整、加噪声,扩充样本量。但注意不要对同一张图做太多夸张变换,否则模型会学到“光照变化等于目标变体”这种错误关联。
  • 难例挖掘:先训练一个初步模型,拿到那些预测错误的图片,把它们补充进训练集。这个办法在工业项目中非常有效,属于进阶玩法,但值得一试。

2.2 图像分辨率与拍摄规范

VOC对图像分辨率没有硬性要求,但有个隐性约束——标注框的大小要和实际应用场景匹配。如果你的应用场景是“远距离摄像头监控”,那目标在画面中通常很小,训练图片里就不要全是目标占满屏幕的大特写。反之,如果应用是“近景拍照识别”,目标太小反而会导致特征不足。

我踩过的一个坑是:收集图像时没注意分辨率统一,有的图是1920x1080,有的是640x480。训练时为了适配模型输入尺寸,所有图都会被resize,这导致小图的标注框位置在resize后被压缩得很严重,检测效果不稳定。所以采集图像时,尽量统一分辨率,或者至少保证分辨率不要太低。

还有一个细节:图像格式。JPEG和PNG都可以,但建议统一使用JPEG格式。PNG虽然无损,但文件体积大,训练时读盘开销高。如果原始素材是PNG,批量转换成JPG再入库,处理速度会快不少。

2.3 文件命名规范:这是一道送命题

命名规范看起来是小事,做不好会直接让整个数据集报废。VOC官方数据集的图片命名是六位数字编号,比如000001.jpg、000002.jpg。但你自己做数据集,不一定要完全照搬,只要保证“唯一、纯数字或纯字母、无空格、无中文”即可。

为什么这么强调?因为很多训练脚本在解析文件名时,会用正则表达式提取图片编号(比如匹配[0-9]+)。如果你的文件名里混入了中文、空格、特殊符号,轻则脚本报错,重则标注文件与图片对不上号。

我自己习惯的命名格式是:

日期_序号_类别 20250101_001_person

但后来发现这种命名有个问题——如果日期是15位,类别是英文还好,万一类别里有下划线,解析时就容易出错。现在我的标准做法是:全部用纯数字编号,000000、000001、000002这样递增。类别信息写在标注XML里,不需要体现在文件名上。这个方案最稳。

3. 标注工具选型:别在这个环节浪费时间

标注工具的选择,直接影响你的效率。市面上主流的有三款:LabelImg、Labelme、RectLabel。我三款都用过,说说实际体验。

3.1 三款工具对比

工具标注类型输出格式平台适合场景
LabelImg矩形框VOC XMLWindows/Linux/Mac目标检测,最快上手
Labelme多边形/矩形JSONWindows/Linux/Mac实例分割,多边形标注
RectLabel矩形/多边形VOC/COCOmacOSMac用户,导出格式灵活

如果你的任务就是目标检测(矩形框),首选LabelImg,没有悬念。它直接生成VOC格式的XML文件,省去格式转换的麻烦。Labelme生成的JSON要转成VOC XML,多一步操作,虽然官方提供了转换脚本,但何必多此一举。

3.2 LabelImg的安装与使用要点

安装LabelImg,在Linux下最省事:

pip install labelImg labelImg # 启动

Windows用户通过Anaconda安装也很简单:

conda install -c conda-forge labelimg labelImg

打开软件后,界面很简洁:左边是图片列表,中间是画布,右边是标注框和类别列表。

操作流程:

  1. 点击“Open Dir”打开图片文件夹
  2. 点击“Create RectBox”画标注框
  3. 弹出对话框输入类别名
  4. 标注完一张图后按Ctrl+S保存XML
  5. 按D或W键切换到下一张

这里有个很多人不知道的快捷键:按X键可以切换标注框是“被遮挡”还是“可见”状态。VOC格式的XML里有个truncated字段,记录目标是否被截断。如果目标有一部分超出画面边界,应该把truncated设为1。很多人不关心这个字段,但在训练时,模型对截断目标的检测能力会受影响。如果你想让模型在真实场景中表现好,标注时务必如实填写truncated。

3.3 标注的基本原则:什么该标,什么不该标

这是整个过程中最有经验含量的一环。我见过不少新手标注的图,模型训练出来效果差,回头查标注文件发现一堆低级错误。

标注的第一原则是:只标完整可见的目标主体。如果一个人只露出了半张脸,另外半边被墙挡住了,那就不要标。如果一辆车大部分在画面内,只有保险杠超出画面边界,这种情况标注框可以包含超出部分,但要标记truncated=1。

第二个原则是:标注框要贴合目标轮廓,不要留大片空白。很多新手习惯把目标物体连同周围背景一起框进去,以为这样模型能“学得更准”。恰恰相反,过大的标注框会让模型学会背景噪声。正确的做法是:标注框紧贴目标边缘,上下左右只留1-2像素的余量。

第三个原则是:小目标也要标注。有些图片里目标只有十几个像素,你觉得“这太小了,标了也没用”,于是跳过。但实际场景中,小目标恰恰是最常见的检测需求。VOC数据集本身就包含大量小目标样本,这是它在目标检测领域久经考验的原因之一。如果一张20像素的猫你都标注了,模型对小目标的鲁棒性会明显提升。

3.4 多人协作与校验机制

如果你是单兵作战,标完直接检查一遍就行。但如果是团队协作,一定要求标注规范提前写好,并且设置两道校验流程:

  1. 标注者自检:每标完100张图,抽查10张,检查框位置和类别是否准确
  2. 交叉抽检:由另一个人随机抽20张图,人工核对标注结果,发现错误率超过5%就退回重标

交叉抽检这条,很多团队嫌麻烦不做。但相信我,等到训练完发现bad case再回头排查,时间成本是抽检的数十倍。

4. Annotation文件核心字段全解析:每个标签都是“为什么”

标注完成后,你会得到一个XML文件。打开它,长这样:

<annotation> <folder>VOC2007</folder> <filename>000001.jpg</filename> <source> <database>Unknown</database> <annotation>Unknown</annotation> <image>Unknown</image> </source> <size> <width>1920</width> <height>1080</height> <depth>3</depth> </size> <segmented>0</segmented> <object> <name>helmet</name> <pose>Unspecified</pose> <truncated>0</truncated> <difficult>0</difficult> <bndbox> <xmin>100</xmin> <ymin>200</ymin> <xmax>300</xmax> <ymax>450</ymax> </bndbox> </object> </annotation>

这个文件里的每个字段都有讲究,我逐个拆解一下。

4.1 根节点与图片信息

folder字段记录图片所在文件夹名称。这个字段看起来没用,但有些训练脚本读XML时会校验文件名是否和图片对应,如果folder里写的和实际路径不一致,会有隐患。我建议直接写成VOC2007,和你的数据集目录保持一致。

filename字段必须和JPEGImages里的文件名完全一致,包括扩展名。注意是000001.jpg,不是000001,也不是000001.JPG。Linux系统下大小写敏感,jpg和JPG是两个不同的文件,踩过坑的人才知道这个细节有多关键。

size节点里的三个值,width、height、depth,是标注工具自动填写的,一般不会有错。但如果你手动修改过XML,或者用脚本批量生成XML,一定要检查这三个值和实际图片尺寸是否一致。有个隐蔽的坑:模型训练时会用这个size做坐标归一化,如果size写错了,标注框坐标全错位,训练不会报错,但检测精度会莫名其妙地差。

4.2 object节点的核心字段

name是类别名,只允许小写英文字母、数字、下划线,不要用中文。这个字段直接对应你训练脚本里的类别列表。

truncated字段记录目标是否被截断(目标超出图像边界)。取值范围0或1。前面说过,目标有部分超出画面时填1,否则填0。这个字段影响模型的边界处理逻辑,值得如实填写。

difficult字段是个隐藏的坑。在VOC官方数据集中,difficult=1表示这个目标很难辨识,比如极小、遮挡严重、光线极暗。很多训练脚本在计算mAP时会自动忽略difficult=1的样本。如果你在标注时把大量样本标记为difficult=1,看起来mAP很漂亮,实际上是“虚高”,模型真实性能没那么好。我的建议是:除非目标真的模糊到人眼都很难判断,否则一律填0。

pose字段记录目标姿态。VOC官方定义了Unspecified、Frontal、Rear、Left、Right等几个值。对绝大多数目标检测任务来说,这个字段不会被用到,训练脚本会忽略它。但LabelImg默认会写入这个字段,就让它保持Unspecified即可,不用纠结。

4.3 bndbox坐标的书写规范

bndbox是整个XML里最重要的字段,记录标注框的左上角和右下角坐标。

<bndbox> <xmin>100</xmin> <ymin>200</ymin> <xmax>300</xmax> <ymax>450</ymax> </bndbox>

坐标单位是像素,基于原始图片尺寸,不是resize之后的尺寸。这里有三个容易犯错的地方:

第一,坐标系原点在图像左上角。x轴向右增大,y轴向下增大。这和数学坐标系不一样,新手容易把ymin和ymax写反。

第二,xmin必须小于xmax,ymin必须小于ymax。听上去像废话,但手动修改XML时极易搞错。有些训练脚本会直接校验这个规则,不满足就报错。

第三,坐标必须是整数。VOC官方数据集中,坐标都是整数类型。虽然某些训练脚本支持浮点数坐标,但为了兼容性,还是写成整数更稳妥。

4.4 一个对象多个实例的处理

一张图里有多个目标时,就在XML里写多个object节点:

<object> <name>person</name> <truncated>0</truncated> <difficult>0</difficult> <bndbox>...</bndbox> </object> <object> <name>dog</name> <truncated>0</truncated> <difficult>0</difficult> <bndbox>...</bndbox> </object>

这里要注意的是:同一个类别在一张图里出现多次,也需要写多个object节点。它们可以有相同的name,但bndbox不同。LabelImg会自动处理这种情况,不用手动操心。

5. ImageSets划分:训练、验证、测试的黄金比例

图片标注完,你以为就结束了?还早。下一步是做数据划分,也就是生成ImageSets/Main下的txt文件。

5.1 三种划分的用途

按VOC惯例,训练数据被划分为三份:

  • train:用于训练模型权重
  • val:用于训练过程中的验证,帮助调参和early stopping
  • trainval:train和val的并集,某些模型在最终训练阶段会用完整trainval重新训练

注意还有一份test,它在VOC中不是必须的。因为目标检测领域有一种做法是:验证集和测试集合并,用val来评估模型性能。很多开源模型的VOC实现中,只生成train和val两个文件就够了。

5.2 划分比例怎么定

最常用的比例是:

数据集占比用途
train70%训练模型权重
val30%验证模型性能、调参
test可选最终效果评测,一般单独划分

但这里有个实践经验:当你数据量不大(几百张)时,不要单独划分test集。数据量小时,单独留出test集会让训练数据更少,模型性能反而下降。正确的做法是:训练阶段用train和val,调完参数后,用全部数据训练最终模型。

5.3 划分的关键细节:类别分布要均衡

直接随机划分有一个隐患,就是某个类别在训练集里有80张,验证集里只有5张。模型在验证集上对这个类别的检测效果就会有很大的方差,你无法判断模型到底是真学会了,还是靠运气。

我推荐的做法是分层划分:先按类别把图片分组,然后在每个类别的图片内分别按比例随机选取,最后合并成训练集和验证集。这样做能保证每个类别在两个集合中的分布比例大致相同。

如果图片类别是单标签的(一张图只有一类目标),划分脚本很简单:

import random import os from collections import defaultdict # 图片文件列表 img_dir = "JPEGImages" all_imgs = [f.split(".")[0] for f in os.listdir(img_dir) if f.endswith(".jpg")] # 读标注XML,统计每张图的类别(简化逻辑:取第一个object的name) from xml.etree import ElementTree def get_label(img_id): xml_path = os.path.join("Annotations", f"{img_id}.xml") tree = ElementTree.parse(xml_path) objs = tree.findall("object") return [obj.find("name").text for obj in objs] # 按类别分组 category_imgs = defaultdict(list) for img_id in all_imgs: labels = get_label(img_id) # 多标签图,每个类别都计入该图的类别列表 for label in set(labels): category_imgs[label].append(img_id) # 每个类别单独按7:3划分 train_set = set() val_set = set() for label, img_ids in category_imgs.items(): random.shuffle(img_ids) split_idx = int(len(img_ids) * 0.7) train_set.update(img_ids[:split_idx]) val_set.update(img_ids[split_idx:]) # 写出txt文件 with open("ImageSets/Main/train.txt", "w") as f: for img_id in sorted(train_set): f.write(img_id + "\n") with open("ImageSets/Main/val.txt", "w") as f: for img_id in sorted(val_set): f.write(img_id + "\n")

我差点漏掉的一个操作是:写出的txt文件最后一行必须有换行符,否则最后一个文件名读不完整。另外,文件里不要有空行,有些解析脚本遇到空行会报错或者产生空索引。

5.4 数据清洗:这一步不要省

在正式训练前,我强烈建议做一次全量自检,包含以下检查项:

  1. 孤儿图片:JPEGImages里有图片,但Annotations里没有对应的XML文件。这种图要么补标,要么删掉。
  2. 孤儿标注:Annotations里有XML,但JPEGImages里没有对应的图片。这种直接删XML。
  3. 空标注:XML里没有object节点,或object节点里没有bndbox。
  4. 坐标越界:bndbox坐标值超出图片尺寸范围。
  5. 文件损坏:XML文件本身无法解析(比如标签不闭合、编码错误)。

我之前写过一个检查脚本,一键扫描上述问题,这里分享核心逻辑:

import os from xml.etree import ElementTree img_dir = "JPEGImages" ann_dir = "Annotations" for ann_file in os.listdir(ann_dir): if not ann_file.endswith(".xml"): continue img_id = ann_file[:-4] img_path = os.path.join(img_dir, img_id + ".jpg") if not os.path.exists(img_path): print(f"[ERR] 无对应图片: {ann_file}") continue try: tree = ElementTree.parse(os.path.join(ann_dir, ann_file)) except Exception as e: print(f"[ERR] XML解析失败: {ann_file}") continue objs = tree.findall("object") if not objs: print(f"[WARN] 无标注对象: {ann_file}") continue for obj in objs: box = obj.find("bndbox") if box is None: print(f"[ERR] 缺bndbox: {ann_file}") continue # 这里可以从size节点里取图片尺寸做越界校验

这个脚本花不了10分钟,但能帮你避开训练中后期才暴露出来的各种奇葩问题。

6. 从VOC到其他格式:别被工具绑架

VOC是“母格式”,但实际训练时,很多框架并不直接用VOC格式,而是要转成其他格式。最常见的两种转换是:

6.1 VOC转YOLO

YOLO系列用的是txt标注文件,每行格式:

<class_id> <x_center> <y_center> <width> <height>

注意:YOLO格式的坐标是归一化后的中心点坐标和宽高,不是左上角和右下角。

转换公式:

x_center = (xmin + xmax) / 2 / img_width y_center = (ymin + ymax) / 2 / img_height width = (xmax - xmin) / img_width height = (ymax - ymin) / img_height

所有值都是0到1之间的浮点数。

用Python批量转换:

import os from xml.etree import ElementTree # 类别映射表,顺序要和训练配置里的类别顺序一致 class_map = {"person": 0, "helmet": 1, "car": 2} out_dir = "labels" os.makedirs(out_dir, exist_ok=True) for xml_file in os.listdir("Annotations"): if not xml_file.endswith(".xml"): continue tree = ElementTree.parse(os.path.join("Annotations", xml_file)) root = tree.getroot() size = root.find("size") img_w = int(size.find("width").text) img_h = int(size.find("height").text) img_id = xml_file[:-4] lines = [] for obj in root.findall("object"): name = obj.find("name").text if name not in class_map: print(f"[WARN] 类别 {name} 不在映射表中,跳过 {img_id}") continue box = obj.find("bndbox") xmin = int(box.find("xmin").text) ymin = int(box.find("ymin").text) xmax = int(box.find("xmax").text) ymax = int(box.find("ymax").text) x_center = (xmin + xmax) / 2.0 / img_w y_center = (ymin + ymax) / 2.0 / img_h w = (xmax - xmin) / img_w h = (ymax - ymin) / img_h lines.append(f"{class_map[name]} {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f}") with open(os.path.join(out_dir, img_id + ".txt"), "w") as f: f.write("\n".join(lines))

转换过程中最常见的错误是类别映射表顺序不一致。如果你的训练配置里class的顺序是["helmet", "person", "car"],那class_map里必须按这个顺序编码,一旦顺序对不上,模型学到的类别和实际标签会错位,而且训练不会报错,只能靠测试时发现。

6.2 VOC转COCO

COCO格式是JSON,结构比VOC复杂一些。转换的核心是把VOC里每个object的bndbox转换成COCO的segmentation和bbox字段。

COCO的bbox格式是[x, y, width, height],注意这里的x,y是左上角坐标,width和height是宽高。

转换关键代码如下:

import json coco_output = { "images": [], "annotations": [], "categories": [] } # 构建类别id映射 for idx, name in enumerate(class_map.keys()): coco_output["categories"].append({ "id": idx + 1, "name": name, "supercategory": "none" }) ann_id = 1 for img_id, image_id in enumerate(all_imgs): # 构造image节点 coco_output["images"].append({ "id": image_id, "file_name": f"{img_id}.jpg", "width": img_w, "height": img_h }) # 遍历每个object构造annotation节点 for obj in objs: coco_output["annotations"].append({ "id": ann_id, "image_id": image_id, "category_id": class_map[obj_name] + 1, "bbox": [xmin, ymin, xmax - xmin, ymax - ymin], "area": (xmax - xmin) * (ymax - ymin), "segmentation": [], "iscrowd": 0 }) ann_id += 1

每次转换后,都要写一个验证脚本,读回转换后的标注文件,画框检查是否与原始XML一致。这个步骤虽然繁琐,但能发现转换代码里的隐蔽bug。我见过有人转换时把xmin和ymin写反,导致所有标注框都错位了,模型训练完才发现,白费了一周时间。

7. 常见问题速查与避坑经验

以下这些问题,是我和身边朋友在做VOC数据集时真实踩过的坑,每条都对应一次熬夜排查的代价。

7.1 训练脚本报“No such file or directory”

多半是路径配置问题。检查VOC数据集的根目录是否和训练脚本中的voc_root一致。有些脚本默认路径是data/VOCdevkit/VOC2007,你的数据集可能放在别的地方。还有一个容易被忽略的点:脚本里的VOC年份,你建的是VOC2007,脚本却默认读VOC2012。

7.2 训练跑起来但loss不下降

先检查标注文件是否真的对应训练脚本读取的目录。我犯过一个低级错误:改了XML文件名,但忘了同步修改图片文件名,导致所有标注都对应到错误的图片上。训练脚本不会报错,但模型学的是完全错误的内容。

7.3 mAP计算的坑:difficult样本的影响

前面提到difficult字段会影响mAP计算。如果你发现训练完mAP很高,但实际检测效果差,检查一下标注文件中difficult=1的样本占比。如果占比超过10%,建议统一把difficult改成0,重新训练或重新评估。

7.4 LabelImg闪退或保存失败

大多是图片路径中带有中文或特殊字符导致的。把整个数据集路径改为纯英文,问题就能解决。这一点在Windows上尤其常见,因为Win10以后的系统默认用户目录可能包含中文用户名。

7.5 一张图上多个类别重叠怎么标

VOC允许两个不同类别的框部分重叠,比如“人”牵着“狗”,人和狗的框可以交叠。但如果两个同类别的框几乎完全重合,通常是重复标注了,需要删掉一个。这类问题用人工检查成本太高,我看过有人写抽检脚本,按IoU判断重合度,超过阈值就报警,算是比较高阶的做法。

8. 完整实操流程总结:从零到一

最后把整个流程串一遍,方便你对照执行。

第一阶段:准备工作(约1天)

  1. 明确项目需求:检测哪几个类别、应用场景是什么、目标最小尺寸大约多少
  2. 写入标注规范:框的边界规则、truncated和difficult的填写标准、类别名的拼写统一
  3. 创建目录结构:VOCdevkit/VOC2007下的JPEGImages、Annotations、ImageSets/Main

第二阶段:采集与清洗图片(约2-3天)

  1. 按需求收集图片,每个类别至少300张
  2. 统一格式为JPG,统一分辨率(或至少保证最小分辨率达标)
  3. 重命名为6位纯数字编号
  4. 剔除模糊、过度曝光、无目标的废图

第三阶段:画像标注(约一周)

  1. 安装LabelImg,配置类别列表(可以用predefined_classes.txt提前定义好类别,省去手动输入)
  2. 按规范逐张标注,每标完100张自查一遍
  3. 标注完成后全量扫描,查找孤儿文件、空标注、坐标越界

第四阶段:数据划分(约半天)

  1. 按类别分层划分train和val,比例7:3
  2. 生成train.txt和val.txt
  3. 校验两个txt中是否有空行、重复行、和图片文件名的错字

第五阶段:格式转换与验证(约1天)

  1. 根据训练框架的需要,选择保留VOC格式或转成YOLO/COCO格式
  2. 写转换脚本,转换后反向解析、画框对比,确认无误
  3. 用一个小模型(比如之前下载的预训练模型)跑一个快速训练few shot验证,确认数据能正常加载

整个流程走下来,一个人做2000张图、3个类别的数据集,大概需要两周时间。如果是团队协作,一周可以完成。前期规范和校验做到位,后期训练阶段会顺畅很多。

我个人在实际操作中的体会是:制作VOC数据集这件事,真正费时间的不是画框,而是前期的规范制定和后期的质量校验。很多项目工期延误,不是毁在标注速度上,而是毁在“标完了才发现规范不对,推倒重来”上。所以我的建议是:标注启动之前,先拿10张图试标,开会确认标准;标注中期再抽检一次,发现偏差及时纠正。这两步做好了,你的数据集中间就有保障,整个项目后面就少折腾。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进