昇腾应用案例体验:(2) AI 图像修复

本文介绍了使用 Generative Adversarial Networks (GANs) 进行高分辨率图像修复的方法,详细阐述了修复流程,包括两阶段自编码器结构、注意力矩阵的应用以及图像锐化技术。通过对抗性训练,生成器和判别器协同工作,以提高修复图像的真实性和清晰度。此外,还讨论了模型的预处理和后处理步骤,以及如何利用昇腾AI软件栈进行模型转换和推理。
摘要由CSDN通过智能技术生成

昇腾AI应用,探索人工智能的无限可能,使能千行百业

AI 图像修复

概述

以Generative Adversarial Networks(GAN)为基础,其架构包括一个生成器(Generator)和一个判别器(Discriminator),通过迭代地训练两个网络(即生成器和判别器),由判别器提供的对抗性损失可以对修复的图像进行真假判别。系统整体结构如下图。

概述

系统将生成器替换为两阶的自编码结构。第一阶的自编码器是一个粗糙的自编码器,用来生成待修复部分的图像的大体轮廓,自编码器在训练时记录了大量的图像信息,即使图像部分缺失,也具有重建图像的能力。但自编码器生成的图像会模糊,这是自编码器的固有缺陷。假定擦除区域的图像是Mask内的图像,此时自编码器修复出来的Mask内的图像会非常的模糊,之后再将该图像送入到第二阶的自编码器。

第二阶自编码器是一个精细的自编码器,会对上阶生成的修复的Mask内的图像进行精细加工,使得该区域图像变得清晰。该阶自编码器的原理是将图像切成指定数量的Patch,比如对大小为512*512的原始图像切成32*32个相同大小的Patch,那么每个Patch大小是16*16。在该阶的编码器高层将特征图也切成32*32个Patch,比如在高层的特征图大小是96*96,Channel数为256,此时在高层用3*3的卷积核对每个Patch内进行特征提取,这样每个Patch会生成一个256维的向量,这256维的向量记录了原图对应Patch的特征。当两个Patch的特征相似时它们对应的256维向量的余弦相似度就会较大,用这个原理可以生成Patch之间的相关性,这个两两之间的相关性可以用注意力矩阵来表示,反应了图片内所有Patch之间的相似程度,相似度越大注意力分数越高。

修复Mask内的图像就是要利用相似度从Mask外找到与Mask内待修复Patch相似性高的Patch,在编码器的低层将Mask外的Patch与以对应的注意力分数作为权值相乘后再相加得到待修补Patch内的特征值。

对于生成器,希望它尽可能生成真实清晰的图像,而判别器则希望尽可能的区分真实样本与生成样本,以促使生成器尽可能生成更真实清晰的图像。

由于处理的是高分辨率的图像,修补的图像可能还不那么清晰,接下来要用图像锐化的原理将图像修补的区域进行增强,锐化需要获取图片的细节,细节是由原图减去下采样再上采样生成的模糊图片得到的,此时得到的细节图片的Mask区域需要再进一步增强,增强的原理是把细节图片也分成32*32个Patch再利用上面得到的注意力矩阵,加权求和Mask外的细节特征到Mask内,对增强的Mask内细节区域的图片再加到修复后图像对应的Mask内的区域,使得修复后的图像更加清晰自然。

基于GAN的对抗性训练方式,我们列出生成器和判别器的损失函数。如下是判别器损失函数,就是要让生成的图片分布的期望尽可能小,真实图片分布的期望尽可能大。

 

概述

如下是生成器的损失函数,由两部分组成,一部分是重构损失,重构损失也由两部分组成分别是Mask内和Mask外的重构损失,生成器另一部分是生成器损失,就是要让生成器生成的图片分布期望尽可能大。

概述

如下图所示,昇腾软件栈中存在一个ATC模型转换工具,针对本应用,我们需要使用该工具将原始模型转换成系统支持的om模型。

概述

本应用采用了下图所示的模块化设计,通过各模块之间的协调配合完成一张图片的推理输出。

概述

其中各个模块的主要功能点如下所示:

  • 1.运行管理资源申请;
  • 2.加载模型文件,构建模型输出内存;
  • 3.数据获取,获取要进行推理的原始图像和Mask图像;
  • 4.数据预处理,模型的输入图像进行预处理;
  • 5.模型推理,将数据输入到模型进行推理;
  • 6.解析模型推理结果,得到修复后的图像和注意力矩阵进行后处理,利用注意力矩阵对图像的细节的Mask内区域进行增强,将增强的细节叠加到修复的图像的Mask内区域即对修复的区域进行锐化,再把锐化Mask内区域的图像与原图Mask外区域合并得到修复后的高清图像。
  • 论文参考:Contextual Residual Aggregation for Ultra High-Resolution Image Inpainting
  • 原始模型部署:Image Inpainting Project based on CVPR 2020 Oral Paper on HiFill

图像预处理

预处理部分首先使用OpenCV读取原图和Mask图,将原图和Mask图进行大小缩放,缩放至3072*3072大小,之后再将原图像和Mask图像缩小到512*512用于送入模型进行推理,之所以将图片缩小后送入模型推理是为了节省算力和内存空间,加速推理时间;将读取到的图像数据拷贝至设备侧申请的内存空间中,为接下来构建模型输入数据做好准备。最后分别得到3072*3072和512*512的原图和Mask图。

上述功能函数原型为:

1. def pre_process(raw_img, raw_Mask):

参数说明:

raw_img[in]: 原始图像

raw_Mask[in] : Mask图像

原图像和Mask图像函数定义及相关源码注释如下所示:

 def pre_process(raw_img, raw_Mask):
     # normalization Mask
     raw_Mask = raw_Mask.astype(NPTYPE_FLOAT32) / 255.
     raw_img = raw_img.astype(NPTYPE_FLOAT32)
 
     # resize raw image & Mask to desinated size
     large_img = cv2.resize(raw_img,  (MULTIPLE * INPUT_SIZE, MULTIPLE * 	INPUT_SIZE), interpolation = cv2. INTER_LINEAR)
     large_Mask = cv2.resize(raw_Mask, (MULTIPLE * INPUT_SIZE, MULTIPLE * INPUT_SIZE), interpolation = cv2.INTER_NEAREST)
     
     # down-sample large image & Mask to 512x512
     small_img = resize_ave(large_img, MULTIPLE)
     small_Mask = cv2.resize(raw_Mask, (INPUT_SIZE, INPUT_SIZE), interpolation = cv2.INTER_NEAREST)
     
     # set hole region to 1. and backgroun to 0.
     small_Mask = 1. - small_Mask
     return large_img, large_Mask, small_img, small_Mask

由于模型接受输入的图像是NHWC所以这里要做个转换

 #input to om model should be NHWC
 img_512_hwc = np.ascontiguousarray(img_512)
 Mask_512_hwc = Mask_512[:,:,0:1]
 Mask_512_hwc = Mask_512_hwc.transpose(2,0,1).copy() 

模型推理结果后处理

模型的推理结果后处理函数原型为:

 def post_process(model,raw_img, large_img, large_Mask, inpainted_512, img_512, 	Mask_512, attention):

后处理是整个实验中最复杂的部分,主要是对模型生成器修复的图像对修复区域进行增强,增强方式是采用了图像锐化的原理,将修复图像的修复区域与原图细节抽取的图片对应的区域相加,另外本实验中利用注意力矩阵对原图细节图片也做了进一步加工,使得细节图片包含更丰富的信息,以此得到锐化后的修复区域图像更清晰。这部分代码实现使用了很多的技巧,接下来会对该段代码作重点讲解。

这段代码首先将修复后的图片和原始的512*512的图片分别放大到3076*3076,之后再将放大后的两张图片相减得到3076*3076的细节图片,再将细节图片乘以放大的3076*3076的Mask图片之后得到去除了Mask区域的细节图片,将处理后的细节图片再使用注意力矩阵利用Mask外的细节图片信息对Mask内的细节图片进行修补,生成细节图片Mask内区域的图片。再将细节图片与修复后放大的图片相加,以达到对修复区域的图片锐化的效果,使得修复的区域变得更清晰,之后再分别将锐化后的图片和Mask图片缩放到与原图尺寸大小一致,最后再将缩放后锐化的图片的Mask内区域与原图Mask外的区域合并组成一张完整的图像。

后处理代码如下:

 def post_process(model,raw_img, large_img, large_Mask, inpainted_512, img_512, Mask_512, attention):
     # compute the raw residual map
     h, w, c = raw_img.shape
     low_base = cv2.resize(inpainted_512.astype(NPTYPE_FLOAT32), (INPUT_SIZE * MULTIPLE, INPUT_SIZE * MULTIPLE), interpolation = cv2.INTER_LINEAR) 
     low_large = cv2.resize(img_512.astype(NPTYPE_FLOAT32), (INPUT_SIZE * MULTIPLE, INPUT_SIZE * MULTIPLE), interpolation = cv2.INTER_LINEAR)
     residual = (large_img - low_large) * large_Mask
 
     # reconstruct residual map using residual aggregation module
     residual = residual_aggregate(model,residual, attention)
 
     # compute large inpainted result
     res_large = low_base + residual
     res_large = np.clip(res_large, 0., 255.)
 
     # resize large inpainted result to raw size
     res_raw = cv2.resize(res_large, (w, h), interpolation = cv2.INTER_LINEAR)
 
     # paste the hole region to the original raw image
     Mask = cv2.resize(Mask_512.astype(NPTYPE_FLOAT32), (w, h), interpolation = cv2.INTER_LINEAR)
     Mask = np.expand_dims(Mask, axis=2)
 
     res_raw = res_raw * Mask + raw_img * (1. - Mask)
     return res_raw.astype(np.uint8)

在上面的后处理中用到矩阵乘法加速功能,由于细节图片有3072*3072大小,而且计算过程复杂,如果在CPU中对这么大的图片做矩阵运算会非常的耗时,所以这里使用了ACL对外提供的矩阵乘法单算子接口,利用NPU强大的算力来加速大矩阵的相乘运算。

首先在extract_image_Patches中将细节图像按宽高等距切割成32*32个Patch,这样每个Patch大小为96*96(3072/32 = 96),考虑到一张细节图有3个channel, 所以每个Patch有96*96*3=27648个像素, 再将这32*32个Patch按顺序排成一列一共有1024列,再把每个Patch所有像素按序拉成一行,共有27648行,由此组成了一个1024*27648的矩阵。由于attention矩阵大小为1024*1024,如果直接让attention矩阵(1024*1024)与reshape后的细节图矩阵(1024*27648)相乘有可能会把NPU的内存撑爆,所以这里就把reshape后的细节图矩阵平均分成了9份,每份大小为1024*3072(27648/9=3072),这样每次在NPU中实现1024*1024和1024*3072两个矩阵相乘,具体见matmul_ex接口。

reconstruct_residual_from_Patches是将上面reshape的细节图矩阵还原成之前的模样,是之前操作的一个逆过程,最终得到3072*3072*3的细节图像。

GenOutputImage函数定义及相关源码注释如下所示:

 # residual aggregation module
 def residual_aggregate(model,residual, attention):
     residual = extract_image_Patches(residual, MULTIPLE * INPUT_SIZE//ATTENTION_SIZE)
     residual = np.reshape(residual, [1, residual.shape[0] * residual.shape[1], -1])
     residual = matmul_om(model,attention,residual)
     #residual = np.matmul(attention, residual)
     residual = reconstruct_residual_from_Patches(residual, MULTIPLE * INPUT_SIZE//ATTENTION_SIZE)
     return residual
 
 # extract image Patches
 def extract_image_Patches(img, multiple):
     h, w, c = img.shape
     img = np.reshape(img, [h//multiple, multiple, w//multiple, multiple, c])
     img = np.transpose(img, [0,2,1,3,4])
     return img
 
 # reconstruct residual from Patches
 def reconstruct_residual_from_Patches(residual, multiple):
     residual = np.reshape(residual, [ATTENTION_SIZE, ATTENTION_SIZE, multiple, multiple, 3])
     residual = np.transpose(residual, [0,2,1,3,4])
     return np.reshape(residual, [ATTENTION_SIZE * multiple, ATTENTION_SIZE * multiple, 3])

效果展示

 

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值