做目标检测那两年,最让我难受的一次事故,是用imgaug做数据扩增后,训练集的标注框整体向右偏了十几个像素。模型 loss 看着在降,mAP 却一直上不去,排查了整整两天才发现:图像用它做了一次仿射变换,标注框却用另一个增强器实例又做了一次。imgaug这个库就是这样,功能足够全,全到你能在一行代码里塞进翻转、旋转、裁剪、加噪、起雾,但它的“全”建立在一条隐式约定上——图像和标注必须共享同一次随机采样。这条约定一旦被打破,程序不报错,标签却悄悄错位,等你发现时已经浪费了大量训练时间。
这篇内容围绕imgaug这个图像增强库展开,讲的不是 API 手册式的罗列,而是一个实际用过它做关键点检测、目标检测和分割任务的人,会关心的几件事:它的设计边界在哪里,装环境时会被哪些版本问题绊住,随机状态怎么管才可复现,几何与像素两类增强各自的参数该怎么选,标注同步怎么做才不出错,以及上生产前必须处理好的批处理与持久化问题。适合已经写过几行 Python、准备把数据扩增流程做扎实的读者,也适合被标注错位坑过、想回头补基础的人。
1. imgaug解决的是“标签同步”,不是“图像变形”
1.1 一个增强器就是一个可组合的函数对象
很多人第一次接触imgaug,会觉得它写起来啰嗦:要构造增强器、要包Sequential、还要处理to_deterministic()。相比之下,直接把图像丢进一个transforms.Compose里要简单得多。但这种“啰嗦”恰恰是它的设计取向——每个增强器都是一个带独立随机状态的对象,而不是一个纯函数。这个差别在只处理图像时看不出来,一旦你手里还有关键点、边界框、分割掩码、热力图,它就是决定性的。
在imgaug的模型里,一次增强是“同一个随机决策作用于一组相关数据”。Augmenter基类提供了augment_images、augment_keypoints、augment_bounding_boxes、augment_segmentation_maps、augment_heatmaps等一组方法,它们共享同一个内部随机状态。你在同一个确定性实例上依次调用这几个方法,得到的就是同一套变换参数下的结果。这个设计让“图像转了 12 度”和“框也转了 12 度”变成一件不需要手工对齐的事。
它的组合能力也来自这个抽象。Sequential按顺序串联,SomeOf从候选里随机挑若干个执行,OneOf每次只执行一个,Sometimes按概率决定是否执行,WithProbability给任意增强器加概率外壳。这几种组合器可以嵌套,能拼出相当复杂的策略,比如“50% 概率翻转,然后从旋转、缩放、平移里随机选两个执行”。这种表达力是它相对轻量增强方案最大的优势,尤其是在需要复现论文里那套复杂扩增策略的时候。
代价也很明确:所有调度都发生在 Python 层。单个增强器处理一张 512×512 的图可能只要几毫秒,但叠上七八个增强器,再加上关键点和框的同步处理,单张图的耗时很容易上到几十毫秒。十万张图的数据集,单进程跑一轮就是几十分钟。这是理解它适用边界的关键前提。
1.2 该用它和不该用它的分界线
我自己的判断标准很朴素:标注越复杂、数据量越小,越该用它;标注越简单、数据量越大,越该换方案。
适合的场景包括:关键点检测(人脸、姿态、工业缺陷定位),需要关键点和图像严格同步;小样本的医学图像分割,一张图配一个掩码,扩增时掩码必须用最近邻插值;目标检测里框数量不多但需要精细控制的场合,比如遥感小目标;以及任何需要“天气效果”“弹性形变”“透视变换”这类复杂增强的研究型任务。这些场景里,扩增代码写错一次的代价,远高于多花几十分钟跑数据。
不太适合的场景是纯图像分类的大规模训练。分类任务只需要图像和标签,没有几何标注要同步,用更轻量的方案在吞吐上会舒服很多。我做过一个粗略的对比,同一台机器、同一批 224×224 的图,一个包含翻转、随机裁剪、颜色抖动的组合,用imgaug单进程处理大约是每秒一百多张,而用底层基于 C++ 加速的同类库能到每秒六七百张,差距接近五倍。数据量上到百万级,这个差距就是几小时和几十小时的差别。
还有一类场景要特别小心:实时性要求高的在线扩增。imgaug的天气效果、弹性变换、分段仿射都很重,单张图可能几十毫秒起步,放进训练循环里会直接把 GPU 饿死。这类增强更适合离线预生成,或者只在小比例样本上启用。
2. 安装环节的版本泥潭与锁定策略
2.1 依赖链条比想象中长
imgaug不是一个自包含的库,它的增强器实现大量依赖底层数值和图像处理库。装它的时候,pip会顺带拉进来一串东西:numpy、scipy、opencv-python、scikit-image、imageio、Shapely、matplotlib、six。这几个包各自的版本演进节奏不一样,任何一个跳到大版本,都可能让imgaug的某个增强器在运行时炸掉。
我目前在用的组合是 Python 3.9 +imgaug 0.4.0,配套锁定numpy 1.23.5、opencv-python 4.5.5.64、scipy 1.9.3、scikit-image 0.19.3、imageio 2.31.1、Shapely 1.8.5。这套组合不是最新的,但它在我手里跑过关键点、检测框、分割图三条链路,稳定性有保障。下面这张表是我踩过坑之后整理出来的对应关系,可以直接拿去写requirements.txt。
| 依赖包 | 推荐版本 | 不锁定的风险 |
|---|---|---|
| numpy | 1.23.5 | 1.24 起移除了np.bool等别名,导入即报错 |
| opencv-python | 4.5.5.64 | 新版轮廓接口行为变化,尺寸类增强器可能异常 |
| scipy | 1.9.3 | 部分插值路径依赖旧接口 |
| scikit-image | 0.19.3 | 新版移除了若干旧函数 |
| Shapely | 1.8.5 | 2.x 的几何对象接口有变动,多边形增强不稳 |
| imageio | 2.31.1 | 与 ffmpeg 插件版本强相关,读写视频时容易出问题 |
有一点要说明:imgaug上游的更新节奏这些年明显放慢了,最后几个版本之间的改动不大,这意味着“锁版本”是比“追新版”更务实的策略。新项目里我一般会在requirements.txt里写死这几个版本,并且在 CI 里跑一个冒烟脚本,把翻转、仿射、裁剪、加噪各跑一遍,确认没有导入错误再往下走。
2.2 三类高频报错的实际处理
第一类是AttributeError: module 'numpy' has no attribute 'bool'。这是numpy 1.24移除旧别名导致的,imgaug内部还有些地方在用np.bool、np.float这类写法。处理方式不是去改库源码,而是把numpy降到 1.23.x,顺便检查一下opencv-python和scipy有没有被pip顺手升上去。
第二类是导入imgaug.augmenters.size时报ValueError: not enough values to unpack,或者调用Resize、CropToAspectRatio时抛cv2.error。这多半是opencv-python版本太新,findContours的返回值结构改变了。解决办法是把opencv-python降到 4.5.x,或者干脆绕开这些依赖轮廓计算的增强器,用Affine的scale参数自己实现缩放。
第三类是Shapely相关的AttributeError,通常出现在使用多边形、线串增强的时候。Shapely 2.x把一部分几何对象的接口重写了,imgaug里的多边形处理代码没有完全跟上。锁定Shapely<2.0是最省事的做法。
提示:如果你的环境里还装了别的计算机视觉库,注意它们对
opencv-python的版本要求可能和imgaug冲突。稳妥的做法是给扩增流程单独建一个虚拟环境,把训练主环境和数据预处理环境隔开,用离线生成的方式对接。
3. 随机状态管理:imgaug最容易翻车的一环
3.1 Augmenter、Sequential与确定性副本的关系
imgaug里的随机性有三层:增强器构造时的random_state参数、调用to_deterministic()生成的确定性副本、以及每次调用augment_*时消耗的随机数。理解这三层的关系,比记住任何一个增强器的参数都重要。
每个Augmenter对象内部持有一个随机数生成器。你构造iaa.Fliplr(0.5)时如果不指定random_state,它就自己初始化一个,这意味着跨进程、跨运行的结果不可复现。指定了random_state=42之后,这个增强器每次采样会沿着一条确定的序列走,但要注意——它是“有状态的”,第一次调用和第二次调用拿到的随机数不一样。
to_deterministic()做的事情,是从当前随机状态里抽一个种子,生成一个随机状态被冻结的副本。这个副本在被反复调用时,会用同一套参数。这正是同步图像和标注所需要的:你先拿副本处理图像,再用同一个副本处理关键点和框,两者必然对齐。
但这里有个极易被忽略的点:确定性副本的“确定性”只对同一批数据有效。如果你在一个训练循环里,构造一次seq_det然后对每个 batch 都调它,那所有 batch 用的都是同一套变换参数,数据多样性直接归零。正确的做法是在每个 batch 处理前重新调一次to_deterministic()。这个错误我在早期项目里犯过,表现是训练 loss 下降得异常平滑,模型几乎没有泛化能力。
3.2 一份可直接复用的可复现骨架
下面这段是我现在项目里的标准写法,图像、关键点、检测框三路一起处理,种子可控,每个 batch 重新采样变换参数。
import imgaug as ia import imgaug.augmenters as iaa import numpy as np def build_augmenter(seed=42): seq = iaa.Sequential([ iaa.Fliplr(0.5), iaa.Flipud(0.2), iaa.Affine( rotate=(-15, 15), scale=(0.9, 1.1), translate_percent=(-0.05, 0.05), mode="constant", cval=0, order=1, ), iaa.Sometimes(0.3, iaa.GaussianBlur(sigma=(0.0, 1.0))), ], random_order=True, random_state=seed) return seq seq = build_augmenter(seed=2024) for batch in dataloader: batch_images = [item["image"] for item in batch] batch_kps = [item["keypoints"] for item in batch] batch_bbs = [item["bboxes"] for item in batch] seq_det = seq.to_deterministic() images_aug = seq_det.augment_images(batch_images) kps_aug = seq_det.augment_keypoints(batch_kps) bbs_aug = seq_det.augment_bounding_boxes(batch_bbs)几个细节值得展开。random_order=True让Sequential内部的执行顺序也随机化,这在多增强器组合时能减少“固定顺序”带来的偏差,但也会让调试变难,建议在排查问题时先关掉。order=1表示双线性插值,处理图像没问题,但如果同一个Affine还要处理分割掩码,必须换成order=0,否则类别值会被插值成不存在的中间值。
如果你需要跨进程严格复现,比如在多机训练里保证每台机器的扩增结果一致,那to_deterministic()不够用,得用iaa.Sequential(..., random_state=ia.new_random_state(seed))配合每个 batch 基于batch_idx派生种子。我一般写一个seed = base_seed + batch_idx * 10000的简单规则,够用且好排查。
注意:
to_deterministic()返回的是副本,原对象不受影响。如果你写了seq.to_deterministic()却没接收返回值,等于什么都没做,这是另一个高频错误。
4. 几何增强的参数地图与插值陷阱
4.1 Affine是几何增强的主力,也是参数最多的一个
iaa.Affine能覆盖旋转、缩放、平移、剪切、翻转组合,是几何增强里最值得花时间吃透的一个。它的参数设计有个容易混淆的地方:平移有两个版本,translate_px按像素给值,translate_percent按图像边长的比例给值。我一般用后者,因为数据集中图像尺寸不一致时,按比例平移更符合直觉,不会出现小图上平移过大、大图上平移几乎看不见的情况。
scale支持两种写法:一个元组(0.9, 1.1)表示各向同性缩放,每个样本在这个区间里采一个值;也可以传字典{"x": (0.9, 1.1), "y": (1.0, 1.0)}分别控制横纵方向,做形变增强的时候很有用。rotate同理,传区间就在区间内采样。
真正容易出事的是mode、cval、order这三个参数。mode决定边界外怎么填充,常见取值有constant(填常量)、reflect(镜像)、edge(复制边缘)、wrap(循环)。做检测任务我倾向constant配cval=0,因为填充区域是纯背景,语义清晰;做分类任务可以用reflect,避免大片黑色影响模型对图像整体亮度的判断。order是插值阶数,0 是最近邻,1 是双线性,3 是双三次。图像用 1 或 3 都行,分割掩码必须用 0,这一点没有商量余地。
还有一个参数fit_output,默认False。设成True时,变换后的图像会被扩展以容纳所有内容,输出尺寸会变。这个选项对分类任务很友好,但对检测和关键点任务是个陷阱——图像尺寸变了,标注坐标的参考系也跟着变,后续和原图对比或者做可视化时极容易搞混。我的建议是检测和关键点任务一律保持False,让变换后的内容被裁到原尺寸内,接受一部分信息丢失。
4.2 Crop与Resize的组合顺序会影响标签有效性
裁剪和缩放经常一起用,但顺序不同,结果差别很大。先缩放再裁剪,裁剪窗口是相对缩放后图像的,框落在窗口外的概率更高;先裁剪再缩放,裁剪位置相对原图,框被裁掉的比例更容易预估。
iaa.Crop(px=(0, 32))是按像素裁剪,四个边各自在区间内采样;iaa.Crop(percent=(0, 0.1))按比例裁剪。用比例版本时要注意,裁剪比例过大会让原本就小的目标彻底消失。我在一个小目标检测任务里用过percent=(0, 0.15),结果标注框面积小于 32×32 的样本里,大约三成在裁剪后完全丢失,而框对象还在,只是退化成了零面积。这种样本喂进网络会直接污染 loss,所以裁剪之后必须加一步框过滤。
import imgaug.augmenters as iaa seq = iaa.Sequential([ iaa.Crop(percent=(0, 0.1)), iaa.Resize({"height": 512, "width": 512}), ])iaa.Resize支持传固定尺寸、传比例、传字典分别指定高宽。传固定尺寸时会改变长宽比,如果你的数据里目标形状本身有语义(比如遥感里的车辆朝向),拉伸会让形状失真,这时候要改用iaa.Resize配合iaa.PadToFixedSize,先等比缩放再补边,保持几何关系。
iaa.PiecewiseAffine和iaa.ElasticTransformation属于形变类,适合医学图像、手写字符这类需要模拟组织形变或书写变形的场景。它们的参数scale控制形变幅度,值越大形变越剧烈。这两个增强器很重,单张图耗时可能是Affine的好几倍,而且形变过强会让标注框的轴对齐近似严重失真,我一般把scale控制在 0.02 到 0.05 之间,并且只在小比例样本上启用。
5. 像素级增强与天气效果的使用前提
5.1 噪声、模糊、对比度的参数区间怎么定
像素级增强看起来比几何增强简单,实际上更容易把图像改坏。iaa.Add是整体加减一个值,iaa.Multiply是整体乘一个系数,iaa.ContrastNormalization做对比度归一化。这三个都直接作用于像素值,对uint8输入会自动裁剪到 0 到 255,所以给参数时不要给太宽的范围。iaa.Add((-40, 40))已经能明显改变亮度,(-100, 100)就会让大片区域直接饱和成纯黑或纯白,细节全丢。
iaa.GaussianBlur(sigma=(0.0, 3.0))里的sigma是高斯核标准差,超过 2.0 之后 512 尺寸的图上小目标边缘就开始糊了。我一般在检测任务里用(0.0, 1.5),分类任务可以放到(0.0, 2.5)。iaa.MotionBlur模拟运动模糊,参数是核大小和角度,适合车载、监控类场景。iaa.CoarseDropout做随机遮挡,模拟物体被部分遮挡的情况,参数size_percent控制遮挡块占图像的比例,per_channel控制是否每个通道独立遮挡。这个增强器对检测任务很有价值,但对分割任务要慎用,因为遮挡区域在掩码里没有对应标注,模型会学到矛盾信号。
iaa.SaltAndPepper和iaa.GaussianNoise都属于加性噪声。前者按比例随机把像素置为极值,后者叠加高斯噪声。用的时候要注意输入数据类型:uint8输入下噪声幅度以像素为单位,float32输入下以归一化后的数值为单位,同一个参数值在两种类型下效果差很多。我的习惯是统一在uint8空间做增强,最后再转float32归一化,这样参数直觉更稳定。
5.2 天气效果很强,但不是所有任务都能用
imgaug提供了一组天气和自然现象模拟:iaa.Rain、iaa.Snow、iaa.Fog、iaa.Clouds、iaa.SunFlare,还有一组基于图像退化的iaa.imgcorruptlike系列。这些增强器的视觉效果很抓眼球,但使用前提必须先想清楚。
第一,它们假设输入是 RGB 顺序。如果你用 OpenCV 读图,默认拿到的是 BGR,直接喂给天气增强器,天空和植被的颜色会反过来,雾的效果会变成奇怪的偏色。要么在读图后转 RGB,要么在增强前加一步iaa.ChangeColorspace。
第二,它们对图像亮度和动态范围很敏感。iaa.Fog在大面积高亮背景上几乎看不出效果,在暗图上又会把整体压得灰蒙蒙。iaa.Rain的雨滴密度和长度参数需要根据图像分辨率调整,同样的参数在 256 尺寸和 1024 尺寸上视觉差异巨大。
第三,也是最重要的一点:天气效果会改变目标的视觉特征,但不会改变标注。一片浓雾盖住了一辆车,框还在,模型被要求从几乎看不见的区域回归出准确位置,这会拉低收敛质量。我的做法是把天气类增强的概率压到 0.05 到 0.1 之间,并且只在训练后期、模型已经具备基础检测能力时启用,或者干脆只用于鲁棒性评测集,不参与训练。
iaa.imgcorruptlike系列需要额外安装imagecorruptions包,它提供的 15 种退化(高斯噪声、散粒噪声、脉冲噪声、散焦模糊、玻璃模糊、运动模糊、雾、霜、雪、雾霾、亮度、对比度、弹性形变、像素化、JPEG 压缩)在研究型评测里很有用,但同样要注意它假设输入为 RGB。
6. 标注同步:关键点、框、掩码的完整处理链
6.1 三类标注容器的构造方式与注意事项
imgaug里所有标注都要先包成特定对象才能参与增强。关键点用ia.KeypointsOnImage,构造时需要一个shape参数,也就是对应图像的(H, W, C)。这个 shape 不是可选的装饰,它决定了关键点坐标的合法范围,也决定了翻转、裁剪这类操作怎么算新坐标。
import imgaug as ia kps = ia.KeypointsOnImage([ ia.Keypoint(x=120, y=80), ia.Keypoint(x=200, y=95), ], shape=(480, 640, 3))边界框用ia.BoundingBoxesOnImage,每个框是ia.BoundingBox(x1, y1, x2, y2, label=...)。构造时如果框有部分落在图像外,imgaug会打印一条警告,提示存在越界框。这条警告不要忽略,它往往意味着你的标注数据本身有问题。
bbs = ia.BoundingBoxesOnImage([ ia.BoundingBox(x1=30, y1=40, x2=180, y2=220, label="target"), ], shape=(480, 640, 3))分割掩码用ia.SegmentationMapsOnImage(arr, shape=...),其中arr的最后一维是类别通道数。单类别分割用(H, W, 1),多类别用(H, W, N)的 one-hot 形式。这里有个细节:imgaug内部会把掩码存成int32,增强时用最近邻插值,所以类别值不会被插值成小数。
6.2 越界、裁剪与顺序问题:三类实际会遇到的故障
故障一:翻转后关键点跑到图像外。水平翻转时,如果关键点原本在x=0附近,翻转后坐标会算到x=-1之类的负值。imgaug不会自动裁剪关键点,这些越界点会原样保留。下游如果按坐标去原图上取值,就会索引越界或者取到错误位置。
处理方式是在增强后做一次过滤:
def filter_visible_keypoints(kps, height, width): valid = [] for kp in kps.keypoints: if 0 <= kp.x < width and 0 <= kp.y < height: valid.append(kp) else: valid.append(ia.Keypoint(x=np.nan, y=np.nan)) return ia.KeypointsOnImage(valid, shape=kps.shape)用 NaN 占位而不是直接删除,是为了保持关键点数量不变,方便后续按固定索引取点。如果下游不支持 NaN,就得在数据集层面维护一个可见性掩码,这部分工作没有捷径。
故障二:裁剪后边界框退化。裁剪会把框的一部分切掉,imgaug会生成新的轴对齐框,但可能出现宽或高小于等于零的退化框。这类框必须在进网络前过滤掉:
def clean_bboxes(bbs, min_size=8): kept = [] for bb in bbs.bounding_boxes: bb = bb.clip_out_of_image() if (bb.x2 - bb.x1) >= min_size and (bb.y2 - bb.y1) >= min_size: kept.append(bb) return ia.BoundingBoxesOnImage(kept, shape=bbs.shape) bbs_aug = clean_bboxes(bbs_aug)clip_out_of_image()把越界部分裁到图像边界内,这一步最好在增强之后立刻做。remove_out_of_image()是直接删除越界框,适合做更激进的过滤。
故障三:仿射变换后框被放大。Affine旋转一个矩形框,真实形状是平行四边形,但BoundingBox只能表示轴对齐矩形,所以库会用能包住平行四边形的最小轴对齐矩形来近似。旋转角度越大,框的“膨胀”越明显。15 度旋转下,一个 100×100 的框可能膨胀到 120×120 左右。如果你标注的是精确的物体边界,这个膨胀会让回归目标变得不准确。缓解办法是控制旋转角度范围,或者改用关键点标注(比如标注四个角点)而不是检测框。
7. 批处理、多进程与配置持久化的工程细节
7.1 batch接口与pool的适用场景
imgaug提供了批量处理接口augment_batches,配合ia.Batch对象使用。相比自己写循环,批量接口在内部做了一些优化,能减少一部分 Python 层的重复开销。但要注意它仍然是在单进程里顺序执行,提速有限。
真正的提速手段是augmenter.pool(processes=N),它把增强器复制到多个进程里并行处理。这个方法在 Linux 上工作良好,在 Windows 上需要把调用放在if __name__ == "__main__":保护块里,否则会因为进程启动方式不同而递归创建子进程。在 Jupyter 里也经常出问题,表现是进程挂起或者报序列化错误。
更关键的一个坑是:pool()之后不能再调to_deterministic()来保证图像和标注同步。多进程各自持有自己的随机状态,你没法保证处理图像的那个进程和处理框的那个进程采到同一套参数。所以如果你的任务需要标注同步,只有两条路:要么在单进程里用确定性副本处理完图像和标注,要么把图像和标注打包成同一个Batch对象丢给pool()。后者是正确的做法:
import imgaug as ia import imgaug.augmenters as iaa aug = iaa.Sequential([...]).pool(processes=4) batches = [ia.Batch(images=[img], keypoints=[kps], bounding_boxes=[bbs]) for img, kps, bbs in dataset] batches_aug = list(aug.augment_batches(batches, background=False))background=False表示阻塞等待全部完成,改成True会返回一个可以迭代的对象,边处理边取结果,适合数据量大、内存吃紧的场景。
7.2 保存配置比保存代码更可靠
扩增策略一旦定下来,最好把它持久化成文件,而不是散落在代码里。imgaug支持两种持久化方式:iaa.save写 HDF5 文件,以及augmenter.to_dict()转成 JSON 结构的字典。
import imgaug.augmenters as iaa seq = iaa.Sequential([ iaa.Fliplr(0.5), iaa.Affine(rotate=(-15, 15)), ]) iaa.save(seq, "augmenter.h5") loaded = iaa.load("augmenter.h5")HDF5 方式对增强器类型有要求,Lambda这类包含任意函数的增强器没法序列化,会直接报错。JSON 方式更通用,但读回来之后需要自己重建对象。我的做法是给每个实验目录存三样东西:augmenter.h5、一份记录版本号的requirements.txt、一份把关键参数写成注释的augmenter.md。这样半年后回头复现实验,不用去猜当时sigma到底给的是 1.0 还是 1.5。
还有一个小技巧:用augmenter.to_dict()打印出来的结构,可以反过来当配置模板用。先在代码里调好增强器,导出字典,之后用字典驱动构造,参数调整就不用改代码逻辑了。
8. 一张清单:我踩过的坑和调参习惯
下面这张表是我这两年在imgaug上踩过的坑里,反复出现、值得写进团队文档的那部分。
| 现象 | 根因 | 处理方式 |
|---|---|---|
| 训练 loss 平滑下降但 mAP 很低 | 图像和框用了不同的增强器实例 | 统一用同一个to_deterministic()副本处理 |
| 所有 batch 的增强效果一模一样 | 只在循环外调了一次to_deterministic() | 每个 batch 开头重新调用 |
| 云端训练结果和本地不一致 | 未固定随机种子,依赖版本不同 | 构造器传random_state,锁死依赖版本 |
| 分割掩码出现不存在的类别值 | 增强时用了双线性插值 | 掩码相关增强一律order=0 |
| 雾效果让天空变绿 | OpenCV 读图是 BGR,增强器假设 RGB | 读图后立即转 RGB |
| 裁剪后大量零面积框进网络 | 未过滤退化框 | 增强后clip_out_of_image加最小尺寸过滤 |
| 多进程处理后标注全部错位 | pool()与确定性副本混用 | 改用Batch对象统一投递 |
| 内存占用翻倍 | augment_images一次性返回全部结果 | 分批处理,配合background=True流式取 |
调参这块,我自己的习惯是先把所有增强器的概率和幅度压到最低,跑一轮看基线指标,然后按“几何优先、像素其次、天气最后”的顺序逐项放开。每放开一项,观察验证集指标的变化幅度。如果某一项让指标掉超过两个点,基本可以判断这个增强和当前任务的数据分布不匹配,直接砍掉,不要抱侥幸心理。
还有一个经验:增强策略的复杂度要和数据量成反比。数据量几千张的时候,可以放心上七八种增强组合,因为模型见过的原始样本少,多样性主要靠扩增提供。数据量上到十万级,扩增策略反而要收,过强的增强会让模型学不到真实分布,尤其是检测任务里框的回归会变得很不稳定。
最后分享一个排查技巧:遇到标注错位,不要盯着增强器参数看,先做一个可视化脚本,把原图、增强后的图、原标注、增强后的标注画在同一张画布上,逐样本看。我遇到过的所有同步问题,用这个方法都能在十分钟内定位到具体是哪一步出的错,比读代码快得多。画的时候记得把框的坐标轴和图对齐,不要用错 scale。