最近比较有空,这几天又一直在P图,感觉换脸的过程其实就是一个枯燥的重复过程,于是就产生了做一个自动换脸工具的想法。


先来看一下最终的效果:

效果图

やりますねぇ!(赞赏)


准备工作

要实现自动P脸,肯定会用到人脸的识别。这里我直接使用了github上的一个轮子:face_recognition,这里面用到了dlib,在安装它之前要先装好cmake,接着在命令行里用pip安装即可(当然还需要用到PIL和numpy,就假装已经装好了吧)

这个库可以支持在一张图片中识别人脸,并返回每张人脸的所有特征点。后面做特征匹配的时候用到的特征点就是用它找出来的。

人脸定位

现在我们有两张人脸,每张人脸都有许多不同的特征点...

feature

我们要对其中一张人脸进行变换,使得变换后的所有特征点与另一张人脸的特征点尽可能接近。这里的变换只涉及到旋转,平移,缩放,平行扭曲(仿射变换),可以用$6$个参数$a,b,c,d,e,f$来表示:

$$x_1=ax_0+by_0+c,y_1=dx_0+ey_0+f$$

我们定义代价函数为$S=\frac 12\sum_i{({x_1}_i-{x_2}_i)^2+({y_1}_i-{y_2}_i)^2}$,其中${x_1}_i,{y_1}_i$是人脸$1$经过变换后的第$i$个特征点的坐标,${x_2}_i,{y_2}_i$是人脸$2$中第$i$个特征点的坐标。这里乘上$\frac 12$是为了后面求导方便。

我们代入变换的公式:

$$ S=\frac 12\sum(ax_0+by_0+c-x_2)^2+(dx_0+ey_0+f-y_2)^2 $$

这里省略了下标$i$。我们对六个参数求偏导:

$$ \frac{\partial S}{\partial a}=\sum(ax_0+by_0+c-x_2)x_0\\ \frac{\partial S}{\partial b}=\sum(ax_0+by_0+c-x_2)y_0\\ \frac{\partial S}{\partial c}=\sum ax_0+by_0+c-x_2\\ \frac{\partial S}{\partial d}=\sum(dx_0+ey_0+f-y_2)x_0\\ \frac{\partial S}{\partial e}=\sum(dx_0+ey_0+f-y_2)y_0\\ \frac{\partial S}{\partial f}=\sum dx_0+ey_0+f-y_2\\ $$

本来想直接梯度下降最优化这个东西的,后来发现令所有偏导数都为$0$,可以直接得到关于参数的线性方程组,直接解方程组就能得到最优的仿射变换参数了。具体方程组为:

$$ \begin{cases} (\sum x_0^2)a&+(\sum y_0x_0)b&+(\sum x_0)c&=\sum x_2x_0\\ (\sum x_0y_0)a&+(\sum y_0^2)b&+(\sum y_0)c&=\sum x_2y_0\\ (\sum x_0)a&+(\sum y_0)b&+nc&=\sum x_2\\ \end{cases} $$

($d,e,f$类似)

这样我们就能在$O(n)$的时间内求出对应的仿射变换了(虽然这里对性能并没有什么要求。。因为特征点并不多)。找到仿射变换后,可以用PIL.Image.transform把变换作用到图片上。

为了解决面部中心特征点过于密集的问题,我们还可以让特征点带上权值,降低较密集的特征点的权值。具体可以看代码。

我们把所有经过对应变换的人脸叠加在一张新图中,后面需要把这张图和原图进行合成。

图片合成

现在我们有两张图片:底图(记为图1)和一张由新的人脸组成的图(记为图2)。

一个比较直接的方法是直接把图2按alpha通道混合在图1中(这意味着什么都没有处理)。这样的话新图的颜色可能会跟原图对不上,人脸的边缘也会比较粗糙。

为了解决这两个问题,我们做两个额外的步骤:颜色调整和边缘羽化。

为了让颜色更加自然,我们需要调整图2中的颜色,让其接近图1的同时尽可能地保留细节。解决方案是把图1对图2遮罩得到图3(即削除了图1中不含人脸的部分),把图2和图3分别高斯模糊得到图4和图5,把图2对应像素除以图4再乘以图5,就能得到这个像素调整后的颜色。这么做是因为高斯模糊消除了图片细节,所以这样调整后即不会表现出覆盖掉的内容中的细节,也能体现其原有的大体颜色。

处理完颜色后,我们还需要对边缘进行羽化,让过渡更加自然。我没想到什么特别好的方法,直接对图2的alpha通道做高斯模糊后,再令新的alpha值$\alpha'$为$\min(0,\max(1,(\alpha-0.5)\times 2))$。这样做的原理是把让边缘的$alpha$值取$0$,且往内部扩展时$alpha$值变大,在边缘平滑的时候效果比较好,因为平滑边缘处的高斯模糊值可近似看作$0.5$,但在边缘鬼畜的时候可能会有一些奇妙问题。。不过一般情况下效果还是挺不错的。


具体实现

https://github.com/Alif-01/AutoHeadReplace

求一波star QwQ

神秘链接