写点什么

聊聊人像抠图背后的算法技术

发布于: 2021 年 05 月 13 日

​​摘要:本文将从算法概述、工程实现、优化改进三个方面阐述如何实现一个实时、优雅、精确的视频人像抠图项目。


本文分享自华为云社区《人像抠图:算法概述及工程实现(一)》,原文作者:杜甫盖房子 。

 

本文将从算法概述、工程实现、优化改进三个方面阐述如何实现一个实时、优雅、精确的视频人像抠图项目。

什么是抠图


对于一张图 I,我们感兴趣的人像部分称为前景 F,其余部分为背景 B,则图像 I 可以视为 F 与 B 的加权融合:I = alpha * F + (1 - alpha) * BI=alphaF+(1−alpha)∗B,而抠图任务就是找到合适的权重 alpha。值得一提的是,如图,查看抠图 ground truth 可以看到,alpha 是[0, 1]之间的连续值,可以理解为像素属于前景的概率,这与人像分割是不同的。如图,在人像分割任务中,alpha 只能取 0 或 1,本质上是分类任务,而抠图是回归任务。


抠图 ground truth:



分割 groundtruth:



相关工作


我们主要关注比较有代表性的基于深度学习的抠图算法。目前流行的抠图算法大致可以分为两类,一种是需要先验信息的 Trimap-based 的方法,宽泛的先验信息包括 Trimap、粗糙 mask、无人的背景图像、Pose 信息等,网络使用先验信息与图片信息共同预测 alpha;另一种则是 Trimap-free 的方法,仅根据图片信息预测 alpha​,对实际应用更友好,但效果普遍不如 Trimap-based 的方法。

Trimap-based


Trimap 是最常用的先验知识,顾名思义 Trimap 是一个三元图,每个像素取值为{0,128,255}其中之一,分别代表前景、未知与背景,如图。



Deep ImageMatting


多数抠图算法采用了 Trimap 作为先验知识。Adobe 在 17 年提出了 Deep Image Matting[^1],这是首个端到端预测 alpha 的算法,整个模型分 Matting encoder-decoder stage 与 Matting refinement stage 两个部分,Mattingencoder-decoder stage 是第一部分,根据输入图像与对应的 Trimap,得到较为粗略的 alpha matte。Matting refinement stage 是一个小的卷积网络,用来提升 alpha matte 的精度与边缘表现。



​网络训练时使用了两个 loss:alpha prediction loss 与 compositional loss。alpha prediction loss 是网络预测得到的 alpha matte 与 ground truth 的逐像素绝对差的可微近似:L_{\alpha}^i =\sqrt{(\alpha_p^i - \alpha_g^i)^2 + \epsilon^2} \ , \alpha_p^i, \alpha_g^i \in[0, 1]Lαi​=(αpi​−αgi​)2+ϵ2​ ,αpi​,αgi​∈[0,1];类似的,compositionalloss 通过预测 alpha matte 得到预测前景,与真实背景结合得到预测 RGB 图像,与 ground truth RGB 图像逐像素计算绝对差计算 loss:L_{c}^i = \sqrt{(c_p^i - c_g^i)^2 + \epsilon^2}Lci​=(cpi​−cgi​)2+ϵ2​。训练时首先更新 encoder-decoder 部分,收敛后再更新后一部分,最后微调整个网络。这种分段训练的技巧在很多其他任务里也较为常见。


本文在当时达到了 state-of-the-art,后续很多文章都沿用了这种“粗略-精细”的抠图思路,此外,由于标注成本高,过去抠图任务的数据是非常有限的。本文还通过合成提出了一个大数据集 Composition-1K,将精细标注的前景与不同背景融合,得到了 45500 训练图像和 1000 测试图像,大大丰富了抠图任务的数据。

BackgroundMatting


Background Matting[^2]是华盛顿大学提出的抠图算法,后续发布了 BackgrounMattingV2,方法比较有创新点,并且在实际工程应用中取得了不错的效果。


Backgroun Matting 不需要输入 trimap,而是输入图像或视频与对应的无人物背景,大大降低了实际应用的难度。网络由一个监督网络和一个半监督网络组成,作者首先使用 Adobe 数据集训练监督网络。原图经过处理得到 soft segmentation S,此外还通过临近帧生成辅助信息 motion prior M,与背景图一起送入监督网络。输入首先通过 Context Switching block,相当于一个编码器,优势在于可以使网络更倾向于忽略相似背景,关注人物分割特征,同时也会使网络更适应真实世界的输入。接下来通过残差与解码模块输出 alpha matte 与前景 F,计算 alpha、F 与 RGB 的误差得到 loss 进行训练:

L=\left\|\alpha-\alpha^{*}\right\|_{1}+\left\|\nabla(\alpha)-\nabla\left(\alpha^{*}\right)\right\|_{1}+2\left\|F-F^{*}\right\|_{1}+\|I-\alphaF-(1-\alpha) B\|_{1}L=∥αα∗∥1​+∥∇(α)−∇(α∗)∥1​+2∥FF∗∥1​+∥IαF−(1−α)B∥1​。


同时,由于 Adobe 的数据都是基于合成的,为了更好的适应真实输入,文中提出一个自监督网络训练 G_{Real}GReal​来对未标注的真实输入进行学习。G_{Real}GReal​输入与 G_{Adobe}GAdobe​相同,用 G_{Adobe}GAdobe​输出的 alpha matte 与 F 来监督 G_{Real}GReal​的输出得到 loss,此外,G_{Real}GReal​的输出合成得到的 RGB 还将通过一个鉴别器来判断真伪得到第二个 loss,共同训练 G_{Real}GReal​。



​文中列举了一些使用手机拍摄得到的测试结果,可以看到大部分情况结果还是很不错的。



BackgroundMatting V2


Background Matting 得到了不错的效果,但该项目无法实时运行,也无法很好的处理高分辨率输入。所以项目团队又推出了 Background Matting V2[^3],该项目可以以 30fps 的速度在 4k 输入上得到不错的结果。



​文章实现高效高分辨率抠图的一个重要想法是,alpha matte 中大部分像素是 0 或 1,只有少量的区域包含过渡像素。因此文章将网络分为 base 网络和 refine 网络,base 网络对低分辨率图像进行处理,refine 网络根据 base 网络的处理结果选择原始高分辨率图像上特定图像块进行处理。



​base 网络输入为 c 倍下采样的图像与背景,通过 encoder-decoder 输出粗略的 alpha matte、F、error map 与 hiddenfeatures。将采样 c 倍得到的 errormap E_cEc​上采样到原始分辨率的\frac{1}{4}41​为 E_4E4​,则 E_4E4​每个像素对应原图 4x4 图像块,从 E_4E4​选择 topk error 像素,即为原始 topk error 4x4 图像块。在选择出的像素周围裁剪出多个 8x8 图像块送入 refine 网络。refine 网络是一个 two-stage 网络,首先将输入通过部分 CBR 操作得到第一阶段输出,与原始输入中提取的 8x8 图像块 cat 后输入第二阶段,最后将 refine 后的图像块与 base 得到的结果交换得到最终的 alpha matte 和 F。



​网络训练时计算多种 loss:对于 alpha matte 计算 L_{\alpha}=\left\|\alpha-\alpha^{*}\right\|_{1}+\left\|\nabla\alpha-\nabla \alpha^{*}\right\|_{1}​=∥αα∗∥1​+∥∇α−∇α∗∥1​,对于前景 F 计算\left.L_{F}=\|\left(\alpha^{*}>0\right)*\left(F-F^{*}\right)\right) \|_{1}LF​=∥(α∗>0)∗(FF∗))∥1​,对于 error map 定义 ground truth error map 为 E^{*}=\left|\alpha-\alpha^{*}\right|E∗=∣αα∗∣,计算 errormap loss 为:L_{E}=\left\|E-E^{*}\right\|_{2}LE​=∥EE∗∥2​,总的 base 网络 loss 为:L_{\text {base}}=L_{\alpha_{c}}+L_{F_{c}}+L_{E_{c}}Lbase ​=Lαc​​+LFc​​+LEc​​。refine 网络 loss 为:L_{refine} = L_\alpha + L_FLrefine​=​+LF​。


此外文章还发布了两个数据集:视频抠图数据集 VideoMatte240K 与图像抠图数据集 PhotoMatte13K/85。VideoMatte240K 收集了 484 个高分辨率视频,使用 Chroma-key 软件生成了 240000+前景和 alpha matte 对。PhotoMatte13K/85 则是在良好光照下拍摄照片使用软件和手工调整的方法得到 13000+前景与 alpha matte 数据对。大型数据集同样是本文的重要贡献之一。




​此外还有一些文章如 Inductive Guided Filter[^4]、MGMatting[^5]等,使用粗略的 mask 作为先验信息预测 alpha matte,在应用时也比 trimap 友好很多。MGMatting 同时也提出了一个有 636 张精确标注人像的抠图数据集 RealWorldPortrait-636,可以通过合成等数据增广方法扩展使用。

Trimap-free


实际应用中先验信息获取起来是很不方便的,一些文章将先验信息获取的部分也放在网络中进行。

Semantic HumanMatting


阿里巴巴提出的 Semantic Human Matting[^6]同样分解了抠图任务,网络分为三个部分,T-Net 对像素三分类得到 Trimap,与图像 concat 得到六通道输入送入 M-Net,M-Net 通过 encoder-decoder 得到较为粗糙的 alpha matte,最后将 T-Net 与 M-Net 的输出送入融合模块 Fusion Module,最终得到更精确的 alpha matte。



​网络训练时的 alphaloss 分为 alpha loss 与 compositionalloss,与 DIM 类似,此外还加入了像素分类 lossL_tLt​,最终 loss 为:L = L_p + L_t=L_\alpha + L_c +L_tL=Lp​+Lt​=​+Lc​+Lt​。文章实现了端到端 Trimap-free 的抠图算法,但较为臃肿。此外文章提出 Fashion Model 数据集,从电商网站收集整理了 35000+标注的图片,但并没有开放。

Modnet


modnet[^7]认为神经网络更擅长学习单一任务,所以将抠图任务分为三个子任务,分别进行显式监督训练和同步优化,最终可以以 63fps 在 512x512 输入下达到 soft 结果,因此在后续的工程实现中我也选择了 modnet 作为 Baseline。



​网络的三个子任务分别是 Semantic Estimation、Detail Prediction 和 Semantic-Detail Fusion,Semantic Estimation 部分由 backbone 与 decoder 组成,输出相对于输入下采样 16 倍的 semantics,用来提供语义信息,此任务的 ground truth 是标注的 alpha 经过下采样与高斯滤波得到的。 Detail Prediction 任务输入有三个:原始图像、semantic 分支的中间特征以及 S 分支的输出 S_pSp​,D 分支同样是 encoder-decoder 结构,值得留意的该分支的 loss,由于 D 分支只关注细节特征,所以通过 ground truth alpha 生成 trimap,只在 trimap 的 unknown 区域计算 d_pdp​与\alpha_gαg​的 L_1L1​损失。F 分支对语义信息与细节预测进行融合,得到最终的 alpha matte 与 ground truth 计算 L_1L1​损失,网络训练的总损失为:L=\lambda_sL_s +\lambda_dL_d+\lambda_{\alpha}L_{\alpha}L=λsLs​+λdLd​+λα​。



此外,modnet 认为模型在某些场景中实际结果不理想是因为分支预测不一致导致的,因此为了适应真实图像域,modnet 基于子目标一致性提出了自监督的 SOC 策略。具体来说对于未标注的图像,经由网络得到了 semantics、details、alphamatte 三个输出,文章倾向于认为 semantics 是更可信的结果,因此计算一致性损失:L_{\text {cons}}=\frac{1}{2}\left\|G\left(\alpha_{p}\right)-s_{p}\right\|_{2}+mask\left\|\alpha_{p}-d_{p}\right\|_{1}Lcons ​=21​∥G(αp​)−sp​∥2​+maskαp​−dp​∥1​,强制 alpha 语义与 s_psp​一致,细节与 d_pdp​一致。在 semantics 结果比较理想时,这种方法还是比较有效的。



​最后,文章还提出了一种使视频结果在时间上更平滑的后处理方式 OFD,在前后两帧较为相似而中间帧与前后两帧距离较大时,使用前后帧的平均值平滑中间帧,但该方法会导致实际结果比输入延迟一帧。



​此外,U^2U2-Net、SIM 等网络可以对图像进行显著性抠图,感兴趣的话可以关注一下。

数据集


评价指标


常用的客观评价指标来自于 2009 年 CVPR 一篇论文[^8],主要有:


  • SAD:绝对差和,SAD=\sum_i|\alpha_i-\alpha_i^*|SAD=∑i​∣αi​−αi∗​∣

  • MSE:均方误差,MSE=\frac{1}{n}\sum_i(\alpha_i-\alpha_i^*)^2MSE=n1​∑i​(αi​−αi∗​)2

  • Gradient error:梯度误差,Gradient\ error = \sum(\nabla\alpha_i-\nabla\alpha_i^*)^qGradient error=∑(∇αi​−∇αi∗​)q

  • Connectivity error:连通性误差,Connectivity \error=\sum_{i}\left(\varphi\left(\alpha_{i},\Omega\right)-\varphi\left(\alpha_{i}^{*}, \Omega\right)\right)Connectivity error=∑i​(φ(αi​,Ω)−φ(αi∗​,Ω))


此外,可以在 paperwithcode 上查看 Image Matting 任务的相关文章,在 Alpha Matting 网站上查看一些算法的 evaluation 指标。


本项目的最终目的是在HiLens Kit硬件上落地实现实时视频读入与背景替换,开发环境为 HiLens 配套在线开发环境HiLens Studio,先上一下对比 baseline 的改进效果:


使用 modnet 预训练模型 modnet_photographic_portrait_matting.ckpt 进行测试结果如下:



​可以看到由于场景较为陌生、逆光等原因会导致抠图结果有些闪烁,虽然 modnet 可以针对特定视频进行自监督 finetune,但我们的目的是在普遍意义上效果更好,因此没有对本视频进行自监督学习。

优化后的模型效果如下:



本视频并没有作为训练数据。可以看到,抠图的闪烁情况减少了很多,毛发等细节也基本没有损失。

工程落地


为了测试 baseline 效果,首先我们要在使用场景下对 baseline 进行工程落地。根据文档导入/转换本地开发模型可知


昇腾 310 AI 处理器支持模型格式为".om",对于 Pytorch 模型来说可以通过"Pytorch->Caffe->om"或"Pytorch->onnx->om"(新版本)的转换方式得到,这里我选择的是第一种。Pytorch->Caffe模型转换方法与注意事项在之前的博客中有具体阐述过,这里不赘述。转换得到 Caffe 模型后,可以在HiLens Studio中直接转为 om 模型,非常方便。


首先在HiLens Studio中新建一个技能,此处选择了空模板,只需要修改一下技能名称就可以。



​将 Caffe 模型上传到 model 文件夹下:



在控制台中运行模型转换命令即可得到可以运行的 om 模型:


/opt/ddk/bin/aarch64-linux-gcc7.3.0/omg --model=./modnet_portrait_320.prototxt --weight=./modnet_portrait_320.caffemodel --framework=0 --output=./modnet_portrait_320 --insert_op_conf=./aipp.cfg
复制代码



​接下来完善 demo 代码。在测试时 HiLens Studio 可以在工具栏选择使用视频模拟摄像头输入,或连接手机使用手机进行测试:



具体的 demo 代码如下:


# -*- coding: utf-8 -*-# !/usr/bin/python3# HiLens Framework 0.2.2 python demo
import cv2import osimport hilensimport numpy as npfrom utils import preprocessimport time

def run(work_path): hilens.init("hello") # 与创建技能时的校验值一致
camera = hilens.VideoCapture('test/camera0_2.mp4') # 模拟输入的视频路径 display = hilens.Display(hilens.HDMI)
# 初始化模型 model_path = os.path.join(work_path, 'model/modnet_portrait_320.om') # 模型路径 model = hilens.Model(model_path)
while True: try: input_yuv = camera.read() input_rgb = cv2.cvtColor(input_yuv, cv2.COLOR_YUV2RGB_NV21) # 抠图后替换的背景 bg_img = cv2.cvtColor(cv2.imread('data/tiantan.jpg'), cv2.COLOR_BGR2RGB) crop_img, input_img = preprocess(input_rgb) # 预处理 s = time.time() matte_tensor = model.infer([input_img.flatten()])[0] print('infer time:', time.time() - s) matte_tensor = matte_tensor.reshape(1, 1, 384, 384)
alpha_t = matte_tensor[0].transpose(1, 2, 0) matte_np = cv2.resize(np.tile(alpha_t, (1, 1, 3)), (640, 640)) fg_np = matte_np * crop_img + (1 - matte_np) * bg_img # 替换背景 view_np = np.uint8(np.concatenate((crop_img, fg_np), axis=1)) print('all time:', time.time() - s)
output_nv21 = hilens.cvt_color(view_np, hilens.RGB2YUV_NV21) display.show(output_nv21)
except Exception as e: print(e) break
hilens.terminate()
复制代码


其中预处理部分的代码为:


import cv2import numpy as np
TARGET_SIZE = 640MODEL_SIZE = 384
def preprocess(ori_img): ori_img = cv2.flip(ori_img, 1) H, W, C = ori_img.shape x_start = max((W - min(H, W)) // 2, 0) y_start = max((H - min(H, W)) // 2, 0) crop_img = ori_img[y_start: y_start + min(H, W), x_start: x_start + min(H, W)] crop_img = cv2.resize(crop_img, (TARGET_SIZE, TARGET_SIZE)) input_img = cv2.resize(crop_img, (MODEL_SIZE, MODEL_SIZE))
return crop_img, input_img
复制代码


demo 部分的代码非常简单,点击运行即可在模拟器中看到效果:



​模型推理耗时 44ms 左右,端到端运行耗时 60ms 左右,达到了我们想要的实时的效果。

效果改进


预训练模型在工程上存在着时序闪烁的问题,原论文中提出了一种使视频结果在时间上更平滑的后处理方式 OFD,即用前后两帧平均误差大的中间帧。但这种办法只适合慢速运动,同时会导致一帧延迟,而我们希望可以对摄像头输入进行实时、普适的时序处理,因此 OFD 不适合我们的应用场景。


在 Video Object Segmentation 任务中有一些基于 Memory Network 的方法(如 STM),抠图领域也有新论文如DVM考虑引入时序记忆单元使抠图结果在时序上更稳定,但这些方法普遍需要前后 n 帧信息,在资源占用、推理实时性、适用场景上都与我们希望的场景不符合。


考虑到资源消耗与效果的平衡,我们采用将前一帧的 alpha 结果 cat 到当前帧 RGB 图像后共同作为输入的方法来使网络在时序上更稳定。


网络上的修改非常简单,只需在模型初始化时指定 in_channels = 4:


modnet = MODNet(in_channels=4, backbone_pretrained=False)
复制代码


​训练数据方面,我们选择一些 VideoMatting 的数据集:VideoMatte240K、ConferenceVideoSegmentationDataset。


最初,我们尝试将前一帧 alpha 作为输入、缺失前帧时补零这种简单的策略对模型进行训练:


if os.path.exists(os.path.join(self.alpha_path, alpha_pre_path)):    alpha_pre = cv2.imread(os.path.join(self.alpha_path, alpha_pre_path))else:    alpha_pre = np.zeros_like(alpha)    net_input = torch.cat([image, alpha_pre], dim=0)
复制代码


​收敛部署后发现,在场景比较稳定时模型效果提升较大,而在人进、出画面时模型适应较差,同时如果某一帧结果较差,将对后续帧产生很大影响。针对这些问题,考虑制定相应的数据增强的策略来解决问题。


  • 人进、出画面时模型适应较差:数据集中空白帧较少,对人物入画出画学习不够,因此在数据处理时增加空白帧概率:


if os.path.exists(os.path.join(self.alpha_path, alpha_pre_path)) and random.random() < 0.7:    alpha_pre = cv2.imread(os.path.join(self.alpha_path, alpha_pre_path))else:    alpha_pre = np.zeros_like(alpha)
复制代码


  • 某一帧结果较差,将对后续帧产生很大影响:目前的结果较为依赖前一帧 alpha,没有学会抛弃错误结果,因此在数据处理时对 alpha_pre 进行一定概率的仿射变换,使网络学会忽略偏差较大的结果;


  • 此外,光照问题仍然存在,在背光或光线较强处抠图效果较差:对图像进行光照增强,具体的,一定概率情况下模拟点光源或线光源叠加到原图中,使网络对光照更鲁棒。光照数据增强有两种比较常用的方式,一种是通过 opencv 进行简单的模拟,具体可以参考augmentation.py,另外还有通过 GAN 生成数据,我们使用 opencv 进行模拟。


  • 重新训练后,我们的模型效果已经可以达到前文展示的效果,在 16T 算力的HiLens Kit上完全达到了实时、优雅的效果。进一步的,我还想要模型成为耗时更少、效果更好的优秀模型~目前在做的提升方向是:


  • 更换 backbone:针对应用硬件选择合适的 backbone 一向是提升模型性价比最高的方法,直接根据耗时与资源消耗针对硬件搜一个模型出来最不错,目前搜出来的模型转为 onnx 测试结果(输入 192x192):


GPU:Average Performance excluding first iteration. Iterations 2 to 300. (Iterations greater than 1 only bind and evaluate)  Average Bind: 0.124713 ms  Average Evaluate: 16.0683 ms
Average Working Set Memory usage (bind): 6.53219e-05 MB Average Working Set Memory usage (evaluate): 0.546117 MB
Average Dedicated Memory usage (bind): 0 MB Average Dedicated Memory usage (evaluate): 0 MB
Average Shared Memory usage (bind): 0 MB Average Shared Memory usage (evaluate): 0.000483382 MB CPU:Average Performance excluding first iteration. Iterations 2 to 300. (Iterations greater than 1 only bind and evaluate) Average Bind: 0.150212 ms Average Evaluate: 13.7656 ms
Average Working Set Memory usage (bind): 9.14507e-05 MB Average Working Set Memory usage (evaluate): 0.566746 MB
Average Dedicated Memory usage (bind): 0 MB Average Dedicated Memory usage (evaluate): 0 MB
Average Shared Memory usage (bind): 0 MB Average Shared Memory usage (evaluate): 0 MB
复制代码


  • 模型分支:在使用的观察中发现,大部分较为稳定的场景可以使用较小的模型得到不错的结果,所有考虑 finetune LRBranch 处理简单场景,HRBranch 与 FusionBranch 依旧用来处理复杂场景,这项工作还在进行中。


[^1]: Xu, Ning, et al. “Deepimage matting.” Proceedingsof the IEEE conference on computer vision and pattern recognition.2017[^2]:Sengupta, Soumyadip, et al. “Background matting:The world is your green screen.” Proceedings of the IEEE/CVF Conference on ComputerVision and Pattern Recognition. 2020.[^3]:Lin, Shanchuan, et al. “Real-Time High-ResolutionBackground Matting.” arXivpreprint arXiv:2012.07810 (2020).[^4]:Li, Yaoyi, et al. “Inductive Guided Filter:Real-Time Deep Matting with Weakly Annotated Masks on Mobile Devices.” 2020 IEEE International Conference onMultimedia and Expo (ICME). IEEE, 2020.[^5]: Yu, Qihang, et al. “Mask Guided Matting viaProgressive Refinement Network.” arXiv e-prints (2020): arXiv-2012.[^6]: Chen, Quan, et al. “Semantic human matting.” Proceedings of the 26th ACMinternational conference on Multimedia. 2018.[^7]: Ke, Zhanghan, et al. “Is a Green Screen ReallyNecessary for Real-Time Human Matting?.” arXiv preprint arXiv:2011.11961 (2020).[^8]:Rhemann, Christoph, et al. “A perceptuallymotivated online benchmark for image matting.” 2009 IEEE Conference on Computer Vision and PatternRecognition. IEEE, 2009.


点击关注,第一时间了解华为云新鲜技术~

发布于: 2021 年 05 月 13 日阅读数: 365
用户头像

提供全面深入的云计算技术干货 2020.07.14 加入

华为云开发者社区,提供全面深入的云计算前景分析、丰富的技术干货、程序样例,分享华为云前沿资讯动态,方便开发者快速成长与发展,欢迎提问、互动,多方位了解云计算! 传送门:https://bbs.huaweicloud.com/

评论

发布
暂无评论
聊聊人像抠图背后的算法技术