4、数据预处理:图像增强(Mosaic、MixUp)、归一化、锚框聚类(K-means)、数据加载器实现

好,咱们进入第四讲。数据预处理这块,说白了就是给模型「喂饭」——饭喂得好不好,直接决定模型能不能长壮实。我刚开始做YOLO项目时,总觉得数据预处理差不多就行,结果模型训练出来,检测效果惨不忍睹。后来才明白,这一步才是真正的「隐形冠军」。

4.1 图像增强:让模型见过世面

图像增强的目的很简单——让模型在有限的训练数据里,见过尽可能多的「世面」。你想想看,如果训练集里全是白天拍的图,模型到了晚上就抓瞎了。

4.1.1 Mosaic 增强

Mosaic 是 YOLOv4 开始引入的骚操作。它把四张图拼成一张大图,然后随机裁剪、缩放。这样做的好处很明显:

  • 小目标检测能力提升:四张图拼在一起,小目标出现的概率自然高了
  • 上下文信息丰富:模型能学到不同场景的组合
  • 相当于变相增大 batch size:一次处理四张图的信息

我在项目中遇到过一个问题:Mosaic 拼图时,如果四张图的亮度差异太大,模型会学到一些奇怪的「伪影」。后来我加了个亮度均衡处理,效果好了不少。

核心要点:Mosaic 的拼图比例建议 1:1:1:1,但实际项目中可以按场景重要性调整权重。比如夜间场景少,可以多拼几张夜间的图。

4.1.2 MixUp 增强

MixUp 就更「暴力」了——直接把两张图按比例混合。公式很简单:

new_image = alpha * image1 + (1 - alpha) * image2
new_label = alpha * label1 + (1 - alpha) * label2

alpha 通常取 0.5 左右。这样做的好处是让模型学会「模糊边界」——说白了就是让模型不那么自信,减少过拟合。

我的经验:MixUp 的 alpha 值不要固定,可以随机从 Beta 分布采样。我曾经试过固定 0.5,结果模型训练到后期 loss 死活降不下去。

4.2 归一化:把数据拉到同一个频道

归一化这步,很多人觉得简单,但坑不少。YOLO 里常用的归一化方式有两种:

归一化方式 公式 适用场景
Min-Max 归一化 (x - min) / (max - min) 像素值范围已知(0-255)
Z-score 归一化 (x - mean) / std 数据分布未知,需要标准化

YOLO 官方用的是 Min-Max 归一化,把像素值缩放到 [0, 1] 区间。但注意——千万别忘了除以 255。我见过有人直接拿原始像素值训练,结果 loss 直接爆炸。

避坑指南:我曾经在迁移学习时,忘了对验证集做同样的归一化操作。结果模型在训练集上表现很好,验证集上一塌糊涂。排查了半天才发现是归一化没对齐。

4.3 锚框聚类:K-means 的妙用

锚框(Anchor Box)是 YOLO 的核心设计之一。但锚框的大小怎么定?总不能拍脑袋吧。这时候 K-means 聚类就派上用场了。

具体做法:

  1. 从训练集中提取所有标注框的宽高
  2. 用 K-means 对这些宽高进行聚类
  3. 聚类中心就是我们要的锚框尺寸

这里有个细节——距离度量不能用欧氏距离。为什么?因为大框和小框的尺度差异太大,欧氏距离会让大框主导聚类结果。YOLO 用的是 IoU 距离

d(box, centroid) = 1 - IoU(box, centroid)

这样不管框的大小,都能公平竞争。

实战经验:K 值(锚框数量)怎么选?YOLOv5 默认是 9 个锚框,分 3 个尺度。我建议你先跑一次聚类,看看每个聚类中心的样本数量是否均衡。如果某个聚类只有几个样本,说明 K 值太大了。

4.4 数据加载器实现

数据加载器是连接硬盘和 GPU 的「传送带」。设计得好,训练效率翻倍;设计得不好,GPU 天天在「等饭」。我给大家一个核心思路:

  • 多进程加载:用 Python 的 multiprocessing,让 CPU 提前准备下一批数据
  • 缓存机制:把常用的增强结果缓存起来,避免重复计算
  • 动态增强:每个 epoch 用不同的随机种子,让模型每次看到的数据都不一样

代码实现上,我习惯用 PyTorch 的 DataLoader,配合自定义的 Dataset 类:

class YOLODataset(Dataset):
    def __init__(self, img_dir, label_dir, transforms=None):
        self.img_dir = img_dir
        self.label_dir = label_dir
        self.transforms = transforms
        self.imgs = list(Path(img_dir).glob('*.jpg'))
    
    def __getitem__(self, idx):
        img = cv2.imread(str(self.imgs[idx]))
        label = self.parse_label(self.imgs[idx])
        
        # 应用增强
        if self.transforms:
            img, label = self.transforms(img, label)
        
        # 归一化
        img = img / 255.0
        
        return img, label

性能优化:我建议把 num_workers 设为 CPU 核心数的一半。设太多反而会因为线程切换导致性能下降。另外,pin_memory=True 能加速 GPU 数据传输。

4.5 总结

数据预处理这块,说白了就是「磨刀不误砍柴工」。Mosaic 和 MixUp 让模型见过更多场景,归一化让数据更规整,K-means 聚类让锚框更贴合数据分布,数据加载器让训练更高效。

嗯,这四步走完,你的数据就准备好了。下一章咱们聊聊模型结构——YOLO 的 backbone 到底是怎么设计的。