第三章:数据集准备——从标注到转换的完整流程
说实话,做目标检测最磨人的不是调模型,而是准备数据。我见过太多同学花两周调参,结果发现是标注格式不对,白忙活一场。这一章,咱们就把数据准备这件事彻底讲透。
3.1 LabelImg 标注工具使用
LabelImg 是我个人最常用的标注工具。它轻量、开源,而且支持 YOLO 和 Pascal VOC 两种格式。说白了,它就是给图片画框、打标签的。
安装与启动
安装其实很简单。我建议直接用 pip 装:
pip install labelImg
labelImg
如果你在 macOS 上遇到权限问题,加个 sudo 就行。Windows 用户记得用管理员模式打开命令行。嗯,这里要注意:第一次启动可能会有点慢,别急。
标注流程
打开软件后,你会看到一个简洁的界面。我个人习惯按以下步骤操作:
- 打开图片目录:点击 "Open Dir" 选择你的图片文件夹
- 选择保存格式:点击 "Change Save Dir" 设置标注文件保存位置
- 开始标注:按 W 键进入画框模式,拖拽鼠标框选目标
- 输入标签:弹窗里输入类别名称,比如 "person"、"car"
- 保存:按 Ctrl+S 保存当前标注
标注注意事项
我在项目中遇到过不少标注翻车的情况。这里列几个避坑点:
- 框要贴边:标注框必须紧贴目标边缘,留白太多会影响模型学习
- 遮挡处理:目标被遮挡时,只标注可见部分,不要脑补完整轮廓
- 小目标:小于 10x10 像素的目标,我建议直接放弃标注,模型基本学不到
- 类别统一:同一个类别在不同图片里必须用完全相同的标签名,大小写都不能错
3.2 COCO 数据集格式详解
COCO 格式是目前目标检测领域最通用的数据格式。YOLOv10 原生支持 COCO 格式,所以理解它很重要。
COCO 的 JSON 结构
COCO 标注文件是一个 JSON,核心包含三个字段:
{
"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"
}
]
}
你想想看,这个结构其实很清晰:images 记录图片信息,annotations 记录标注框,categories 记录类别映射。说白了就是三张表。
bbox 格式说明
COCO 的 bbox 是 [x, y, width, height] 格式,注意不是 YOLO 的 [x_center, y_center, width, height]。这里容易搞混,我刚开始也犯过这个错。
| 格式 | 表示方式 | 示例 |
|---|---|---|
| COCO | [左上角x, 左上角y, 宽, 高] | [100, 150, 200, 300] |
| YOLO | [中心x, 中心y, 宽, 高] | [0.3, 0.4, 0.2, 0.3] |
3.3 自定义数据集转换脚本
实际项目中,我们很少直接用 COCO 格式的数据。更多时候是从 LabelImg 标注的 XML 或 TXT 文件转成 COCO JSON。我写了一个通用的转换脚本,分享给你。
从 LabelImg XML 转 COCO JSON
LabelImg 默认保存为 Pascal VOC 格式的 XML 文件。下面这个脚本可以把 XML 批量转成 COCO 格式:
import xml.etree.ElementTree as ET
import json
import os
from glob import glob
def xml_to_coco(xml_dir, output_json):
images = []
annotations = []
categories = []
cat_id_map = {}
ann_id = 1
# 先收集所有类别
for xml_file in glob(os.path.join(xml_dir, "*.xml")):
tree = ET.parse(xml_file)
root = tree.getroot()
for obj in root.findall("object"):
name = obj.find("name").text
if name not in cat_id_map:
cat_id = len(categories) + 1
cat_id_map[name] = cat_id
categories.append({"id": cat_id, "name": name})
# 处理每张图片
for img_id, xml_file in enumerate(glob(os.path.join(xml_dir, "*.xml")), 1):
tree = ET.parse(xml_file)
root = tree.getroot()
# 图片信息
filename = root.find("filename").text
size = root.find("size")
width = int(size.find("width").text)
height = int(size.find("height").text)
images.append({
"id": img_id,
"file_name": filename,
"width": width,
"height": height
})
# 标注信息
for obj in root.findall("object"):
name = obj.find("name").text
bbox = obj.find("bndbox")
xmin = int(bbox.find("xmin").text)
ymin = int(bbox.find("ymin").text)
xmax = int(bbox.find("xmax").text)
ymax = int(bbox.find("ymax").text)
# 转成 COCO 格式
w = xmax - xmin
h = ymax - ymin
annotations.append({
"id": ann_id,
"image_id": img_id,
"category_id": cat_id_map[name],
"bbox": [xmin, ymin, w, h],
"area": w * h,
"iscrowd": 0
})
ann_id += 1
# 保存
coco_data = {
"images": images,
"annotations": annotations,
"categories": categories
}
with open(output_json, "w") as f:
json.dump(coco_data, f, indent=2)
print(f"转换完成,共 {len(images)} 张图片,{len(annotations)} 个标注")
# 使用示例
xml_to_coco("path/to/xml_folder", "annotations.json")
验证转换结果
转换完成后,我习惯用下面这段代码快速验证:
import json
with open("annotations.json") as f:
data = json.load(f)
print(f"图片数量: {len(data['images'])}")
print(f"标注数量: {len(data['annotations'])}")
print(f"类别数量: {len(data['categories'])}")
print("类别列表:", [c["name"] for c in data["categories"]])
如果数据量对不上,多半是 XML 解析出了问题。我建议先拿一两张图手动验证,确认没问题再批量处理。
3.4 数据划分与目录结构
数据准备好后,还需要按训练集、验证集、测试集划分。YOLOv10 要求的目录结构是这样的:
dataset/
├── train/
│ ├── images/
│ │ ├── 000001.jpg
│ │ └── ...
│ └── labels/
│ ├── 000001.txt
│ └── ...
├── val/
│ ├── images/
│ └── labels/
└── test/
├── images/
└── labels/
我个人习惯按 8:1:1 的比例划分。注意:划分时要保证每个类别在三个集合中都有分布,不然验证集里缺了某个类别,评估指标会出问题。
好了,数据准备这块就讲到这里。下一章咱们开始搭建 YOLOv10 的训练环境,到时候你就能用今天准备的数据跑起来了。