
1. 先搞清楚图像修复的数据集版图做图像修复Image Restoration这一行算法层面的创新固然吸引眼球但真正决定你能不能复现出一篇论文、能不能把模型跑出论文里那个数字的往往是数据集这一环。我见过太多人在群里问为什么我的 PSNR 只有 28论文里写 32聊到最后发现不是网络结构写错了而是测试集用了不同的预处理方式或者训练时把 DIV2K 的验证集混进了训练集。图像修复、算法、数据集这三件事在工程上其实是绑死的任务定义决定了退化模型退化模型决定了数据怎么造数据怎么造决定了你能不能用某个现成的开源数据集。这篇文章我打算把 Image Restoration 这条线上主流的数据集从考试卷到训练粮仓再到真实场景采集集梳理一遍同时把退化建模、评测协议、目录组织、DataLoader 实现这些落地细节一起写出来。适合刚入门做超分/去噪/去模糊的同学也适合已经在跑实验但总觉得哪里不对劲、想回头补一补数据基础的人。我不会只丢一堆链接而是把我自己在搭管线时踩过的坑、参数为什么这么定、哪些数据集现在还在用、哪些已经基本被淘汰都尽量讲清楚。1.1 Image Restoration 的六个子任务与数据需求差异很多人把图像修复当成一个任务其实它是一族任务的统称。常见的至少有六类图像去噪Denoising、超分辨率Super-Resolution, SR、去模糊Deblurring、去雨去雾去雪Deraining / Dehazing / Desnowing、压缩伪影去除Compression Artifact Removal、低光增强Low-Light Enhancement。这六类任务对数据集的要求差别很大不是随便一个配对数据集就能通用。去噪和超分往往可以用同一批高清图来合成配对数据因为退化过程可以建模得很干净去模糊则高度依赖真实相机抖动核合成核和真实核之间的域差非常明显去雨去雾这类任务雨条纹和雾的物理模型复杂纯合成数据训练的模型放到真实照片上经常糊成一片。所以你在选数据集之前先要问自己一句我这个任务合成数据和真实数据哪个更接近我的目标场景。另一个常被忽略的点是数据规模与任务难度的匹配。去噪任务因为有很强的局部先验BSD68 这种只有 68 张图的小测试集也能拉开模型差距但超分到了 ×4 这种高倍率模型需要大量纹理多样性Set5 那种 5 张图的测试集只能作为快速冒烟测试正式对比还是得挂到 Urban100、Manga109 上跑。1.2 中文语境下容易混淆的图像修复这一点必须单独说。中文里图像修复经常同时指代两个完全不同的任务一个是 Image Restoration输入是有退化的图输出是清晰图输入输出尺寸一致属于低级视觉另一个是 Image Inpainting输入是缺失了一块区域的图输出是把洞填上的图和 GAN、扩散模型关系更近。你在搜图像修复数据集的时候很容易搜到 Places2、CelebA-HQ、FFHQ 这些 inpainting 常用数据集然后拿去训超分模型结果发现配对关系根本对不上。判断方法很简单Restoration 的数据集是成对的paired有 GT 和 LQ 两张图尺寸一致Inpainting 的数据集通常只有一张干净图mask 是训练时随机生成的或者数据集额外提供 mask。看目录结构就能区分Restoration 数据集一般是HR/和LR/两个平行文件夹Inpainting 数据集一般只有一个images/加一个masks/。我在早期项目里就被这个坑过一次拿了一个标注为图像修复的包解压后发现只有单张图还在纳闷 LR 在哪后来才发现是 inpainting 的数据。所以后面我在项目里做的第一件事就是把任务定义和数据集的对应关系写成一张表贴在文档里避免团队里其他人再踩。2. 配对小图基准Set5 到 Urban100 这类考试卷这一批数据集在圈内常被叫做 benchmark规模都很小从 5 张到 100 张不等但几乎所有超分论文都必须在上面报数因为它们已经形成了事实上的可比口径。理解它们的来历和局限比记住名字更重要。2.1 五个老牌基准的来历与定位Set55 张图来自 Bevilacqua 等人 2012 年的工作包含 baby、bird、butterfly、head、woman 五张。它的定位纯粹是极小规模快速验证因为只有 5 张跑一次几秒钟任何模型改动后都可以先在上面看一眼有没有崩。Set1414 张图Zeyde 等人 2010 年提出的稀疏编码超分工作里用的多样性比 Set5 好一些同样属于小规模验证集。BSD100从 Berkeley Segmentation Dataset 的 500 张里挑了 100 张覆盖自然场景较多纹理比 Set5 丰富。Urban100100 张城市建筑图Huang 等人 2015 年提出。它的价值在于大量规则重复结构——窗格、砖墙、栏杆这些结构对超分模型来说是照妖镜因为一旦模型只会做平滑插值这些规律纹理立刻会出现混叠和振铃PSNR 掉得特别明显。Manga109109 张日本漫画页主要用于 ×2 和 ×4 超分以及漫画专用模型评测。它的线条是纯黑白的锐利边缘比自然图像更考验边缘重建能力。这几个数据集现在基本只用于测试不用于训练原因也很直接图太少训练一定过拟合。但它们的另一面价值在于它们是一把标尺。你如果只报自己的 10000 张测试集结果别人没法判断你的模型到底强不强挂上 Set5 和 Urban100同行一眼就能横向对比。2.2 这些基准现在还能不能用能用但要用对。我的做法是分两级Level 1 冒烟测试用 Set5 Set14模型改完先跑这两个几十秒出结果看趋势对不对Level 2 正式对比用 Urban100 BSD100 Manga109三个都报避免有人质疑你挑数据集。真正发论文或者做严肃对比实验还会加上 DIV2K 的验证集 100 张。需要提醒的是这几个基准的 GT 分辨率普遍不高Urban100 原图大概在 1000×1000 上下Manga109 页面尺寸也不大。如果你做的是 ×8 甚至更高倍率的超分这些图下采样后 LR 会小到只有几十个像素边缘信息几乎不可逆指标意义会打折。这种情况下更适合用 DIV2K 里裁出来的 2K 图或者干脆用 LSDIR 这类高分辨率大集。2.3 基准数据集的参数速查数据集图像数量典型用途分辨率量级主要评测倍率Set55冒烟测试约 200–500 px×2/×3/×4/×8Set1414冒烟测试约 200–700 px×2/×3/×4BSD100100对比测试约 300–500 px×2/×3/×4Urban100100对比测试约 700–1000 px×2/×3/×4/×8Manga109109对比测试约 800–1200 px×2/×4提示这些基准在学术界有固定的 bicubic 下采样协议如果你自己重新生成 LR一定要和公开的 LR 包对齐否则报出来的数字没有可比性。3. 大规模训练集DIV2K、Flickr2K、DF2K 与后起之秀训练集才是真正决定模型上限的东西。超分和去噪领域最常用的三件套是 DIV2K、Flickr2K 和它们的合并版 DF2K最近两年 LSDIR 也开始被大量使用。3.1 DIV2K 为什么成了事实标准DIV2K 全称 DIVerse 2K resolution image dataset是 NTIRE 2017 挑战赛放出来的共 1000 张 2K 分辨率长边 2048 左右的高质量图划分是 800 训练 / 100 验证 / 100 测试。它成为标准的几个原因很实在第一分辨率足够高。2K 图裁 48×48 或 64×64 的 patch纹理多样性远比 BSD 系列丰富模型不容易记住全局布局。第二内容多样性好涵盖人像、风景、建筑、动物、静物不至于让模型偏科。第三赛道配套齐全NTIRE 每年基于它组织比赛退化设置、评测脚本、下采样方式都是公开且被反复验证过的减少了各跑各的的混乱。DIV2K 的 LR 版本官方提供了 ×2、×3、×4 三套用的是标准的 bicubic 下采样对应 MATLAB 的imresize。这个细节非常重要MATLAB 的 bicubic 和 OpenCV 的INTER_CUBIC并不是完全等同的特别是涉及抗混叠时。如果你用官方 HR 自己下采样再用 Python 的cv2.resize生成的 LR 和官方包会有细微差别测试时用官方 LR 就会对不上。我一般建议直接下载官方 LR 包别自己造。3.2 DF2K、LSDIR 与更现代的替代方案Flickr2K是从 Flickr 爬的 2650 张 2K 图规模和 DIV2K 接近内容更多样。把 DIV2K 的 800 张训练图加上 Flickr2K 的 2650 张就得到DF2K共 3450 张。这个组合是 Real-ESRGAN、BSRGAN 这类工作常用的训练集因为规模够大训 ×4 超分不容易过拟合。LSDIRLarge Scale Dataset for Image Restoration是 2023 年前后放出来的84991 张高分辨率图规模比 DF2K 大一个数量级。它的出现主要是为了解决DF2K 训大模型不够用的问题尤其是 Transformer 类超分模型参数动辄几十 M3450 张图反复训很容易记住数据。不过 LSDIR 体积也大得多粗略估计原始数据在 100GB 级别硬盘和 IO 要提前规划。选型上我的经验是小模型 5M 参数用 DF2K 足够大模型或者要冲高指标再上 LSDIR。另外要注意 Flickr2K 的图片来自网络授权情况不像 DIV2K 那么明确如果项目对版权有要求优先用 DIV2K 或者自己采购。3.3 下载、校验与目录组织实操DIV2K 的下载和解压有几个固定套路我一般写成脚本一把梭# 以 DIV2K 为例下载训练集 HR 与对应 LR mkdir -p data/DIV2K cd data/DIV2K # 官方提供多个压缩包训练 HR 通常在 2GB 以上注意磁盘空间 wget -c http://data.vision.ee.ethz.ch/cvl/DIV2K/DIV2K_train_HR.zip wget -c http://data.vision.ee.ethz.ch/cvl/DIV2K/DIV2K_train_LR_bicubic_X4.zip unzip -q DIV2K_train_HR.zip -d . unzip -q DIV2K_train_LR_bicubic_X4.zip -d . # 校验文件数量HR 应该是 800 张LR 也应该是 800 张文件名一一对应 ls DIV2K_train_HR | wc -l ls DIV2K_train_LR_bicubic/X4 | wc -l注意官方 LR 包解压后目录层级一般是DIV2K_train_LR_bicubic/X4/0001x4.png而 HR 是DIV2K_train_HR/0001.png写 Dataset 时要处理这个命名差异别想当然地以为两个目录文件名一样。我习惯在建数据集的时候顺手写一个配对校验脚本逐个确认 HR 和 LR 文件都存在、都能打开、尺寸比例正确。这种校验看着没必要但真出问题的时候——比如下载中断、解压少了几个文件——训练跑到一半报 KeyError排查起来更费时间。4. 真实退化数据集从 SIDD、DND 到 RealSR合成数据训出来的模型放到真实手机上拍的照片上往往效果断崖式下跌这就是所谓的 domain gap。为了填这个坑学术界专门采集了一批真实退化数据集用真实相机拍同一场景的多次曝光或多设备配对来构造 GT 和 LQ。4.1 去噪的 SIDD、DND 与 PolyUSIDDSmartphone Image Denoising Dataset是去噪领域最常被引用的真实数据集用多台智能手机在多种光照下拍摄同一静态场景每张场景拍几百张取均值作为近似 GT。它的 sRGB 版本有 320 个场景对分训练和验证。因为是从真实手机采集的噪声分布和合成高斯噪声差别很大用 SIDD 训出来的模型在手机夜景上表现明显更稳。DNDDarmstadt Noise Dataset规模小50 个场景只提供无 GT 的测试图和在线提交评测。它的价值在于在线评测的公平性——你没法偷看 GT 来调参结果可信度相对高。PolyU提供了 sRGB 和 RAW 两种格式的真实去噪配对RAW 域的去噪对研究噪声物理模型的人比较友好因为 RAW 域噪声更接近泊松-高斯混合分布退化更可建模。用真实去噪数据集的一个坑是GT 本身也是估计出来的多帧平均并不能完全消除噪声只是把噪声压到很低。所以在 SIDD 上评估时PSNR 的绝对值不能和合成高斯噪声场上的数字直接比较只能在同一数据集内横向比。4.2 真实超分的 RealSR、DRealSR 与 City100真实超分的经典数据集是RealSR用同一场景的微距/长焦镜头拍高分辨率 GT广角/短焦镜头拍低分辨率 LQ然后做几何对齐。因为两套镜头的光学特性不同配对图像的退化更接近真实相机的模糊和噪声而不是单纯的 bicubic。DRealSR思路类似但采集设备和方式有调整规模更大一些提供 ×2 到 ×8 的多倍率配对。City100则专门采集城市场景包含大量建筑纹理和文字适合评测模型在结构化场景下的表现。这几个数据集的对齐精度是个大问题。镜头之间有轻微的位移和透视差异即使做了配准边缘仍然可能有 1 到 2 个像素的错位。如果直接拿这种未完全对齐的数据去算 PSNR指标会被拉低还会让模型学到错误的映射。实际使用时很多工作会先做一波筛选剔掉对齐质量差的样本。4.3 去模糊的 GoPro、HIDE 与 REDSGoPro是去模糊领域用得最多的数据集3214 对图像用 GoPro 相机以不同曝光时间连拍构造模糊-清晰配对。它的模糊主要来自相机抖动和物体运动核是真实记录下来的。缺点是训练集和测试集来自同一批采集条件泛化性有限很多模型在 GoPro 上跑到 33dB换个真实模糊数据集就掉到 25dB 出头。HIDE偏向人像和行人场景2400 多对图像专门针对人体运动模糊做行人检测前置增强的人会比较关注它。REDS是视频版的300 段 720p 视频每段 100 帧主要用于视频去模糊和视频超分。视频任务要多考虑时序一致性评测时除了 PSNR 还会看 tOFtemporal consistency之类的指标。用 GoPro 的经验它的测试集绝对不能碰它的 1111 对测试图是标准评测集训练只用 2103 对训练图。我见过有人把整个 3214 对都拿去训练然后报了个特别好看的数字这种结果在审稿或同行交流里基本是要被质疑的。5. 恶劣天气与特定退化去雨、去雾、去雪、低光这一族任务的特点是物理模型复杂合成数据和真实数据的差距比超分去噪更大。5.1 去雨数据集Rain100 系列与真实雨图合成去雨数据集常用的是Rain100H重雨1800 训练 / 100 测试和Rain100L轻雨200 训练 / 100 测试雨条纹是用渲染引擎按物理模型叠加的。还有Rain800、Rain12、DID-MDN等各有侧重。这些合成集的问题是雨条纹的形状、方向和密度都是程序生成的和真实雨天的雨线差别明显。真实雨图数据集如RealRain、SPA-Data采集难度大数量少。所以去雨领域常见的做法是合成预训练 真实微调先在大规模合成集上把模型训起来再用少量真实配对数据做 fine-tune。5.2 去雾数据集RESIDE 是绕不开的去雾最标准的数据集是RESIDE它是基于 NYU2 深度图渲染的合成雾图包含 ITS室内、OTS室外和 SOTS 测试集。RESIDE 的雾是通过大气散射模型渲染的物理上比较自洽所以被广泛用作训练集。但 RESIDE 的合成雾和真实雾在颜色偏移、光照分布上有差异。真实雾图数据集如I-HAZY、D-HAZY、HazeRD规模较小主要用来做跨域评测。做去雾项目的实际路线通常是先在 RESIDE 上把网络训到收敛再用真实雾图做无监督或半监督适配。5.3 去雪与低光增强去雪数据集里Snow100K是规模比较大的合成集把雪分成细雪、中雪、大雪三种密度共 10 万张。它的测试集有合成和真实两部分真实雪图部分数量不多但很有参考价值。SRRS是从真实雪景和合成雪景混合采集的强调真实感。低光增强这边LOLLOw-Light dataset是最常被引用的500 对低光-正常光配对图用不同曝光时间拍摄并调整得到。LOL-v2分了 Real 和 Synthetic 两个子集规模更大。SID和SMID是 RAW 域的低光数据对研究传感器噪声的人比较有用。MIT-Adobe FiveK虽然主要用于图像增强和色调映射但因为提供了专业修图师的调整版本也常被拿来做低光增强的参考。用低光数据集要注意配对的两张图曝光不同虽然做过亮度对齐但色偏和噪声分布差异仍在。直接做像素级 L1 损失容易让输出发灰实践中通常要配合感知损失和颜色一致性约束。6. 退化建模与数据合成配对数据到底怎么造的现成的配对数据集不一定适合你的任务很多时候你得自己造。造数据的核心是退化模型它决定了你的模型在学什么样的映射。6.1 通用退化公式与各任务的具体形式图像修复的通用退化模型可以写成y D(x; θ) n其中 x 是清晰图y 是退化图D 是退化算子θ 是退化参数n 是加性噪声。不同任务对应不同的 D超分D(x) (x ⊛ k) ↓sk 是模糊核↓s 是 s 倍下采样去噪D(x) xn 是噪声通常建模为高斯噪声或泊松-高斯混合去模糊D(x) x ⊛ kk 是运动模糊核压缩伪影D(x) JPEG(x, q)q 是质量因子去雾y x·t A·(1 - t)t 是透射率A 是大气光把这些算子组合起来就能构造更接近真实的退化。比如 Real-ESRGAN 的思路就是把模糊、下采样、噪声、JPEG 压缩按随机顺序叠两轮模拟相机成像 后期压缩的完整链路。6.2 高阶退化管线的实现要点以 Real-ESRGAN 的经典管线为例核心是二阶退化第一阶模拟成像过程第二阶模拟压缩和二次采样。我在实际项目里简化过一个版本代码大致长这样import cv2 import numpy as np def random_blur(img, kernel_size_range(7, 21), sigma_range(0.2, 3.0)): # 各向异性高斯核模拟不同方向的相机抖动 k np.random.randint(*kernel_size_range) | 1 sigma np.random.uniform(*sigma_range) kernel cv2.getGaussianKernel(k, sigma) kernel kernel kernel.T return cv2.filter2D(img, -1, kernel) def random_resize(img, scale4, up_prob0.3): h, w img.shape[:2] mode np.random.rand() if mode up_prob: # 先上采样再下采样模拟某些相机的过采样链路 img cv2.resize(img, (int(w * 1.5), int(h * 1.5)), interpolationcv2.INTER_LINEAR) h, w img.shape[:2] return cv2.resize(img, (w // scale, h // scale), interpolationnp.random.choice( [cv2.INTER_LINEAR, cv2.INTER_CUBIC, cv2.INTER_AREA, cv2.INTER_NEAREST])) def random_noise(img, sigma_range(1, 15)): sigma np.random.uniform(*sigma_range) / 255.0 noise np.random.randn(*img.shape) * sigma return np.clip(img.astype(np.float32) / 255.0 noise, 0, 1) def random_jpeg(img, quality_range(30, 95)): q np.random.randint(*quality_range) _, enc cv2.imencode(.jpg, (img * 255).astype(np.uint8), [cv2.IMWRITE_JPEG_QUALITY, q]) return cv2.imdecode(enc, cv2.IMREAD_COLOR) / 255.0值得说明的几个设计意图模糊核一定要各向异性用一维高斯外积得到的是各向同性核真实相机抖动是有方向的只用各向同性核训出来的模型对方向性模糊会不敏感。下采样插值要随机化不同相机的 ISP 用的是不同插值算法固定用 bicubic 会让模型过拟合到某一种退化。噪声强度要控制范围sigma 太大模型学不会太小又没意义1 到 150-255 尺度是我试下来比较合理的区间。6.3 合成数据的常见坑第一个坑是退化强度分布和测试集不匹配。你在训练时用 sigma 1 到 15测试时用 sigma 25模型必然崩。解决办法是先确定目标场景的退化强度再把这个范围作为训练分布的中心。第二个坑是JPEG 压缩的二次编码损失。用cv2.imencode再imdecode图像会经历一次编解码如果反复多次伪影会累积。做多阶退化时要注意每一阶之后的数值范围处理别让裁剪和量化误差把图像搞偏色。第三个坑是颜色空间。很多退化应该在 YCbCr 的 Y 通道上做因为人眼对亮度更敏感且真实 ISP 的降噪和锐化也主要在 Y 通道。如果你在 RGB 上做高斯噪声再去比较 Y 通道的 PSNR噪声的等效强度是对不上的。我一般训练时用 RGB 输入输出但评测时统一转到 Y 通道算指标这是超分领域的惯例。7. 评测协议与指标别让 PSNR 骗了你数据集选对了指标算错了照样白搭。指标这一块看着简单实际上细节极多。7.1 PSNR 与 SSIM 的计算细节PSNR 的公式是PSNR 10·log10(MAX² / MSE)MAX 是像素最大值8bit 图就是 255。看起来简单但有三个地方容易出错第一裁剪边界。超分模型在图像边缘通常有 padding 带来的伪影标准协议是在计算前裁掉 scale 倍的边界像素。你如果不裁边缘那几行像素会把 PSNR 拉低零点几个 dB横向对比就失真了。第二通道选择。超分领域通常在 YCbCr 的 Y 通道上算 PSNR/SSIMRGB 三通道平均是另一种口径。两种数字不可混比论文里会写明用的是哪种。第三数值精度。用 float32 还是 uint8 计算 MSE结果会有差异。标准做法是把输出 clamp 到 [0, 1] 后转成 8bit 再算或者全程 float 但要避免量化误差。SSIM 的坑更多主要有高斯窗口大小常用 11×11sigma 1.5、是否用 MATLAB 版本MATLAB 的ssim会自动处理边界Python 的skimage.metrics.structural_similarity默认行为不同。很多论文报的 SSIM 是 MATLAB 结果你用 skimage 直接算对不上需要设置gaussian_weightsTrue, use_sample_covarianceFalse并指定 sigma才能接近。7.2 感知指标与无参考指标PSNR 和 SSIM 的局限在于它们衡量的是像素级差异和人眼观感不一致。一个模型 PSNR 高但输出很糊这种情况在超分里非常常见。所以现在论文基本都会同时报感知指标指标类型特点适用场景PSNR全参考对像素误差敏感越高越清晰但可能越不自然传统对比SSIM全参考衡量结构相似性比 PSNR 更贴近人眼传统对比LPIPS全参考基于深度特征越低感知越好感知质量对比DISTS全参考结合纹理和结构抗噪性好感知质量对比NIQE无参考不需要 GT越低越好真实场景评测FID分布级衡量生成分布与真实分布的差异GAN/扩散模型这里有个很重要的认知PSNR 和 LPIPS 往往是此消彼长的。追求高 PSNR 的模型比如 L1 损失训练的输出偏平滑感知分不占优用 GAN 损失训的模型纹理更真实但 PSNR 会掉 1 到 2 dB。所以现在很多工作会报两个版本PSNR-oriented 和 Perception-oriented比如 Real-ESRGAN 的 plus 版本和普通版本。你在做项目选型时要先明确业务目标是要数字好看还是要肉眼好看。7.3 评测协议常见错误我整理了几条实测中踩过的测试集混入训练集DIV2K 的 800 训练 / 100 验证划分一定要遵守别为了多几百张图把验证集也加进训练。用了错误的 LR 包×4 的模型拿 ×2 的 LR 去测指标会莫名其妙地好或者差一定要对齐倍率。多尺度评测的 resize 方式不一致有些数据集评测时会对输出做一次额外 resize 再比较这个操作必须和基线一致。忽略了 GT 本身的压缩损伤某些数据集的 GT 是从 JPEG 恢复的本身带有压缩痕迹把它当完美 GT 会让模型学到伪影。8. 从零搭一条训练/验证数据管线数据集理解到位之后真正落地就是把它组织成 DataLoader 能吃的格式。这一步做不好训练速度会被 IO 卡死或者出现各种玄学 bug。8.1 目录结构设计我推荐的结构是这样datasets/ ├── DIV2K/ │ ├── train_HR/ # 800 张 │ ├── train_LR/X4/ # 800 张命名与 HR 对应 │ ├── valid_HR/ # 100 张 │ └── valid_LR/X4/ # 100 张 ├── benchmarks/ │ ├── Set5/HR, LR/X4 │ ├── Urban100/HR, LR/X4 │ └── Manga109/HR, LR/X4 └── meta/ ├── train_pairs.txt # 每行: HR路径 LR路径 └── valid_pairs.txt用pairs.txt显式列出配对关系比在 Dataset 里靠文件名拼接更稳妥。原因是不同数据集的命名规范不一样DIV2K 是0001.png对0001x4.pngRealSR 可能是scene1_HR.png对scene1_LR4.png硬编码规则很容易出错。提前生成一份配对清单训练时直接读出问题也方便人工核对。8.2 Dataset 与退化管线的实现结合前面讲的退化模型一个能跑的训练 Dataset 大概长这样import os import random import cv2 import numpy as np import torch from torch.utils.data import Dataset class RestorationDataset(Dataset): def __init__(self, pair_file, patch_size64, scale4, degradeTrue): self.pairs [line.strip().split() for line in open(pair_file)] self.patch_size patch_size self.scale scale self.degrade degrade # True 表示在线合成False 表示用现成 LR def __len__(self): return len(self.pairs) def _random_crop(self, hr, lr, ps): h, w lr.shape[:2] if h ps or w ps: # 图太小时先保证不越界直接返回全图 return hr, lr top random.randint(0, h - ps) left random.randint(0, w - ps) lr_patch lr[top:topps, left:leftps] hr_patch hr[top*self.scale:(topps)*self.scale, left*self.scale:(leftps)*self.scale] return hr_patch, lr_patch def __getitem__(self, idx): hr_path, lr_path self.pairs[idx] hr cv2.cvtColor(cv2.imread(hr_path), cv2.COLOR_BGR2RGB) if self.degrade or not os.path.exists(lr_path): # 在线退化从 HR 现造 LR能提供更多退化多样性 lr cv2.resize(hr, None, fx1/self.scale, fy1/self.scale, interpolationcv2.INTER_AREA) else: lr cv2.cvtColor(cv2.imread(lr_path), cv2.COLOR_BGR2RGB) hr_p, lr_p self._random_crop(hr, lr, self.patch_size) # 随机翻转和旋转注意 HR/LR 必须同步 if random.random() 0.5: hr_p, lr_p hr_p[:, ::-1], lr_p[:, ::-1] if random.random() 0.5: hr_p, lr_p hr_p[::-1, :], lr_p[::-1, :] to_tensor lambda x: torch.from_numpy( np.ascontiguousarray(x.transpose(2, 0, 1))).float() / 255.0 return {hr: to_tensor(hr_p), lr: to_tensor(lr_p)}这段代码里有几个点值得展开说。裁剪坐标要乘 scaleLR 上裁 64×64对应 HR 上就是 256×256如果忘了乘 scaleHR 和 LR 就对不上了训练会完全不收敛。几何增强必须同步翻转和旋转要在 HR 和 LR 上同时做而且对 LR 做 90 度旋转时HR 也要对应旋转不能一个转一个不转。我用的是最简单的水平/垂直翻转如果要用 90 度旋转增强务必写清楚同步逻辑。在线退化和离线 LR 二选一在线退化每次取数据都重新生成 LR退化多样性更好但 CPU 开销大离线 LR 速度快但多样性有限。我的一般策略是小模型 快速迭代用在线退化大模型 长训练用离线 LR 预生成或者两者混合一半在线一半离线。8.3 patch 采样、缓存与 IO 加速到了这个阶段训练速度往往不卡在 GPU 上而是卡在数据加载。几个实用的优化手段第一用 lmdb 或 webdataset 打包。DIV2K 八千多个小文件零散存储机械盘上随机读会非常慢打成单个 lmdb 后顺序读速度能提升好几倍。这一点在那些动辄十万张图的数据集如 LSDIR、FFHQ上尤其明显。第二预生成 patch 缓存。如果你固定用 64×64 的 patch可以在训练前把所有可能的 patch 位置生成一份索引训练时按索引读取避免每次随机裁剪时的边界计算开销。但注意patch 缓存会让每个 epoch 见到的高度重复建议配合在线退化使用。第三增大num_workers并设置persistent_workersTrue。PyTorch 的 DataLoader 在 worker 每次 epoch 结束时会重建设置持久化可以省掉反复启动进程的开销。同时pin_memoryTrue能让 CPU 到 GPU 的拷贝走 pinned 内存提速明显。第四注意 CPU 侧的瓶颈。如果你用了复杂的在线退化模糊 噪声 JPEG单个 worker 可能每秒只能处理几十张图这时候 GPU 利用率会掉到 30% 以下。用nvtop或nvidia-smi dmon看一眼 GPU 利用率如果长期低于 70%八成是数据管线拖后腿了。9. 常见问题与排查实录这一节是我这些年实际遇到的典型问题整理成速查表遇到症状可以对着查。9.1 配对不准、错位与色偏症状模型训练 loss 下降但输出有重影或者输出整体偏色。原因通常是配对图像没对齐或者 HR/LR 的颜色空间处理不一致。排查顺序先随机抽 5 对图把 LR 上采样到 HR 尺寸后做一次叠加对比看边缘是否重合。如果位移明显说明对齐有问题如果颜色不一致检查是不是 HR 用了 RGB 而 LR 用了 BGROpenCV 读图默认 BGR这是最常见的坑。RealSR 和 DRealSR 因为镜头差异天然存在轻微错位使用前建议自己写一个基于特征点的配准筛选把对齐差的样本剔掉。9.2 数据泄露与测试集污染症状验证指标异常高但换一个测试集就崩。原因往往是数据泄露。排查方法首先检查训练集和验证集的文件名是否有重叠其次检查有没有同名不同版本的图比如 DIV2K 的 0801-0900 既在 train 又在 valid 里第三如果你用了预训练模型确认它的训练集和你测试集不重叠。我见过最隐蔽的一次是某开源项目的测试脚本里默认加载的是验证集报出来的数字比论文高了一截实际是数据泄露。9.3 常见问题速查表症状可能原因排查动作训练 loss 不降HR/LR 配错坐标没乘 scale打印一对图的可视化对比PSNR 比论文低 2dB 以上测试集裁剪、通道、LR 版本不一致对齐评测协议裁 scale 边界GPU 利用率低于 50%数据加载是瓶颈增大 num_workers评估是否需要 lmdb输出发灰、颜色淡低光配对亮度不一致或损失函数不合适检查配对亮度加入颜色损失训练好但真实图效果差合成退化与真实域差大加入真实退化或做真实微调验证指标波动大patch 采样随机性导致固定随机种子或改为全图评测显存炸了patch 太大或多尺度输入降 patch用梯度累积替代大 batch10. 我个人踩过的坑与选型建议聊到最后分享几个我的实际体会。选数据集不是越大越好而是越匹配越好。我做手机端去噪的时候一开始用 BSD68 合成高斯噪声训模型在电脑上看着漂亮部署到手机上效果差得离谱。换成 SIDD 真实噪声之后参数量只有原来三分之一的小模型反而效果更好。原因是噪声的分布特性不一样真实手机的噪声有明显的行噪声和色度噪声这些是合成高斯噪声里没有的。在线退化的随机因子不要太多收敛会很慢。我试过把模糊、噪声、JPEG、色彩抖动全塞进退化管线退化多样性是够了但训练到收敛的迭代次数翻了两倍多。后来我把退化因子精简到模糊加噪声两个核心项收敛速度明显改善效果也没掉多少。经验是退化因子的数量要和你的训练 iteration 预算匹配预算紧就少放。先跑通小数据再上大数据。我现在的习惯是先用 Set5 的几对图做一次 end-to-end 的过拟合测试确认数据加载、前向、损失、反向都没问题再换 DIV2K 正式训练。这个流程帮我省了很多次训练了半天才发现数据读错了的时间。过拟合测试的标准是在几对图上训到 loss 接近 0输出和 GT 几乎一致说明管线是对的。测试集一定要留一个从没碰过的。DIV2K 的验证集在很多项目里被反复用来调参实际上已经失去了验证的意义。我的做法是把验证集拆成两半一半用来调参一半只在最终出结果时用一次。如果数据量允许最好再留一个跨数据集的测试集比如用 DIV2K 训练用 Urban100 做最终评测这样至少能保证不是过拟合到单一分布。数据的存储格式首选 PNG 或无损格式。我见过有人为了省空间把 GT 存成 JPEG结果模型学到的是 JPEG 伪影PSNR 高得离谱但输出全是方块。前所未见的高指标往往不是好事先怀疑数据再怀疑模型。