024、自定义数据集训练:从数据采集到退化模拟的全流程Pipeline
上周有个读者私信我,说用公开数据集训练的超分模型,换到自己拍的监控视频上直接崩了——细节糊成一团,边缘还带伪影。我一看他发的截图,心里就有数了:模型没见过真实世界的退化分布。这其实是超分落地最头疼的问题,公开数据集里的双三次下采样太干净了,真实场景里的模糊、噪声、压缩伪影、运动模糊,一个都没学到。今天这篇笔记,就把我从数据采集到退化模拟踩过的坑,一条龙写清楚。
数据采集:别迷信公开数据集
很多人上来就下载DIV2K、Flickr2K,跑个基线就完事。但你要做的是自定义数据集,就得先想清楚:你的目标场景是什么?如果是监控视频超分,那就去采集监控摄像头拍的原图;如果是手机拍照超分,那就找不同手机拍的照片。我去年做医疗内窥镜超分,直接去医院要了原始内窥镜视频流——注意,是未经压缩的原始流,不是H.264编码后的,因为编码会引入块效应,干扰退化建模。
采集时有个关键点:尽量收集高分辨率原图。别觉得超分任务只需要低分辨率图,你需要HR原图来构造训练对。比如监控场景,找那些能输出1080p甚至4K的摄像头,拍一段原始视频。如果实在拿不到HR原图,那就退而求其次,用多帧融合的方式从视频里重建HR——但这个方法精度有限,我一般只作为备选。
采集数量上,别贪多。500-1000张高质量HR图,比10000张模糊的、有运动模糊的图强得多。我习惯先拍200张,跑一遍退化模拟看看效果,再决定要不要补拍。数据清洗也很重要:把过曝的、欠曝的、对焦不准的图直接删掉,这些图会让模型学到错误的映射关系。
数据预处理:裁剪和配对,别踩这些坑
拿到HR原图后,第一步是裁剪。别直接resize到固定尺寸,那样会丢失高频细节。我一般用随机裁剪,尺寸选256x256或320x320。这里有个坑:如果原图分辨率不高(比如只有720p),裁剪尺寸别太大,否则裁剪出来的块内容太单一,模型学不到丰富的纹理。我试过从720p图里裁512x512,结果大部分块都是天空或墙壁,训练出来的模型对纹理细节毫无感知。
裁剪时还要注意重叠。我习惯用滑动窗口裁剪,步长设为裁剪尺寸的一半,这样相邻块之间有重叠,能保证边缘区域也被充分训练。别用不重叠的裁剪,否则模型在推理时遇到边界区域会表现很差——这是我在做视频超分时发现的,帧与帧之间的边界区域伪影特别明显。
配对环节是另一个重灾区。很多人直接把HR图双三次下采样得到LR图,然后直接训练。但真实场景的LR图不是这么来的——它经过了镜头模糊、传感器噪声、压缩、甚至运动模糊。所以配对时,LR图应该由退化模拟生成,而不是简单的下采样。我后面会详细讲退化模拟,这里先记住:训练对里的LR图,必须模拟真实退化过程。
退化模拟:这才是核心
退化模拟是自定义数据集训练的灵魂。公开数据集里的双三次下采样,在真实场景面前就是个玩具。我总结了一套退化模拟pipeline,按顺序执行:
第一步:模糊。真实镜头都有光学模糊,我用高斯模糊模拟,核大小随机选3-7,sigma随机选0.5-2.0。别固定一个值,否则模型会过拟合到特定模糊程度。这里踩过坑:一开始我只用高斯模糊,结果模型对运动模糊毫无抵抗力。后来加了运动模糊,用随机方向和长度,效果好了很多。
第二步:下采样。这里不是简单的双三次,而是用不同的插值方法随机组合。双线性、双三次、最近邻,甚至Lanczos,每个方法都有不同的频率响应。我写了个随机插值函数,每次训练时随机选一种。别这样写:只用双三次下采样,那你的模型永远学不会处理锯齿边缘。
第三步:噪声。真实传感器噪声是异方差的,暗部噪声大,亮部噪声小。我用泊松-高斯噪声模型,先加泊松噪声模拟光子散粒噪声,再加高斯噪声模拟读出噪声。参数要调:泊松噪声的缩放因子我一般设0.01-0.05,高斯噪声的sigma设0.01-0.03。别用固定噪声水平,否则模型会学会去噪而不是超分。
第四步:压缩伪影。这是最容易被忽略的。真实图像经过JPEG或HEVC压缩后,会有块效应和振铃效应。我用JPEG压缩模拟,质量因子随机选50-95。注意:压缩要在下采样之后做,因为压缩是在低分辨率上进行的。我一开始搞反了,先压缩再下采样,结果模型学到的伪影模式完全不对。
第五步:颜色退化。真实相机的色彩空间和响应曲线跟sRGB不一样。我加了一个随机颜色矩阵变换,模拟不同相机的色彩偏移。这个步骤不是必须的,但如果你的目标场景是特定相机拍的,强烈建议加上。
退化模拟的参数不是固定的,要根据你的目标场景调整。比如监控场景,噪声和压缩伪影更严重,模糊相对较轻;医疗内窥镜场景,模糊和颜色偏移更突出。我一般先拿10张真实LR图,统计它们的模糊核大小、噪声水平、压缩质量,然后反过来调整模拟参数。
数据增强:别让模型学偏了
数据增强是防止过拟合的重要手段,但超分任务的数据增强有特殊性。常规的翻转、旋转、颜色抖动都可以用,但要注意:LR和HR图要同步做相同的变换。我见过有人只对HR图做翻转,LR图不变,结果模型学到的映射关系完全错乱。
还有一个容易忽略的点:亮度对比度增强。真实场景的光照变化很大,我习惯对HR图做随机亮度调整(-0.2到0.2),然后LR图用同样的参数调整。别单独调整LR或HR,否则模型会学到错误的亮度映射。
裁剪增强也很重要。我每次训练时,从HR图里随机裁剪256x256块,然后生成对应的LR块。这样每个epoch看到的训练样本都不一样,相当于隐式地做了数据增强。但要注意:裁剪位置要随机,别固定在一个区域,否则模型会过拟合到特定纹理。
训练策略:从粗到细
数据集准备好后,训练策略也有讲究。我习惯分两阶段训练:第一阶段用简单的双三次下采样做预训练,让模型先学会基本的超分能力;第二阶段用自定义的退化模拟pipeline,微调模型适应真实退化。
为什么这么做?因为退化模拟的参数空间太大,模型一开始很难收敛。先用干净数据让模型站稳脚跟,再引入复杂退化,训练会更稳定。我试过直接上退化模拟,结果loss震荡得很厉害,模型根本学不进去。
学习率也要调整。第一阶段用1e-4,第二阶段降到1e-5。别用固定学习率,否则第二阶段微调时,模型容易在局部最优附近震荡。我用余弦退火调度,效果比阶梯下降好。
batch size方面,如果退化模拟的参数是随机的,batch size别太小。我一般设16,这样每个batch里能看到多种退化模式,模型不会过拟合到某一种退化。如果显存不够,可以减小裁剪尺寸,别减小batch size。
验证:别只看PSNR
训练过程中,验证集的选择很关键。别用公开数据集验证,要用你自己采集的真实LR-HR对。如果没有真实HR图,可以用多帧重建的方式生成伪HR,但精度有限,只能作为参考。
验证指标也别只看PSNR和SSIM。PSNR对像素级误差敏感,但真实场景里,人眼更关注纹理细节和边缘锐利度。我习惯加上LPIPS(感知相似度)和NIQE(无参考图像质量评估)。LPIPS能反映感知质量,NIQE能评估自然度。如果PSNR高但LPIPS也高,说明模型只是把像素对齐了,但纹理是假的。
还有一个实用技巧:在验证集上做主观评估。把模型输出的LR、HR和真实HR放在一起,让几个人盲评。别只看数值,数值高不一定视觉好。我见过一个模型PSNR比另一个高0.5dB,但视觉上明显更模糊,因为它在平滑区域压低了噪声,但牺牲了纹理。
个人经验性建议
写到最后,说点实在的。自定义数据集训练,最耗时的不是写代码,而是调退化模拟的参数。我建议你准备一个“退化参数调优脚本”,每次改参数后,生成一批LR-HR对,然后肉眼对比真实LR图,看模拟的LR图在模糊程度、噪声模式、压缩伪影上是否一致。这个过程可能要迭代几十次,但值得。
另外,别把所有希望都寄托在退化模拟上。如果条件允许,尽量采集真实LR-HR对。比如用两台相机同时拍同一个场景,一台低分辨率,一台高分辨率。这样得到的训练对最真实,模型效果也最好。退化模拟只是退而求其次的方案。
最后,别忘了记录实验日志。每次改参数、换数据增强策略、调学习率,都记下来。我踩过最大的坑就是:调了一周参数,模型效果变好了,但忘了之前用的什么参数,结果回退不了。现在我用wandb或tensorboard,每次实验都自动记录,方便回溯。
自定义数据集训练,本质上是让模型理解你的目标场景的退化分布。没有万能的数据集,只有最适合你场景的数据集。希望这篇笔记能帮你少走弯路,下次模型崩了,先检查退化模拟对不对,别急着换网络结构。