简介:基于Pytorch的SRCNN图像超分辨率重建复现工程,面向深度学习与底层视觉入门者,覆盖x2、x3、x4三档放大倍率训练及推理全流程。压缩包共41个文件,包含9个Python源码、6个H5格式数据集、3个PTH权重文件及BMP样例图等,数据集涵盖91-image训练集与Set5标准测试集,已预处理为h5格式可直接调用。代码涵盖模型构建、数据封装、指标计算与评估绘图,权重为最优PSNR/SSIM模型,可一键加载复现超分结果。配套教程链接提供了保姆级使用说明,帮助理解训练细节与文件组织逻辑。包内还提供了单张图像测试、benchmark批量测试以及Loss/PSNR/SSIM曲线绘制脚本,便于对比双三次插值与SRCNN在各放大倍数下的客观指标差异。从数据准备到模型评估均有对应脚本,适合系统学习SRCNN的搭建思路。已有712人学习,压缩包大小213.2MB,整体结构清晰,适合作为超分辨率入门的实践模板和二次开发基础。
1. 图像超分辨率SRCNN的PyTorch复现:卡在评估指标而不是网络本身
把图像超分辨率SRCNN用PyTorch复现出来,通常不会卡在网络结构上——三层卷积半天就能写完,真正花时间的是三个环节:bicubic降采样和论文不一致、PSNR和SSIM统计口径对不上、权重文件在换设备或换scale后加载失败。下面这套方案围绕带详细注释的PyTorch复现工程展开,覆盖数据管线、模型定义、训练验证和科研绘图全流程,并给出x2、x3、x4三档scale下按SSIM和PSNR筛选的最优权重文件用法。适合两类人:第一次接触图像超分辨率重建、想借SRCNN跑通pytorch基础框架的初学者,以及能训练但结果指标异常、需要核对评估细节的从业者。读完之后,应该能复现出与论文同一量级的PSNR/SSIM,并把训练曲线和对比图直接放进论文或报告。
2. SRCNN原理与PyTorch数据管线:bicubic降采样与YCbCr通道
2.1 三步走的结构:patch提取、非线性映射、重建
SRCNN(Super-Resolution Convolutional Neural Network)是图像超分辨率重建的奠基工作,核心思路不是从低分辨率图直接生成高分辨率图,而是先把低分辨率图用bicubic放大到目标尺寸,再让网络学习“放大后的模糊图→清晰图”的映射。这个前提决定了整个PyTorch复现的数据流:网络输入和输出shape完全一致,训练时拿放大图当输入、原始高分辨率图当标签,做的是逐像素回归。
网络只有三个卷积层。第一层9×9卷积把输入切成重叠patch并提取特征,输出64张特征图;第二层1×1卷积做非线性映射,把64维特征压到32维,这一层是SRCNN参数量小的关键;第三层5×5卷积把特征聚合成一张亮度图,给出重建结果。层与层之间用ReLU,第三层输出不加激活,直接作为回归值。整个网络没有池化、没有BatchNorm,输入输出分辨率不变,padding按kernel算:(9-1)//2=4,(5-1)//2=2,1×1卷积padding为0。
一个常见误解是SRCNN在学残差、输出会和输入相加。原始设计里没有全局残差连接,网络直接输出完整重建图。残差学习属于VDSR那一代改进工作的范畴,复现论文指标时别画蛇添足,加了残差会改变收敛行为,最终PSNR量级和论文对不上。
2.2 modcrop与bicubic降采样:先裁齐再缩放
数据管线第一步是读图。训练数据常用T91、BSDS200或DIV2K,读取后先把宽高裁成scale的整数倍,否则降采样再放大回原尺寸时,边界会多出或缺失像素,训练标签错位。这个操作在SRCNN系列代码里约定叫modcrop。
import cv2 import numpy as np def modcrop(img, scale): """把图像宽高裁剪为scale的整数倍,避免降采样后尺寸无法对齐""" h, w = img.shape[:2] h = h - h % scale w = w - w % scale return img[:h, :w] def make_lr_hr_pair(img, scale): img = modcrop(img, scale) h, w = img.shape[:2] # 先缩小再放大回原尺寸,得到与HR同shape的LR放大图 lr = cv2.resize(img, (w // scale, h // scale), interpolation=cv2.INTER_CUBIC) lr_up = cv2.resize(lr, (w, h), interpolation=cv2.INTER_CUBIC) ycrcb_hr = cv2.cvtColor(img, cv2.COLOR_BGR2YCrCb) ycrcb_lr = cv2.cvtColor(lr_up, cv2.COLOR_BGR2YCrCb) return ycrcb_hr[:, :, 0], ycrcb_lr[:, :, 0]关键在interpolation=cv2.INTER_CUBIC。cv2的resize默认用INTER_LINEAR双线性插值,换成它之后指标通常掉1dB以上,因为训练输入和论文的实验设定不一致。先缩小再放大的两步不能省:SRCNN的输入是“已经放大回目标尺寸的低分辨率图”,不是原尺寸小图,这一步在复现时最容易做错。返回时转成YCbCr颜色空间,只取第0通道Y(亮度),Cb和Cr色度通道不参与训练。训练和评估都统一在Y通道,这是复现论文数字的前提。
2.3 训练patch采样:33×33随机裁剪与数据增强
全图直接进网络不是不行,但SRCNN参数量小、每张图能提供的梯度有限,常见做法是裁patch训练。固定patch_size=33,因为网络有效感受野是9+5-1=13,33×33既覆盖感受野又留出上下文;裁剪步长取14时patch之间有重叠,数据利用率高。每个epoch随机裁一批,不把所有patch一次性落盘,内存和显存都省。
| 数据管线参数 | 取值 | 作用 |
|---|---|---|
| 插值方式 | cv2.INTER_CUBIC | 对齐论文的bicubic设定 |
| 训练通道 | YCrCb的Y通道 | 亮度通道对人眼更敏感,评估口径统一 |
| patch_size | 33×33 | 覆盖13×13感受野并留上下文 |
| 裁剪stride | 14 | 控制patch重叠与采样密度 |
| 数据增强 | 水平翻转+90°旋转 | 同等数据量扩充8倍 |
下面是训练用的Dataset实现,数值归一化到[0,1],同时做随机裁剪和翻转旋转。翻转和旋转组合起来等效8倍数据增强,对T91这种只有91张图的小数据集,这一项能让验证集PSNR提升0.2~0.4 dB。
import torch from torch.utils.data import Dataset class SRCNNDataset(Dataset): def __init__(self, hr_paths, scale, patch_size=33, train=True): self.hr_paths = hr_paths self.scale = scale self.patch_size = patch_size self.train = train def __len__(self): return len(self.hr_paths) def __getitem__(self, idx): img = cv2.imread(self.hr_paths[idx]) hr_y, lr_y = make_lr_hr_pair(img, self.scale) if self.train: # 随机裁33×33:lr和hr在同位置裁,因为两者shape一致 h, w = hr_y.shape x = np.random.randint(0, w - self.patch_size + 1) y = np.random.randint(0, h - self.patch_size + 1) hr_p = hr_y[y:y+self.patch_size, x:x+self.patch_size] lr_p = lr_y[y:y+self.patch_size, x:x+self.patch_size] if np.random.rand() > 0.5: # 水平翻转 hr_p = hr_p[:, ::-1] lr_p = lr_p[:, ::-1] if np.random.rand() > 0.5: # 90°旋转 hr_p = np.rot90(hr_p) lr_p = np.rot90(lr_p) else: hr_p, lr_p = hr_y, lr_y # float32 + [0,1]归一化,MSE损失在这个尺度下数值更稳 hr_t = torch.from_numpy(hr_p.astype(np.float32) / 255.0).unsqueeze(0) lr_t = torch.from_numpy(lr_p.astype(np.float32) / 255.0).unsqueeze(0) return lr_t, hr_t裁剪坐标用np.random.randint生成,hr和lr在同一个坐标裁,因为make_lr_hr_pair返回的两张图shape相同。验证阶段不做随机裁剪,直接返回整图,评估时在完整图上算PSNR和SSIM。最后用unsqueeze(0)补通道维,得到(1, 33, 33)张量,通道维在前是PyTorch卷积层的默认布局,也符合NCHW约定。
3. 三层卷积网络定义与训练配置:损失函数、学习率和批量大小
3.1 注释详细的SRCNN模型定义
模型定义是整个工程里最短的部分,但注释值得写细,因为后面的科研绘图和权重筛选都依赖这个网络结构能被准确重建。环境准备上,只要通过pip正常安装好的PyTorch就够,不需要额外算力,一块2GB显存的卡就能训练。
import torch.nn as nn class SRCNN(nn.Module): """SRCNN:3层全卷积,无池化、无BN、无残差,输入输出shape一致""" def __init__(self, num_channels=1): super(SRCNN, self).__init__() # 9×9:patch提取,pad=4保持分辨率不变,输出64通道 self.conv1 = nn.Conv2d(num_channels, 64, kernel_size=9, padding=4) # 1×1:非线性映射,把64维特征降到32维,参数集中在这层 self.conv2 = nn.Conv2d(64, 32, kernel_size=1, padding=0) # 5×5:重建,pad=2,输出1通道亮度图 self.conv3 = nn.Conv2d(32, num_channels, kernel_size=5, padding=2) self.relu = nn.ReLU(inplace=True) def forward(self, x): x = self.relu(self.conv1(x)) x = self.relu(self.conv2(x)) x = self.conv3(x) # 第三层不经过ReLU,输出可为负 return xpadding=4和padding=2分别对应(9-1)//2和(5-1)//2,这三个padding值保证特征图尺寸逐层不变,输入(1, H, W)输出也是(1, H, W)。第三层不加ReLU是因为回归目标可以是任意实数,ReLU会把负的预测值截断,强边缘处出现伪影,肉眼可见的黑点就是这么来的。inplace=True省显存,对小网络影响不大,但写成习惯没坏处。
3.2 损失函数与优化器:MSE配Adam是复现默认组合
损失函数选MSE而不是L1,理由很直接:PSNR由MSE换算得到,最小化MSE等价于直接优化PSNR指标。L1损失是SRGAN、EDSR这类后续工作更常用的选择,复现SRCNN时不要换,换了之后训练曲线形态和最终PSNR都会偏移,和论文对照就失去了意义。
优化器有两条路线。原论文用带动量的SGD,学习率1e-4;PyTorch复现时更多人直接上Adam。Adam对学习率不敏感、前期收敛快,在SRCNN这种小网络上通常几十个epoch就能看到平台期;SGD更贴近论文原始行为,但要配动量0.9和阶梯式降学习率。两条路线的最终PSNR差距在0.1 dB以内,选哪个取决于你想更快看到结果还是严格复现论文配置。
| 训练配置 | 推荐值 | 说明 |
|---|---|---|
| 损失函数 | nn.MSELoss() | 与PSNR定义直接对应 |
| 优化器 | Adam,lr=1e-4 | 收敛快、对学习率不敏感 |
| 或优化器 | SGD+momentum(0.9),lr=1e-4 | 更贴近原论文行为 |
| batch_size | 32 | 16~64都行,2GB显存可训练 |
| epoch | 50~100 | 以验证集PSNR平台期为准 |
| 学习率衰减 | StepLR(30, gamma=0.5) | 每30轮衰减一半 |
批量大小对SRCNN的影响没有分类网络那么敏感,每个patch只有33×33,batch=32在2GB显存上就能跑,这也是SRCNN适合当pytorch基础框架入门项目的直接原因。学习率不要拍脑袋调到1e-2,MSE损失在[0,1]数据范围下梯度量级很小,1e-4起步、等验证集PSNR连续5个epoch不涨再降,是最省心的策略。
关于固定随机种子
复现SRCNN的另一个隐性要求是固定随机种子。训练前对Python、NumPy、PyTorch分别设seed,并把cudnn的benchmark关掉,否则两次训练虽然指标接近,但“最优权重”的PSNR/SSIM会有零点几分贝的随机浮动。科研绘图时曲线对不上,多半是没做这一步。
import random import numpy as np import torch def set_seed(seed=42): random.seed(seed) np.random.seed(seed) torch.manual_seed(seed) torch.cuda.manual_seed_all(seed) torch.backends.cudnn.deterministic = True torch.backends.cudnn.benchmark = False3.3 训练循环与checkpoint记录
训练循环的写法决定“最优SSIM和PSNR的模型权重文件”能不能顺利产出。每轮epoch结束在验证集上算一次PSNR,比历史最优值高就把模型存下来,这是最朴素的早停加权重筛选逻辑。
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') model = SRCNN().to(device) criterion = nn.MSELoss() optimizer = torch.optim.Adam(model.parameters(), lr=1e-4) scheduler = torch.optim.lr_scheduler.StepLR(optimizer, step_size=30, gamma=0.5) train_loader = DataLoader(train_ds, batch_size=32, shuffle=True, num_workers=4, pin_memory=True) best_psnr = 0.0 for epoch in range(100): model.train() total_loss = 0.0 for lr_t, hr_t in train_loader: lr_t, hr_t = lr_t.to(device), hr_t.to(device) optimizer.zero_grad() pred = model(lr_t) loss = criterion(pred, hr_t) loss.backward() optimizer.step() total_loss += loss.item() * lr_t.size(0) scheduler.step() # 每轮结束验证一次,验证逻辑见第4章 val_psnr, val_ssim = evaluate(model, val_paths, scale) if val_psnr > best_psnr: best_psnr = val_psnr torch.save({ 'scale': scale, 'state_dict': model.state_dict(), 'val_psnr': val_psnr, 'val_ssim': val_ssim, 'epoch': epoch, }, f'srcnn_x{scale}_best.pth')checkpoint里同时存scale、state_dict和两个指标,比只存权重多出一条关键信息:加载时能直接读出这个文件是为哪一档scale训练出来的,x2、x3、x4三个文件不会搞混。total_loss按样本量加权累加,除以数据集长度就是当前epoch的平均MSE,这个值留给第5章画训练曲线用。
4. PSNR和SSIM计算精度坑:Y通道评估与最优权重筛选
4.1 shave边界、data_range与float64
PSNR和SSIM公式本身简单,但复现结果对不上论文,十个里有八个出在计算口径。第一个口径是边界裁剪。卷积在图像边缘会有无效响应,评估时把四周裁掉一圈,裁多少以scale为准,常见做法是shave=scale,即x2裁2像素、x3裁3像素、x4裁4像素。
def calc_psnr(pred, gt, shave, data_range=255.0): """pred和gt都是uint8的Y通道图,裁掉shave边界后按float64算""" pred = pred[shave:-shave, shave:-shave].astype(np.float64) gt = gt[shave:-shave, shave:-shave].astype(np.float64) mse = np.mean((pred - gt) ** 2) return 10.0 * np.log10(data_range * data_range / (mse + 1e-10)) def calc_ssim(pred, gt, shave): from skimage.metrics import structural_similarity pred = pred[shave:-shave, shave:-shave] gt = gt[shave:-shave, shave:-shave] return structural_similarity(pred, gt, data_range=255.0)第二个口径是通道。论文的SRCNN在Y通道训练和评估,如果训练时把RGB三通道都送进网络、再在RGB图上算PSNR,结果和论文参考值没有可比性,通常还会偏低。第三个口径是data_range和数据精度。uint8图做差值计算前要先转float64,避免整数溢出;skimage的SSIM必须显式传data_range=255.0,不传的话按dtype推断,float图默认1.0,混用时指标直接失真。这三个口径任何一个出错,标题里说的“最优PSNR和SSIM”就成了自说自话的数字。
4.2 验证集与最优权重筛选逻辑
验证集选Set5或Set14,这两组图全图评估、公开结果多,便于核对。评估函数把每张图转成Y通道、过模型、算指标,最后取平均。
def evaluate(model, img_paths, scale, device): model.eval() psnr_list, ssim_list = [], [] shave = scale # 边界裁剪以scale为准 for path in img_paths: img = cv2.imread(path) hr_y, lr_y = make_lr_hr_pair(img, scale) lr_t = torch.from_numpy(lr_y.astype(np.float32) / 255.0) lr_t = lr_t.unsqueeze(0).unsqueeze(0).to(device) with torch.no_grad(): sr = model(lr_t).squeeze().cpu().numpy() sr_y = np.clip(sr * 255.0, 0, 255) psnr_list.append(calc_psnr(sr_y, hr_y, shave)) ssim_list.append(calc_ssim(sr_y, hr_y, shave)) return float(np.mean(psnr_list)), float(np.mean(ssim_list))筛选逻辑以PSNR为主、SSIM为辅。存入checkpoint的val_ssim不是用来做早停的,因为SSIM在高分区域区分度不如PSNR,两个模型PSNR差0.3 dB、SSIM可能只差千分之几。把两个值同时存进权重文件,论文审稿人问起“最优模型怎么选的”,能直接给出当时的完整指标。输出先clip到[0, 255]再算指标,不clip会把越界的预测值算进MSE,PSNR会被异常像素拉低。
4.3 公开参考值的核对区间
论文在Set5上的公开结果大致是:x2约36.6 dB、x3约32.7 dB、x4约30.4 dB,SSIM对应在0.95、0.90、0.86附近。自己的复现受训练集、补丁采样和数据增强影响,与这个值差0.5 dB以内都属正常;差1 dB以上就要回头查数据管线而不是怀疑模型写错。
| 评估项目 | 常见错误 | 正确做法 |
|---|---|---|
| PSNR偏低1dB+ | 输入是RGB或数据范围混用 | Y通道统一,pred/gt同range |
| SSIM异常偏高 | shave没裁或data_range传错 | 裁掉scale圈边界,data_range=255 |
| 训练正常但验证掉点 | 验证时忘了model.eval() | 关Dropout和BN统计 |
| 数字对不上论文 | 用了FID/LPIPS等生成式指标 | SRCNN是MSE训练,看PSNR/SSIM |
SRCNN这个阶段基本不看FID和LPIPS,那两个指标是GAN类超分评测用的。MSE训练的模型拿FID对比没有区分度,论文里也不报告,别在复现工程里混用两套评估体系。
5. 科研绘图与SRCNN效果对比图:训练曲线、局部放大和结果表
5.1 训练曲线:loss和验证PSNR双y轴
科研绘图的第一步是把训练过程的loss和验证集PSNR画到一张图里。两者量纲不同,一个在0.01量级、一个在30以上,直接共用y轴会压扁一边,用双y轴是通用做法。
import matplotlib matplotlib.use('Agg') import matplotlib.pyplot as plt def plot_training_curve(train_losses, val_psnrs, save_path): fig, ax1 = plt.subplots(figsize=(8, 5)) epochs = range(1, len(train_losses) + 1) # 左边y轴画MSE loss,用蓝色 ax1.plot(epochs, train_losses, color='#1f77b4', label='MSE Loss') ax1.set_xlabel('Epoch', fontsize=12) ax1.set_ylabel('MSE Loss', color='#1f77b4', fontsize=12) ax1.tick_params(axis='y', labelcolor='#1f77b4') # 右边y轴画验证PSNR,用红色 ax2 = ax1.twinx() ax2.plot(epochs, val_psnrs, color='#d62728', label='Val PSNR') ax2.set_ylabel('PSNR (dB)', color='#d62728', fontsize=12) ax2.tick_params(axis='y', labelcolor='#d62728') fig.tight_layout() fig.savefig(save_path, dpi=300, bbox_inches='tight') plt.close(fig)ax1.plot画训练loss,ax1.twinx()创建共享x轴的第二个坐标系画验证PSNR,两个曲线互不压缩。颜色用matplotlib默认配色里的蓝红,黑白打印也能区分。savefig务必开dpi=300和bbox_inches='tight',前者满足期刊分辨率要求,后者避免坐标轴标签被裁掉。train_losses和val_psnrs就是第3章训练循环里每轮累加和evaluate函数返回的两个列表。
图例与坐标轴范围
双y轴图的图例容易叠在一起,手动指定loc='upper right',x轴范围设成(0, epochs+1)避免曲线贴边。PSNR的y轴起始值不要从0开始,否则30和36 dB的区别在图上只是一条平线,把ylim设为(min(val_psnrs)-1, max(val_psnrs)+1)就能看出上升趋势,这也是论文里常见的纵轴截断画法。
5.2 效果对比图:LR、Bicubic、SRCNN、HR四联图
超分论文的核心图是视觉效果对比。一张图排四列:低分辨率原图、bicubic放大图、SRCNN输出、高分辨率真值,下面配PSNR/SSIM标注。
def plot_comparison(lr_img, bicubic_img, sr_img, hr_img, scale, psnr_sr, ssim_sr, save_path): fig, axes = plt.subplots(1, 4, figsize=(16, 5)) titles = [f'LR (x{scale})', 'Bicubic', f'SRCNN\nPSNR {psnr_sr:.2f} dB\n' f'SSIM {ssim_sr:.4f}', 'HR'] for ax, img, title in zip(axes, [lr_img, bicubic_img, sr_img, hr_img], titles): ax.imshow(img, cmap='gray', vmin=0, vmax=255) ax.set_title(title, fontsize=12) ax.axis('off') fig.tight_layout() fig.savefig(save_path, dpi=300, bbox_inches='tight') plt.close(fig)imshow用cmap='gray'画Y通道灰度图,vmin/vmax锁死在0和255,不然matplotlib会自动拉伸对比度,把噪声也“增强”出来,视觉上误导读者。SRCNN和HR的PSNR/SSIM标注在标题里,是审稿人最先看的位置。另外要补一张局部放大图:取图像中纹理密集的区域,比如建筑边缘或动物毛发,裁剪后放大成子图,超分重建有没有恢复出高频细节,只有放大才能看出来。
5.3 把逐图指标导出成表格
除了曲线和对比图,论文或报告还需要一张逐图指标表。用csv模块把每张验证图的文件名、PSNR、SSIM落盘,后续粘进LaTeX表格或直接转Excel都方便。
| 导出配置 | 推荐值 | 用途 |
|---|---|---|
| 图片格式 | PDF或PNG | PDF用于论文,PNG用于网页 |
| 分辨率 | dpi=300 | 满足期刊印刷要求 |
| 字体 | 默认或Times | 中文报告另设中文字体 |
| 指标表 | CSV按图导出 | 便于逐图核对与排版 |
科研绘图的最基本红线是“图里出现的数据必须能由脚本复现”,训练曲线对应train_losses列表,对比图对应evaluate函数的输出,指标表对应CSV里的行。图和数字脱钩,这张图就失去了科研意义。
6. x2、x3、x4权重文件的使用:单图推理与排错清单
6.1 权重加载与单图推理
拿到x2、x3、x4三个权重文件后,加载方式统一。checkpoint里存的是完整字典,用map_location='cpu'保证在无GPU机器上也能加载。
def load_model(weight_path, device): ckpt = torch.load(weight_path, map_location='cpu') model = SRCNN() model.load_state_dict(ckpt['state_dict']) model.to(device).eval() print(f"scale={ckpt['scale']}, val_psnr={ckpt['val_psnr']:.2f}, " f"val_ssim={ckpt['val_ssim']:.4f}") return model def infer_y(img_path, model, scale, device): img = cv2.imread(img_path) hr_y, lr_y = make_lr_hr_pair(img, scale) lr_t = torch.from_numpy(lr_y.astype(np.float32) / 255.0) lr_t = lr_t.unsqueeze(0).unsqueeze(0).to(device) with torch.no_grad(): sr = model(lr_t).squeeze().cpu().numpy() return np.clip(sr * 255.0, 0, 255).astype(np.uint8)load_state_dict默认严格模式,key对不上会直接报错。如果看到missing key或unexpected key,先检查网络定义里卷积层的名字,最常见的问题是自建类里层的命名和checkpoint不一致。
6.2 三档scale的边界:权重不能混用
x2、x3、x4三个权重文件结构完全一样,差在训练时的降采样倍数。用x4的权重去推x2的图,输入图的分辨率关系就不对,输出会出现明显的过度平滑或振铃;反过来用x2权重推x4,则细节严重不足。推理时输入图必须先按对应scale做一次bicubic放大再进网络,Cb、Cr通道直接用bicubic放大结果,只有Y通道走模型,最后三通道合并回BGR再保存。
提示:换机器后权重文件报“size mismatch”,几乎都是PyTorch版本差异或网络定义里num_channels改了,核对第一行加载信息里的scale和val_psnr即可确认文件完整性。
6.3 低指标的排错顺序
拿到权重文件后指标偏低,按下面顺序排查,前三条覆盖了九成情况。
| 现象 | 原因 | 处理 |
|---|---|---|
| PSNR比标注低2dB+ | RGB全通道推理或range混用 | 统一Y通道,pred和gt同为[0,255] |
| 输出图像偏灰 | 推理时忘了把[0,1]乘回255 | 检查clip前的数据范围 |
| 边缘有黑色条纹 | 用了INTER_LINEAR做bicubic | 换成cv2.INTER_CUBIC |
| 结果有棋盘格伪影 | 输入图没按scale先放大 | 先bicubic放大到HR尺寸再过网络 |
最低成本的验证方式是挑Set5里的baby或butterfly图跑一遍推理,和公开参考值对比,误差在0.5 dB内说明权重和管线都对,误差超1 dB就按这张表从数据范围开始查。
本文还有配套的精品资源,点击获取