简介:面向YOLOv5等目标检测与分割任务的数据增强工具包,专注解决标注图片数量不足、模型泛化能力弱的问题,尤其适合小样本项目快速扩充数据。通过随机组合翻转、剪切、仿射变换、高斯模糊、平移、自适应高斯噪声和亮度调整等策略,能够在变换图片的同时同步更新对应标签,免去手工重新标注的繁琐步骤,可无缝适配LabelImg和LabelMe两种主流标注格式,覆盖检测与分割两类场景,有效提升样本多样性。压缩包内共包含23个文件,核心为4个Python脚本:重命名脚本用于批量整理文件,两个增强脚本分别处理LabelImg与LabelMe标注的数据,另有16张示例图片、xml/json标注文件及简要说明文档,整体仅1.64MB,轻量易部署。目前已有876人学习下载。实际使用中,参考demo目录结构,将图片和标注文件放入指定文件夹即可自动输出增强后的样本,操作门槛低,非常适合初学数据增强的开发者快速上手。
1. 带标签的 yolo 数据集增强:扩图的同时让 bbox 和 mask 跟着变
标了十几张 Camera_1 的图就急着开训?YOLO 的 mAP 大概率不给你面子。与其咬咬牙再标几百张,不如先把这批带标注的数据做一遍带标签扩增:图翻转变亮了,标注框和分割多边形也同步变,扩出来的数据直接进 yolov5/yolov8 的训练目录。这份资源里三个 python 脚本,分别负责重命名、LabelImg 检测标注增强、LabelMe 分割标注增强,支持模糊、亮度、裁剪、旋转、平移、镜像以及随机组合,能同时处理目标检测和目标分割两类标注。适合刚标完一批数据发现样本量不足的入门玩家,也适合想给现有数据集补充难度的老手。下文先把三个脚本的数据走向盘清楚,再带你跑通两条增强链路。
2. 先盘数据走向:三个脚本的分工和带标签增强的原理
2.1 检测与分割两条链路:XML、JSON 各走各的
这套工具把任务拆成两条独立链路:目标检测走 LabelImg 的 Pascal VOC XML,目标分割走 LabelMe 的 JSON。之所以不统一格式,是因为两个标注工具的存储结构完全不同,增强时要改的坐标对象也不一样。检测改的是 bbox 的(xmin, ymin, xmax, ymax)四个数,分割改的是 shape 里一串 polygon 点集。
| 脚本 | 配对标注格式 | 增强方式 | 适配任务 |
|---|---|---|---|
| rename_file.py | 任意 | 批量重命名 | 数据整理 |
| DataAugmentforLabelImg.py | LabelImg 导出的 XML | 模糊、亮度、裁剪、旋转、平移、镜像 | YOLO 系目标检测 |
| DataAugmentforLabelMe.py | LabelMe 导出的 JSON | 模糊、亮度、平移、镜像 | 实例分割 / 语义分割 |
跑之前先把依赖装齐,opencv_python、numpy、Pillow这三个基本跑不掉。README 里也专门提醒了 opencv 的问题,这步偷懒,后面所有图像变换直接给你报module not found。我个人习惯用 conda 建一个干净环境再装,避免和别的项目的 opencv 版本互相踩。
提示:LabelImg 版增强只吃 Pascal VOC 格式的 XML,YOLO 的 txt 格式不是它的输入。你需要先有 XML,增强完再转成 YOLO 训练用的 txt,这个后面细说。
2.2 bbox 和 polygon 为什么能跟着图变:坐标换算与随机组合
带标签增强的可行性,建立在“图像变换和坐标变换用同一组参数”这个前提上。检测侧最典型的是旋转:VOC 的(xmin, ymin, xmax, ymax)要先换成中心坐标加宽高,旋转后再取外接矩形换回xyxy。平移简单,直接给四个坐标各加偏移量;水平镜像则是交换xmin和xmax。模糊、亮度这类像素级操作根本不碰坐标,所以最简单。
分割侧的 polygon 是一串 N×2 的坐标数组。平移就是每个点加同一个偏移;镜像对 x 坐标取反;旋转、剪切、仿射变换则是对点集做矩阵乘法,和图像用同一个变换矩阵M。这里很容易出 bug 的地方是:有人只对图像做了warpAffine,忘了对 polygon 点集做同样的cv2.transform,结果就是增强图上 poly 还躺在老位置。
下面是几个增强原语,直观感受一下哪些动像素、哪些动坐标:
import cv2 import numpy as np img = cv2.imread("Imgs/Camera_1.jpg") h, w = img.shape[:2] # 亮度:alpha 是增益,beta 是加常数,只改像素,不影响 bbox bright = cv2.convertScaleAbs(img, alpha=1.2, beta=10) # 高斯模糊:ksize 核越大越糊,同样不碰标注坐标 blur = cv2.GaussianBlur(img, (7, 7), 0) # 水平镜像:图左右互换,xml 里 xmin 和 xmax 必须同步交换 flip = cv2.flip(img, 1)前面两个增强不碰坐标,第三个镜像改完图必须改 box。这就是带标签增强的核心逻辑:每动一次几何变换,都要找到对应坐标结构并同步更新。摘要里提到的“随机组合”也建立在这个基础上,本质是给每项增强一个随机概率,满足条件才执行,所以同一张图每次跑出来的结果都可能不同,这正是数据增强想要的多样性。
2.3 随机组合的默认逻辑和你要留意的概率变量
这套工具的增强策略按“随机引入不同方向翻转、剪切、仿射变换、高斯模糊、平移、自适应高斯噪声、亮度改变”设计,然后随机组合。实际源码里一般会有一排概率阈值,比如if random.random() < p_flip:、if random.random() < p_blur:这种结构。每项一个独立判断,命中了才执行,所以增强后文件数量会按组合数成倍膨胀。
这里要提醒一个常见误用:直接把所有概率都调到 1.0,结果每张图同时叠加了旋转、噪声、亮度、平移,样本难度陡增,训练 loss 直接不收敛。我一般会把几何类增强控制在两张叠加以内,像素类增强控制在三张以内。先看一遍两个 DataAugment 脚本顶部或函数里的概率变量,再决定要不要调,别盲目改。
3. 跑通检测增强:LabelImg 的 XML 怎么跟着图片一起扩
3.1 目录准备:demo 的图、xml 和输出路径怎么摆
先照着 demo 的把目录搭出来。下载解压后你会看到Imgs目录放演示原图,DataAugForObjectDetection下有个data目录,你的图和你标好的 XML 就按下面这样放:
Data-enhancement/ ├── rename_file.py ├── DataAugForObjectDetection/ │ ├── DataAugmentforLabelImg.py │ └── data/ │ ├── Annotations/ # 放 LabelImg 导出的 xml │ └── JPEGImages/ # 放原图 └── Imgs/ # demo 原图示例注意几点:xml 文件名必须和图片文件名一一对应,Camera_1.jpg配Camera_1.xml,对不上脚本会直接跳过或者报找不到文件;原始标注目录提前备份一份,增强脚本跑完会往输出目录写新图和新 xml,没有后悔药。我一般先把 data 整个复制一份叫data_raw,再拿data目录给脚本当输出,这样原始标注始终保底。
3.2 运行脚本与六种增强的参数设置在哪儿改
路径摆好之后,进DataAugForObjectDetection目录直接跑:
cd DataAugForObjectDetection python DataAugmentforLabelImg.py如果脚本里写死了路径,就先打开文件把头部那几个目录变量改成你自己的路径。常见做法是把输入输出指到上面那个data目录下,输出建议单独开一个augmented子目录,别覆盖原图。这个脚本支持的增强类型比较多,我把对照关系整理如下:
| 增强项 | 涉及函数 | 我一般建议的区间 |
|---|---|---|
| 模糊 | cv2.GaussianBlur | 核大小 3~13,概率 0.3 |
| 亮度 | cv2.convertScaleAbs | alpha 0.7~1.3,beta 5~20 |
| 裁剪 | cv2.Crop / 数组切片 | 裁剪比例 0.7~0.9 |
| 旋转 | cv2.warpAffine | 角度 -10°~10°,超过 15° 外接框膨胀明显 |
| 平移 | cv2.warpAffine | 偏移量 0.05~0.1 倍图宽高 |
| 镜像 | cv2.flip | 水平镜像居多,垂直慎用 |
不同版本的脚本命名可能不一样,但变量名的意思基本都能看出来。改参数的时候认准两个东西:概率阈值和幅度区间。概率决定这张图会不会被增强,幅度决定增强得有多狠。新手最容易翻车的是把旋转角度拉到 30°,检测框会变得非常大且不贴合目标,这个我在第 5 章详细说。
3.3 增强结果自检:先数文件再抽看图片
跑完脚本之后,先做两个最基础的自检。第一,数输出目录里 jpg 和 xml 文件数量是否一致;第二,检查是否出现只有图没有 xml 的孤儿文件。如果数量对不上,多半是某张图的 xml 没有配对成功。这一步查文件比查坐标快得多,我建议养成习惯,每次跑完先执行一次:
ls data/augmented/*.jpg | wc -l ls data/augmented/*.xml | wc -l两个数字一致才算过了第一关。接下来才是抽三四张图,把 xml 框画上去看位置对不对。这套检查流程放到第 6 章统一写成脚本,这里先做到能判断“有没有生成、有没有配对”。
4. 跑通分割增强:LabelMe 的 JSON 点集同步与重命名
4.1 LabelMe 增强脚本:polygon 点集跟着变换走
分割增强走DataAugForObjectSegmentation目录下的DataAugmentforLabelMe.py,配对的是 LabelMe 标注的 JSON。LabelMe 的 JSON 里有个shapes数组,每个元素代表一个标注对象,里面的points就是 polygon 的关键点坐标数组。脚本要做的核心工作,就是读取每个 shape 的 points,做完几何变换后把新坐标写回 JSON。
import json import numpy as np with open("Camera_1.json", "r", encoding="utf-8") as f: ann = json.load(f) pts = np.array(ann["shapes"][0]["points"]) # 平移 (10, 5):每个点的 x + 10, y + 5,偏移量和图像平移必须一致 pts_shift = pts + np.array([10, 5]) print(pts_shift)这段代码演示了最简的平移,实际脚本里还包含模糊、亮度、镜像这些变换。关键在于:图像和 polygon 必须用同一组参数,比如图像做了cv2.flip(img, 1),那 polygon 的 x 坐标就必须做w - x的换算,否则扩出来的 mask 就是错位的。实例分割任务对坐标比检测更敏感,polygon 有一两个点没同步好,扩出来的 mask 就是缺角的。
进入分割脚本目录直接运行同理:
cd DataAugForObjectSegmentation python DataAugmentforLabelMe.py4.2 rename_file.py:先统一命名,再跑增强
分割和检测两条链路,都会遇到同一个问题:手工标注时的文件名往往是乱的。demo 里Camera_1_1.jpg、Camera_1_10.jpg这种命名,如果原图、xml、json 之间命名不统一,增强脚本跑一半就断了。rename_file.py就是干这个事的:批量统一文件名,改成等宽编号,避免乱序和同名覆盖。
python rename_file.py跑之前务必先打开脚本看头部路径变量,确认它指向的是你要重命名的目录。这个脚本是原地改名的,路径填错会把文件改成看不懂的名字。我一般建议在标注完成后、增强之前先跑一遍重命名,把Camera_1_1.jpg这种改成img_0001.jpg风格,xml 和 json 同步改名,增强出来的文件名才可控,后面转 YOLO txt 时也不会因为文件名错位找不到对应标注。
4.3 两个增强脚本的边界:它们不直接输出 YOLO 训练格式
有一个边界必须提前说清楚:这个资源增强完输出的是 Pascal VOC XML 和 LabelMe JSON,不是 yolov5/yolov8 直接能用的labels/*.txt。你增强完之后,还得把 XML 转成 YOLO 格式的归一化 txt。常见做法是写个转换脚本,遍历增强后的 XML,读 bbox,换算成(cx, cy, w, h)再除以图宽高。这一步最好放在增强之后做,避免同一套转换逻辑跑两遍。
如果你后面打算往旋转框检测方向走,比如用 mmrotate 去训 DOTA 这类数据集,那要注意这个工具生成的是水平外接框,旋转框数据集的theta角需要你自己重新标注或另行转换,工具本身不产旋转框标注。
5. 带标签扩增避坑:标注漂移、坐标越界、文件错位一次排查
5.1 先看现象再定位:高频坑速查
跑这套工具翻车点比较集中,我先给一张速查表,按“现象 → 原因 → 涉及环节”定位,下面再逐条展开。
| 现象 | 原因 | 涉及环节 |
|---|---|---|
| 增强后框和物体错开一截 | 图像变换和坐标变换参数不一致,或旋转后外接矩形未重算 | 旋转 / 仿射 |
| 训练时报坐标越界、NaN | 裁剪或平移后坐标超出图宽高,没有 clip | 裁剪 / 平移 |
| 只有图没有 xml,或 xml 对不上号 | 文件名配对错位,重命名顺序没统一 | rename_file / 配对 |
| 增强完 loss 不降反升 | 概率全开,每张图叠加了太多增强,噪声过大 | 随机组合 |
| opencv 读不出图,程序跑完结果全空 | 中文路径或特殊字符导致 imread 失败 | 环境 / 路径 |
5.2 五个高频坑的展开排查
坑一是“xml 没跟着图走”。现象很直接:图翻转了,框还留在老位置,或者训练时 YOLO 坐标直接报越界。原因基本有两种:要么增强函数里只处理了 image 忘记处理 bbox,要么 opencv 读图失败但程序没报错继续往下跑,把原始坐标原样写进了输出 XML。解决方法是跑之前先备份原始标注,跑完挑一张做了镜像的图,看输出 XML 的xmin是否大于原xmin,水平镜像后这个值必须反过来。YOLO 训练目录里如果出现 0~1 范围外的坐标值,先回来查这一步。
坑二是旋转增强后框大面积包住背景。现象:旋转 15°,框把四个角全包进去了,目标只占框的一小块。原因是旋转后用外接矩形重算坐标必然膨胀,角度越大越严重。解决方法是把旋转角度上限控制在 10° 以内,或者旋转后对框做一次收缩校正。我自己的习惯是超过 10° 的旋转增强单独评估,检测小目标的场景直接不开旋转。
坑三是裁剪或平移后坐标越界。现象:转 YOLO txt 时出现负坐标,或者画框时框线跑到图外面。原因是裁剪后坐标没有限制在图宽高范围内。解决方法是给所有输出坐标做一次 clip,并把面积小于阈值的过小框过滤掉。下面的修复片段我一般会插在转换脚本里:
xmin = max(0, xmin) ymin = max(0, ymin) xmax = min(w, xmax) ymax = min(h, ymax) if (xmax - xmin) < 10 or (ymax - ymin) < 10: print("过滤过小框:", xmin, ymin, xmax, ymax) continue参数说明:10 是像素阈值,小于 10×10 的框在增强后基本失去训练意义;continue 表示跳过这个框,不写进输出标注。这个过滤逻辑对检测和分割都适用,polygon 也可以按新坐标围成的面积做同样过滤。
坑四是增强后模型 loss 不降。现象:数据量翻了几倍,训练反而比只用原始数据还差。原因基本是概率全开,旋转加噪声加亮度加平移同时叠加,样本难度超出模型初始学习能力。解决方法是控制单图增强叠加数量,几何增强最多两张叠加,像素增强最多三张,或者先只开镜像和亮度跑一轮基线,再逐步加难度。
坑五是 opencv 读不出中文路径图片。现象:脚本不报错,但增强结果文件数量明显偏少,输出里全是空白图。原因是cv2.imread对中文路径和中文文件名支持不好,Windows 下最容易踩。解决方法是把工程路径全部改成英文,图片文件名统一改成英文字符,rename_file.py 在这个场景下正好派上用场。
6. 扩增完的验收习惯:坐标检查与可视化一次过
增强完不是直接扔进训练目录就完事,我建议每次扩增后固定走一遍验收脚本。这个习惯是从一次血泪经验里养成的:当时赶进度,扩了五千张图没抽查,训练到第二个 epoch 才发现坐标漂移,整个数据集白扩。从那以后,我每次跑完增强都强制自己先抽 10 张图做可视化验收,再进训练目录。下面的脚本读增强后的 XML,把框画到图上逐张看:
import cv2 import xml.etree.ElementTree as ET def draw_xml(img_path, xml_path): img = cv2.imread(img_path) h, w = img.shape[:2] root = ET.parse(xml_path).getroot() for obj in root.iter("object"): box = obj.find("bndbox") xmin = int(float(box.find("xmin").text)) ymin = int(float(box.find("ymin").text)) xmax = int(float(box.find("xmax").text)) ymax = int(float(box.find("ymax").text)) cv2.rectangle(img, (xmin, ymin), (xmax, ymax), (0, 255, 0), 2) cv2.imshow("check", img) cv2.waitKey(0) # 随机抽 10 张增强后的图和对应的 xml 验证 draw_xml("data/augmented/Camera_1_1.jpg", "data/augmented/Camera_1_1.xml")rectangle的第三个参数是 BGR 颜色,(0, 255, 0)是绿色;第四个参数是线宽,2 像素在 640 分辨率图上比较明显。waitKey(0)表示按任意键看下一张,不填会直接黑屏闪退。抽看的时候重点看两类问题:框是否贴住目标主体、镜像图上框是否在对称位置。
可视化过了之后,再做一步数值检查:XML 转 YOLO txt 时顺手输出归一化中心的比值,如果cx / w或cy / h出现负数或大于 1,说明坐标没同步好,直接毙掉这批增强结果重新跑。分割数据同理,把 JSON 的 polygon 画成 mask,和原图叠加看边缘是否贴合。这两步加起来不到十分钟,却能拦住绝大多数静默错误。
从那以后我每次扩完数据都强制走一遍上面的流程,先看框,再看 mask,最后过数值检查,确认没问题才往训练集里放。别嫌这几分钟麻烦,扩标错了数据,后面赔进去的是整个调参周期的精力。希望帮到你。
本文还有配套的精品资源,点击获取