程序整体思想分为以下几个部分,
(1)使用Dlib进行人脸检测和68个关键点定位
这里使用的是ERT方法,源自论文 One Millisecond Face Alignment with an Ensemble of RegressionTrees,算是一种比较老的方法了。但是效果很好。
(2)进行旋转,缩放,平移等变换,即aligement操作,使得第二个图和原始图相互吻合
这里使用的是普式变换(Ordinary Procrustes Analysis),最终结果是要使得变换过程满足下面式子最小化,简单的说,就是第一个图的68个点经过仿射变换(warp_affine)后,和第二个图的68个点的距离最小。
其中,R是2*2的旋转变换矩阵,S是一个线性缩放矩阵,T是一个二维平移向量,p,q分别为上面计算的2个人的68个点。
那么问题来了,现在只已经2个图的68个点,如何求解上面的S,R,T呢,这里使用奇异值SVD分解( Singular Value Decomposition),从而求出R。
U, S, Vt = numpy.linalg.svd(points1.T * points2)
R = (U * Vt).T
最终返回变换矩阵
return numpy.vstack([numpy.hstack(((s2 / s1) * R,
c2.T - (s2 / s1) * R * c1.T)),
numpy.matrix([0., 0., 1.])])
有了该变换矩阵,就可以使用opencv的warpAffine函数,将第二张图片变为和第一种图片同样的空间位置。
(3)将第二个图的颜色匹配到第一个图
如果直接将第二个图覆盖到第一个图,会发现,在边缘地方2个人的肤色和关照等会出现明显的区别,这里使用了颜色平衡( RGB scaling colour-correction)的方法,这里主要进行的操作就是,分别对图片1,2进行高斯滤波,生成高斯滤波后的1,2图片,然后对第二个图片除以自己的高斯滤波后的图片,再乘以第一个图片的高斯滤波后的图片,从而生成最终变换后的图片。
这里高斯核的选择是一个重点,太小的话,会将第一个图的特征显示到第二个图上,太大又会将人脸以外的区域覆盖到第二个图上,这里选取0.6倍的瞳距。
im1_blur = cv2.GaussianBlur(im1, (blur_amount, blur_amount), 0)
im2_blur = cv2.GaussianBlur(im2, (blur_amount, blur_amount), 0)
# Avoid divide-by-zero errors.
im2_blur += 128 * (im2_blur <= 1.0)
return (im2.astype(numpy.float64) * im1_blur.astype(numpy.float64) /
im2_blur.astype(numpy.float64))
(4)将第二个图的人脸特征,通过使用掩码的方式混合融合进第一个图
这里主要是要生成一个掩码,通过该掩码来进行图像融合,在掩码为1的地方显示图像2,掩码为1的地方显示图像1,掩码0-1之间的地方进行融合。
生成掩码的思路为,先通过人脸关键点生成一个凸包(convexHull),然后扣出凸包中的像素,进行高斯滤波,生成mask,然后将1,2两幅图的mask进行max操作,生成最终的mask。
最终通过一个阿尔法变换,用2个图片生成最终想要的结果。
output_im = im1 * (1.0 - combined_mask) + warped_corrected_im2 * combined_mask
当然这个并没有网上的H5的效果好,但是基本还是有模有样的。
实验效果如下,第一张是变换后的图,第二张是原图。
reference:
https://github.com/matthewearl/faceswap
http://matthewearl.github.io/2015/07/28/switching-eds-with-python/
https://github.com/hrastnik/FaceSwap