图像处理(九)人物肖像风格转换-Siggraph 2014

人物肖像风格转换

原文地址:http://blog.csdn.net/hjimce/article/details/45534333

作者:hjimce

一、前言

对于风格转换,2014年siggraph上面出了一篇比较不错的paper:《Style Transfer for Headshot Portraits》 ,这篇文献涉及到的算法非常多,可以说,如果要把这篇paper的代码从头到尾写过一遍,相当复杂。即使是paper作者本人,也只是通过代码拼凑实现的。因为这篇文章涉及到十几篇paper的算法。我这边主要讲解这篇文献的总流程,如果你打算把这篇文献完全看懂,那么对于图像融合、抠图、sift、图像变形等这些基础算法都要非常熟悉,因为这篇paper就是通过这些基础算法组合在一起实现的。当然如果自己把这篇paper搞过一遍,那么真的是可以学到好多经典算法。

先看一下paper的效果图:

图像处理(九)人物肖像风格转换-Siggraph 2014_第1张图片

这篇paper如果不考虑速度问题,那么真的是有很广泛的工程应用,paper实现的功能:输入一副图片Input,然后在输入一张Example图片,然后通过算法,可以把input图片的光照风格转换成Example图片的风格,感觉牛逼哄哄的样子。

它的工程应用,文章为我们列出了几个,我在这里简单讲解一下它的可能工程应用:

1、首先是化妆,我们知道现在天天P图、美妆等一系列软件化妆软件最近挺火的,天天p图的就是因为武媚娘妆而成名。利用本篇算法我们可以实现基于模板的化妆算法:

图像处理(九)人物肖像风格转换-Siggraph 2014_第2张图片

如图所示,也就是输入一张用户图片Input,然后输入一张化妆好的模板图片,可以实现用户图片Input的化妆,把Example的化妆结果,传输到Input上。不过这个我个人感觉很难达到工程应用,因为它对图片的对齐要求很高,而且用户输入的图片的背景也会跟着Example的背景进行转换,所以这就是为什么paper中演示的图片实例,基本上都是用单一背景图片。当然虽然不能达到工程应用,但是学了这篇paper我们可以学到很多东西,所以非常值得学习,特别是对于搞图片美化、美容软件的算法人,更必须学习。

2、瞳孔光照转换

这个在现有的美图秀秀、天天p图就有类似的功能,就是“亮眼”功能,当然这篇paper的亮眼比较高级,因为它不仅仅可以实现亮眼,而且可以把Exmaple的眼睛风格转换到Input图片上,如果Example的眼睛是蓝色的,那么Input也会跟着转换,牛逼哄哄。在paper的主页上,有很多眼睛光照转换结果,看一下下面一张效果图片:

图像处理(九)人物肖像风格转换-Siggraph 2014_第3张图片

总之,学完这篇paper你会感觉自己学到了n多种算法,虽然因为背景问题,很难被用于工程APP中,但是对于我们学算法的,必须好好解读。

二、算法流程

开始这篇paper之前,我们需要知道paper的主要创新点,paper的主要创新点是提出了基于拉普拉金字塔的图像风格转换,因此主要创新点是利用图像融合算法,实现风格转换。下面开始讲解算法流程:

1、对齐阶段

人脸对齐,这一步涉及到稠密sift对齐、基于特征线的图像变形算法,对应的文献分别为:《Sift flow: Dense correspondence across scenes and its applications》、《Feature-based image metamorphosis》。这一步算法主要流程:

(1)先通过人脸特征点检测算法,检测到68个face landmark,这一步如果要自己实现,可以用AAM算法,或者用CNN,相关的人脸特征点检测的paper很多,近几年单单face++就发表了好几篇高精度人脸特征点检测的算法。基于CNN人脸特征点定位精度,从《Deep Convolutional Network Cascade for Facial Point Detection》的效果看是精度挺高的,不过要实现这一步,对于我们仅仅只是为了实现风格转换,要花很长时间。所以还是建议直接到face++官网注册免费人脸特征点检测吧。

(2)以face landmark作为控制顶点,利用图像变形算法,对Example图像进行变形,把Example的人脸特征点对齐到Input的人脸特征点上,这一步我们又称之为粗对齐。对齐算法采用《Feature-based image metamorphosis》,这个变形算法,测试了一下,相比于《As-rigid-as-possible_shape_manipulation》、《Image Deformation Using Moving Least Squares》变形算法来说,其特点是可以实现保证图像人脸变形弧度比较大的时候,脸型的曲线过度比较自然。因此这个变形算法,可以用于瘦脸、眼睛放大等算法中。

(3)精对齐。这一步使用Dense sift flow进行对齐。其实我感觉这一步可以省了,因为用Dense sift flow进行对齐,有的时候感觉对齐效果很不好。

通过对齐步骤,我们可以把Example图片,变形到与Input一样形状,如下图所示:

图像处理(九)人物肖像风格转换-Siggraph 2014_第4张图片

最左边的图片便是Example变形结果了。图像对齐这一步不是paper的创新点,我们可以选择粗略的看一下就好了。

2、融合阶段

这一步的算法很重要,因为它是paper的主要创新点,是最值得我们学习的地方,需要好好琢磨,因为这一步就是实现风格转换的原理实现。主要这一步算法的主要流程如下:

图像处理(九)人物肖像风格转换-Siggraph 2014_第5张图片

算法总流程图

(1)构建拉普拉斯金字塔。对两幅图像:input、Example分别进行多尺度分解,说的简单一点就是构造拉普拉斯金子塔。这个算法如果了解金字塔图像融合算法的人,应该是挺熟悉的。当然拉普拉斯金字塔和高斯金字塔有点区别,高斯金字塔包含采样,在图像融合领域里的一大经典算法,这个扯得有点离题了。具体多尺度分解方法如下:

以高斯卷积核的卷积半径第0层为2,根据2n次方依次递增。也就是第0,1,2……的卷积半径依次为2,4,8,……进行高斯卷积,这就是所谓的多尺度,因为卷积核σ不同,所以人们又把它称之为多尺度。

根据如下公式进行构建拉普拉斯金子塔:

a.金子塔0~n-1层。金子塔从底层开始(L=0)计算,每一层的计算方法如下:

 

b.金字塔最后一层(L=n)

 

最后一层,我们又称之为残差层。

     说白了就是,用不同的卷积半径对一张图片分别进行卷积,然后进行相邻层之间相减,这样得到的多张图片就称之为金字塔了。如上面的公式说是,I代表输入图片,G(2)就是卷积σ为2的高斯核,然后上面的运算符就是卷积的意思了。这样把金字塔的每一层相加在一起,你可以发现刚好等于原图像I,而这个过程就称之为金字塔重建。而金字塔融合的原理,就是对金字塔的每一层进行融合处理,然后进行重建。而本篇paper实现光照风格转换的原理,就是对金子塔的每一层进行相关处理,然后再把处理后的每一层相加在一起,就可以得到重建结果。

记住,这一步对Input、Example都要构建金字塔。金字塔的层数,paper默认选择了6层,还有最后一层的残差层。

2、计算金字塔每层的能量图。

这个能量的计算,是根据上面计算得到的金字塔进行计算的。

能量计算公式如下:

 

对于Input image 根据上面的式子,就可以计算出每一层的能量图了。上面的公式,说的简单一点,就是对金子塔的每一层进行平方,然后在进行高斯卷积。得到的图像,有称之为能量图

对于Example image来说,我们也需要计算出其每一层的能量图(后面变形的时候,在对能量图进行变形)

 图像处理(九)人物肖像风格转换-Siggraph 2014_第6张图片

也就是先计算能量图,然后对能量图进行W运算,W指的是变形操作。

3、风格转换

对Input image金子塔的每一层(除了最后一层残差层之外),进行风格转换,每一层的计算公式如下:

 图像处理(九)人物肖像风格转换-Siggraph 2014_第7张图片

其中ε为较小的数,取,文章介绍,如果直接使用5b的计算结果,代入公司5a中,会出现一些相关的问题。因此在计算完5b之后,还需进一步的处理:

 

其中:

最后一层残差层,直接为Example的残差层图像。

看上面的算法流程,需要结合算法总流程图。上面的卷积核大小,并不一定要是2^n,卷积半径大小,对效果影响挺大的,这个paper有讲到,当卷积半径太小的时候,会出现转换过度。如果卷积半径太大了,又会出现转换不足的现象,可以看一下下面的半径对结果的影响图:

图像处理(九)人物肖像风格转换-Siggraph 2014_第8张图片

当然paper为了使得过度更好,还用了mask,需要用到grab cut算法。不过那都是效果提升阶段了。其实算法上面的这一步骤,就可以看见比较粗糙的风格转换结果了,因此我就讲到这里。

三、算法实现

 我这里只贴一下我自己写的paper的主要创新点部分,也就是上面的步骤2(融合阶段),因为只要实现了这一步,就可以看效果了。OK,融合阶段算法首先是构造拉普拉斯金子塔

<span style="font-size:18px;">//文献的公式1  创建拉普拉斯金字塔
void CStyleTransfer::CreateLaplacianStack(LAPSACK&lapsack,cv::Mat Img,int n,float first_sigma,int rsize)
{
	lapsack.ori_img=Img;

	//颜色空间转换,文献中提到才lab空间进行处理,效果会比较好,因此需要先转换成lab空间
	cv::Mat tempimg;//=Img;
	cv::cvtColor(Img,tempimg,CV_RGB2Lab);


	//卷积
	vector<cv::Mat>L(n);
	vector<cv::Mat>LG(n);
	for (int i=0;i<n;i++)
	{
		float sigma=pow(first_sigma,i+1);
		cv::Mat lgtemp;
		cv::GaussianBlur(tempimg,lgtemp,cv::Size(sigma+1,sigma+1),sigma);
		lgtemp.convertTo(LG[i],CV_32FC3);
	}
	//构建金字塔
	for (int i=0;i<n;i++)
	{
		if (i==0)
		{
			cv::Mat convertfloat;
			tempimg.convertTo(convertfloat,CV_32FC3);
			L[i]=convertfloat-LG[i];
		}
		else if(i>0&&i<n)
		{
			L[i]=LG[i-1]-LG[i];
		}	
	}
	lapsack.laplacian_stack=L;
	lapsack.residual=LG[LG.size()-1];
	//Reconstruction(lapsack.laplacian_stack,lapsack.residual);
	//计算能量图
	for (int i=0;i<lapsack.laplacian_stack.size();i++)
	{
		cv::Mat sqrI;
		cv::Vec3f img00=lapsack.laplacian_stack[i].at<cv::Vec3f>(100,100);
		cv::multiply(lapsack.laplacian_stack[i],lapsack.laplacian_stack[i],sqrI);
		cv::Vec3f img001=sqrI.at<cv::Vec3f>(100,100);
		float sigma=pow(first_sigma,i+1);
		cv::Mat dst;
		cv::GaussianBlur(sqrI,dst,cv::Size(sigma+1,sigma+1),sigma);
		cv::Vec3f img0011=dst.at<cv::Vec3f>(100,100);
		lapsack.pow_map.push_back(dst);
	}


/*
	for (int i=0;i<lapsack.laplacian_stack.size();i++)
	{
		string str;
		std::stringstream stream;
		stream<< i; //将int输入流
		stream >> str; //从stream中抽取前面插入的int值


/ *
		cvNamedWindow(str.c_str());


		cv::Mat convert;
		lapsack.laplacian_stack[i].convertTo(convert,CV_8UC3);
		cv::cvtColor(convert,convert,CV_Lab2RGB);
		imshow(str,convert);* /
	}*/

}</span>

上面的金子塔的高斯模糊半径,需要根据自己需要进行调整,如果你希望转换的粒度大一些,那么就把模糊半径选择的小一些。

金子塔重建部分:

<span style="font-size:18px;">//金字塔重建
cv::Mat CStyleTransfer::Reconstruction(vector<cv::Mat>LaplacianStack,cv::Mat residual)
{
	cv::Mat result=residual;//=LaplacianStack[LaplacianStack.size()-1].clone();//最后一层
	for (int i=0;i<LaplacianStack.size();i++)
	{

		result=result+LaplacianStack[i];
	}
	cv::Mat r;
	result.convertTo(r,CV_8UC3);
	cv::cvtColor(r,r,CV_Lab2RGB);
//	cv::normalize(result,r, 0, 1.,cv::NORM_MINMAX);


	
	imshow("reconstrution",r);
	return result;
}</span>

然后计算Gain的部分的代码如下:

<span style="font-size:18px;">//计算Gain
cv::Mat CStyleTransfer::RobustGain(cv::Mat input_pow,cv::Mat example_pow,int layer)
{
	int heigth=input_pow.rows;
	int width=input_pow.cols;
	cv::Mat outimg(heigth,width,CV_32FC3);
	float thetah=4;
	float thetal=0.25;
	float beta=3;
	for (int i=0;i<heigth;i++)
	{
		for (int j=0;j<width;j++)
		{
			float s=0.01*0.01;
			cv::Vec3f pow_example=example_pow.at<cv::Vec3f>(i,j);
			cv::Vec3f pow_input=input_pow.at<cv::Vec3f>(i,j)+cv::Vec3f(s,s,s);
			cv::Vec3f gain;//(1,1,1);
			cv::divide(pow_example,pow_input,gain);
			cv::sqrt(gain,gain);
			cv::Vec3f mingain=cv::Vec3f(min(gain[0],thetah),min(gain[1],thetah),min(gain[2],thetah));
			cv::Vec3f maxgain=cv::Vec3f(max(mingain[0],thetal),max(mingain[1],thetal),max(mingain[2],thetal));

			outimg.at<cv::Vec3f>(i,j)=maxgain;
		}
	}
	float sigma=3*pow(2.f,layer+1);
	cv::Mat robustgain;
	cv::GaussianBlur(outimg,robustgain,cv::Size(sigma+1,sigma+1),sigma);
	return robustgain;
</span>
这一部分得到的robustgain,模糊半径选得越大,那么过度越自然,不会出现过度不连续的现象。
效果测试:我这边贴一下,最粗糙的版本,也就是不经过对齐,不经过mask等处理,仅仅使用了paper的创新点部分的算法,进行验证测试,因为没有经过对齐,所以我选择了两张脸型,还有位置看起来比较对齐的图片作为测试图片,如下:

 图像处理(九)人物肖像风格转换-Siggraph 2014_第9张图片

这个图片可以看到转换上基本可以了,然而你可以看到在结果图片的最下方,过渡有点不自然,这个时候,就是因为选择的robustgain这一步的模糊半径选择太小了。在看另外一张结果图片:

图像处理(九)人物肖像风格转换-Siggraph 2014_第10张图片

这个就是我第一次得到的结果,刚开始一直以为自己代码写错了,想了n久。从图片上看,它实现了部分的转换,但是转换又不够彻底,皮肤的颜色还是有点黄。这是因为我在计算能量图的时候,根据作者的公式进行写,连卷积的半径也是根据公式来,最后经过把计算能量图的:

<span style="font-size:18px;">	//计算能量图
	for (int i=0;i<lapsack.laplacian_stack.size();i++)
	{
		cv::Mat sqrI;
		cv::Vec3f img00=lapsack.laplacian_stack[i].at<cv::Vec3f>(100,100);
		cv::multiply(lapsack.laplacian_stack[i],lapsack.laplacian_stack[i],sqrI);
		cv::Vec3f img001=sqrI.at<cv::Vec3f>(100,100);
		float sigma=pow(first_sigma,i+1);
		cv::Mat dst;
		cv::GaussianBlur(sqrI,dst,cv::Size(sigma+1,sigma+1),sigma);
		cv::Vec3f img0011=dst.at<cv::Vec3f>(100,100);
		lapsack.pow_map.push_back(dst);
	}</span>
中的高斯模糊的半径扩大了一倍,才得到最后的结果。于是去查看了作者写的matlab源码,果然,作者源码中的卷积半径也是比较大的,paper作者也并不是根据文献所写的一模一样的卷积半径进行实现,这个paper也提到了,卷积半径对于结果的影响非常大,然而papaer也没有给出比较合理的卷积半径的计算方法,估计是经过调参。具体paper相关的测试效果还有相关的源码在paper作者的主页上可以看到,这里就不罗嗦了。

**********************作者:hjimce   时间:2015.5.6  联系QQ:1393852684   地址:http://blog.csdn.net/hjimce 原创文章,版权所有,转载请保留本行信息********************

 参考文献:

1、《Style Transfer for Headshot Portraits》


你可能感兴趣的:(图像处理(九)人物肖像风格转换-Siggraph 2014)