4、数据集准备:从原始图片到YOLO能吃的数据

做目标检测,模型选得再好,训练代码写得再溜,如果数据这关没过,一切都是白搭。我见过太多人花大把时间调参,结果发现是标注格式错了——那种挫败感,啧。

这一章,咱们就把数据准备这条流水线彻底打通。从标注工具怎么用,到COCO和YOLO格式到底差在哪,再到数据增强怎么玩才有效。你想想看,模型看到的每一张图,都是从这里开始的。

数据集准备全流程 原始图片 LabelImg标注 格式转换 数据增强 JPEG/PNG XML (Pascal VOC) JSON / TXT 增强后数据集 关键:标注质量 > 标注数量。100张高质量标注,胜过1000张粗标数据。 格式转换是最大坑点,坐标归一化、类别索引从0开始,错一个全废。

4.1 LabelImg标注工具——别嫌它土,够用就行

LabelImg这工具,界面说不上好看,但胜在稳定。我用了快五年,从没出过标注错位的问题。相比之下,有些在线标注平台花里胡哨的,导出格式反而各种坑。

安装与启动

# 方式一:pip安装(推荐)
pip install labelImg
labelImg

# 方式二:源码运行
git clone https://github.com/tzutalin/labelImg
cd labelImg
python labelImg.py
我的习惯:用pip安装,然后在桌面建个快捷方式。标注是个重复劳动,启动越方便越好。

标注流程(三步走)

  1. 打开图片目录——左侧自动显示图片列表,支持翻页
  2. 画框——按住鼠标拖拽,框住目标物体
  3. 打标签——输入类别名称,或者从预定义列表选

快捷键一定要记住:W 画框,A/D 翻页,Ctrl+S 保存。我刚开始标注时不知道快捷键,一个个点按钮,300张图标了一整天——后来用快捷键,半天搞定。

曾经踩过的坑:标注完直接关软件,没保存。结果XML文件是空的,白干两小时。记住:每标完一张图就Ctrl+S,养成肌肉记忆。

4.2 COCO数据集格式——业界标杆,必须看懂

COCO格式说白了就是一个大JSON文件,里面装了三块核心信息。你不需要背下来,但得知道去哪查、怎么改。

字段 作用 说明
images 图片信息列表 每张图有id、宽高、文件名
annotations 标注信息列表 每个框有image_id、bbox[x,y,w,h]、category_id
categories 类别字典 id从1开始,name是类别名
{
  "images": [{"id": 1, "file_name": "001.jpg", "width": 640, "height": 480}],
  "annotations": [{
    "id": 1, "image_id": 1,
    "bbox": [100, 150, 200, 300],  // x, y, w, h
    "category_id": 1
  }],
  "categories": [{"id": 1, "name": "person"}]
}

关键点:COCO的bbox是[x, y, width, height],左上角坐标+宽高。很多人转YOLO时在这里翻车——YOLO要的是中心点坐标+宽高,而且必须归一化。

4.3 YOLO格式转换——最容翻车的环节

YOLO的标注格式极其简单:每个图片对应一个TXT文件,每行一个目标。但越简单的东西,越容易在细节上出错。

# YOLO格式:class_id center_x center_y width height
# 所有坐标都是归一化的(除以图片宽高)
0 0.5 0.5 0.3 0.4
1 0.2 0.8 0.1 0.15

COCO转YOLO的核心代码

import json
import os

def coco_to_yolo(coco_json_path, output_dir):
    with open(coco_json_path, 'r') as f:
        coco = json.load(f)

    # 建立image_id到文件名的映射
    img_map = {img['id']: img for img in coco['images']}

    for ann in coco['annotations']:
        img_info = img_map[ann['image_id']]
        img_w, img_h = img_info['width'], img_info['height']

        # COCO: [x, y, w, h] → YOLO: [cx, cy, w, h] 归一化
        x, y, w, h = ann['bbox']
        cx = (x + w / 2) / img_w
        cy = (y + h / 2) / img_h
        nw = w / img_w
        nh = h / img_h

        # 写入对应TXT文件
        txt_name = img_info['file_name'].replace('.jpg', '.txt')
        with open(os.path.join(output_dir, txt_name), 'a') as f:
            f.write(f"{ann['category_id']} {cx:.6f} {cy:.6f} {nw:.6f} {nh:.6f}\n")
我曾经犯过的错:类别ID没减1。COCO的category_id从1开始,但YOLO要求从0开始。结果训练出来的模型,检测到人输出的是类别1,但实际类别0才是人——整个模型全废,排查了两天才发现。

4.4 数据增强策略——别让模型死记硬背

数据增强不是为了把1张图变成10张图,而是为了让模型学会「不变性」。说白了,就是让模型知道:不管物体在左边还是右边,不管光线亮还是暗,它都是同一个东西。

我常用的增强组合

增强方式 适用场景 注意点
水平翻转 几乎所有场景 文字、对称物体慎用
随机裁剪 小目标检测 别裁掉目标主体
色彩抖动 光照变化大的场景 HSV范围别太大
马赛克增强 YOLOv4+常用 小batch时效果明显

用OpenCV实现基础增强

import cv2
import numpy as np

def augment_image(image, bboxes):
    """bboxes: [[cx, cy, w, h], ...] 归一化坐标"""
    h, w = image.shape[:2]

    # 1. 水平翻转(50%概率)
    if np.random.rand() > 0.5:
        image = cv2.flip(image, 1)
        # 坐标变换:cx = 1 - cx
        bboxes[:, 0] = 1 - bboxes[:, 0]

    # 2. 亮度调整
    value = np.random.randint(-30, 30)
    hsv = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)
    hsv[:, :, 2] = np.clip(hsv[:, :, 2].astype(int) + value, 0, 255).astype(np.uint8)
    image = cv2.cvtColor(hsv, cv2.COLOR_HSV2BGR)

    return image, bboxes
我的经验:增强不是越多越好。我见过有人把亮度、对比度、模糊、噪声全加上,结果模型在验证集上反而掉点。一般选3-4种,每种概率0.3-0.5就够了。另外,标注框一定要跟着图一起变换,这个最容易忘。

4.5 数据集目录结构——规范能救命

项目做多了你就会发现,目录结构乱的人,最后自己都找不到数据在哪。我有一套固定的组织方式,用了好几年,分享给你:

dataset/
├── images/
│   ├── train/    # 训练图片
│   ├── val/      # 验证图片
│   └── test/     # 测试图片
├── labels/
│   ├── train/    # 训练标注(TXT)
│   ├── val/      # 验证标注
│   └── test/     # 测试标注
├── data.yaml     # 类别配置文件
└── classes.txt   # 类别列表
# data.yaml 内容
train: ./images/train
val: ./images/val
nc: 2  # 类别数量
names: ['person', 'car']

一句话总结:标注要准,格式要对,增强要稳,目录要清。这四点做到位,模型训练就成功了一半。