以前在论坛、微博经常看到一张脸,五官长得像A,脸型似乎又是B,觉得很有意思。

比如像这张图片。这张图片应该是网友用Photoshop完成的,他们取了郭大爷的五官,放在金元帅的脸上,在把边缘处理平滑。

而上面这张图片是另外一种效果,它不仅改变了五官,连脸型轮廓也一起改变了。这种技术称为face morphing,这篇随笔就聊一聊它吧。

Morphing是指把一张照片变换成另一张照片,中间的变换过程如行云流水一般自然。

Cross-Disolve

最方便的方法是像素值叠加,第一幅照片的像素值乘以系数k,加上第二幅照片的对应像素值乘以系数(1-k)。这个方法尽管操作方便,但是效果并不好。首先,要把两幅照片的尺寸调整到一样大小,然后图像中主要内容的位置也应该保持一致,否则直接叠加会形成虚影。

Feature-based Morphing

Beier和Neely对这个方法做了改进,他们提出了一种基于特征的图像变换新方法。

假设目标图像里有一条直线PQ表示施瓦辛格的鼻梁,在源图像里小布什的鼻梁是P'Q'。那么我们就可以得到一个函数对应关系P'(x,y) = f(P(x,y)), Q'(x,y) = f(Q(x,y)). 而图像里的其它任意点X则可以根据PQ和P'Q'的关系来推算。

如果只用这一对特征线段来计算,那么在线段附近的区域可以很好的预测,稍远的区域就难以估计了。于是,可以选择多条特征线段,比如眼眶、发际线、下巴、领口等区域。多对线段和一对线段的方法类似,根据前面一对特征线段的方法,点X对每一对线段PiQi都计算得到一个点Xi,同时按照点X和线段PQ距离的远近得到一个权重值wi,距离越远权重越小。最后,X= Σwi*Xi。对于照片上的每一个像素X(x,y)都做这个运算,就得到了合成图像。

 

Mesh-based Morphing

另一种方法是基于网格的变换,它的思路就是在源图像和目标图像上标注若干对应的特征点,按照特征点把整张图像分割成若干块三角形区域。为保证五官在变换中的完整,五官和脸的轮廓应该多放置特征点,但是过多的特征点又会使运算速度降低。图像A的特征点数目和图像B特征点的数目是一致的,所以可以按照PC = α*PA + (1-α)*PB 的公式融合生成特征点在新图像中的位置。α∈[0,1]表示图像A对新图像的贡献率,α=1时新图像就是图像A。

下一步,用inverse warping的方法,找出图像C的点在图像A和图像B的对应位置。用插值的方法求出在图像A和图像B的像素值。同样按照PC = α*PA + (1-α)*PB 的公式将两者混合。

上面三张照片是α=0.25, 0.56, 0.75时的效果。

 

比较

Cross-Disolve方法最简单,但是效果不好。Feature-based Morphing方法直观,容易理解,实现也比较方便,但是如果取得特征线段不好,会有“意外效果”出现。Mesh-based Morphing方法人工选择特征点的过程略微复杂,但是效果比较好。

 

附Matlab代码:

A = imread('imA.jpg');
height = size(A,1);
width = size(A,2);

imshow(A);
[xA,yA] = ginput(64);
xA = [xA;1;width;width;1];
yA = [yA;1;1;height;height];

 

B = imread('C:\ZHYH\code\pic\wb.jpg');
imshow(B);
[xB,yB] = ginput(64);
xB = [xB;1;width;width;1];
yB = [yB;1;1;height;height];

 

alpha = 0.75;

C = zeros(height,width,3);
xC = alpha*xA + (1-alpha)*xB;
yC = alpha*yA + (1-alpha)*yB;
triC = delaunay(xC,yC);
ntri = size(triC,1);


xCA = zeros(height,width);
yCA = zeros(height,width);
xCB = zeros(height,width);
yCB = zeros(height,width);


[X,Y] = meshgrid(1:width,1:height);

for k = 1:ntri
[w1,w2,w3,r] = inTri(X, Y, xC(triC(k,1)), yC(triC(k,1)), xC(triC(k,2)), yC(triC(k,2)), xC(triC(k,3)), yC(triC(k,3)));
w1(~r)=0;
w2(~r)=0;
w3(~r)=0;
xCA = xCA + w1.*xA(triC(k,1)) + w2.*xA(triC(k,2)) + w3.*xA(triC(k,3));
yCA = yCA + w1.*yA(triC(k,1)) + w2.*yA(triC(k,2)) + w3.*yA(triC(k,3));
xCB = xCB + w1.*xB(triC(k,1)) + w2.*xB(triC(k,2)) + w3.*xB(triC(k,3));
yCB = yCB + w1.*yB(triC(k,1)) + w2.*yB(triC(k,2)) + w3.*yB(triC(k,3));
end

VCA(:,:,1) = interp2(X,Y,double(A(:,:,1)),xCA,yCA);
VCA(:,:,2) = interp2(X,Y,double(A(:,:,2)),xCA,yCA);
VCA(:,:,3) = interp2(X,Y,double(A(:,:,3)),xCA,yCA);

VCB(:,:,1) = interp2(X,Y,double(B(:,:,1)),xCB,yCB);
VCB(:,:,2) = interp2(X,Y,double(B(:,:,2)),xCB,yCB);
VCB(:,:,3) = interp2(X,Y,double(B(:,:,3)),xCB,yCB);

C = alpha*VCA + (1-alpha)*VCB;
imshow(uint8(C));

 

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% inTri checks whether input points (vx, vy) are in a triangle whose
% vertices are (v0x, v0y), (v1x, v1y) and (v2x, v2y) and returns the linear
% combination weight, i.e., vx = w1*v0x + w2*v1x + w3*v2x and
% vy = w1*v0y + w2*v1y + w3*v2y. If a point is in the triangle, the
% corresponding r will be 1 and otherwise 0.
%
% This function accepts multiple point inputs, e.g., for two points (1,2),
% (20,30), vx = (1, 20) and vy = (2, 30). In this case, w1, w2, w3 and r will
% be vectors. The function only accepts the vertices of one triangle.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

function [w1,w2,w3,r] = inTri(vx, vy, v0x, v0y, v1x, v1y, v2x, v2y)
v0x = repmat(v0x, size(vx,1), size(vx,2));
v0y = repmat(v0y, size(vx,1), size(vx,2));
v1x = repmat(v1x, size(vx,1), size(vx,2));
v1y = repmat(v1y, size(vx,1), size(vx,2));
v2x = repmat(v2x, size(vx,1), size(vx,2));
v2y = repmat(v2y, size(vx,1), size(vx,2));
w1 = ((vx-v2x).*(v1y-v2y) - (vy-v2y).*(v1x-v2x))./...
((v0x-v2x).*(v1y-v2y) - (v0y-v2y).*(v1x-v2x)+eps);
w2 = ((vx-v2x).*(v0y-v2y) - (vy-v2y).*(v0x-v2x))./...
((v1x-v2x).*(v0y-v2y) - (v1y-v2y).*(v0x-v2x)+eps);
w3 = 1 - w1 - w2;
r = (w1>=0) & (w2>=0) & (w3>=0) & (w1<=1) & (w2<=1) & (w3<=1);
end