利用CImg实现人脸融合

实验目的

输入两张人脸图像,根据Image Morphing的方法完成中间 11 帧的差值,得到一张人脸渐变的动图。

实验原理

Cross-Dissolve 交叉融合,对两张图片每个像素点按一定的比例进行混合,
公式:Imagehalfway = (1-t) * Image1 + t * image2
但这种方法只适合图像对齐的情况,对于没有对齐的情况,可以采用局部变形的思想,先根据特征点划分出局部图像,然后对局部图像求出平均图像,在平均图像上使用交叉融合,需要的像素值可以使用原图像的双线性插值,最后把局部图像拼接成整体图像。

实验步骤

  • 首先,对图片进行特征点标记,可以使用dlib库对人脸实现自动标记,但是需要用到opencv库,这里进行手动标记,并把标记的点存入文件中,方便下次使用。
    对应代码实现在getDetectionPoints函数中。
    利用CImg实现人脸融合_第1张图片利用CImg实现人脸融合_第2张图片
    除了手动检测的这几个点,还给每张图片加入了8个点,分别是4个角点和每条片的中点。

  • 进行Delaunay三角剖分:如果点集V的一个三角剖分T只包含Delaunay边,那么
    该三角剖分称为Delaunay三角剖分。Delaunay边是指:假设E中的一条边e(两个端点为a,b),e若满足下列条件,则称之为Delaunay边:存在一个圆经过a,b两点,圆内(注意是圆内,圆上最多三点共圆)不含点集V中任何其他的点,这一特性又称空圆特性。最优化:在散点集可能形成的三角剖分中,Delaunay三角剖分所形成的三角形的最小角最大。从这个意义上讲,Delaunay三角网是“最接近于规则化的”三角网。

Delaunay剖分是一种三角剖分的标准,实现它有多种算法。目前常用的一种算法是Bowyer-Watson算法,主要步骤如下:

  1. 构造一个超级三角形,包含所有散点,放入三角形链表。
  2. 将点集中的散点依次插入,在三角形链表中找出其外接圆包含插入点的三角形(称为该点的影响三角形),删除影响三角形的公共边,将插入点同影响三角形的全部顶点连接起来,从而完成一个点在Delaunay三角形链表中的插入。
  3. 根据优化准则对局部新形成的三角形进行优化。将形成的三角形放入Delaunay三角形链表。
  4. 循环执行上述第2步,直到所有散点插入完毕。

对应代码实现在getDelaunayTriangles 函数中。

	// --- step 3 : get Delaunay triangles --- 
	vector<triangle*> triangles_A, triangles_B;
	getDelaunayTriangles(triangles_A, points_A);

	// get couterpart delaunay triangles of B from A
	for (int i = 0; i < triangles_A.size(); i++) {
		triangles_B.push_back(new triangle(*points_B.at(triangles_A[i]->index[0]),
			*points_B.at(triangles_A[i]->index[1]),
			*points_B.at(triangles_A[i]->index[2]),
			triangles_A[i]->index[0],
			triangles_A[i]->index[1],
			triangles_A[i]->index[2]));
	}
	drawTriangles(triangles_A, img_A);
	drawTriangles(triangles_B, img_B);

在获得了A的三角剖分后,根据对应特征点获得B的三角剖分,效果如下:
利用CImg实现人脸融合_第3张图片利用CImg实现人脸融合_第4张图片

  • 将每个Delaunay三角形对映射到同一个三角形区域

在上一步我们获得了A和B的三角剖分,下一步,就是求出过渡三角形,这里的方法是用两张图片的比例进行加权。

// transition between source A and B
triangle* morph::getTransitionTriangle(const triangle* A, const triangle* B, double rate) {
	int ax = (int)(rate*(A->a.x) + (1 - rate)*(B->a.x));
	int ay = (int)(rate*(A->a.y) + (1 - rate)*(B->a.y));
	int bx = (int)(rate*(A->b.x) + (1 - rate)*(B->b.x));
	int by = (int)(rate*(A->b.y) + (1 - rate)*(B->b.y));
	int cx = (int)(rate*(A->c.x) + (1 - rate)*(B->c.x));
	int cy = (int)(rate*(A->c.y) + (1 - rate)*(B->c.y));
	return new triangle(point(ax, ay), point(bx, by), point(cx, cy));
}
  • 对每个映射三角形区域进行morphing

首先求过渡三角形到两张图的变换矩阵,TA=B,那么T=B*inv(A),利用变化矩阵算出过渡点,可以直接调用CImg的矩阵除法T=B/A。这里需要使用两个三角形的顶点坐标构造矩阵A和B。需要注意的是,CImg里的坐标是以左上角为原点,向右为x,向下为y。
在这里插入图片描述

CImg<float> morph::getTransTriangle2Triangle(const triangle* src, const triangle* dst) {

	// transform src to dst
	// !!! CImg 下标是先y后x !!!
	CImg<float> m1(3, 3);
	m1(0, 0) = src->a.x;
	m1(1, 0) = src->b.x;
	m1(2, 0) = src->c.x;
	m1(0, 1) = src->a.y;
	m1(1, 1) = src->b.y;
	m1(2, 1) = src->c.y;
	m1(0, 2) = m1(1, 2) = m1(2, 2) = 1;

	CImg<float> m2(3, 3);
	m2(0, 0) = dst->a.x;
	m2(1, 0) = dst->b.x;
	m2(2, 0) = dst->c.x;
	m2(0, 1) = dst->a.y;
	m2(1, 1) = dst->b.y;
	m2(2, 1) = dst->c.y;
	m2(0, 2) = m2(1, 2) = m2(2, 2) = 1;

	return m2 / m1;
}

然后计算过度图像每一帧的像素值,采用反向映射的方法,用过渡图像的点坐标求出原来两幅图对应点坐标,然后采用双线性插值的方法求出原图像对应点的像素值,然后根据交叉融合公式得到过渡图像素值。

	cimg_forXY(result, x, y) {
		if (trans_tri->isInTriangle(point(x, y))) {

			float tx_a = x * H1(0, 0) + y * H1(1, 0) + H1(2, 0);
			float ty_a = x * H1(0, 1) + y * H1(1, 1) + H1(2, 1);
			float pixel_a[3] = { 0 };
			cimg_forC(img_A, c) {
				pixel_a[c] = img_A.linear_atXY(tx_a, ty_a, 0, c);
			}

			float tx_b = x * H2(0, 0) + y * H2(1, 0) + H2(2, 0);
			float ty_b = x * H2(0, 1) + y * H2(1, 1) + H2(2, 1);
			float pixel_b[3] = { 0 };
			cimg_forC(img_B, c) {
				pixel_b[c] = img_B.linear_atXY(tx_b, ty_b, 0, c);
			}

			// morph
			cimg_forC(result, c) {
				result(x, y, 0, c) = rate * pixel_a[c] + (1 - rate)*pixel_b[c];
			}
		}
	}

这里使用了CImg的linear_atXY函数。还用到了isInTriangle判断一个点是否在三角形内,采用的是向量点积的方法,如果P在三角形ABC内部,则满足以下三个条件:P,A在BC的同侧、P,B在AC的同侧、PC在AB的同侧。某一个不满足则表示P不在三角形内部。

int cross3(const point &a, const point &b, const point &p) {
	return (b.x - a.x)*(p.y - a.y) - (b.y - a.y)*(p.x - a.x);
}

bool triangle::isInTriangle(const point& p) {

	if (cross3(a, b, p) >= 0 && cross3(b, c, p) >= 0 && cross3(c, a, p) >= 0)
		return true;
	else if (cross3(a, b, p) <= 0 && cross3(b, c, p) <= 0 && cross3(c, a, p) <= 0)
		return true;
	else
		return false;
}

实验结果

利用CImg实现人脸融合_第5张图片

完整代码

Github

参考博客

http://www.demodashi.com/demo/13644.html
http://www.yanglajiao.com/article/qq_31578409/70049516
https://blog.kinpzz.com/2017/04/25/face-morphing/

你可能感兴趣的:(计算机视觉)