【感知实战·数据增强篇】深度解析目标检测中的图片数据增强算法,多图演示效果
作者:探物 AI > 核心导读: 在视觉深度学习中,数据决定了模型上限。对于算力有限或样本稀缺的开发者,数据增强(Data Augmentation)就是性价比最高的"炼丹秘籍",很多模型准确度的提高就是靠增加数据,增加数据的普适性,因此本文统计数据增强算法,进行汇总,并进行效果演示。
一、 数据增强的四个维度
1. 几何增强和像素增强
这是最经典的策略,旨在告诉模型:物体在不同光照、不同角度下还是同一个东西。
-
几何变换:水平/垂直翻转、旋转、随机裁剪(Crop)、缩放。
python# ─── 1. 几何变换 ──── def aug_geometric(img: np.ndarray) -> dict: h, w = img.shape[:2] results = {} # 水平翻转 results["HorizontalFlip"] = cv2.flip(img, 1) # 垂直翻转 results["VerticalFlip"] = cv2.flip(img, 0) # 旋转 45° M = cv2.getRotationMatrix2D((w // 2, h // 2), 45, 1.0) results["Rotate45"] = cv2.warpAffine(img, M, (w, h)) # 随机裁剪(取中心 70%) cy, cx = int(h * 0.15), int(w * 0.15) crop = img[cy:h - cy, cx:w - cx] results["CenterCrop70%"] = cv2.resize(crop, (w, h)) # 缩放(先缩小再 resize 回来) small = cv2.resize(img, (int(w * 0.6), int(h * 0.6))) results["Scale0.6"] = cv2.resize(small, (w, h)) return results -
色彩抖动 :调整亮度、对比度、饱和度及色相(Hue)。
python# ─── 2. 色彩抖动 ────── def aug_color_jitter(img: np.ndarray) -> dict: results = {} img_hsv = cv2.cvtColor(img, cv2.COLOR_RGB2HSV).astype(np.float32) # 亮度 +60 bright = img.astype(np.float32) bright = np.clip(bright + 60, 0, 255).astype(np.uint8) results["Brightness+60"] = bright # 对比度 ×1.8 contrast = np.clip(img.astype(np.float32) * 1.8, 0, 255).astype(np.uint8) results["Contrast×1.8"] = contrast # 饱和度 ×2(HSV 的 S 通道) sat = img_hsv.copy() sat[..., 1] = np.clip(sat[..., 1] * 2, 0, 255) results["Saturation×2"] = cv2.cvtColor(sat.astype(np.uint8), cv2.COLOR_HSV2RGB) # 色相偏移 +30°(HSV 的 H 通道,范围 0-179) hue = img_hsv.copy() hue[..., 0] = (hue[..., 0] + 30) % 180 results["HueShift+30"] = cv2.cvtColor(hue.astype(np.uint8), cv2.COLOR_HSV2RGB) return results -
噪声注入:高斯噪声、椒盐噪声(模拟传感器在极端环境下的干扰)。
python
# ─── 3. 噪声注入 ─────
def aug_noise(img: np.ndarray) -> dict:
results = {}
h, w = img.shape[:2]
# 高斯噪声
gauss = np.random.normal(0, 25, img.shape).astype(np.float32)
noisy = np.clip(img.astype(np.float32) + gauss, 0, 255).astype(np.uint8)
results["GaussianNoise"] = noisy
# 椒盐噪声(1% 像素)
sp = img.copy()
n_pixels = int(h * w * 0.01)
# 盐
rows = np.random.randint(0, h, n_pixels)
cols = np.random.randint(0, w, n_pixels)
sp[rows, cols] = 255
# 椒
rows = np.random.randint(0, h, n_pixels)
cols = np.random.randint(0, w, n_pixels)
sp[rows, cols] = 0
results["Salt&PepperNoise"] = sp
return results
2. 抗遮挡强化数据增强
为了防止模型"死记硬背",我们故意破坏图像的局部信息,强迫模型根据残缺特征识别物体,增强模型对于遮挡物的检测。
-
Random Erasing:随机擦除一个矩形区域。
python# ─── 4. Random Erasing ─────── def aug_random_erasing(img: np.ndarray, erase_ratio: float = 0.15, fill: int = 128) -> np.ndarray: """随机擦除一个矩形区域,填充灰色""" out = img.copy() h, w = out.shape[:2] eh = int(h * erase_ratio) ew = int(w * erase_ratio) top = np.random.randint(0, h - eh) left = np.random.randint(0, w - ew) out[top:top + eh, left:left + ew] = fill return out -
GridMask:这个主要在于目标分割算法中进行的数据增强,有规则地打上黑色网格。在多任务分割中非常有效,能强迫模型学习物体的全局结构而非局部纹理。
python# ─── 5. GridMask ──────── def aug_gridmask(img: np.ndarray, d: int = 48, ratio: float = 0.4) -> np.ndarray: """ 生成规则网格掩码并应用到图像 d : 网格单元大小(像素) ratio : 每个单元内遮挡区域的比例 """ out = img.copy() h, w = out.shape[:2] mask = np.ones((h, w), dtype=np.uint8) keep = int(d * (1 - ratio)) # 每格保留的像素数 for y in range(0, h, d): for x in range(0, w, d): # 遮挡右下方 (d-keep) × (d-keep) 区域 y1 = min(y + keep, h) x1 = min(x + keep, w) mask[y1:y + d, x1:x + d] = 0 out[mask == 0] = 0 return out
3. 跨样本混合(YOLO 的成名作)
这是目前感知类算法(尤其是 YOLOv5 到最新的 YOLO26)的标配,也是提分最明显的手段,核心代码如下。
-
Mixup:将两张图按比例透明叠加,标签也同步融合。
python# ─── 6. Mixup ─── def aug_mixup(img_a: np.ndarray, img_b: np.ndarray, alpha: float = 0.4) -> np.ndarray: """ 按 alpha 比例线性叠加两张图 标签融合:label = alpha * label_a + (1-alpha) * label_b """ lam = np.random.beta(alpha, alpha) mixed = (lam * img_a.astype(np.float32) + (1 - lam) * img_b.astype(np.float32)) return np.clip(mixed, 0, 255).astype(np.uint8) -
CutMix:把 A 图的一部分剪掉,贴上 B 图的内容。这里的作用其实是很多的,鉴于篇幅限制,简单说可以提高目标被遮挡情况下的检测能力(把A图的一部分截取)、第二是提高每一张图片的密度,迫使卷积核在每一个像素点都进行学习,从而提高mAP(平均精度),第三则是提高模型对于目标边界的敏感度,AI必须分清楚是物体A的像素还是物体B的像素,这个对物体定位或者目标分割作用提升巨大。
python## ─── 7. CutMix ───── def aug_cutmix(img_a: np.ndarray, img_b: np.ndarray, cut_ratio: float = 0.3) -> np.ndarray: """ 将 img_b 的一块矩形区域贴到 img_a 上 标签权重 = 1 - 被替换区域面积占比 """ out = img_a.copy() h, w = out.shape[:2] ch = int(h * cut_ratio) cw = int(w * cut_ratio) top = np.random.randint(0, h - ch) left = np.random.randint(0, w - cw) out[top:top + ch, left:left + cw] = img_b[top:top + ch, left:left + cw] return out -
Mosaic (马赛克增强):将 4 张图随机拼接。极大丰富了背景,在一个 Batch 里大幅增加了目标密度,对小目标检测有奇效。
python# ─── 8. Mosaic ───── def aug_mosaic(imgs: list) -> np.ndarray: """ 将 4 张图随机拼接成 2×2 马赛克图 imgs: list of 4 H×W×3 numpy arrays(尺寸需相同) """ assert len(imgs) >= 4, "Mosaic 需要至少 4 张图" h, w = imgs[0].shape[:2] # 拼接:上下各拼两张 top = np.concatenate([imgs[0], imgs[1]], axis=1) bottom = np.concatenate([imgs[2], imgs[3]], axis=1) mosaic = np.concatenate([top, bottom], axis=0) return cv2.resize(mosaic, (w, h)) # resize 回原始尺寸
二、 进阶:针对感知类算法的普适性数据增强
如果你正在处理诸如**"海面船舶检测"** 或**"复杂工业视觉"**,以下两种方法是提分关键:
1. Copy-Paste (复制粘贴大法)
这是实例分割中的神技。通过将目标从原图中精确抠出,随机粘贴到不同的背景图中。它解决了数据不平衡的问题、一些长尾分布的问题,比如你当前的训练数据集,5个类别,但其中1个类别只有5%,这是明显的数据不平衡,此时可以选择此数据增强算法来解决准确度不达标。
-
优势:完美解决"长尾分布"问题。如果某种罕见样本只有 5 张,通过 Copy-Paste,你可以让它出现在 5000 张图中。
python# ─── 9. Copy-Paste(简化版像素级演示)──────────────── def aug_copy_paste(src: np.ndarray, dst: np.ndarray, obj_ratio: float = 0.25) -> np.ndarray: """ 从 src 中心裁出目标区域(模拟"抠图"), 随机贴到 dst 的随机位置上。 真实场景需配合实例分割掩码使用。 """ out = dst.copy() h, w = src.shape[:2] oh = int(h * obj_ratio) ow = int(w * obj_ratio) # 裁出 src 正中间的矩形作为"目标" cy, cx = h // 2, w // 2 obj = src[cy - oh // 2:cy + oh // 2, cx - ow // 2:cx + ow // 2] # 随机贴到 dst dh, dw = out.shape[:2] max_top = max(dh - oh, 1) max_left = max(dw - ow, 1) top = np.random.randint(0, max_top) left = np.random.randint(0, max_left) out[top:top + oh, left:left + ow] = obj return out
2. 特征级增强 (ISDA)
传统的数据增强是"皮囊"的改变,而ISDA相当于"灵魂"的改变,它不再对原始像素进行翻转或裁剪,而是在模型的"特征空间"里进行改动。实际上他的工作原理,是通过计算图片特征的协方差矩阵,沿着语义变化的概率方向添加噪声 ,这意味着模型在训练时,实际上是在成千上万种"不存在但合理"的变体中进行学习。
(ISDA并没有现成的函数,其实它是直接集成在模型的损失函数部分,通过实时统计特征的分布,让模型在每一次迭代时,都类似训练了千万种图片,这种隐式增强,让轻量化模型也可以跑出大模型的效果。)
💻 核心实现(代码演示):
python
import torch
import torch.nn as nn
class ISDALoss(nn.Module):
def __init__(self, feature_num, class_num):
super(ISDALoss, self).__init__()
# 记录每个类别的特征期望和协方差
self.estimator = EstimatorCV(feature_num, class_num)
def forward(self, features, y, target_layer):
# 1. 实时更新特征的分布统计(均值和方差)
self.estimator.update_CV(features, y)
# 2. 计算特征在空间中的"扰动"
# 这里就是 ISDA 的核心:利用协方差矩阵对分类器的权重进行补偿
isda_logits = target_layer(features) + self.estimator.get_purturb(features, y)
# 3. 依然使用标准的交叉熵,但输入的是"增强后"的逻辑值
return nn.CrossEntropyLoss()(isda_logits, y)
🔍 总结:如何挑选你的"增强全家桶"?
| 业务痛点 | 推荐策略 |
|---|---|
| 小目标、远距离检测 | Mosaic + Mixup |
| 目标遮挡、背景嘈杂 | CutMix + GridMask |
| 样本极少、算力吃紧 | Copy-Paste + ISDA |
🔜 下期预告:雨雾数据集构造,提升恶劣环境下的目标检测能力
- 工业级流水线 :深度解析基于
Albumentations的增强方案。 - 一键适配 :完美支持 YOLO 格式,实现 Bbox 边界框坐标自动同步。
- 实战模拟 :手把手教你如何构造逼真的雨天、雾天传感器数据集。
关注"探物 AI",明天见!
💡 资源获取 :后台已为你整理好本文涉及的核心算法 Python 源代码文件。关注并回复"数据增强"即可获取网盘下载链接。