本文为稀土掘金技术社区首发签约文章,30天内禁止转载,30天后未获授权禁止转载,侵权必究!
beginning
不知道小伙伴们注没注意到抠图在咱们生活中的应用呢?其实,电影拍摄的绿幕特效、在线会议软件的背景更换、B站不挡脸的蒙版弹幕 等等都是基于人像抠图实现的,本质上呢是计算机视觉上的语义分割问题。今天就手把手带大家利用Mediapipe中的人工智能工具包Selfie Segmentation来轻松实现人像抠图这一黑科技。废话不多说啦,如果你也想感受一下科技的魅力,让我们一起愉快的学习叭🎈🎈🎈

1.实战代码
1.1Web网页Demo体验
在人像抠图这一趣味应用中,我们仍然采用谷歌的开源工具包Mediapipe来实现。说起Mediapipe,小伙伴是不是很眼熟腻,没戳,在上节的手部关键点检测中就用到啦。它功能非常强大,有很多内置的solutions,比如人脸检测、眼球运动检测、手部关键点检测、人体姿态估计、头发分割、目标检测等等。它让我们可以很方便地实现这些个功能,而不需要从零开始编写代码。这么好用的工具,赶紧放个链接:Mediapipe主页、Github主页🍭🍭🍭
在这里先给大家上一个web网页的demo,抢先体验一下抠图➡codepen.io/tommyzihao/...,可以直接把链接复制在自己的浏览器里面就可以运行啦,不需要安装配置环境,直接调用摄像头在浏览器前端就可以看到实时人像抠图效果辽,既可以把人像的前景抠出来,也可以只保留背景,如下所示:

咱们今天用到的是Mediapipe里的Selfie Segmentation模块,可以把画面中占主导地位的人给分割出来,浅浅介绍一下叭🌻🌻🌻:
Selfie Segmentation包含两个模型,general大模型和landscape小模型,两个模型都是基于MobileNetV3这个轻量级网络。general大模型输入的是256×256×3的图像,输出的是256×256×1的张量,256×256×1其实是Segmentation mask,每个数字表明了该像素是不是人。landscape小模型和大模型差不多,只不过它输入的是144×256×3的图像,并且计算量更小,运行得更快,性能上会差一点儿。Mediapipe Selfie Segmentation会自动地在图像输入到模型之前对图像进行缩放,所以我们并不需要把图像缩放成256×256×3或者144×256×3,直接把原图喂到模型的接口里面,模型自己内部会进行缩放滴。
剩下的文档说明感兴趣的可以去瞅瞅➡Selfie Segmentation🌴🌴🌴
1.2人像抠图
下面呢手把手教大家用代码实现Mediapipe人像抠图的效果
<math xmlns="http://www.w3.org/1998/Math/MathML"> 1. 导入工具包和模型 \color{blue}{1. 导入工具包和模型} </math>1.导入工具包和模型:
python
import cv2
import numpy as np
import mediapipe as mp
import matplotlib.pyplot as plt
# 使用ipython的魔法方法,将绘制出的图像直接嵌入在notebook单元格中
%matplotlib inline
python
# 定义可视化图像函数
def look_img(img):
'''opencv读入图像格式为BGR,matplotlib可视化格式为RGB,因此需将BGR转RGB'''
img_RGB = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
plt.imshow(img_RGB)
plt.show()
python
# 从solution中导入模型
seg = mp.solutions.selfie_segmentation.SelfieSegmentation(model_selection=0)
导入工具包和定义可视化图像函数,这和前几节讲的一样,就不过多赘述啦。接下来从solution中导入模型,从Mediapipe的solutions模块里面的selfie_segmentation模块中实例一个SelfieSegmentation模型。在实例化的时候要指定model_selection=0,0代表上文中提到的general大模型⛳⛳⛳
<math xmlns="http://www.w3.org/1998/Math/MathML"> 2. 读入图像 \color{blue}{2. 读入图像} </math>2.读入图像:
python
# 从图片文件读入图像,opencv读入为BGR格式
img = cv2.imread('person1.jfif')
img.shape
look_img(img)
读入图像,这个图像可以是你自己本地的png、jpg、jpeg或者jfif等格式的图像都可以。用shape属性可以看到我这张图片是1002行,668列,3通道的彩色图像,用上文定义的辅助函数可视化一下,看到:

<math xmlns="http://www.w3.org/1998/Math/MathML"> 3. 将图像输入模型,获取预测结果 \color{blue}{3. 将图像输入模型,获取预测结果} </math>3.将图像输入模型,获取预测结果:
python
# BGR转RGB
img_RGB = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
# # 将RGB图像输入模型,获取预测结果
results = seg.process(img_RGB)
results.segmentation_mask
results.segmentation_mask.min()#看一下mask的最大值和最小值
results.segmentation_mask.max()
plt.imshow(results.segmentation_mask)
python
mask = results.segmentation_mask.astype("uint8")
mask
mask.max()#看一下mask的最大值和最小值
mask.min()
plt.imshow(mask)
再输入模型进行分割之前,需要先利用cv2.cvtColor()函数把BGR图像转成RGB格式,把转化后的图像喂到seg.process()函数里(这里的seg就是上文导入的图像分割模型),就获得了包含分割的全部结果results。再利用results.segmentation_mask看到分割结果如下:

可以看到,这个mask里面有很多的0,还有一些很复杂的数,因为是用float32表示的,我们可以把它转成更方便处理的uint8格式。转化之后就会看到一个非0即1的mask啦,可视化一下,得到分割的效果图如下

1.3背景更换
下面实现人像抠图基础上的背景更换。在上文得到一个单通道的非0即1的mask分割二值图后,进行以下操作:
<math xmlns="http://www.w3.org/1998/Math/MathML"> 1. 获取三通道逻辑符号的 m a s k \color{blue}{1. 获取三通道逻辑符号的mask} </math>1.获取三通道逻辑符号的mask:
python
# 单通道转三通道
mask_3 = np.stack((mask,mask,mask), axis=-1)
mask_3.shape
# 整数转逻辑符号
mask_3 = mask_3 > 0.1
mask的shape目前是(1002,668),现在呢我把三张一模一样的单通道mask摞到一起,变成了一个三通道图,shape就变为了(1002,668,3);然后再把非0即1的mask转成true或false表示的逻辑符号,转化后得到的mask呢就用于条件判断,判断它是不是属于人像的像素。
<math xmlns="http://www.w3.org/1998/Math/MathML"> 2. 更换背景颜色 \color{blue}{2. 更换背景颜色} </math>2.更换背景颜色:
python
MASK_COLOR = [0,200,0]
fg_image = np.zeros(img.shape, dtype=np.uint8)
fg_image[:] = MASK_COLOR
# 获得前景人像
FG_img = np.where(mask_3, img, fg_image)
# 获得抠掉前景人像的背景
BG_img = np.where(~mask_3, img, fg_image)
look_img(FG_img)
look_img(BG_img)
首先初始化一张新的图片,这个图片的所有像素都是绿色[0,200,0]。然后利用np.where()获取前景人像,函数np.where(mask_3, img, fg_image)是啥意思腻 🧐🧐🧐如果它符合mask_3的true,那就返回img原图,如果不符合mask_3的true,那就返回fg_image前景图(这里的前景图就是刚刚定义的绿色图)。最后可视化一下,分别看到前景人像和抠掉前景人像的背景图:

<math xmlns="http://www.w3.org/1998/Math/MathML"> 3. 更换背景图片 \color{blue}{3. 更换背景图片} </math>3.更换背景图片:
python
bkgd_img = cv2.imread("beach3.png")
bkgd_img.shape
# 从新背景图中切出原图大小的图块
# 高度方向(Y方向)
BOTTOM = bkgd_img.shape[0]
TOP = BOTTOM-img.shape[0]
# 宽度方向(X方向)
LEFT = bkgd_img.shape[1]//2 - img.shape[1]//2 # 从X方向中点向左偏移原始人像图片一半的宽
RIGHT = LEFT + img.shape[1]
new_bkgd = bkgd_img[TOP:BOTTOM,LEFT:RIGHT,:]
# 抠出来的图块和原始人像图片长宽一致
new_bkgd.shape
look_img(new_bkgd)
# 将mask_3定义的人像区加在新图块上
# np.where(condition, A, B) 符合condition返回A,不符合则返回B
new_bkgd_mask_img = np.where(mask_3, img, new_bkgd)
# 将完整背景对应的图块区域更换为加了人像的图块
bkgd_img[TOP:BOTTOM,LEFT:RIGHT,:] = new_bkgd_mask_img
look_img(bkgd_img)
这里要换的背景图片是一张海滩的图像:

海滩图的shape为(1031,1555,3),注意❗❗❗新背景的长宽应大于原始人像图片背景 ,这也很好理解,毕竟是要把原图嵌入到新背景中嘛。那对这个新的背景图该怎么操作腻?上面的代码有点多有些小伙伴看不懂呢🧐🧐🧐其实也特别好理解,首先从新背景图的中央把包含人像的原图给它切出来,然后把人像更换到这个小图块里面,最后把更换完的小图块嵌入到新背景图中就完成啦。没戳,就是这么简单,对应着代码注释小伙伴们应该都懂了叭

你看,得到上面包含人像的小图块后,再把小图块加到新背景图中就可以实现转换啦,如下图所示。同理,你也可以实现背景锐化、优化、模糊的效果喽,是不是很神奇腻🌈🌈🌈:

除了对图片进行处理,还可以实现摄像头实时人像抠图,其实就是把刚刚我们对单张图片的操作,变成了摄像头逐帧进行处理,再实时地显示出来,步骤都是一样的。只不过我们要定义一个处理单帧的函数,之后再调用摄像头获取每帧 。学有余力的小伙伴可以继续研究研究,类比一下上次介绍的手部关键点检测代码,或者看这里的详细教学------人像抠图黑科技🧸🧸🧸
2.背后原理Mobilenet V3
看到这里相信盆友们都能自己动手实现人像抠图啦,那有没有人对背后的原理感兴趣腻?人像抠图是基于深度学习算法Mobilenet V3这一轻量级卷积神经网络,接下来就简单介绍一下叭(为什么简单介绍呢,因为Mobilenet V3是在V1 V2的基础上改进的,理解起来有点困难,得需要很大的储备量腻)🌟🌟🌟
<math xmlns="http://www.w3.org/1998/Math/MathML"> 1. 网络构成🎈🎈🎈 \color{blue}{1. 网络构成🎈🎈🎈} </math>1.网络构成🎈🎈🎈
- MobileNet V3 主要由一系列的卷积层、激活函数和全局池化层组成;
- 卷积层采用了深度可分离卷积(Depthwise Separable Convolution)结构,它将标准卷积拆分为深度卷积和逐点卷积两个步骤;
- 激活函数使用了非线性函数 h-swish,它在保持计算效率的同时提供了更好的表示能力;
- 全局平均池化层用于将特征图转换为全局特征,减少模型参数量;

<math xmlns="http://www.w3.org/1998/Math/MathML"> 2. 网络原理🎈🎈🎈 \color{blue}{2. 网络原理🎈🎈🎈} </math>2.网络原理🎈🎈🎈
- MobileNet V3 的设计目标是在保持较高准确率的同时大幅减少模型大小和计算量;
- 自动搜索:MobileNet V3 使用网络搜索算法进行自动搜索,以找到最优的网络结构。这使得网络可以根据具体任务的需求进行调整,提高了模型的适应性;
- 宽度多样性:MobileNet V3 引入了网络宽度的可调节参数,使得模型在不同资源约束下都能获得较好的性能,可以根据设备或应用的资源限制来调整模型宽度;
- 网络候选层:MobileNet V3 通过引入网络候选层的结构池化策略,根据输入图像的特点选择不同的卷积层和操作,以提高网络的多样性和灵活性;
<math xmlns="http://www.w3.org/1998/Math/MathML"> 3. M o b i l e N e t V 3 的优点🎈🎈🎈 \color{blue}{3. MobileNet V3 的优点🎈🎈🎈} </math>3.MobileNetV3的优点🎈🎈🎈
- 高性能:尽管是轻量级模型,MobileNet V3 在保持较低计算复杂度的同时,取得了与传统模型相媲美甚至更好的性能。它在图像分类、目标检测和语义分割等任务上表现出色;
- 轻量级:由于模型结构的优化,MobileNet V3 的参数量大幅减少,使得模型更加轻量级,适用于移动设备和边缘计算场景;
- 自适应性:通过自动搜索机制和可调节的宽度参数,MobileNet V3 可以适应不同的任务需求和资源约束,具有较好的灵活性和适应性;
总体而言,MobileNetV3 在网络结构设计、自动搜索、宽度多样性和网络候选层等方面进行了创新和改进。相较于 MobileNetV1 和 MobileNetV2,MobileNetV3 通过引入深度可分离卷积、自动搜索、宽度可调节和网络候选层等机制,进一步提高了模型的性能、灵活性和适应性。
ending
好啦,今天关于人像抠图语义分割就介绍到这儿啦🌴🌴🌴很开心能把学到的知识以文章的形式分享给大家。如果你也觉得我的分享对你有所帮助,please一键三连嗷!!!下期见
