060、超分数据集构建从 DIV2K 到 REDS 的数据预处理与增强方法上周帮师弟调一个EDSR的复现他拿着DIV2K训练了三天PSNR死活上不去。我一看他的数据加载代码好家伙直接把HR图片resize成LR再用双三次插值放大回来当输入——这哪是超分这是让网络学双三次插值啊。这种坑我当年也踩过今天就把数据预处理这块的实战经验掰开揉碎讲清楚。数据集的“脏活累活”才是决定上限的关键很多人觉得超分就是堆网络结构其实数据预处理做不好再好的模型也白搭。DIV2K和REDS是目前单图像超分和视频超分最常用的两个基准数据集但它们的原始数据格式、退化方式、增强策略完全不同。DIV2K单图超分的“标准试卷”DIV2K包含800张训练图、100张验证图、100张测试图全是2K分辨率。拿到手第一件事不是直接切patch而是检查图片的位深度——DIV2K原始是PNG格式但有些图是16位深度的直接读成uint8会丢失细节。我习惯用OpenCV的imread加IMREAD_UNCHANGED标志importcv2importnumpyasnp# 这里踩过坑imread默认转成8位16位图会截断imgcv2.imread(path/to/image.png,cv2.IMREAD_UNCHANGED)ifimg.dtypenp.uint16:img(img/256).astype(np.uint8)# 别用//256会丢失低8位信息DIV2K官方提供了LR版本但那是用MATLAB的双三次插值生成的和PyTorch的F.interpolate结果有细微差异。如果你要复现论文建议直接用官方LR别自己生成——我见过有人自己生成LR导致和论文结果对不上的惨案。REDS视频超分的“时间刺客”REDS有240个训练视频片段每个片段100帧分辨率1280x720。视频超分的数据预处理比单图复杂得多核心在于时间维度的对齐和光流计算。REDS的原始帧是PNG序列文件名格式是00000000.png到00000099.png。这里有个坑有些帧是纯黑或纯白相机标定帧需要提前过滤掉。我写了个简单的帧质量检查defis_valid_frame(img_path,threshold10):imgcv2.imread(img_path,cv2.IMREAD_GRAYSCALE)# 别这样写if np.mean(img) 10 or np.mean(img) 245# 应该检查方差纯色帧方差接近0returnnp.std(img)threshold数据增强别让网络学会“偷懒”数据增强不是越多越好关键是让网络学到不变性。超分任务里旋转和翻转是安全的但颜色抖动要谨慎——超分本质是恢复高频细节颜色变化会干扰网络对纹理的学习。单图超分的增强策略我常用的增强组合是随机旋转0/90/180/270度、随机水平翻转、随机裁剪。裁剪时有个细节LR和HR的裁剪位置必须对齐。很多人用random_crop分别裁LR和HR结果位置对不上网络学了个寂寞。defpaired_random_crop(lr,hr,patch_size,scale):# 这里踩过坑lr和hr的坐标要对应scale倍数h_lr,w_lrlr.shape[:2]h_hr,w_hrhr.shape[:2]# 别这样写直接随机裁lr再放大# 应该先确定hr的裁剪位置再映射到lrx_hrnp.random.randint(0,w_hr-patch_size1)y_hrnp.random.randint(0,h_hr-patch_size1)x_lrx_hr//scale y_lry_hr//scale lr_patchlr[y_lr:y_lrpatch_size//scale,x_lr:x_lrpatch_size//scale]hr_patchhr[y_hr:y_hrpatch_size,x_hr:x_hrpatch_size]returnlr_patch,hr_patch视频超分的时序增强视频超分里时间反转倒放是个很有效的增强手段能让网络学到双向运动信息。但要注意光流也要跟着反转。我见过有人只反转帧序列光流还是正向的结果训练时梯度乱飞。deftemporal_reverse(frames,flows):# 别这样写只反转framesflows不变# 应该同时反转flows的方向reversed_framesframes[::-1].copy()reversed_flows[-fforfinflows[::-1]]# 光流取反returnreversed_frames,reversed_flows退化模型从“简单粗暴”到“以假乱真”DIV2K和REDS的官方退化都是双三次下采样但真实场景的退化复杂得多。如果你想做真实世界超分需要模拟更复杂的退化模糊、噪声、下采样、压缩伪影的组合。经典退化流水线我常用的退化模型是先高斯模糊核大小随机再加噪声高斯或泊松然后双三次下采样最后JPEG压缩质量因子随机。这个流水线在RCAN和SwinIR的论文里都有提到。defdegrade_hr(hr,scale,blur_kernel5,noise_std0.01,jpeg_q90):# 这里踩过坑模糊核大小必须是奇数ifblur_kernel%20:blur_kernel1# 强制奇数blurredcv2.GaussianBlur(hr,(blur_kernel,blur_kernel),0)# 别这样写先下采样再加噪声# 应该先加噪声再下采样更符合物理模型noisenp.random.randn(*hr.shape)*noise_std*255noisynp.clip(blurrednoise,0,255).astype(np.uint8)lrcv2.resize(noisy,(noisy.shape[1]//scale,noisy.shape[0]//scale),interpolationcv2.INTER_CUBIC)# JPEG压缩模拟encode_param[int(cv2.IMWRITE_JPEG_QUALITY),jpeg_q]_,enc_imgcv2.imencode(.jpg,lr,encode_param)lrcv2.imdecode(enc_img,1)returnlr视频超分的退化特殊性视频超分还要考虑帧间的退化一致性。比如模糊核在连续帧之间应该是平滑变化的不能每帧随机一个模糊核——否则网络会学到帧间闪烁。我通常让模糊核参数在时间轴上做线性插值deftemporal_smooth_degrade(frames,scale,kernel_range(3,7)):n_frameslen(frames)# 首尾帧的模糊核大小k_startnp.random.randint(*kernel_range)k_endnp.random.randint(*kernel_range)# 中间帧线性插值kernelsnp.linspace(k_start,k_end,n_frames).astype(int)# 确保奇数kernelskernels1-kernels%2return[degrade_hr(f,scale,blur_kernelk)forf,kinzip(frames,kernels)]数据加载的工程化技巧内存管理DIV2K的HR图每张约10MB800张就是8GB全加载到内存会爆。我习惯用lmdb或h5py做持久化存储训练时随机读取。REDS更夸张240个片段×100帧全加载要上百GB。importlmdbdefcreate_lmdb(dataset_path,output_path):# 这里踩过坑map_size要设大默认1MB不够envlmdb.open(output_path,map_size1099511627776)# 1TBwithenv.begin(writeTrue)astxn:forimg_pathinsorted(glob(dataset_path/*.png)):imgcv2.imread(img_path)keyimg_path.split(/)[-1].encode()txn.put(key,cv2.imencode(.png,img)[1].tobytes())多尺度训练很多超分模型如EDSR、RCAN支持多尺度训练即同一个模型同时学习×2、×3、×4。数据加载时需要为每张HR图生成多个尺度的LR。这里有个技巧先下采样到最大尺度比如×4再上采样到其他尺度避免重复计算。defmulti_scale_lr(hr,scales[2,3,4]):# 别这样写对每个scale都从HR下采样# 应该先下采样到最大scale再上采样max_scalemax(scales)lr_maxcv2.resize(hr,(hr.shape[1]//max_scale,hr.shape[0]//max_scale))lrs{}forsinscales:ifsmax_scale:lrs[s]lr_maxelse:# 从lr_max上采样到目标尺度target_size(lr_max.shape[1]*max_scale//s,lr_max.shape[0]*max_scale//s)lrs[s]cv2.resize(lr_max,target_size,interpolationcv2.INTER_CUBIC)returnlrs个人经验性建议别迷信官方数据集DIV2K和REDS的退化方式太理想化如果你的应用场景是监控视频或手机拍照建议自己构建退化模型。我做过一个实验用DIV2K训练的模型在真实监控视频上PSNR掉了3dB后来加了运动模糊和噪声模拟才追回来。数据增强要“对症下药”如果你的超分模型要处理老照片修复多加点JPEG压缩和划痕模拟如果是卫星图像超分重点加高斯模糊和大气湍流模拟。别一股脑把所有增强都用上网络会学成“万金油”什么场景都做不好。视频超分的数据加载是性能瓶颈我见过有人用PyTorch的DataLoader直接读PNG序列训练速度被IO拖慢5倍。建议用torchvision.io.read_video直接读视频文件或者用decord库做高效解码。REDS的帧序列可以提前打包成.mp4读取速度提升明显。验证集和测试集的退化方式必须和训练集一致这个看似废话但很多人犯过。比如训练时用了随机模糊核验证时却用固定模糊核导致验证结果虚高。我习惯在训练脚本里固定随机种子确保每次生成的退化参数可复现。数据预处理代码要版本控制超分实验的预处理逻辑经常调整今天加个噪声明天改个裁剪策略。如果不做版本控制过两个月你自己都搞不清当时用的什么参数。我每个实验都会在代码里写一个preprocess_config.yaml记录所有预处理参数。最后说句实在话超分领域现在卷得厉害模型结构越来越复杂但很多顶会论文的数据预处理其实很粗糙。你把数据预处理做到极致哪怕用个简单的SRCNN效果也能超过那些花里胡哨的模型。数据才是王道这句话在超分领域尤其适用。
060、超分数据集构建:从 DIV2K 到 REDS 的数据预处理与增强方法
060、超分数据集构建从 DIV2K 到 REDS 的数据预处理与增强方法上周帮师弟调一个EDSR的复现他拿着DIV2K训练了三天PSNR死活上不去。我一看他的数据加载代码好家伙直接把HR图片resize成LR再用双三次插值放大回来当输入——这哪是超分这是让网络学双三次插值啊。这种坑我当年也踩过今天就把数据预处理这块的实战经验掰开揉碎讲清楚。数据集的“脏活累活”才是决定上限的关键很多人觉得超分就是堆网络结构其实数据预处理做不好再好的模型也白搭。DIV2K和REDS是目前单图像超分和视频超分最常用的两个基准数据集但它们的原始数据格式、退化方式、增强策略完全不同。DIV2K单图超分的“标准试卷”DIV2K包含800张训练图、100张验证图、100张测试图全是2K分辨率。拿到手第一件事不是直接切patch而是检查图片的位深度——DIV2K原始是PNG格式但有些图是16位深度的直接读成uint8会丢失细节。我习惯用OpenCV的imread加IMREAD_UNCHANGED标志importcv2importnumpyasnp# 这里踩过坑imread默认转成8位16位图会截断imgcv2.imread(path/to/image.png,cv2.IMREAD_UNCHANGED)ifimg.dtypenp.uint16:img(img/256).astype(np.uint8)# 别用//256会丢失低8位信息DIV2K官方提供了LR版本但那是用MATLAB的双三次插值生成的和PyTorch的F.interpolate结果有细微差异。如果你要复现论文建议直接用官方LR别自己生成——我见过有人自己生成LR导致和论文结果对不上的惨案。REDS视频超分的“时间刺客”REDS有240个训练视频片段每个片段100帧分辨率1280x720。视频超分的数据预处理比单图复杂得多核心在于时间维度的对齐和光流计算。REDS的原始帧是PNG序列文件名格式是00000000.png到00000099.png。这里有个坑有些帧是纯黑或纯白相机标定帧需要提前过滤掉。我写了个简单的帧质量检查defis_valid_frame(img_path,threshold10):imgcv2.imread(img_path,cv2.IMREAD_GRAYSCALE)# 别这样写if np.mean(img) 10 or np.mean(img) 245# 应该检查方差纯色帧方差接近0returnnp.std(img)threshold数据增强别让网络学会“偷懒”数据增强不是越多越好关键是让网络学到不变性。超分任务里旋转和翻转是安全的但颜色抖动要谨慎——超分本质是恢复高频细节颜色变化会干扰网络对纹理的学习。单图超分的增强策略我常用的增强组合是随机旋转0/90/180/270度、随机水平翻转、随机裁剪。裁剪时有个细节LR和HR的裁剪位置必须对齐。很多人用random_crop分别裁LR和HR结果位置对不上网络学了个寂寞。defpaired_random_crop(lr,hr,patch_size,scale):# 这里踩过坑lr和hr的坐标要对应scale倍数h_lr,w_lrlr.shape[:2]h_hr,w_hrhr.shape[:2]# 别这样写直接随机裁lr再放大# 应该先确定hr的裁剪位置再映射到lrx_hrnp.random.randint(0,w_hr-patch_size1)y_hrnp.random.randint(0,h_hr-patch_size1)x_lrx_hr//scale y_lry_hr//scale lr_patchlr[y_lr:y_lrpatch_size//scale,x_lr:x_lrpatch_size//scale]hr_patchhr[y_hr:y_hrpatch_size,x_hr:x_hrpatch_size]returnlr_patch,hr_patch视频超分的时序增强视频超分里时间反转倒放是个很有效的增强手段能让网络学到双向运动信息。但要注意光流也要跟着反转。我见过有人只反转帧序列光流还是正向的结果训练时梯度乱飞。deftemporal_reverse(frames,flows):# 别这样写只反转framesflows不变# 应该同时反转flows的方向reversed_framesframes[::-1].copy()reversed_flows[-fforfinflows[::-1]]# 光流取反returnreversed_frames,reversed_flows退化模型从“简单粗暴”到“以假乱真”DIV2K和REDS的官方退化都是双三次下采样但真实场景的退化复杂得多。如果你想做真实世界超分需要模拟更复杂的退化模糊、噪声、下采样、压缩伪影的组合。经典退化流水线我常用的退化模型是先高斯模糊核大小随机再加噪声高斯或泊松然后双三次下采样最后JPEG压缩质量因子随机。这个流水线在RCAN和SwinIR的论文里都有提到。defdegrade_hr(hr,scale,blur_kernel5,noise_std0.01,jpeg_q90):# 这里踩过坑模糊核大小必须是奇数ifblur_kernel%20:blur_kernel1# 强制奇数blurredcv2.GaussianBlur(hr,(blur_kernel,blur_kernel),0)# 别这样写先下采样再加噪声# 应该先加噪声再下采样更符合物理模型noisenp.random.randn(*hr.shape)*noise_std*255noisynp.clip(blurrednoise,0,255).astype(np.uint8)lrcv2.resize(noisy,(noisy.shape[1]//scale,noisy.shape[0]//scale),interpolationcv2.INTER_CUBIC)# JPEG压缩模拟encode_param[int(cv2.IMWRITE_JPEG_QUALITY),jpeg_q]_,enc_imgcv2.imencode(.jpg,lr,encode_param)lrcv2.imdecode(enc_img,1)returnlr视频超分的退化特殊性视频超分还要考虑帧间的退化一致性。比如模糊核在连续帧之间应该是平滑变化的不能每帧随机一个模糊核——否则网络会学到帧间闪烁。我通常让模糊核参数在时间轴上做线性插值deftemporal_smooth_degrade(frames,scale,kernel_range(3,7)):n_frameslen(frames)# 首尾帧的模糊核大小k_startnp.random.randint(*kernel_range)k_endnp.random.randint(*kernel_range)# 中间帧线性插值kernelsnp.linspace(k_start,k_end,n_frames).astype(int)# 确保奇数kernelskernels1-kernels%2return[degrade_hr(f,scale,blur_kernelk)forf,kinzip(frames,kernels)]数据加载的工程化技巧内存管理DIV2K的HR图每张约10MB800张就是8GB全加载到内存会爆。我习惯用lmdb或h5py做持久化存储训练时随机读取。REDS更夸张240个片段×100帧全加载要上百GB。importlmdbdefcreate_lmdb(dataset_path,output_path):# 这里踩过坑map_size要设大默认1MB不够envlmdb.open(output_path,map_size1099511627776)# 1TBwithenv.begin(writeTrue)astxn:forimg_pathinsorted(glob(dataset_path/*.png)):imgcv2.imread(img_path)keyimg_path.split(/)[-1].encode()txn.put(key,cv2.imencode(.png,img)[1].tobytes())多尺度训练很多超分模型如EDSR、RCAN支持多尺度训练即同一个模型同时学习×2、×3、×4。数据加载时需要为每张HR图生成多个尺度的LR。这里有个技巧先下采样到最大尺度比如×4再上采样到其他尺度避免重复计算。defmulti_scale_lr(hr,scales[2,3,4]):# 别这样写对每个scale都从HR下采样# 应该先下采样到最大scale再上采样max_scalemax(scales)lr_maxcv2.resize(hr,(hr.shape[1]//max_scale,hr.shape[0]//max_scale))lrs{}forsinscales:ifsmax_scale:lrs[s]lr_maxelse:# 从lr_max上采样到目标尺度target_size(lr_max.shape[1]*max_scale//s,lr_max.shape[0]*max_scale//s)lrs[s]cv2.resize(lr_max,target_size,interpolationcv2.INTER_CUBIC)returnlrs个人经验性建议别迷信官方数据集DIV2K和REDS的退化方式太理想化如果你的应用场景是监控视频或手机拍照建议自己构建退化模型。我做过一个实验用DIV2K训练的模型在真实监控视频上PSNR掉了3dB后来加了运动模糊和噪声模拟才追回来。数据增强要“对症下药”如果你的超分模型要处理老照片修复多加点JPEG压缩和划痕模拟如果是卫星图像超分重点加高斯模糊和大气湍流模拟。别一股脑把所有增强都用上网络会学成“万金油”什么场景都做不好。视频超分的数据加载是性能瓶颈我见过有人用PyTorch的DataLoader直接读PNG序列训练速度被IO拖慢5倍。建议用torchvision.io.read_video直接读视频文件或者用decord库做高效解码。REDS的帧序列可以提前打包成.mp4读取速度提升明显。验证集和测试集的退化方式必须和训练集一致这个看似废话但很多人犯过。比如训练时用了随机模糊核验证时却用固定模糊核导致验证结果虚高。我习惯在训练脚本里固定随机种子确保每次生成的退化参数可复现。数据预处理代码要版本控制超分实验的预处理逻辑经常调整今天加个噪声明天改个裁剪策略。如果不做版本控制过两个月你自己都搞不清当时用的什么参数。我每个实验都会在代码里写一个preprocess_config.yaml记录所有预处理参数。最后说句实在话超分领域现在卷得厉害模型结构越来越复杂但很多顶会论文的数据预处理其实很粗糙。你把数据预处理做到极致哪怕用个简单的SRCNN效果也能超过那些花里胡哨的模型。数据才是王道这句话在超分领域尤其适用。