在开始之前先看一下效果图(提前声明一下:图片来源于网络侵删),因为人脸反差有点大因此有点辣眼睛,,,
左右原图,中间为生成图把图片中的角色互换,再来看一下转换后人脸替换的效果:
角色转换人脸替换图emm,结果怎么说呢,效果感觉还是不错的(产生的替换接缝不会那么失真、突兀),但是感觉生成新的人脸就是畸形的呢。
好了,下面将详细介绍 人脸替换技术并用 OpenCV 来实现;介绍到这里,如果没有看过之前写的几篇文章 ,
建议你提前了解一下,因为本文将用到这里面将用到 人脸特征点提取、Delaunay 三角剖分。
人脸替换技术是相对较难的,原因之一为人与人之间脸型的区别较大(纹理、脸型、区域凹凸部位等),使得替换后人脸区域与周围的皮肤组织差别较大,产生失真、不自然的视觉效果,
就下面两张图给出的最终人脸替换图,之所以结果非常辣眼睛,主要的原因如下几点:
1,年龄差别,脸部皱纹、纹理区域差别较大;
2,种族差别;一个是亚裔黄种人,一个是白种人,眼睛凹陷程度面部构造差别较大;
3,性别差别;男士和女士的脸型还是会有细微的区别的;
1,脸部区域大小不一致问题,例如一个体型较胖的和题型较瘦的人大小是明显不一样的,直接替换操作明显是不合适的,需要我们提前统一脸型才能有后续的操作;
2,人脸替换后,替换的人脸区域跟周围的皮肤组织会明显存在颜色差别,灯光等问题使得替换缝隙比较突兀,如下图,这个问题如果得不到解决,最终处理后的图像会非常 “假”;
combina.jpg3,人脸拍摄角度问题,有的图片展示的是正脸、有的展示的是侧脸;
4,最终皮肤问题,需要把替换的的脸部区域纹理与周围组织的保持一致,最终变化差别不大;
由于技术有限,本文利用 OpenCV 只解决了问题 1 和 2,问题 3 和 4 的解决方法有兴趣的同学可以继续深挖以下
1,特征点提取、找到 Convexhull
利用 dlib 程序包进行人脸特征点提取,根据特征点 find Convexhull(凸包)人脸区域轮廓勾勒 (这里只需要把脸部轮廓勾勒出来即可,不需要脸部中间的特征点), 人脸特征点提取请参考:
fillbox.png2,Delaunay 三角剖分
利用步骤 1 计算得到的 Convexhull(凸包),进行 Delaunay 三角剖分,结果如下图,三角剖分的具体操作请参考:
fillbox_tri.png3,仿射变换
对 2 中的每个三角区域计算 仿射变换矩阵,并应用到人脸区域,最终实现初步对齐:
combina.jpg4,Seamless Cloning(无缝克隆)
步骤 3 中得到凸显边缘处缝隙明显,失真程度较大,这里最终借助于 OpenCV 的 Seamless Cloning 函数进行后处理,这里需要一个 人脸 Mask ( 借助于fillConvexPoly 函数),一个比对图,以及要处理的图像
output = cv2.seamlessClone(np.uint8(img1Warped),img2,mask,center,cv2.NORMAL_CLONE)
out_mask.png
看起来还不错吧
本文用到的技术比较多,涉及 dlib 特征点提取,Subdiv2D 计算 Delaunay 三角剖分,find Convexhull计算多边形区域凸包,fillConvexpoly 多边形区域填充等技术
本文也算 OpenCV 的一个进阶应用,对于刚了解的小伙伴们来说,完全掌握需要一些时间,还是建议跟着代码敲一遍理解一下 Coding 的基本流程顺序,由于篇幅原因,核心代码放在下面:
def warpTriangle(img1,img2,t1,t2):
# Find bounding rectangle for each triangle
r1 = cv2.boundingRect(np.float32([t1]))
r2 = cv2.boundingRect(np.float32([t2]))
# Offset points by left top corner of respective rectangles
t1Rect =[]
t2Rect = []
t2RectInt = []
for i in range(0,3):
t1Rect.append(((t1[i][0]-r1[0]),(t1[i][1]-r1[1])))
t2Rect.append(((t2[i][0]-r2[0]),(t2[i][1]-r2[1])))
t2RectInt.append(((t2[i][0] - r2[0]),(t2[i][1]-r2[1])))
# Get mask by filling triangle
mask = np.zeros((r2[3],r2[2],3),dtype = np.float32)
cv2.fillConvexPoly(mask,np.int32(t2RectInt),(1.0,1.0,1.0),16,0)
# Apply warpImage to small rectangular patches
img1Rect = img1[r1[1]:r1[1]+r1[3],r1[0]:r1[0]+r1[2]]
size = (r2[2],r2[3])
img2Rect = applyAffineTransform(img1Rect,t1Rect,t2Rect,size)
img2Rect = img2Rect*mask
# Copy triangular region of the rectangular patch to the output image
img2[r2[1]:r2[1]+r2[3],r2[0]:r2[0]+r2[2]] = img2[r2[1]:r2[1]+r2[3],r2[0]:r2[0]+r2[2]] *((1.0,1.0,1.0)-mask)
img2[r2[1]:r2[1] +r2[3],r2[0]:r2[0]+r2[2]] = img2[r2[1]:r2[1]+r2[3],r2[0]:r2[0]+r2[2]] +img2Rect
文章 涉及的代码文件已上传至 Github,关注公众号: Z先生点记 后台回复关键词 Face Swap 即可获取。