上周在部署YOLOv11到边缘设备时遇到个头疼的问题:模型在测试集上mAP挺漂亮,一到真实场景就漏检小目标。查了一轮发现,训练数据里小目标样本太少,模型没见过“世面”。这让我重新审视数据增强——不是简单旋转翻转就能解决的,得用点“猛药”。

一、Mosaic:把四张图拼成一张的“全景训练”

Mosaic说穿了就是数据穷人的福音。你只有640x640的输入尺寸,但想让模型学会看大场景?直接把四张训练图随机裁剪拼接成一张,相当于让模型一次看四个样本。

def mosaic_augmentation(images, labels, size=640):
    # 初始化输出画布
    output_img = np.full((size, size, 3), 114, dtype=np.uint8)
    output_labels = []
    
    # 随机选中心点
    cx, cy = random.randint(size//4, 3*size//4), random.randint(size//4, 3*size//4)
    
    for i in range(4):
        img, lbl = images[i], labels[i]
        h, w = img.shape[:2]
        
        # 确定当前图片在画布中的位置
        if i == 0:  # 左上
            x1a, y1a, x2a, y2a = 0, 0, cx, cy
            x1b, y1b, x2b, y2b = w-cx, h-cy, w, h  # 从原图右下角裁剪
        elif i == 1:  # 右上
            x1a, y1a, x2a, y2a = cx, 0, size, cy
            x1b, y1b, x2b, y2b = 0, h-cy, w-cx, h
        # ... 其他两个象限类似
        
        # 贴图到画布
        output_img[y1a:y2a, x1a:x2a] = img[y1b:y2b, x1b:x2b]
        
        # 调整标注框坐标(这里容易出错!)
        for label in lbl:
            x, y, w_box, h_box = label[1:]
            # 坐标转换要分两步:先裁剪偏移,再平移到新位置
            new_x = (x * w - x1b) / (x2b - x1b) * (x2a - x1a) + x1a
            new_y = (y * h - y1b) / (y2b - y1b) * (y2a - y1a) + y1a
            # 记得归一化到0-1
            new_x /= size
            new_y /= size
            output_labels.append([label[0], new_x, new_y, w_box, h_box])
    
    return output_img, output_labels

关键点:坐标转换是Mosaic最容易翻车的地方。我建议先画个坐标系在纸上推演一遍,别直接抄代码。另外,四张图拼接后可能出现大量小目标,正负样本比例会变,可以适当调整anchor匹配阈值。

二、MixUp:两张图线性混合的“柔和增强”

MixUp的思路很数学——把两张图按比例α混合,标签也按同样比例混合。这强迫模型学习更平滑的决策边界。

def mixup(img1, labels1, img2, labels2, alpha=0.5):
    # alpha从Beta分布采样,别固定死
    lam = np.random.beta(alpha, alpha) if alpha > 0 else 0.5
    
    # 混合图像(注意数据类型转换)
    mixed_img = (img1.astype(np.float32) * lam + 
                 img2.astype(np.float32) * (1 - lam)).astype(np.uint8)
    
    # 混合标签(YOLO格式需要特殊处理)
    mixed_labels = []
    for label in labels1:
        mixed_labels.append([label[0], label[1], label[2], label[3], label[4], lam])  # 最后加个权重
    for label in labels2:
        mixed_labels.append([label[0], label[1], label[2], label[3], label[4], 1-lam])
    
    return mixed_img, mixed_labels

坑点:直接混合两张图的边界框会破坏YOLO的标签格式。我的做法是保留所有框但增加一个混合权重,计算loss时按权重加权。也有团队直接复制所有框不做混合,但这样会让模型困惑——一个位置对应两个物体?

三、CutMix:挖掉一块补上另一张的“局部移植”

CutMix更激进:随机挖掉一张图的部分区域,用另一张图的对应区域补上。标签按面积比例混合。

def cutmix(img1, labels1, img2, labels2, beta=1.0):
    h, w = img1.shape[:2]
    
    # 随机生成裁剪区域
    lam = np.random.beta(beta, beta)
    cut_ratio = np.sqrt(1 - lam)  # 保持面积比例
    cut_w = int(w * cut_ratio)
    cut_h = int(h * cut_ratio)
    
    cx = np.random.randint(w)
    cy = np.random.randint(h)
    
    x1 = max(0, cx - cut_w // 2)
    y1 = max(0, cy - cut_h // 2)
    x2 = min(w, cx + cut_w // 2)
    y2 = min(h, cy + cut_h // 2)
    
    # 执行“移植手术”
    mixed_img = img1.copy()
    mixed_img[y1:y2, x1:x2] = img2[y1:y2, x1:x2]
    
    # 计算实际混合比例(可能因为边界裁剪而改变)
    actual_lam = 1 - ((x2 - x1) * (y2 - y1) / (w * h))
    
    # 合并标签并调整框的位置
    mixed_labels = []
    for label in labels1:
        # 检查框是否在裁剪区域内
        x_center, y_center = label[1] * w, label[2] * h
        if not (x1 <= x_center <= x2 and y1 <= y_center <= y2):
            mixed_labels.append(label)  # 保留完整框
    
    for label in labels2:
        # 只保留在裁剪区域内的框
        x_center, y_center = label[1] * w, label[2] * h
        if x1 <= x_center <= x2 and y1 <= y_center <= y2:
            # 坐标要转换到新图像的位置
            new_label = label.copy()
            new_label[1] = (x_center - x1) / w  # 注意这里!x1是偏移量
            new_label[2] = (y_center - y1) / h
            mixed_labels.append(new_label)
    
    return mixed_img, mixed_labels, actual_lam

经验:CutMix后标签处理要格外小心。原图的框如果被切到,应该直接丢弃(因为不完整了);补丁图的框只有完全在裁剪区内才保留。这个逻辑和实际业务场景强相关——如果你的目标经常被遮挡,可以适当放宽保留条件。

四、组合策略:不是越多越好

实际项目中我试过三种组合方式:

方案A:顺序流水线
Mosaic → 常规增强(旋转、色彩抖动)→ 随机选MixUp或CutMix
适合数据量中等、场景复杂的项目

方案B:概率抽样
每张图以0.3概率走Mosaic,0.3概率走MixUp,0.3概率走CutMix,0.1概率原图
适合追求模型鲁棒性的场景

方案C:分阶段策略

  • 前50轮:Mosaic + MixUp(强增强)
  • 中间30轮:只做CutMix(中等增强)
  • 最后20轮:只做基础增强(弱增强)
    适合小目标检测任务,前期丰富上下文,后期稳定收敛

我在交通监控项目里用方案C,小目标漏检率降了7%。关键发现是:Mosaic虽然好,但训练后期要逐渐关闭,否则模型会过度依赖“拼图”上下文,反而影响泛化。

五、部署时的注意事项

  1. 推理时别增强:这是新手常犯的错误,训练时用了Mosaic,部署时也拼四张图输入——完全错误!增强只在训练阶段。

  2. 标签格式兼容:自己写增强代码时,先统一转换成绝对坐标计算,最后再归一化。中间用相对坐标很容易累积误差。

  3. 内存瓶颈:Mosaic一次加载四张图,如果原图很大(比如1920x1080),批量稍大就OOM。建议在dataloader里做降采样预处理。

  4. 验证集处理:验证集绝对不要用这些增强!否则指标会虚高。有些框架默认对验证集做随机增强,一定要关掉。


最后给个实用建议:别盲目堆叠增强方法。先分析你的数据缺什么——缺小目标就加强Mosaic,缺遮挡场景就加强CutMix,缺类别平衡就加强MixUp。每次只改一个变量,在验证集上观察mAP变化,特别是看各类别的AP是否均衡提升。好的增强策略应该让“短板类别”受益最大。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐