人脸变形算法的实现
BabyMaker需要”预测”未来孩子的长相,一种显而易见的实现思路:取出父母照片上比较明显的特征(如眉毛、眼睛),与模板库的Baby按照某个比例进行图像混合,输出融合后的Baby即可。第一个步骤因为有Luxand SDK变得很简单,第二步图像混合也非常简单,但这里有个问题:Parent器官的相对位置和Baby的相对位置不一致,如果强行融合结果会非常别扭(比如两只嘴巴),所以在融合前我们必须对图片进行变形使相对位置一致,这里介绍人脸变形的实现。
比如有以下2张人脸图片,我们需要怎么去变形呢?
划分
首先我们需要划分图片,划分图片方式很灵活,需求不同划分方式也不同,不过本质都是根据人脸上的特征点进行划分。
因为BabyMaker需要的是父母的面部特征,对于面部以外的我会完全采用Baby的,所以变形我也只是针对父母的面部了,下面是我的划分方式,更专业的划分方式参考Delaunay triangulation。
转换
划分后我们就要开始对2张脸转换了,对于Baby上的点A和对应的Parent上点B,转换后为点为F,有如下公式:
其中alpha是用来控制更“像”谁的,极端的当alpha等于1时,Baby不变形,Parent根据Baby各个特征点进行变形。
Point F = new Point( (int)(alpha * A.X + (1 - alpha) * B.X), (int)(alpha * A.Y + (1 - alpha) * B.Y));
变形
我们需要对每个划分出来的三角形进行变形,这里我们使用一种叫做”Affine Transformation”的转换法。
假设Baby上的三角形ABC和对应的Parent上的DEF,我们有以下等式:
这里有2个未知变量,我们只需带入ABC的X坐标和Y坐标值,这样2个方程、2个未知量,2元一次方程,我们可以很轻松的解出来是吧,给个C#的参考代码,写得非常烂:
public static Point getAffineTransformationQ(Point p, Point A, Point B, Point C, Point D, Point E, Point F) { int XA = A.X; int XB = B.X; int XC = C.X; int XP = p.X; int YA = A.Y; int YB = B.Y; int YC = C.Y; int YP = p.Y; int a1 = XA - XC; int b1 = XB - XC; int c1 = XP - XC; int a2 = YA - YC; int b2 = YB - YC; int c2 = YP - YC; double x = (c1 * b2 - c2 * b1 + 0.0) / (a1 * b2 - a2 * b1 + 0.0); double y = (c1 * a2 - c2 * a1 + 0.0) / (a2 * b1 - a1 * b2 + 0.0); int XD = D.X; int XE = E.X; int XF = F.X; int YD = D.Y; int YE = E.Y; int YF = F.Y; int XQ = (int)(x * XD + y * XE + (1 - x - y) * XF); int YQ = (int)(x * YD + y * YE + (1 - x - y) * YF); Point q = new Point(XQ, YQ); return q; }
在转换时,我取alpha为1,这样经过转换,我们可以很明显的看到Parent的特征位置已经和Baby基本一致了。
融合
好了,经过以上的变形,我们的两张脸上各个器官的坐标已经对应了,我们就可以对两张图片进行融合了,公式如下:
A和B分别是Baby和Parent上对应的点,alpha依然是用来控制更趋向于谁的。我们遍历两张图片所有像素点,对ARGB进行上面这个公式进行计算,得到一个新的Color,放在一张新的Image上,合成完毕。
public static Color getMorphingColor(double alpha, Color x, Color y) { Color morphingColor = Color.Empty; int r = (int)(alpha * x.R + (1 - alpha) * y.R); int g = (int)(alpha * x.G + (1 - alpha) * y.G); int b = (int)(alpha * x.B + (1 - alpha) * y.B); int a = (int)(alpha * x.A + (1 - alpha) * y.A); morphingColor = Color.FromArgb(a, r, g, b); return morphingColor; }
图为当取alpha为0时的结果,非常别扭有木有。。不过对于人脸变形,我们的已经较完美的实现了。
在实际大量测试后得到不少缺陷(对于BabyMaker这个项目而言):
1 是如果用户上传的Parent的肤色过黑,合成之后就会出现一个"妖怪"。。。;
2 是用户上传的Parent特征点坐标和Baby相差过大,经过转换后,会有2两种情况,分别是出现过多的黑点或者像素点重叠,是由于2个三角形的面积相差过大造成的。
当我们检测到肤色明显过于极端时,或者当用户选择不满意此结果时,我们可以适当的提高alpha来暂时弥补这种缺陷。