图像变形算法:实现Photoshop液化工具箱中向前变形工具

时间:2021-10-12 15:12:55

源地址:http://www.cnblogs.com/xiaotie/archive/2009/12/08/1619046.html
有用的地址:http://www.docin.com/p-406100176.html

很多时候,我们需要对一个图像的局部进行调整,这个调整必须是平滑的和可交互式的。Photoshop液化滤镜中向前变形工具就是这样一个工具,很好用。类似工具有美图秀秀(http://www.ddxia.com/view/123008460425144.html)的瘦脸功能。本文描述这类工具背后的原理与算法。

  先以美图秀秀为例子,简单描述下向前变形功能。

  首先,用鼠标控制一个圆形的选区。

图像变形算法:实现Photoshop液化工具箱中向前变形工具

  然后,点击鼠标左键,向某个方向拖动,就可以产生光滑的向前变形图片:

图像变形算法:实现Photoshop液化工具箱中向前变形工具

  通过这个工具,可对图片的局部进行调整,*度比较大,因此比较实用。

  下面讲讲这类算法的原理。

图像变形算法:实现Photoshop液化工具箱中向前变形工具

  上图中,阴影圆环代表一个半径为 rmax 的圆形选区。其中,C点是鼠标点下时的点,也就是圆形选区的圆心。鼠标从C拖到M,致使图像中的点U变换到点X。所以,关键问题是找到上面这个变换的逆变换——给出点X时,可以求出它变换前的坐标U(精确的浮点坐标),然后用变化前图像在U点附近的像素进行插值,求出U的像素值。如此对圆形选区内的每一个像素进行求值,便可得出变换后的图像。

  Andreas Gustafsson 的 Interactive Image Warping 一文给出了这一逆变换公式:作者的官网为:http://www.gson.org/ 

论文下载地址为:http://www.gson.org/thesis/warping-thesis.pdf

图像变形算法:实现Photoshop液化工具箱中向前变形工具

  这个变形算法的特点是:

  1 只有圆形选区内的图像才进行变形

  2 越靠近圆心,变形越大,越靠近边缘的变形越小,边界处无变形

  3 变形是平滑的

  具体实现步骤如下:

  1 对于圆形选区里的每一像素,取出其R,G,B各分量,存入3个Buff(rBuff, gBuff, bBuff)中(也即,三个Buff分别存储选区内的原图像的R,G,B三个通道的数值)

  2 对于圆形选区里的每一个像素X,

  2.1 根据上面的公式,算出它变形前的位置坐标精确值U

  2.2 用插值方法,根据U的位置,和rBuff, gBuff, bBuff中的数值,计算U所在位置处的R,G,B等分量

  2.3 将R,G,B等分量合成新的像素,作为X处的像素值

  代码我就不贴了,真正对这功能有需求的,根据上面的文字可以很容易写出来——解决这类问题,重要的不是代码,而是思路和算法。

  下面是我的实现演示:

图像变形算法:实现Photoshop液化工具箱中向前变形工具

  查看原图(大图)

  上图中,左上角是原图,右下角是变形后的图。红色圆圈圈起来的是变形区域。可以看见,变形很光滑。我在上面的算法中引入了变形强度s(strength),上图中strength=20。

  引入strength,公式就得修改下,下面是我的修改版公式:

图像变形算法:实现Photoshop液化工具箱中向前变形工具

  查看原图(大图)

  看看结果——

  原图:

图像变形算法:实现Photoshop液化工具箱中向前变形工具

  变形,strength=20:

图像变形算法:实现Photoshop液化工具箱中向前变形工具

  变形,strength=120:

图像变形算法:实现Photoshop液化工具箱中向前变形工具

  photoshop 与美图秀秀里这个功能可以连续的进行变形。我猜测,这个连续的变形是由一系列基础变形串联起来的,也就是,鼠标从M0拖到Mn位置,并不是只计算 M0->Mn这个变换,而是在鼠标轨迹上引入一系列中间点,M1,M2…Mn-1,然后,对图像进行 M0->M1,M1->M2,…,Mn-1->Mn等一系列变换。



下面jia_zhengshen原创。。。。并非原作者(小铁)所写。
这仅是我的猜想,我努力的实现了一下串联起来然后实现Photoshop的效果,但是我没有实现。并且这个算法有个天然的弊病只能在圆内进行变形,圆外由于上面提到的公式的特性相当陡,所以我认为并不是很实用。

下面贴一下我自己实现的代码,是使用opencv实现的。如果谁能实现Photoshop的变形,请告诉我哦。代码很乱,还望见谅。

#include<opencv\cv.h>
#include<opencv\highgui.h>
typedef struct warp
{
	double origCenterX;
	double origCenterY;
	double nowCenterX;
	double nowCenterY;
	double radius;
	bool   isFirst;
	IplImage *img;
	IplImage *dst;
	IplImage *temp;
	IplImage *mask;
	IplImage *temp2;
} _warp;
///warp  *w ;
template<class T>
T biLine(double f00,double f01,double f10,double f11,double u,double v)//双线性插值算法。
{
	return (T)(1-u)*(1-v)*f00+(1-u)*v*f01+u*(1-v)*f10 + u*v*f11;
}
int  mapping(warp *w ,int x,int y, double *u,double *v)
{
	double rmax2 = w->radius * w->radius;
	double x_c2  = (x-w->origCenterX)*(x-w->origCenterX) + (y-w->origCenterY) *(y-w->origCenterY);
	double m_c2  = (w->nowCenterX-w->origCenterX)*(w->nowCenterX-w->origCenterX) \
						+(w->nowCenterY-w->origCenterY)*(w->nowCenterY-w->origCenterY);
	double t =rmax2-x_c2+m_c2;
	double f2;
	if(t==0)
		f2 = 0;
	else 
	    f2= ((rmax2-x_c2)/(rmax2-x_c2+m_c2))*((rmax2-x_c2)/(rmax2-x_c2+m_c2));
	double m_cX  = w->nowCenterX - w->origCenterX;
	double m_cY  = w->nowCenterY - w->origCenterY;
	*u = x-f2*m_cX;
	*v = y-f2*m_cY;

	if(*u<0)
		*u = 0;
	if(*u>w->img->width)
		*u = w->img->width-1;

	if(*v<0)
		*v = 0;
	if(*v>w->img->height)
		*v = w->img->height-1;

	return 0;
}
int  mapping2(warp *w ,int x,int y, double *u,double *v)
{
	double fu,fv;
	fu = x;
	fv = y;
	double dx = fu - w->origCenterX;
	double dy = fv - w->origCenterY;
	double rsq = dx*dx+dy*dy;
	if(dx>0.0-w->radius
		&& dx<w->radius
		&& dy>0.0-w->radius
		&& dy<w->radius
		&& rsq<  w->radius*w->radius
		)
	/*if(1)*/
	{
		double cmx = w->nowCenterX - w->origCenterX;
		double cmy = w->nowCenterY - w->origCenterY;
		
		//double msq = (dx-cmx)*(dx-cmx) +(dy-cmy)*(dy-cmy);
		double msq = cmx*cmx + cmy*cmy;
		double edge_dist = w->radius*w->radius -rsq;
		double a   = edge_dist/(edge_dist+msq);
		a *= a;
		fu -= a*cmx;
		fv -= a*cmy;
		*u = fu;
		*v = fv;
	}else
	{
		*u = x;
		*v = y;
	}
	return 0;
}
int imProc(warp * w)
{
	IplImage *img = w->img;
	IplImage *dst = w->dst;
	IplImage *temp = w->temp;
	IplImage *mask = w->mask;
	for(int i=0;i<img->height;i++)
	{
		uchar *ptr =(uchar*)(img->imageData+i*img->widthStep);
		uchar *ptrWarp = (uchar *) (dst->imageData+i*dst->widthStep);
		uchar *ptrTemp = (uchar *) (temp->imageData+i*temp->widthStep);
		//uchar *ptrMask = (uchar *) (mask->imageData+i*mask->widthStep);
		for(int j=0;j<img->width;j++)
		{
			double dx = j-w->nowCenterX;
			double dy = i-w->nowCenterY;
			if(dx> (0.0-w->radius)
				&& dx<(w->radius)
				&& dy>(0.0-w->radius)
				&& dy<w->radius
				&& (dx*dx+dy*dy) <= w->radius *w->radius
				)
			{
				
				double u,v;
				mapping2(w,j,i,&u,&v);
				if(u>img->width)
					u = img->width;
				if(v>img->height)
					v = img->height ;
				uchar *ptr4 = (uchar *)(img->imageData+(int)v*img->widthStep);
				//uchar *ptrMask = (uchar *)(mask->imageData+(int)v*img->widthStep);
				//if(*(ptrMask+j) !
				int iu= (int)u;
				int iv = (int)v;
				ptrWarp[3*j+0] = ptr4[3*iu+0];
				ptrWarp[3*j+1] = ptr4[3*iu+1];
				ptrWarp[3*j+2] = ptr4[3*iu+2];
				ptrTemp[3*j+0] = ptr4[3*iu+0];
				ptrTemp[3*j+1] = ptr4[3*iu+1];
				ptrTemp[3*j+2] = ptr4[3*iu+2];
				//double f00,f01,f10,f11;
				double f00b,f00g,f00r;
				double f01b,f01g,f01r;
				double f10b,f10g,f10r;
				double f11b,f11g,f11r;
				
				int x00 = (int)u;
				int y00 = (int)v;
				uchar *ptr1 = (uchar *)(img->imageData+y00*img->widthStep);
				f00b = ptr1[3*x00+0];
				f00g = ptr1[3*x00+1];
				f00r = ptr1[3*x00+2];
				
				int x01 = (int)(u+0.5);
				int y01 = (int )v;
				uchar *ptr2 = (uchar *)(img->imageData+y01*img->widthStep);
				f01b = ptr2[3*x01+0];
				f01g = ptr2[3*x01+1];
				f01r = ptr2[3*x01+2];
				
				int x10 = (int)u;
				int y10 = (int)(v+0.5);
				uchar *ptr3 = (uchar *)(img->imageData+y10*img->widthStep);
				f10b = ptr3[3*x10+0];
				f10g = ptr3[3*x10+1];
				f10r = ptr3[3*x10+2];
				
				int x11 = (int)(u+0.5);
				int y11 = (int)(v+0.5);
				ptr4 = (uchar *)(img->imageData+y11*img->widthStep);
				f11b = ptr4[3*x11+0];
				f11g = ptr4[3*x11+1];
				f11r = ptr4[3*x11+2];	
				
				//ptr4 = (uchar *)(img->imageData+(int)v*img->widthStep);
				//int iu= (int)u;
				//int iv = (int)v;
				//ptrWarp[3*j+0] = ptr4[3*iu+0];
				//ptrWarp[3*j+1] = ptr4[3*iu+1];
				//ptrWarp[3*j+2] = ptr4[3*iu+2];
				int a = biLine<int>(f00b,f01b,f10b,f11b,u-(int)u,v-(int)v);
				//ptrWarp[3*j+0] = biLine<int>(f00b,f01b,f10b,f11b,u-(int)u,v-(int)v);
				//ptrWarp[3*j+1] = biLine<int>(f00g,f01g,f10g,f11g,u-(int)u,v-(int)v);
				//ptrWarp[3*j+2] = biLine<int>(f00r,f01r,f10r,f11r,u-(int)u,v-(int)v);

			}//if
			else
			{
				ptrWarp[3*j+0] = ptrTemp[3*j+0];
				ptrWarp[3*j+1] = ptrTemp[3*j+1];
				ptrWarp[3*j+2] = ptrTemp[3*j+2];
			}
		}//width
	}//height 
	cvShowImage("ttt",dst);
	//vWaitKey();
	return 0;
}
int imProc2(warp * w)
{
	IplImage *img = w->img;
	IplImage *dst = w->dst;
	IplImage *temp = w->temp;
	IplImage *mask = w->mask;
	for(int i=0;i<img->height;i++)
	{
		uchar *ptr =(uchar*)(img->imageData+i*img->widthStep);
		uchar *ptrWarp = (uchar *) (dst->imageData+i*dst->widthStep);
		uchar *ptrTemp = (uchar *) (temp->imageData+i*temp->widthStep);
		uchar *ptrMask = (uchar *) (mask->imageData+i*mask->widthStep);
		for(int j=0;j<img->width;j++)
		{
			double dx = j-w->nowCenterX;
			double dy = i-w->nowCenterY;
			if(dx> (0.0-w->radius)
				&& dx<(w->radius)
				&& dy>(0.0-w->radius)
				&& dy<w->radius
				&& (dx*dx+dy*dy) < w->radius *w->radius
				)
			/*if(1)*/
			{
				
				double u,v;
				mapping2(w,j,i,&u,&v);
				if(u>img->width)
					u = img->width;
				if(v>img->height)
					v = img->height ;
				if(u<0)
					u =0;
				if(v<0)
					v = 0;
				uchar *ptrMask = (uchar *)(mask->imageData+(int)v*mask->widthStep);
				//if(*(ptrMask+j) ==0)
				if(1)
				{
					//uchar *ptr4 = (uchar *)(img->imageData+(int)v*img->widthStep);
					//uchar *ptr4 = (uchar *)(dst->imageData+(int)v*dst->widthStep);
					uchar *ptr4 = (uchar *) (w->img->imageData+(int)v*img->widthStep);
					int iu= (int)u;
					int iv = (int)v;
					ptrWarp[3*j+0] = ptr4[3*iu+0];
					ptrWarp[3*j+1] = ptr4[3*iu+1];
					ptrWarp[3*j+2] = ptr4[3*iu+2];
					ptrTemp[3*j+0] = ptr4[3*iu+0];
					ptrTemp[3*j+1] = ptr4[3*iu+1];
					ptrTemp[3*j+2] = ptr4[3*iu+2];
					//*(ptrMask+j) =1;
				}else
				{
					uchar *ptr4 = (uchar *)(img->imageData+(int)v*img->widthStep);
					int iu= (int)u;
					int iv = (int)v;
					ptrWarp[3*j+0] = ptr4[3*iu+0];
					ptrWarp[3*j+1] = ptr4[3*iu+1];
					ptrWarp[3*j+2] = ptr4[3*iu+2];
					ptrTemp[3*j+0] = ptr4[3*iu+0];
					ptrTemp[3*j+1] = ptr4[3*iu+1];
					ptrTemp[3*j+2] = ptr4[3*iu+2];
					//*(ptrMask+j) =1;
				}
			}//if
			else
			{
				/*if( *(ptrMask+j)==0)
				{
					ptrWarp[3*j+0] = ptr[3*j+0];
					ptrWarp[3*j+1] = ptr[3*j+1];
					ptrWarp[3*j+2] = ptr[3*j+2];
				}else*/
				{
					ptrWarp[3*j+0] = ptr[3*j+0];
					ptrWarp[3*j+1] = ptr[3*j+1];
					ptrWarp[3*j+2] = ptr[3*j+2];
				}
			}
		}//width
	}//height 
	//cvShowImage("ttt",dst);
	//vWaitKey();
	/*IplImage *ttt ;
	ttt = w->temp;
	w->temp = w->temp2;
	w->temp2 = ttt;*/
	cvCopy(dst,w->temp);
	return 0;
}
void myMouseCallback(int event ,int x,int y,int flags,void *param)
{
	warp *w = (warp*)param;
	if(x<0 ||x>=w->img->width)
		return ;
	if(y<0 ||y>=w->img->height)
		return ;
	switch(event)
	{
	case CV_EVENT_MOUSEMOVE:
		if(w->isFirst ==false)//确定了原来的圆心。
		{
			//w->nowCenterX = x;
			//w->nowCenterY = y;
			////std::cout<<w->nowCenterX<<std::endl;
			//imProc2(w);
			////;
			////;//适当的处理
			////if(fabs((double)w->origCenterX-x)> w->radius/5  )
			//{
			//	w->origCenterX = x;
			//	w->origCenterY = y;
			//}
		}else//没有确定圆心。
		{
			;
		}
		break;
	case CV_EVENT_LBUTTONDOWN:
			w->origCenterX = x;
			w->origCenterY = y;
			w->isFirst= false;
		break;
	case CV_EVENT_LBUTTONUP:
		w->isFirst = true;
		w->nowCenterX = x;
		w->nowCenterY = y;
		//std::cout<<w->nowCenterX<<std::endl;
		imProc2(w);
		break;
	}
	//cvShowImage("dst",w->img);

}





int main()
{
	IplImage *img = cvLoadImage("hello2.jpg");
	IplImage *dst = cvCreateImage(cvGetSize(img),8,3);
	IplImage *temp = cvCreateImage(cvGetSize(img),8,3);
	IplImage *temp2 = cvCreateImage(cvGetSize(img),8,3);
	IplImage *mask = cvCreateImage(cvGetSize(img),8,1);
	cvZero(mask);
	cvCopy(img,dst,NULL);
	cvCopy(img,temp,NULL);
	cvCopy(img,temp2,NULL);
	warp *w = new warp;
	w->img = img;
	w->mask = mask;
	w->dst = dst;
	w->temp = temp;
	w->temp2  = temp2;
	w->isFirst = true;
	w->radius = 100;
	cvNamedWindow("hello");
	cvSetMouseCallback("hello",myMouseCallback,w);
	while(1)
	{
		int key = cvWaitKey(2);
		switch(key)
		{
		case 'q':
		case 27:
			goto hear;
			break;	
		case '1':
		case '2':
		case '3':
		case '4':
		case '5':
		case '6':
		case '7':
		case '8':
		case '9':
			w->radius = (key-0x30)*10;
			std::cout<<w->radius<<std::endl;
			break;
		case 's':
			cvSaveImage("蛋蛋.jpg",w->dst);
		}
		cvShowImage("hello",w->dst);
		//cvShowImage("dst",dst);
	}

hear:
	cvReleaseImage(&img);
	return 0;
}