计算机视觉与模式识别(3)—— FaceMorphing

这是一个非常有意思的应用,可以将一个人的脸逐渐过渡为另一个人的脸。花了大概1天完成了最基本的功能,大概3天去完善它,可能还有不少bug等着我去修改,不过先把目前的进展记录下来吧。

        图形库:CImg

        环境:Win10、C++11、VS2013

效果图一:

效果图二:

思路:

1)      分别对图像A和图像B采集控制点(坐标)

2)      利用Delaunay算法划分三角形,保证图像A和图像B的三角形一一对应且不重合。

3)      计算每对三角形的过渡三角形,过渡量为0~1。

4)      计算三角形到三角形的仿射变换矩阵。

5)      利用变换矩阵分别将图像A和图像B变换到过渡三角形网格。

6)      利用过渡量计算合成图的像素色彩。

我觉得难点主要在于 Delaunay算法 和 Affine Transformation矩阵计算,其次是应用的鲁棒性增强。由于目前还没有学习人脸识别的相关技术,所以还需要人为标定控制点,做一些“Dirty Work”。因此我也把工程分为了两个:一是主工程,专门负责已经三角分割后的图像处理。二是辅助工程,专门负责控制点的采集以及三角分割。可以这么说,主工程的核心是Morphing,辅助工程的核心是Delaunay。

先看看辅助工程吧,事先定义好了常用的结构体后,就可以写Delaunay算法了。Delaunay分割的准则是:任何三角形ABC,不存在一点D,在其外接圆内。考虑到人脸的控制点数量一般在低两位数,计算机平均每秒可计算百万级别的数据,也就是支持每秒O(n^4)的计算量,那么可以采用暴力算法:

void Delaunay::buildDelaunayEx(const std::vector<Dot*>& vecDot, std::vector<Triangle*>& vecTriangle)
{
	int size = vecDot.size();
	if (size < 3)return;

	for (int i = 0; i < size - 2; ++i)
	{
		for (int j = i + 1; j < size - 1; ++j)
		{
			for (int k = j + 1; k < size; ++k)
			{
				Dot* A = vecDot[i];
				Dot* B = vecDot[j];
				Dot* C = vecDot[k];
				Triangle* tri = new Triangle(*A, *B, *C);
				getTriangleCircle(*tri);

				bool find = true;
				for (int m = 0; m < size; ++m)
				{
					Dot* P = vecDot[m];
					if (*P == *A || *P == *B || *P == *C) continue;
					if (pointInCircle(*P))
					{
						find = false;
						break;
					}
				}
				if (find)
				{
					vecTriangle.push_back(tri);
				}
			}
		}
	}
}

我试了一下在控制点不超过40个时,是可以1秒跑完的。当然啦,这是一种最笨最简单的方法。

为了让图像A和图像B的三角分割是一一对应的,我只对图像A进行了Delaunay分割,而图像B则根据图像A的三角构成来分割。因此,我会记录图像A的每个点的序号,以及每个三角形三个点的序号组成。最终将这些序号以文本的方式输出。

#include "CImg.h"
#include "Delaunay.h"
using namespace cimg_library;

#include <iostream>
#include <fstream>
#include <string>
using namespace std;

int main()
{
	// 输入图像路径
	string pathA, pathB;
	cout << "Image A path: "; cin >> pathA;
	cout << "Image B path: "; cin >> pathB;

	CImg<double> sourceA(pathA.c_str()), sourceB(pathB.c_str());
	CImgDisplay Adisp(sourceA, "Image A"), Bdisp(sourceB, "Image B");

	// 控制点数组
	vector<Dot*> vecDotA, vecDotB;

	// 图像角点预先置入数组
	vecDotA.push_back(new Dot(0, 0, 0));
	vecDotA.push_back(new Dot(0, sourceA.height() - 1, 1));
	vecDotA.push_back(new Dot(sourceA.width() - 1, sourceA.height() - 1, 2));
	vecDotA.push_back(new Dot(sourceA.width() - 1, 0, 3));

	vecDotB.push_back(new Dot(0, 0, 0));
	vecDotB.push_back(new Dot(0, sourceB.height() - 1, 1));
	vecDotB.push_back(new Dot(sourceB.width() - 1, sourceB.height() - 1, 2));
	vecDotB.push_back(new Dot(sourceB.width() - 1, 0, 3));

	// 点线颜色
	int color[3] = { 0, 255, 0 };

	// 点击鼠标获取坐标
	while (!Adisp.is_closed())
	{
		Adisp.wait();
		if (Adisp.button() & 1 && Adisp.mouse_y() >= 0)
		{
			Dot* click = new Dot(Adisp.mouse_x(), Adisp.mouse_y(), vecDotA.size());
			sourceA.draw_circle(click->x, click->y, sourceA.width() / 40, color);
			sourceA.display(Adisp);
			vecDotA.push_back(click);
		}
	}

	while (!Bdisp.is_closed())
	{
		Bdisp.wait();
		if (Bdisp.button() & 1 && Bdisp.mouse_y() >= 0)
		{
			Dot* click = new Dot(Bdisp.mouse_x(), Bdisp.mouse_y(), vecDotB.size());
			sourceB.draw_circle(click->x, click->y, sourceB.width() / 40, color);
			sourceB.display(Bdisp);
			vecDotB.push_back(click);
		}
	}

	// 三角形数组
	vector<Triangle*> vecTriA, vecTriB;

	// 对图像A进行Delaunay三角形分割
	 Delaunay().buildDelaunayEx(vecDotA, vecTriA);

	// 同步图像B的三角形分割
	 for (int i = 0; i < vecTriA.size(); ++i)
	 {
		 Dot* A = vecDotB[vecTriA[i]->a.value];
		 Dot* B = vecDotB[vecTriA[i]->b.value];
		 Dot* C = vecDotB[vecTriA[i]->c.value];
		 vecTriB.push_back(new Triangle(*A, *B, *C));
	 }

	 CImg<double> targetA(pathA.c_str()), targetB(pathB.c_str());
	 CImgDisplay TAdisp(targetA), TBdisp(targetB);

	// 点击鼠标逐步显示三角形
	int i = 0;
	while (!TAdisp.is_closed())
	{
		TAdisp.wait();
		if (TAdisp.button() && i < vecTriA.size())
		{
			targetA.draw_line(vecTriA[i]->a.x, vecTriA[i]->a.y, vecTriA[i]->b.x, vecTriA[i]->b.y, color);
			targetA.draw_line(vecTriA[i]->a.x, vecTriA[i]->a.y, vecTriA[i]->c.x, vecTriA[i]->c.y, color);
			targetA.draw_line(vecTriA[i]->b.x, vecTriA[i]->b.y, vecTriA[i]->c.x, vecTriA[i]->c.y, color);
			targetA.display(TAdisp);
			targetB.draw_line(vecTriB[i]->a.x, vecTriB[i]->a.y, vecTriB[i]->b.x, vecTriB[i]->b.y, color);
			targetB.draw_line(vecTriB[i]->a.x, vecTriB[i]->a.y, vecTriB[i]->c.x, vecTriB[i]->c.y, color);
			targetB.draw_line(vecTriB[i]->b.x, vecTriB[i]->b.y, vecTriB[i]->c.x, vecTriB[i]->c.y, color);
			targetB.display(TBdisp);
			++i;
		}
	}

	// 获取图像命名, 改变命名为txt格式
	string filenameA = pathA.substr(0, pathA.find_last_of(".")) + ".txt";
	string filenameB = pathB.substr(0, pathB.find_last_of(".")) + ".txt";

	// 控制点和三角形写入文本
	ofstream outputA(filenameA, ios::out), outputB(filenameB, ios::out);
	outputA << "[Points]" << endl;
	outputB << "[Points]" << endl;
	for (int i = 0; i < vecDotA.size(); ++i)
	{
		outputA << vecDotA[i]->x << "," << vecDotA[i]->y << endl;
		outputB << vecDotB[i]->x << "," << vecDotB[i]->y << endl;
	}
	outputA << "[Triangles]" << endl;
	outputB << "[Triangles]" << endl;
	for (int i = 0; i < vecTriA.size(); ++i)
	{
		outputA << vecTriA[i]->a.value << "," << vecTriA[i]->b.value << "," << vecTriA[i]->c.value << endl;
		outputB << vecTriB[i]->a.value << "," << vecTriB[i]->b.value << "," << vecTriB[i]->c.value << endl;
	}
}

接下来是主工程,有了辅助工程提供的文本数据以后,主工程需要计算每对三角的过渡三角形:

// 计算过渡三角形
Triangle* middleTriangle(Triangle* A, Triangle* B, float rate)
{
	float ax = rate*(A->a.x) + (1 - rate)*(B->a.x);
	float ay = rate*(A->a.y) + (1 - rate)*(B->a.y);
	float bx = rate*(A->b.x) + (1 - rate)*(B->b.x);
	float by = rate*(A->b.y) + (1 - rate)*(B->b.y);
	float cx = rate*(A->c.x) + (1 - rate)*(B->c.x);
	float cy = rate*(A->c.y) + (1 - rate)*(B->c.y);
	return new Triangle(Dot(ax, ay), Dot(bx, by), Dot(cx, cy));
}

然后计算变换矩阵系数:

Matrix3x3 Warp::TriangleToTriangle(float u0, float v0, float u1, float v1, float u2, float v2,
	float x0, float y0, float x1, float y1, float x2, float y2)
{
	// |A|
	int detA;
	detA = u0*v1 + u1*v2 + u2*v0 - u2*v1 - u0*v2 - u1*v0;
	// A*
	int A11, A12, A13, A21, A22, A23, A31, A32, A33;
	A11 = v1 - v2;
	A21 = -(v0 - v2);
	A31 = v0 - v1;
	A12 = -(u1 - u2);
	A22 = u0 - u2;
	A32 = -(u0 - u1);
	A13 = u1*v2 - u2*v1;
	A23 = -(u0*v2 - u2*v0);
	A33 = u0*v1 - u1*v0;

	Matrix3x3 result;
	result.a11 = (float)(x0*A11 + x1*A21 + x2*A31) / detA;
	result.a21 = (float)(y0*A11 + y1*A21 + y2*A31) / detA;
	result.a31 = (float)(A11 + A21 + A31) / detA;
	result.a12 = (float)(x0*A12 + x1*A22 + x2*A32) / detA;
	result.a22 = (float)(y0*A12 + y1*A22 + y2*A32) / detA;
	result.a32 = (float)(A12 + A22 + A32) / detA;
	result.a13 = (float)(x0*A13 + x1*A23 + x2*A33) / detA;
	result.a23 = (float)(y0*A13 + y1*A23 + y2*A33) / detA;
	result.a33 = (float)(A13 + A23 + A33) / detA;
	return result;
}

计算过渡三角形变换矩阵的目的是,把原图像转换为过渡图像,使得两幅原图像在一致的三角网格下进行色彩叠加(Morph)。为了达到这个目的,需要把原图像的三角网格利用变换矩阵映射到过渡三角形网格上。每一对三角形有一个变换矩阵,存入动态数组中。

// 计算三角变换矩阵
vector<Matrix3x3*> vecAB, vecBA;
for (int i = 0; i < vecTriA.size(); ++i)
{
	Matrix3x3 HAM = Warp::TriangleToTriangle(*vecTriA[i], *vecTriM[i]);
	vecAB.push_back(new Matrix3x3(HAM));
	Matrix3x3 HBM = Warp::TriangleToTriangle(*vecTriB[i], *vecTriM[i]);
	vecBA.push_back(new Matrix3x3(HBM));
}

三角映射比较简单了,判断每个像素点是否在原三角形内,然后计算映射三角形内的坐标,最后把该坐标的像素在原图上做一个双线性插值计算,就可以作为该目标像素点的色彩值了。以图像A为例:

// 对原图A进行三角变换
cimg_forXY(targetA, x, y)
{
	bool isFind = false;
	for (int i = 0; i < vecTriB.size(); ++i)
	{
		if (vecTriB[i]->pointInTriangle(Dot(x, y)))
		{
			float tx = x*vecBA[i]->a11 + y*vecBA[i]->a12 + vecBA[i]->a13;
			float ty = x*vecBA[i]->a21 + y*vecBA[i]->a22 + vecBA[i]->a23;
			if (tx >= 0 && tx < width && ty >= 0 && ty < height)
			{
				cimg_forC(sourceA, c)
					targetA(x, y, 0, c) = sourceA.linear_atXY(tx, ty, 0, c);
			}
			isFind = true;
			break;
		}
	}
	if (!isFind)
	{
		cimg_forC(sourceA, c)
			targetA(x, y, 0, c) = sourceA.linear_atXY(x, y, 0, c);
	}
}

最后合成目标图的像素色彩:

// 计算色彩插值
cimg_forXYZC(target, x, y, z, c)
	target(x, y, z, c) = rate*targetB(x, y, z, c) + (1 - rate)*targetA(x, y, z, c);

到此,应用已经基本成型。更多的测试还在进行中,我希望最终能通过Canvas制作成网页版的变脸应用。

时间: 2024-08-24 08:51:09

计算机视觉与模式识别(3)—— FaceMorphing的相关文章

经典的机器学习方面源代码库(非常全,数据挖掘,计算机视觉,模式识别,信息检索相关领域都适用的了)

经典的机器学习方面源代码库(非常全,数据挖掘,计算机视觉,模式识别,信息检索相关领域都适用的了) 今天给大家介绍一下经典的开源机器学习软件: 编 程语言:搞实验个人认为当然matlab最灵活了(但是正版很贵),但是更为前途的是python(numpy+scipy+matplotlib)和 C/C++,这样组合既可搞研究,也可搞商业开发,易用性不比matlab差,功能组合更为强大,个人认为,当然R和java也不错. 1.机器学习开源软件网(收录了各种机器学习的各种编程语言学术与商业的开源软件) h

计算机视觉与模式识别代码合集第二版two

Topic Name Reference code Image Segmentation Segmentation by Minimum Code Length AY Yang, J. Wright, S. Shankar Sastry, Y. Ma , Unsupervised Segmentation of Natural Images via Lossy Data Compression, CVIU, 2007 code Image Segmentation Normalized Cut

计算机视觉与模式识别代码合集第二版three

计算机视觉与模式识别代码合集第二版three     Topic Name Reference code Optical Flow Horn and Schunck's Optical Flow   code Optical Flow Black and Anandan's Optical Flow   code Pose Estimation Training Deformable Models for Localization Ramanan, D. "Learning to Parse I

国内外研究主页集合:计算机视觉-机器学习-模式识别

国内外研究主页集合:计算机视觉-机器学习-模式识别 来自 http://cvnote.info/pages-collection-by-carson2005/ 国际大牛 Adobe研究院 Jianchao Yang研究员 [进入主页]  (稀疏表示,超分辨率.图片检索.去噪.去模糊) CMU Srinivasa Narasimhan副教授 [进入主页] CMU Henry Schneiderman博士 [进入主页]  (目标检测和识别) CMU 田渊栋博士 [进入主页] CMU Alyosha

常用牛人主页链接(计算机视觉、模式识别、机器学习相关方向,陆续更新。。。。)【转】

转自:http://blog.csdn.net/goodshot/article/details/53214935 目录(?)[-] The Kalman Filter 介绍卡尔曼滤波器的终极网页 Navneet DalalHistograms of Oriented Gradients for Human Detection 牛人主页(主页有很多论文代码) Serge Belongie at UC San Diego Antonio Torralba at MIT Alexei Ffros a

计算机视觉与模式识别(2)—— A4纸矫正

上次写了A4纸的边缘提取,发现我的代码还是存在着很多的问题,比如令人诟病的静态阈值,还有非结构化的编程风格.于是我重新整理了一下,把A4纸边缘提取的代码整合为一个类.不过那个该死的阈值啊,我暂时还没有找到完美的方法,使得适用于所有的图像_(:з」∠)_. 优化的方法倒是有一点,那就是降低标准,择优录取.也就是把阈值调得很低,但是峰值提取的结果只取最优的4个.当然啦,这种方法偶尔会取到奇怪的边缘,而且由于阈值的降低,导致的计算量也成倍增长,特别是Hough变换.但综合来看,鲁棒性还是增强了不少.

paper 61:计算机视觉领域的一些牛人博客,超有实力的研究机构等的网站链接

转载出处:blog.csdn.net/carson2005 以下链接是本人整理的关于计算机视觉(ComputerVision, CV)相关领域的网站链接,其中有CV牛人的主页,CV研究小组的主页,CV领域的paper,代码,CV领域的最新动态,国内的应用情况等等.打算从事这个行业或者刚入门的朋友可以多关注这些网站,多了解一些CV的具体应用.搞研究的朋友也可以从中了解到很多牛人的研究动态.招生情况等.总之,我认为,知识只有分享才能产生更大的价值,真诚希望下面的链接能对朋友们有所帮助.(1)goog

计算机视觉领域的一些牛人博客,超有实力的研究机构等的网站链接

提示:本文为笔者原创,转载请注明出处:blog.csdn.net/carson2005 以下链接是本人整理的关于计算机视觉(ComputerVision, CV)相关领域的网站链接,其中有CV牛人的主页,CV研究小组的主页,CV领域的paper,代码,CV领域的最新动态,国内的应用情况等等.打算从事这个行业或者刚入门的朋友可以多关注这些网站,多了解一些CV的具体应用.搞研究的朋友也可以从中了解到很多牛人的研究动态.招生情况等.总之,我认为,知识只有分享才能产生更大的价值,真诚希望下面的链接能对朋

计算机视觉牛人博客和代码汇总

每个做过或者正在做研究工作的人都会关注一些自己认为有价值的.活跃的研究组和个人的主页,关注他们的主页有时候比盲目的去搜索一些论文有用多了,大牛的或者活跃的研究者主页往往提供了他们的最新研究线索,顺便还可八一下各位大牛的经历,对于我这样的小菜鸟来说最最实惠的是有时可以找到源码,很多时候光看论文是理不清思路的. 1 牛人Homepages(随意排序,不分先后): 1.USC Computer Vision Group:南加大,多目标跟踪/检测等: 2.ETHZ Computer Vision Lab