模糊照片秒变高清大图,飞桨PPDE带你复现图像恢复模型CMFNet
本文已在飞桨公众号发布,查看请戳链接:
模糊照片秒变高清大图,飞桨PPDE带你复现图像恢复模型CMFNet
图像恢复技术,就是使用各种图像算法对有缺陷的图像进行修复还原的技术,常见的图像恢复需求有:图像降噪、图像锐化、图像去雾、图像去雨水等等,本篇文章将介绍一种基于复合多分支特征融合的现实图像恢复模型CMFNet[1],并使用飞桨框架实现CMFNet模型,加载官方提供的预训练模型,完成去模糊、去雾霾和去雨水三种图像恢复任务。
点击获得项目链接,欢迎STAR
https://aistudio.baidu.com/aistudio/projectdetail/3732305
开始之前,先来看看模型的恢复效果如何?
图像修复的效果是不是还不错呢?接下来,我们一起了解一下技术原理吧。
CMFNet模型介绍
CMFNet包含三个主要的模块,其总体的模型结构如图4所示。
图4 CMFNet模型结构
本文用简单的块结构将多个复杂块叠加到多个分支中,分离出不同的注意特征。图4中的三个U-Net结构使用不同的注意力模块,如图5所示。
图5 U-Net模型结构
本文还基于监督注意模块 (SAM)[2] 提出RAM来提高性能,SAM模型结构如图6所示。RAM消除了SAM输出图像与真实图像之间的监督损耗,因为作者认为它会限制网络的学习。加载去模糊、去雾、去雨水模型,使用上述代码完成模型推理,分别实现去模糊、去雾、去雨水效果。
图6 SAM结构
本文提出了一种混合跳跃连接 (MSC),如图7所示。将传统的残差连接替换为一个可学习的常数,使得残差学习在不同的恢复任务下更加灵活。
图7 MSC结构
了解过技术原理之后,是不是对模型如何搭建产生了好奇?接下来我将为大家介绍模型搭建过程。
模型搭建介绍
基础模块
构建一个基础的卷积层。
def conv(in_channels, out_channels, kernel_size, bias_attr=False, stride=1): layer = nn.Conv2D(in_channels, out_channels, kernel_size, padding=(kernel_size // 2), bias_attr=bias_attr, stride=stride) return layer
注意力模块
构建多种注意力模块。
## Spatial Attention
class SALayer(nn.Layer):
def __init__(self, kernel_size=7):
super(SALayer, self).__init__()
self.conv1 = nn.Conv2D(2, 1, kernel_size, padding=kernel_size // 2, bias_attr=False)
self.sigmoid = nn.Sigmoid()
def forward(self, x):
avg_out = paddle.mean(x, axis=1, keepdim=True)
max_out = paddle.max(x, axis=1, keepdim=True)
y = paddle.concat([avg_out, max_out], axis=1)
y = self.conv1(y)
y = self.sigmoid(y)
return x * y
# Spatial Attention Block (SAB)
class SAB(nn.Layer):
def __init__(self, n_feat, kernel_size, reduction, bias_attr, act):
super(SAB, self).__init__()
modules_body = [conv(n_feat, n_feat, kernel_size, bias_attr=bias_attr), act, conv(n_feat, n_feat, kernel_size, bias_attr=bias_attr)]
self.body = nn.Sequential(*modules_body)
self.SA = SALayer(kernel_size=7)
def forward(self, x):
res = self.body(x)
res = self.SA(res)
res += x
return res
## Pixel Attention
class PALayer(nn.Layer):
def __init__(self, channel, reduction=16, bias_attr=False):
super(PALayer, self).__init__()
self.pa = nn.Sequential(
nn.Conv2D(channel, channel // reduction, 1, padding=0, bias_attr=bias_attr),
nn.ReLU(),
nn.Conv2D(channel // reduction, channel, 1, padding=0, bias_attr=bias_attr), # channel <-> 1
nn.Sigmoid()
)
def forward(self, x):
y = self.pa(x)
return x * y
## Pixel Attention Block (PAB)
class PAB(nn.Layer):
def __init__(self, n_feat, kernel_size, reduction, bias_attr, act):
super(PAB, self).__init__()
modules_body = [conv(n_feat, n_feat, kernel_size, bias_attr=bias_attr), act, conv(n_feat, n_feat, kernel_size, bias_attr=bias_attr)]
self.PA = PALayer(n_feat, reduction, bias_attr=bias_attr)
self.body = nn.Sequential(*modules_body)
def forward(self, x):
res = self.body(x)
res = self.PA(res)
res += x
return res
## Channel Attention Layer
class CALayer(nn.Layer):
def __init__(self, channel, reduction=16, bias_attr=False):
super(CALayer, self).__init__()
# global average pooling: feature --> point
self.avg_pool = nn.AdaptiveAvgPool2D(1)
# feature channel downscale and upscale --> channel weight
self.conv_du = nn.Sequential(
nn.Conv2D(channel, channel // reduction, 1, padding=0, bias_attr=bias_attr),
nn.ReLU(),
nn.Conv2D(channel // reduction, channel, 1, padding=0, bias_attr=bias_attr),
nn.Sigmoid()
)
def forward(self, x):
y = self.avg_pool(x)
y = self.conv_du(y)
return x * y
## Channel Attention Block (CAB)
class CAB(nn.Layer):
def __init__(self, n_feat, kernel_size, reduction, bias_attr, act):
super(CAB, self).__init__()
modules_body = [conv(n_feat, n_feat, kernel_size, bias_attr=bias_attr), act, conv(n_feat, n_feat, kernel_size, bias_attr=bias_attr)]
self.CA = CALayer(n_feat, reduction, bias_attr=bias_attr)
self.body = nn.Sequential(*modules_body)
def forward(self, x):
res = self.body(x)
res = self.CA(res)
res += x
return res
图像缩放模块
DownSample:下采样,用于缩小特征图尺寸,提取图像特征;
UpSample:上采样,用于放大特征图尺寸,逐级恢复至原始图像尺寸;
SkipUpSample:上采样 + 跳跃连接(Skip Connect)。
##---------- Resizing Modules ----------
class DownSample(nn.Layer):
def __init__(self, in_channels, s_factor):
super(DownSample, self).__init__()
self.down = nn.Sequential(nn.Upsample(scale_factor=0.5, mode='bilinear', align_corners=False),
nn.Conv2D(in_channels, in_channels + s_factor, 1, stride=1, padding=0, bias_attr=False))
def forward(self, x):
x = self.down(x)
return x
class UpSample(nn.Layer):
def __init__(self, in_channels, s_factor):
super(UpSample, self).__init__()
self.up = nn.Sequential(nn.Upsample(scale_factor=2, mode='bilinear', align_corners=False),
nn.Conv2D(in_channels + s_factor, in_channels, 1, stride=1, padding=0, bias_attr=False))
def forward(self, x):
x = self.up(x)
return x
class SkipUpSample(nn.Layer):
def __init__(self, in_channels, s_factor):
super(SkipUpSample, self).__init__()
self.up = nn.Sequential(nn.Upsample(scale_factor=2, mode='bilinear', align_corners=False),
nn.Conv2D(in_channels + s_factor, in_channels, 1, stride=1, padding=0, bias_attr=False))
def forward(self, x, y):
x = self.up(x)
x = x + y
return x
U-Net
使用对称的 Encoder 和 Decoder,对应层级之间相互连接。
SAM 模块
SAM(去除了原版图中的 Loss,并且调整了其中卷积的核大小)。
# Supervised Attention Module
class SAM(nn.Layer):
def __init__(self, n_feat, kernel_size, bias_attr):
super(SAM, self).__init__()
self.conv1 = conv(n_feat, n_feat, kernel_size, bias_attr=bias_attr)
self.conv2 = conv(n_feat, 3, kernel_size, bias_attr=bias_attr)
self.conv3 = conv(3, n_feat, kernel_size, bias_attr=bias_attr)
def forward(self, x, x_img):
x1 = self.conv1(x)
img = self.conv2(x) + x_img
x2 = nn.functional.sigmoid(self.conv3(img))
x1 = x1 * x2
x1 = x1 + x
return x1, img
MSC 模块
Mixed Residual Module实现代码如下。
# Mixed Residual Module
class Mix(nn.Layer):
def __init__(self, m=1):
super(Mix, self).__init__()
self.w = self.create_parameter((1,), default_initializer=nn.initializer.Constant(m))
self.mix_block = nn.Sigmoid()
def forward(self, fea1, fea2, feat3):
factor = self.mix_block(self.w)
other = (1 - factor)/2
output = fea1 * other + fea2 * factor + feat3 * other
return output, factor
CMFNet 模型
上述的多个模块拼接一下即可搭建出完整的 CMFNet。
# CMFNet
class CMFNet(nn.Layer):
def __init__(self, in_c=3, out_c=3, n_feat=96, scale_unetfeats=48, kernel_size=3, reduction=4, bias_attr=False):
super(CMFNet, self).__init__()
p_act = nn.PReLU()
self.shallow_feat1 = nn.Sequential(conv(in_c, n_feat // 2, kernel_size, bias_attr=bias_attr), p_act,
conv(n_feat // 2, n_feat, kernel_size, bias_attr=bias_attr))
self.shallow_feat2 = nn.Sequential(conv(in_c, n_feat // 2, kernel_size, bias_attr=bias_attr), p_act,
conv(n_feat // 2, n_feat, kernel_size, bias_attr=bias_attr))
self.shallow_feat3 = nn.Sequential(conv(in_c, n_feat // 2, kernel_size, bias_attr=bias_attr), p_act,
conv(n_feat // 2, n_feat, kernel_size, bias_attr=bias_attr))
self.stage1_encoder = Encoder(n_feat, kernel_size, reduction, p_act, bias_attr, scale_unetfeats, 'CAB')
self.stage1_decoder = Decoder(n_feat, kernel_size, reduction, p_act, bias_attr, scale_unetfeats, 'CAB')
self.stage2_encoder = Encoder(n_feat, kernel_size, reduction, p_act, bias_attr, scale_unetfeats, 'PAB')
self.stage2_decoder = Decoder(n_feat, kernel_size, reduction, p_act, bias_attr, scale_unetfeats, 'PAB')
self.stage3_encoder = Encoder(n_feat, kernel_size, reduction, p_act, bias_attr, scale_unetfeats, 'SAB')
self.stage3_decoder = Decoder(n_feat, kernel_size, reduction, p_act, bias_attr, scale_unetfeats, 'SAB')
self.sam1o = SAM(n_feat, kernel_size=3, bias_attr=bias_attr)
self.sam2o = SAM(n_feat, kernel_size=3, bias_attr=bias_attr)
self.sam3o = SAM(n_feat, kernel_size=3, bias_attr=bias_attr)
self.mix = Mix(1)
self.add123 = conv(out_c, out_c, kernel_size, bias_attr=bias_attr)
self.concat123 = conv(n_feat*3, n_feat, kernel_size, bias_attr=bias_attr)
self.tail = conv(n_feat, out_c, kernel_size, bias_attr=bias_attr)
def forward(self, x):
## Compute Shallow Features
shallow1 = self.shallow_feat1(x)
shallow2 = self.shallow_feat2(x)
shallow3 = self.shallow_feat3(x)
## Enter the UNet-CAB
x1 = self.stage1_encoder(shallow1)
x1_D = self.stage1_decoder(x1)
## Apply SAM
x1_out, x1_img = self.sam1o(x1_D[0], x)
## Enter the UNet-PAB
x2 = self.stage2_encoder(shallow2)
x2_D = self.stage2_decoder(x2)
## Apply SAM
x2_out, x2_img = self.sam2o(x2_D[0], x)
## Enter the UNet-SAB
x3 = self.stage3_encoder(shallow3)
x3_D = self.stage3_decoder(x3)
## Apply SAM
x3_out, x3_img = self.sam3o(x3_D[0], x)
## Aggregate SAM features of Stage 1, Stage 2 and Stage 3
mix_r = self.mix(x1_img, x2_img, x3_img)
mixed_img = self.add123(mix_r[0])
## Concat SAM features of Stage 1, Stage 2 and Stage 3
concat_feat = self.concat123(paddle.concat([x1_out, x2_out, x3_out], 1))
x_final = self.tail(concat_feat)
return x_final + mixed_img
最后一步就是模型推理啦!
模型推理过程介绍
功能函数
加载模型:加载训练完成的模型参数;
图像预处理:读取图像、裁切图像、转置并归一化数据;
结果后处理:阈值处理、反归一化和转置并转换为数据类型uint8的BGR图像;
模型推理 :读取数据、预处理、前向计算最后完成后处理得到推理结果。
import cv2 from IPython.display import Image, display def load_model(model_path): model = CMFNet() model.eval() params = paddle.load(model_path) model.set_state_dict(params) return model def preprocess(img): clip_h, clip_w = [_ % 4 if _ % 4 else None for _ in img.shape[:2]] x = img[None, :clip_h, :clip_w, ::-1] x = x.transpose(0, 3, 1, 2) x = x.astype('float32') x /= 255.0 x = paddle.to_tensor(x) return x def postprocess(y): y = y.numpy() y = y.clip(0.0, 1.0) y *= 255.0 y = y.transpose(0, 2, 3, 1) y = y.astype('uint8') y = y[0, :, :, ::-1] return y @paddle.no_grad() def run(model, img_path, save_path): img = cv2.imread(img_path) x = preprocess(img) y = model(x) deimg = postprocess(y) cv2.imwrite(save_path, deimg) return deimg def show(img_path, save_path): display(Image(img_path)) display(Image(save_path))
去模糊
加载去模糊、去雾、去雨水模型,使用上述代码完成模型推理,分别实现去模糊、去雾、去雨水效果。
总结
基于深度学习实现的图像恢复算法,相比传统的图像恢复算法而言,恢复效果大多数情况下更佳,而且基于深度学习的模型算法适应性更强,通过统一的模型框架使用不同的数据集训练即可实现不同的图像恢复效果,无需针对不同任务实现定制化的处理算法。当然,基于深度学习实现的图像恢复算法也有其缺点,如需要大量的数据支持算法的训练、模型没有很好的可解释性等等。总而言之,基于深度学习实现图像恢复的优缺点兼具,是未来图像恢复领域一个可以持续优化发展的重要技术路径。
参考资料
[1] Fan C M, Liu T J, Liu K H. Compound Multi-branch Feature Fusion for Real Image Restoration[J]. arXiv preprint arXiv:2206.02748, 2022.
[2] Zamir S W , Arora A , Khan S , et al. Multi-Stage Progressive Image Restoration[C]// 2021.
关注【飞桨PaddlePaddle】公众号
获取更多技术内容~
本文同步分享在 博客“飞桨PaddlePaddle”(CSDN)。
如有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
为什么会有这么多编程语言?
为什么会有这么多编程语言? 在编程语言如此丰富的今天,相信许多开发者都有过上面的疑问。不过早在 1960 年代初,人们就已经提出了这个问题。 《ACM通讯》期刊近日发表了题为《Why Are There So Many Programming Languages?》的文章,作者在文中指出了一个重要原因:公司基于商业利益的诉求,希望控制编程语言,所以会发明由自己主导的语言。 1990 年代中期,Visual Basic 和 Visual C++ 是微软主力开发和维护的编程语言。这两种语言都是从已有的编程语言衍生而来,Visual Basic 的优势是为 Windows 桌面平台构建前端应用程序,但它缺乏许多高级语言功能(如数据结构、线程)。Visual C++ 虽然几乎可以处理所有问题,但它十分复杂。然后,Java 在 1996 年问世了,Java 是全功能的面向对象的编程语言,不像 C++ 那么复杂。 Java 的核心功能之一是平台可移植性,但这不是微软希望看到的,因此 Java 背后的公司 Sun Microsystems 与微软产生了冲突,并且从 1997 年开始发起诉讼。双方紧张...
- 下一篇
10分钟自定义搭建行人分析系统,检测跟踪、行为识别、人体属性All-in-One
本文已在飞桨公众号发布,查看请戳链接: 10分钟自定义搭建行人分析系统,检测跟踪、行为识别、人体属性All-in-One 行人分析工具PP-Human重磅升级! 五大异常行为一键识别 10余种预训练模型一站下载 10分钟快速新增识别类型 全流程保姆级教程,从技术选型、数据准备到模型部署全覆盖 图1:PP-Human v2全功能全景图 PP-Human集成了目标检测、目标跟踪、关键点检测、视频分类等硬核能力于一身,直接省去方案选型、模型搭建的步骤,一行命令即可实现快速推理,10分钟即可快速扩展个性化能力模块。不仅核心功能的性能直接拉满,还提供流畅顺滑的pipeline使用体验。 点击获得链接 ★ 欢迎Star收藏 ★ https://github.com/PaddlePaddle/PaddleDetection 图2:10+预训练模型可免费下载 PP-Human经由真实业务场景数据深度打磨优化,拥有适应不同光线、复杂背景下的人体属性特征分析、异常行为识别、出入口人流计数与轨迹绘制、跨镜跟踪四大核心功能。不仅如此,PP-Human还兼容单张图片、单路或多路视频等多种数据输入类型,更符合产业...
相关文章
文章评论
共有0条评论来说两句吧...