4. 数据标注:LabelImg安装与使用、标注规范与技巧、标注格式转换(VOC转YOLO)、数据集划分
数据标注这件事,说实话,是YOLO实战里最枯燥但也是最关键的一环。模型能不能收敛、检测精度高不高,很大程度上取决于你标注的质量。我见过太多同学花大把时间调参,结果模型效果差,最后发现是标注数据出了问题——框歪了、类别标错了、漏标了目标。嗯,咱们这节课就把这些坑都填上。
4.1 LabelImg 安装与使用
LabelImg 是目前最主流的图像标注工具之一,支持 VOC 和 YOLO 两种格式。我个人习惯用 Python 版,跨平台兼容性好,更新也及时。
4.1.1 安装方式
我推荐直接用 pip 安装,干净利落:
pip install labelImg
labelImg
如果遇到 PyQt5 依赖问题,可以试试:
pip install labelImg pyqt5
labelImg
4.1.2 界面与基本操作
启动后界面很简洁,左边是工具栏,中间是图像显示区,右边是标签列表。核心操作就三步:
- 打开图像目录:点击 "Open Dir" 选择存放图片的文件夹
- 创建矩形框:按
w键进入标注模式,按住鼠标左键拖拽 - 输入标签名:松开鼠标后弹出对话框,输入类别名称(如 car、person)
常用快捷键我列在下面,记熟了效率翻倍:
| 快捷键 | 功能 |
|---|---|
w |
创建矩形框 |
a / d |
上一张 / 下一张图片 |
Ctrl + s |
保存当前标注 |
Ctrl + u |
加载下一张未标注图片 |
Delete |
删除选中的标注框 |
Ctrl + z |
撤销上一步操作 |
4.2 标注规范与技巧
标注不是随便画个框就完事了。这里面的门道,我踩过不少坑,总结了几条硬性规范。
4.2.1 标注基本原则
- 紧贴目标边缘:矩形框要刚好包住目标,不要留太多背景,也不要切掉目标的关键部位
- 遮挡处理:目标被遮挡超过 50% 时,建议不标注;轻微遮挡可以标注,但框要尽量贴合可见部分
- 边界目标:目标在图像边缘被切掉超过 30%,不标注;只切掉一点点,正常标注
- 类别一致性:同一个类别在不同图片中的标注标准要统一,比如 "car" 不能有的标 "vehicle"
4.2.2 实战技巧
标注效率和质量可以兼得,关键是用对方法:
- 先标难样本:把光照差、遮挡多、角度刁钻的图片先标出来,让模型优先学习困难场景
- 使用 "Verify" 功能:LabelImg 支持标注验证,标完一轮后可以快速复查
- 多人标注统一规范:团队协作时,一定要写一份标注规范文档,贴在项目目录里
- 定期抽检:每标注 500 张图片,随机抽 50 张检查标注质量
4.3 标注格式转换:VOC 转 YOLO
LabelImg 默认保存为 VOC 格式(XML 文件),但 YOLO 训练需要的是 TXT 格式。这两种格式的核心区别在于坐标表示方式:
| 对比项 | VOC 格式 | YOLO 格式 |
|---|---|---|
| 坐标类型 | 左上角 (xmin, ymin) + 右下角 (xmax, ymax) | 中心点 (x_center, y_center) + 宽高 (width, height) |
| 坐标值 | 像素值 | 归一化到 [0, 1] |
| 文件格式 | XML | TXT(每行一个目标) |
| 文件命名 | 与图片同名,扩展名为 .xml | 与图片同名,扩展名为 .txt |
转换公式其实很简单,我手写了一个脚本,你们可以直接用:
import xml.etree.ElementTree as ET
import os
def voc_to_yolo(xml_file, classes, output_dir):
tree = ET.parse(xml_file)
root = tree.getroot()
# 获取图片尺寸
img_width = int(root.find('size/width').text)
img_height = int(root.find('size/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.findall('object'):
class_name = obj.find('name').text
if class_name not in classes:
continue
class_id = classes.index(class_name)
# 提取 VOC 坐标
xmin = int(obj.find('bndbox/xmin').text)
ymin = int(obj.find('bndbox/ymin').text)
xmax = int(obj.find('bndbox/xmax').text)
ymax = int(obj.find('bndbox/ymax').text)
# 转换为 YOLO 格式
x_center = (xmin + xmax) / 2.0 / img_width
y_center = (ymin + ymax) / 2.0 / img_height
width = (xmax - xmin) / img_width
height = (ymax - ymin) / img_height
f.write(f"{class_id} {x_center:.6f} {y_center:.6f} {width:.6f} {height:.6f}\n")
# 使用示例
classes = ['car', 'person', 'bicycle'] # 你的类别列表
xml_dir = 'Annotations/'
output_dir = 'labels/'
for xml_file in os.listdir(xml_dir):
if xml_file.endswith('.xml'):
voc_to_yolo(os.path.join(xml_dir, xml_file), classes, output_dir)
4.4 数据集划分
数据集划分看似简单,但划分方式直接影响模型评估的可靠性。我见过有人随机划分后,训练集和验证集里出现了同一张图片的不同版本,导致验证指标虚高。
4.4.1 划分比例与策略
对于车辆行人检测这类任务,我推荐以下划分方案:
| 数据集 | 比例 | 用途 |
|---|---|---|
| 训练集 | 70% | 模型学习参数 |
| 验证集 | 15% | 调参和模型选择 |
| 测试集 | 15% | 最终性能评估 |
划分时要特别注意:
- 按场景划分:同一段视频的连续帧不要同时出现在训练集和验证集中
- 保持类别分布:划分后检查每个类别在三个集合中的比例是否接近原始分布
- 固定随机种子:用
random.seed(42)保证每次划分结果一致
4.4.2 自动化划分脚本
我写了一个划分脚本,支持按文件列表或按目录划分:
import os
import random
import shutil
def split_dataset(image_dir, label_dir, output_dir,
train_ratio=0.7, val_ratio=0.15, seed=42):
random.seed(seed)
# 获取所有图片文件
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 = train_end + int(total * val_ratio)
# 划分
splits = {
'train': images[:train_end],
'val': images[train_end:val_end],
'test': images[val_end:]
}
# 创建目录并复制文件
for split_name, file_list in splits.items():
img_out = os.path.join(output_dir, split_name, 'images')
lbl_out = os.path.join(output_dir, split_name, 'labels')
os.makedirs(img_out, exist_ok=True)
os.makedirs(lbl_out, exist_ok=True)
for img_file in file_list:
# 复制图片
shutil.copy(os.path.join(image_dir, img_file),
os.path.join(img_out, img_file))
# 复制对应的标签文件
label_file = os.path.splitext(img_file)[0] + '.txt'
if os.path.exists(os.path.join(label_dir, label_file)):
shutil.copy(os.path.join(label_dir, label_file),
os.path.join(lbl_out, label_file))
print(f"划分完成:训练集 {len(splits['train'])} 张,"
f"验证集 {len(splits['val'])} 张,"
f"测试集 {len(splits['test'])} 张")
# 使用示例
split_dataset('images/', 'labels/', 'dataset/')
4.5 本章知识体系
下面这张图把数据标注的完整流程串起来了,从原始图片到最终可训练的数据集:
数据标注是整个检测项目的基石。你想想看,模型再强大,喂进去的数据是错的,结果能对吗?我建议你在正式标注前,先拿 50 张图片练手,熟悉流程后再批量操作。标注完成后,用转换脚本统一处理,再用划分脚本生成训练集、验证集和测试集。每一步都检查一遍,后面训练的时候就能省很多心。