3、数据集准备:LabelImg标注工具使用、数据集格式转换(VOC转YOLO)、数据集划分与配置文件编写
做目标检测,数据是地基。模型再强,数据不行也白搭。这一章咱们就聊聊怎么把原始图片变成YOLOv11能吃的数据。说白了,就是三件事:标注、格式转换、划分。
3.1 标注工具:LabelImg
LabelImg这个工具,我用了好多年了。它轻量、免费,而且支持VOC和YOLO两种格式输出。我个人习惯先用VOC格式标,后面再转,因为VOC的XML文件可读性更好,方便检查。
3.1.1 安装与启动
安装很简单,直接pip搞定:
pip install labelImg
labelImg
启动后界面很清爽。左边是图片列表,中间是画布,右边是标注框。嗯,这里要注意:默认打开的是图片文件夹,你需要先设置好标注文件的保存路径。
datasets/raw 文件夹,里面放原始图片。标注文件默认存到 datasets/Annotations。这样结构清晰,不容易乱。
3.1.2 标注流程
标注其实就几个步骤,我列一下:
- 打开图片目录 —— 选择存放图片的文件夹。
- 选择输出目录 —— 标注文件存哪。
- 创建矩形框 —— 按住鼠标左键,从目标左上角拖到右下角。
- 输入标签名 —— 比如
person、car。 - 保存 —— 快捷键
Ctrl+S,生成XML文件。 - 下一张 —— 快捷键
D。
快捷键能省不少时间。我刚开始用的时候,一张张点保存,效率极低。后来记住了快捷键,速度翻倍。
3.2 数据集格式转换:VOC转YOLO
VOC格式用XML存标注信息,YOLO格式用TXT存。YOLO的TXT文件每行代表一个目标,格式是:
class_id x_center y_center width height
注意,这里的坐标都是归一化后的,范围0~1。x_center和y_center是目标框中心点相对于图片宽高的比例,width和height是框的宽高相对于图片宽高的比例。
为什么会用归一化坐标?说白了,就是让模型不依赖图片的原始尺寸。你想想看,一张1920x1080的图和一张640x480的图,如果都用像素,模型就懵了。归一化之后,不管图片多大,坐标都在0~1之间,模型学起来更稳定。
3.2.1 转换脚本
我写了个转换脚本,你们可以直接用:
import xml.etree.ElementTree as ET
import os
def convert_voc_to_yolo(xml_file, classes, output_dir):
tree = ET.parse(xml_file)
root = tree.getroot()
# 获取图片尺寸
size = root.find('size')
width = int(size.find('width').text)
height = int(size.find('height').text)
# 生成对应的txt文件名
txt_file = os.path.join(output_dir,
os.path.splitext(os.path.basename(xml_file))[0] + '.txt')
with open(txt_file, 'w') as f:
for obj in root.iter('object'):
class_name = obj.find('name').text
if class_name not in classes:
continue
class_id = classes.index(class_name)
# 解析VOC的bbox坐标
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)
# 转换为YOLO格式
x_center = (xmin + xmax) / 2.0 / width
y_center = (ymin + ymax) / 2.0 / height
w = (xmax - xmin) / width
h = (ymax - ymin) / height
f.write(f"{class_id} {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f}\n")
# 使用示例
classes = ['person', 'car', 'bicycle'] # 你的类别列表
xml_dir = 'datasets/Annotations'
output_dir = 'datasets/labels'
os.makedirs(output_dir, exist_ok=True)
for xml_file in os.listdir(xml_dir):
if xml_file.endswith('.xml'):
convert_voc_to_yolo(os.path.join(xml_dir, xml_file), classes, output_dir)
classes 列表的顺序和YOLO配置文件中的一致。否则类别就全乱了。我见过有人把'person'放在索引0,配置文件里却写成了索引1,结果训练出来的模型把人都识别成了车。
3.3 数据集划分
数据标好了,格式也转了,接下来要分成训练集、验证集和测试集。比例一般是7:2:1或者8:1:1。我个人习惯用8:1:1,数据多的话可以适当调高验证集比例。
3.3.1 划分脚本
import os
import random
from shutil import copyfile
def split_dataset(image_dir, label_dir, output_dir,
train_ratio=0.8, val_ratio=0.1):
images = [f for f in os.listdir(image_dir)
if f.endswith(('.jpg', '.png', '.jpeg'))]
random.shuffle(images)
total = len(images)
train_end = int(total * train_ratio)
val_end = int(total * (train_ratio + val_ratio))
splits = {
'train': images[:train_end],
'val': images[train_end:val_end],
'test': images[val_end:]
}
for split_name, split_images in splits.items():
img_out = os.path.join(output_dir, 'images', split_name)
lbl_out = os.path.join(output_dir, 'labels', split_name)
os.makedirs(img_out, exist_ok=True)
os.makedirs(lbl_out, exist_ok=True)
for img in split_images:
copyfile(os.path.join(image_dir, img),
os.path.join(img_out, img))
label_file = os.path.splitext(img)[0] + '.txt'
label_path = os.path.join(label_dir, label_file)
if os.path.exists(label_path):
copyfile(label_path, os.path.join(lbl_out, label_file))
# 使用
split_dataset('datasets/images', 'datasets/labels', 'datasets/split')
3.4 配置文件编写
YOLOv11用YAML文件来配置数据集路径和类别信息。这个文件是训练时的入口,写错了模型就找不到数据。
3.4.1 标准YAML结构
# dataset.yaml
path: ./datasets/split # 数据集根目录
train: images/train # 训练集图片路径(相对于path)
val: images/val # 验证集图片路径
test: images/test # 测试集图片路径(可选)
# 类别信息
nc: 3 # 类别数量
names: ['person', 'car', 'bicycle'] # 类别名称列表
这里有个容易踩的坑:path 路径建议用路径或者相对于项目根目录的路径。我之前用相对路径,结果换了个目录运行训练脚本,路径就找不到了。后来统一用路径,再也没出过问题。
3.4.2 路径配置对比
| 配置方式 | 示例 | 优点 | 缺点 |
|---|---|---|---|
| 路径 | path: /home/user/project/datasets/split |
稳定,不依赖运行目录 | 换机器要改路径 |
| 相对路径 | path: ./datasets/split |
方便迁移 | 依赖运行目录,容易出错 |
| 软链接 | 创建软链接到固定位置 | 兼顾稳定和迁移 | 多一步操作 |
我个人推荐用路径,然后在项目里写个脚本自动生成YAML文件,这样换机器时只需要改一个变量。
3.5 本章知识体系
下面这张图把整个数据准备流程串起来了,你可以对照着看:
整个流程走下来,数据就准备好了。嗯,这里要提醒一句:标注质量直接决定模型上限。你花一天时间标数据,比花一周调参效果要好得多。我见过太多人急着训练,结果数据标得乱七八糟,最后模型根本没法用。