4、数据集准备与标注:常见数据集格式(COCO、VOC、YOLO)、LabelImg/LabelStudio安装与使用、数据清洗与质量检查、数据集划分(train/val/test)
好,咱们进入第四讲。说实话,数据集准备这一步,往往决定了你整个项目的天花板。模型再牛,喂进去的数据是脏的、乱的,结果一定好不到哪去。我自己踩过这个坑,所以这一章咱们好好聊聊。
4.1 常见数据集格式:COCO、VOC、YOLO
做目标检测,你绕不开这三种格式。它们就像三种不同的语言,但表达的是同一件事——告诉模型「这张图里有什么,在哪」。我个人的习惯是,先搞清楚它们长什么样,再决定用哪个。
4.1.1 COCO 格式(JSON)
COCO 格式是目前学术界和工业界最通用的格式之一。它用 JSON 文件存储所有标注信息。结构上分为几个大块:images、annotations、categories。
举个例子,一张图片的标注大概长这样:
{
"images": [
{
"id": 1,
"file_name": "000001.jpg",
"width": 640,
"height": 480
}
],
"annotations": [
{
"id": 1,
"image_id": 1,
"category_id": 1,
"bbox": [100, 150, 200, 300],
"area": 60000,
"iscrowd": 0
}
],
"categories": [
{
"id": 1,
"name": "person"
}
]
}
注意这里的 bbox 是 [x, y, width, height] 格式。我刚开始用的时候,老把它和 VOC 的格式搞混,后来吃了亏才记住。
area 字段是否准确。有些工具自动生成的 area 可能和 bbox 不匹配,训练时会报奇怪的错误。
4.1.2 VOC 格式(XML)
VOC 格式是 PASCAL VOC 挑战赛用的格式。每个图片对应一个同名的 XML 文件。结构清晰,但文件数量多。
<annotation>
<folder>VOC2007</folder>
<filename>000001.jpg</filename>
<size>
<width>640</width>
<height>480</height>
<depth>3</depth>
</size>
<object>
<name>person</name>
<bndbox>
<xmin>100</xmin>
<ymin>150</ymin>
<xmax>300</xmax>
<ymax>450</ymax>
</bndbox>
</object>
</annotation>
VOC 的 bndbox 是 [xmin, ymin, xmax, ymax] 格式。说白了,就是左上角和右下角的坐标。我个人觉得这种格式更直观,但文件多了管理起来麻烦。
4.1.3 YOLO 格式(TXT)
YOLO 格式最简单,每个图片对应一个同名的 TXT 文件。每行代表一个目标:
class_id x_center y_center width height
注意,这里的坐标都是归一化的(除以图片宽高)。举个例子:
0 0.5 0.5 0.3 0.4
1 0.2 0.3 0.1 0.2
YOLO 格式的好处是轻量、解析快。但坏处是,你没法直接看出坐标对应的像素值,调试时不太方便。我曾经在转换格式时,因为归一化除错了分母,导致模型训练出来全在乱检测。
width 和 height 写反了,结果模型训练了三天,检测框全是扁的。所以,转换格式后一定要可视化检查一遍。
4.2 LabelImg / LabelStudio 安装与使用
标注工具是数据准备的核心。我个人常用两个:LabelImg 和 LabelStudio。前者轻量,适合快速标注;后者功能强大,支持多种任务。
4.2.1 LabelImg 安装与使用
LabelImg 是纯 Python 写的,安装很简单:
pip install labelImg
labelImg
启动后,界面很简洁。左边是图片列表,中间是标注区域,右边是标签列表。操作流程:
- 点击「Open Dir」选择图片文件夹
- 点击「Change Save Dir」选择标注文件保存位置
- 按
w键开始画框 - 输入标签名称,按回车确认
- 按
Ctrl+S保存
嗯,这里要注意:LabelImg 默认保存为 VOC 格式(XML)。如果你想保存为 YOLO 格式,需要在菜单栏选择 View → Auto Save Mode,然后点击 PascalVOC 切换为 YOLO。
4.2.2 LabelStudio 安装与使用
LabelStudio 更现代化,支持图像、文本、音频等多种标注任务。安装方式:
pip install label-studio
label-studio start
启动后,浏览器打开 http://localhost:8080。注册账号,创建项目,导入图片,然后配置标注模板。对于目标检测,选择「Object Detection with Bounding Boxes」模板。
LabelStudio 的优势在于:
- 支持多人协作标注
- 可以导出多种格式(COCO、VOC、YOLO)
- 有自动标注功能(基于预训练模型)
但说实话,LabelStudio 有点重,如果只是几十张图的小项目,我建议用 LabelImg 更快。
4.3 数据清洗与质量检查
数据标注完了,别急着训练。先做数据清洗。这一步我吃过不少亏,现在每次都会严格执行。
4.3.1 常见的数据问题
| 问题类型 | 表现 | 解决方法 |
|---|---|---|
| 标注框超出图片边界 | bbox 的 xmax 或 ymax 大于图片宽高 | 裁剪或修正坐标 |
| 标注框面积为零 | width 或 height 为 0 | 删除该标注 |
| 标签名不一致 | 同一物体用了不同标签(如 "person" 和 "Person") | 统一标签名 |
| 图片损坏 | 图片无法打开或格式错误 | 删除或替换图片 |
我写过一个简单的清洗脚本,检查标注文件是否合法:
import os
import json
def check_coco_annotations(json_path, img_dir):
with open(json_path, 'r') as f:
data = json.load(f)
for ann in data['annotations']:
bbox = ann['bbox']
if bbox[2] <= 0 or bbox[3] <= 0:
print(f"Error: annotation {ann['id']} has zero area")
# 检查是否超出图片边界
img_info = [img for img in data['images'] if img['id'] == ann['image_id']][0]
if bbox[0] + bbox[2] > img_info['width'] or bbox[1] + bbox[3] > img_info['height']:
print(f"Warning: annotation {ann['id']} exceeds image boundary")
4.3.2 质量检查方法
清洗完数据后,我建议做两件事:
- 可视化检查: 随机抽取 10%-20% 的图片,把标注框画出来看看。有没有漏标?有没有错标?
- 统计检查: 统计每个类别的样本数量、标注框大小分布。如果某个类别样本太少,可能需要补充数据。
你想想看,如果某个类别只有 5 个样本,模型怎么可能学得好?
4.4 数据集划分:train / val / test
数据准备好了,接下来要划分数据集。这是训练前的最后一步,也是很多人容易忽视的一步。
4.4.1 划分比例
常见的划分比例是:
- 训练集(train): 70%-80%
- 验证集(val): 10%-15%
- 测试集(test): 10%-15%
我个人习惯用 70% / 15% / 15%。如果数据量很小(比如不到 1000 张),我会用 80% / 10% / 10%。
4.4.2 划分方法
划分时要注意两点:
- 随机划分: 用随机数打乱数据,避免数据分布不均。
- 分层划分: 如果类别分布不均衡,要保证每个类别在 train/val/test 中的比例一致。
下面是一个简单的划分脚本:
import os
import random
import shutil
def split_dataset(img_dir, label_dir, output_dir, train_ratio=0.7, val_ratio=0.15):
images = os.listdir(img_dir)
random.shuffle(images)
total = len(images)
train_end = int(total * train_ratio)
val_end = int(total * (train_ratio + val_ratio))
train_imgs = images[:train_end]
val_imgs = images[train_end:val_end]
test_imgs = images[val_end:]
for split, imgs in [('train', train_imgs), ('val', val_imgs), ('test', test_imgs)]:
os.makedirs(f'{output_dir}/{split}/images', exist_ok=True)
os.makedirs(f'{output_dir}/{split}/labels', exist_ok=True)
for img in imgs:
shutil.copy(f'{img_dir}/{img}', f'{output_dir}/{split}/images/{img}')
label_file = img.replace('.jpg', '.txt')
if os.path.exists(f'{label_dir}/{label_file}'):
shutil.copy(f'{label_dir}/{label_file}', f'{output_dir}/{split}/labels/{label_file}')
4.4.3 划分后的目录结构
划分完成后,目录结构应该是这样的:
dataset/
├── train/
│ ├── images/
│ │ ├── 000001.jpg
│ │ └── ...
│ └── labels/
│ ├── 000001.txt
│ └── ...
├── val/
│ ├── images/
│ └── labels/
└── test/
├── images/
└── labels/
这种结构,YOLO 可以直接读取。如果你用 COCO 格式,需要把标注合并成一个 JSON 文件。
好了,这一章的内容就到这里。数据集准备是基础,但也是关键。下一章,我们会开始搭建训练环境,真正跑起来。