
简介面向目标检测与实例分割场景的数据增强工具主要解决标注样本有限、模型训练易过拟合的问题。工具基于YOLOv5等常用框架设计适合已使用LabelImg或LabelMe完成部分标注、需要扩充带标签数据集的开发者。资源共23个文件以4个Python脚本为处理核心附16张JPG示例图片、1个XML与1个JSON标注文件以及Markdown说明文档压缩包大小1.64MB结构清晰便于直接参照使用目前已有876人学习参考。三个主要脚本各司其职rename_file.py实现批量文件重命名方便数据集整理DataAugmentforLabelImg.py支持模糊、亮度、裁剪、旋转、平移、镜像等增强并同步更新XML标注DataAugmentforLabelMe.py则针对LabelMe格式提供JSON标注同步增强。此外还可引入高斯噪声、仿射变换等策略并随机组合所有处理均保持标签框与分割区域严格对齐帮助快速生成多样化的训练数据集有效提升检测与分割模型的泛化能力。1. YOLO数据集带标签扩增数据不够时最容易踩的坑就是把图和框拆开增强刚接手一个YOLOv8训练任务时最尴尬的往往不是模型选型而是手里数据集没喂饱模型。做检测和做分类不一样分类可以把整张图旋转、平移、加噪标签跟着图走就行检测的标签是坐标框图片一变框不跟着换算增强出来的样本就全是错标。你花一下午写了个翻转脚本训练时模型可能已经学到“框永远在左上角”这种假规律mAP看起来还行一上真实场景就翻车。带标签扩增要解决的就是这件事对图片做旋转、翻转、裁剪、颜色扰动的同时把YOLO格式的归一化标注框一并换算、过滤、重写让扩增后的每一张图都带着一组仍然准确的框。适合谁适合训练集只有几百到几千张、正样本类别不均衡、或者想让模型更抗光线和角度变化的人。下面这套方案我用它扩过无人机视角的小目标数据和工业质检的缺陷数据按步骤就能在自己数据集上复现。2. 带标签扩增的第一道分水岭哪些变换会动坐标哪些只动像素2.1 几何类变换改坐标像素类变换只改图不改框拿到一批图和YOLO txt之后第一步不是写代码是给会用的增强操作分类。分类错了后面全是白干。几何类增强会真实改变物体在画面里的位置和形状水平翻转、垂直翻转、旋转、缩放、平移、裁剪、透视变换。这一类做完标签框的坐标必须同步重算。比如水平翻转一张800×600的图翻转前某个目标中心在归一化坐标(0.3, 0.4)处翻转后中心会跑到(0.7, 0.4)如果标签还写0.3框就落到背景上了。垂直翻转同理cy变成1 - cy。缩放和裁剪更隐蔽。你用RandomResizedCrop把原图裁剪出一块再缩放回800×600原图中心点(0.3, 0.4)在新图里对应的归一化坐标已经不是一个简单的1减关系要先算原图里这个点的像素位置减掉裁剪原点再除以新图尺寸才能得到新坐标。像素类增强不改变物体在画面中的位置亮度、对比度、饱和度、色调、高斯噪声、模糊、锐化、CLAHE。这类操作只改变像素值框的坐标怎么写的还怎么是不需要动标签。很多人不敢把颜色扰动加进检测增强其实是多虑了颜色扰动是这一类里最安全、几乎不会把标签搞坏的增强。2.2 为什么我优先用Albumentations而不是自己拼OpenCV函数自己用OpenCV写翻转和映射并不难难的是把所有操作的坐标换算逻辑都维护对。今天加一个随机旋转明天加一个随机缩放裁剪每一段都要考虑顺序先翻转再裁剪和先裁剪再翻转坐标公式完全不一样。这些组合逻辑全部自己写代码量一上来出错概率是指数级上升的。常见做法是用Albumentations。它的A.Compose接收一个bbox_params参数你只需要在调用时把标签框list传进去库内部会自动根据你配置的格式完成坐标换算。我一般配成这样import albumentations as A transform A.Compose([ A.HorizontalFlip(p0.5), A.RandomBrightnessContrast(p0.5), ], bbox_paramsA.BboxParams( formatyolo, label_fields[class_labels], min_visibility0.3, ))这里的BboxParams有三个关键参数formatyolo告诉库传入的框是YOLO归一化格式(cx, cy, w, h)库内部会转成像素绝对值做变换再转回归一化格式返回。label_fields[class_labels]声明类别标签存在class_labels这个字段里。为什么要单独声明因为Albumentations在过滤越界框时会同步把对应的类别标签也过滤掉你不声明这个字段过滤时就会报错或出现标签和框数量对不上的情况。min_visibility0.3表示变换后如果某个框可见面积不足原来的30%直接丢弃。这个参数后面避坑章还会详细讲。Albumentations目前对YOLO格式的支持是最省心的训练时还可以和Ultralytics的YOLOv8训练流程配合。你可以在训练脚本里先对数据集做离线扩增也可以把增强管线直接接入训练时的数据加载。我倾向于离线扩增因为可以人工抽检扩增后的样本把不合理的过滤掉再送进训练。2.3 YOLO归一化坐标的本质算清楚一次后面全是套路YOLO的txt格式大家都不陌生每行是 class_id cx cy w h五个值全部归一化到0到1之间。归一化的意思是cx、cy是目标中心点相对于图片宽和高的比例w、h是目标宽高相对于图片宽和高的比例。实际做坐标变换时要先把它还原成像素值cx_pixel cx * image_width cy_pixel cy * image_height w_pixel w * image_width h_pixel h * image_height水平翻转时中心点像素x变成image_width - cx_pixel归一化之后就是1 - cx垂直翻转同理cy变成1 - cy。宽高因为翻转不改变目标本身尺寸所以w、h不变。缩放、裁剪、透视变换这类操作像素坐标换算公式就变成# 裁剪后新图左上角在原图中的像素位置为 (offset_x, offset_y) # 新图尺寸为 new_w, new_h new_cx (cx_pixel - offset_x) / new_w new_cy (cy_pixel - offset_y) / new_h new_w_norm w_pixel / new_w new_h_norm h_pixel / new_h不自己写坐标转换的好处就在这Albumentations把这些边界情况都处理好了你只需要记住一个原则传进transform的是归一化坐标拿出来的依然是归一化坐标中间过程不用自己维护。3. 用Albumentations实现YOLO数据集带标签扩增脚本与参数全解3.1 最小可用扩增脚本下面这套脚本是我每次做YOLO数据集离线扩增的基础版本复制下来改一下路径就能跑。我一般把图片和标签放在同一个根目录下的images和labels两个子目录里保持和YOLO训练目录结构一致。import os import cv2 import numpy as np import albumentations as A from glob import glob # 只做一张图片的最小扩增演示 def read_yolo_txt(txt_path): boxes [] class_labels [] with open(txt_path, r, encodingutf-8) as f: for line in f.readlines(): line line.strip() if not line: continue parts line.split() class_id int(parts[0]) cx, cy, w, h map(float, parts[1:5]) boxes.append([cx, cy, w, h]) class_labels.append(class_id) return boxes, class_labels # 定义增强管线bbox_params关键配置 transform A.Compose([ A.HorizontalFlip(p0.5), # 水平翻转几何类自动同步框 A.RandomBrightnessContrast(p0.5), # 亮度对比度像素类不影响框 A.HueSaturationValue(p0.3), # 色调饱和度像素类不影响框 A.RandomSizedBBoxSafeCrop(p0.3, width640, height640), # 安全裁剪自动同步框 A.BBoxSafeRandomRotation(p0.3, border_modecv2.BORDER_CONSTANT), # 旋转自动同步框 ], bbox_paramsA.BboxParams( formatyolo, label_fields[class_labels], min_visibility0.3, )) image cv2.imread(images/000001.jpg) image cv2.cvtColor(image, cv2.COLOR_BGR2RGB) boxes, class_labels read_yolo_txt(labels/000001.txt) # 核心调用图片和框必须同时传入 augmented transform(imageimage, bboxesboxes, class_labelsclass_labels) aug_img augmented[image] aug_boxes augmented[bboxes] aug_labels augmented[class_labels] print(扩增后保留的框数量:, len(aug_boxes))注意几个代码细节。图片读进来之后我转了RGBAlbumentations的很多色彩增强操作是RGB语义的保持RGB能避免色调变换结果不符合直觉。调用transform时传入的参数名必须和A.Compose里定义的一致image、bboxes、class_labels。class_labels这个名字不是固定的但必须和bbox_params里label_fields定义的名字一样否则过滤框时会报错。3.2 每个增强项的参数怎么选很多人拿到脚本直接跑跑完发现扩增效果并不好问题出在参数和数据集特性不匹配。下面是我常用的几个参数档位。HorizontalFlip对大多数通用物体是安全的但对文本、车牌、左右非对称的工业零件要谨慎。文本翻转后虽然框是对的但语义已经不存在了模型学到的可能是“倒着的字也是字”这种错误知识。RandomSizedBBoxSafeCrop是个被低估的增强。它随机裁剪一块区域再缩放回指定尺寸但只裁那些不会把目标框裁掉一半的区域这是带标签扩增里少有的“对标签友好”的裁剪方式。它的width和height参数必须和目标网络输入一致否则训练时还要再做一遍resize。如果原图是1280×720直接设width640, height640会让长宽比变化但因为是安全裁剪目标主体都保留着模型反而能学到尺度不变性。BBoxSafeRandomRotation比普通的Rotate更实用。普通Rotate旋转后四个角会有大片空白区域目标框可能被裁出画面BBoxSafeRandomRotation会智能选择旋转角度范围保证所有框仍然完整落在画面内。很多旋转目标检测任务里大家用mmrotate训练DOTA数据集时才会去处理任意角度通用检测场景小幅旋转就够用了。颜色扰动类增强我固定配RandomBrightnessContrast加HueSaturationValue这是最安全的组合。工业检测里光照不均是常态这两个增强对让模型在复杂光照下有更好表现帮助非常大。还可以加一个A.GaussNoise(p0.2)模拟传感器噪声但对本身画面干净的公开数据集噪声加多了反而影响收敛速度。3.3 批量扩增落盘副本数和目录结构离线扩增的最终目标是把扩增后的图片和标签落盘到硬盘。常见做法是为每张原图生成固定数量的副本副本数量一般取2到5。太少扩不动太多会让同一张图的变体在训练集里占比过高反而导致过拟合。from glob import glob from tqdm import tqdm COPY_NUM 3 # 每张原图生成3张扩增图 image_paths sorted(glob(train/images/*.jpg)) for image_path in tqdm(image_paths): image cv2.imread(image_path) image cv2.cvtColor(image, cv2.COLOR_BGR2RGB) h, w image.shape[:2] stem os.path.splitext(os.path.basename(image_path))[0] txt_path ftrain/labels/{stem}.txt if not os.path.exists(txt_path): continue boxes, class_labels read_yolo_txt(txt_path) if len(boxes) 0: continue for idx in range(COPY_NUM): # 每个副本独立执行一次随机增强互不影响 augmented transform(imageimage, bboxesboxes, class_labelsclass_labels) aug_img augmented[image] aug_boxes augmented[bboxes] aug_labels augmented[class_labels] if len(aug_boxes) 0: # 框全被过滤了这张副本直接丢弃 continue out_img_path faugmented/images/{stem}_aug{idx}.jpg out_txt_path faugmented/labels/{stem}_aug{idx}.txt # Albumentations返回的是RGB写盘前转回BGR cv2.imwrite(out_img_path, cv2.cvtColor(aug_img, cv2.COLOR_RGB2BGR)) with open(out_txt_path, w, encodingutf-8) as f: for box, label in zip(aug_boxes, aug_labels): # box是归一化坐标列表格式化保留6位小数 cx, cy, bw, bh box f.write(f{label} {cx:.6f} {cy:.6f} {bw:.6f} {bh:.6f}\n)批量脚本里有个关键动作每张原图单独调用一次transform而不是先对图片做一次增强再用结果继续增强。每个副本都是基于原图独立做随机变换这样能保证数据多样性。如果对前一张的增强结果继续增强样本之间会形成链式依赖训练集多样性反而下降。还用到了一个容易被忽略的细节框全被过滤掉的副本直接丢弃不写盘。有些场景下比如原图目标特别小旋转后所有框的可见面积都低于min_visibility这时候硬写一个空标签文件会污染训练集YOLO训练时遇到大量空标签图会浪费训练时间。丢弃是更稳的选择。3.4 增强后的训练集划分策略扩增完的augmented目录和原train目录合并成新的训练集验证集不应该包含任何扩增样本。原因很简单验证集的目的是模拟真实场景分布扩增样本是人为制造的变体把验证集也扩增了mAP就失去了参考意义。我一般把原始train里的10%到20%划出来做验证集剩下的加扩增样本一起训练。验证集只做resize到网络输入尺寸不做任何随机增强。4. 带标签扩增避坑与排查5个翻车现场的处理记录4.1 增强后框全乱了目标在左上角框标在右下角现象扩增完的训练集丢进YOLOv8训练loss下降很快但验证时每个目标的框都明显偏移物体和框之间差着半个身子。原因这是最常见的“只增强了图片没同步标签”导致的。手动用cv2翻转图片后标签txt里的坐标还是原图的坐标。水平翻转后cx必须变成1 - cx垂直翻转后cy必须变成1 - cy很多人漏了这一步。还有一种情况是翻转方向搞反了比如先转成BGR又翻转坐标换算和实际翻转不一致。解决优先用Albumentations这类自带bbox同步的库别手动维护坐标变换。如果必须自己写先在代码里加一段自检逻辑把原始框和增强后的框画在同一张图上肉眼确认框还是贴在那个物体上。我自己写过一个简单的可视化脚本随机抽10张增强前后的图叠加显示基本能在一分钟内排查出80%的标签错位问题。4.2 旋转后框全部越界小目标样本被浪费现象用了普通的A.Rotate增强后跑批量脚本发现生成的大量扩增图只有不到一半框被保留有些图所有框都被过滤掉了白白浪费算力和存储。原因普通Rotate会把图片旋转任意角度旋转后图片四个角出现空白原来靠近边缘的目标框会有一部分或者整体超出画面。Albumentations对越界框的默认处理是过滤min_visibility太低时保留一些残缺框太高时会把小目标全部滤掉。很多小目标数据集的框本身就很小旋转15度可能就让可见面积低于阈值。解决在几何增强里优先用BBoxSafeRandomRotation替代Rotate。它会根据图像内容和框的位置自动计算安全的旋转角度范围保证所有框完整保留在画面内。如果用了Rotate但希望保留部分越界框把min_visibility调到0.2以下同时配合border_modecv2.BORDER_CONSTANT填充黑色能减少边界框的误判。4.3 验证集也做增强mAP虚高导致误判模型性能现象扩增后的模型在验证集上mAP涨了四五个点高高兴兴部署到现场实测效果却不如之前的模型准确率掉得很厉害。原因训练集扩增时如果验证集也套了同一套增强管线验证集就包含了大量和训练集风格相近的合成样本mAP自然虚高。更隐蔽的是在线增强的随机性会让同一个验证样本在每次评估时得到不同结果指标时高时低很难判断模型到底是变好了还是变差了。解决验证集只做确定性预处理比如resize到640×640、归一化不做随机翻转、不做随机裁剪、不做颜色扰动。带标签扩增只作用在训练集上。另外训练时确认一下Ultralytics的val配置里没有开启augment有些版本的默认配置会带上一点颜色扰动评估的时候要关掉。4.4 用Mosaic和CopyPaste时标签错位叠加现象加入Mosaic或CopyPaste这类多图合成增强后训练loss波动很大可视化训练样本时会看到有些图上目标框的位置明显不对或者两张图的框叠在一起。原因Mosaic把四张图拼成一张大图子图的每个bbox需要加上该子图在大图中的偏移量再统一除以大图尺寸做归一化。这个偏移量计算漏掉一个坐标轴或者除以了子图尺寸而不是大图尺寸都会导致坐标错位。CopyPaste的问题更复杂粘贴目标时还要考虑目标覆盖区域的旧标签去留。解决能用Ultralytics训练内置的Mosaic就用内置的它处理的是训练时的在线拼接标签同步是框架内部逻辑。自己写多图合成时按这个公式换算子图在大图中的偏移量是(offset_x, offset_y)子图尺寸是(sub_w, sub_h)大图尺寸是(big_w, big_h)目标的新归一化坐标是((cx_pixel offset_x) / big_w, (cy_pixel offset_y) / big_h)宽高比同理。4.5 扩增批处理跑几个小时训练还没开始现象几千张图每张扩5个副本脚本跑了几个小时还没结束磁盘也快满了。有些图存在重复、模糊、亮度异常的问题扩增出来质量很差白白增加了训练时间。原因最常见的原因是每张图都完整读写了一遍磁盘几千张图就是几万次IO操作。其次是副本数设得过大我见过有人把COPY_NUM设成20训练集从2000张膨胀到40000张一个epoch都跑不动。解决副本数按2到4控制先扩一个小批量检查时间再决定全量扩增。批量处理时可以用multiprocessing开4到8个进程并行跑磁盘IO够快的话时间能压到原来的四分之一。给扩增管线加一个简单的过滤条件原图尺寸小于网络输入尺寸的直接跳过因为小图扩增后模糊程度会被放大。这一步对质量提升很明显。5. 增强策略调到位可视化抽检、mAP曲线判断和几个进阶技巧5.1 可视化抽检增强完先看再送训练扩增完的样本不管认为多正确先做一次抽检。写一个简单的可视化脚本把原图和扩增后的图画在一起框也画上去一屏看九张。import matplotlib.pyplot as plt fig, axes plt.subplots(3, 3, figsize(12, 12)) # plot_boxes是自定义函数用cv2.rectangle在原图画框 pairs [ (images/000001.jpg, augmented/images/000001_aug0.jpg), (images/000002.jpg, augmented/images/000002_aug1.jpg), # 放10组左右 ] for i, (orig_path, aug_path) in enumerate(pairs[:9]): ax axes[i // 3][i % 3] orig plt.imread(orig_path) aug plt.imread(aug_path) # 左右拼接方便对比 combined np.concatenate([orig, aug], axis1) ax.imshow(combined) ax.axis(off) plt.tight_layout() plt.show()重点看三类问题框是否还贴着目标目标是否被过度裁剪导致语义缺失小目标是否因为缩小到几个像素而失去存在意义。我个人的标准是抽检50张里不能出现超过5张有明显错框的样本否则就调低对应增强的p值。5.2 用mAP判断增强是涨点还是拖后腿增强不是越多越好。我的做法是把增强配置当作一组超参数先跑一个不带增强的baseline记录mAP再加颜色扰动类增强看涨不涨再加水平翻转看涨不涨最后加旋转和裁剪。每次只加一种跑一个短epoch的快速验证。这样能清楚知道哪个增强在你的数据集上是有效的。重点关注过拟合信号增强后训练集loss下降更慢但验证集mAP持续上升这是正常的如果训练loss下降很快但验证mAP不升反降说明增强强度过高样本分布被扭曲了。YOLO损失函数收敛正常不代表增强合理mAP才是最终裁判。5.3 进阶用CopyPaste给小样本类别补数量如果数据集里某个类别只有几十个框位置都在图像角落扩增怎么旋转都逃不出这个角落。这时可以试试CopyPaste思路把该类别的目标从一张图里抠出来粘贴到其他图上生成新的样本。粘贴时用mask把原目标像素切出来bbox跟着一起贴过去重新计算粘贴位置的归一化坐标。贴完再对粘贴区域做一点亮度抖动让贴块和背景的融合更自然避免模型学到“目标总是带一圈硬边缘”。5.4 最后一句经验做了几年检测项目我现在每次拿到新数据集第一件事就是固定一套增强配置先跑通流程跑通后再花时间调强度。增强参数不是越多越好也不是越强越好而是要让模型“看不腻”你的数据。希望这些方法能帮你少走几步弯路。本文还有配套的精品资源点击获取