源地址: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)的瘦脸功能。本文描述这类工具背后的原理与算法。
先以美图秀秀为例子,简单描述下向前变形功能。
首先,用鼠标控制一个圆形的选区。
然后,点击鼠标左键,向某个方向拖动,就可以产生光滑的向前变形图片:
通过这个工具,可对图片的局部进行调整,*度比较大,因此比较实用。
下面讲讲这类算法的原理。
上图中,阴影圆环代表一个半径为 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:
这个变形算法的特点是:
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处的像素值
代码我就不贴了,真正对这功能有需求的,根据上面的文字可以很容易写出来——解决这类问题,重要的不是代码,而是思路和算法。
下面是我的实现演示:
上图中,左上角是原图,右下角是变形后的图。红色圆圈圈起来的是变形区域。可以看见,变形很光滑。我在上面的算法中引入了变形强度s(strength),上图中strength=20。
引入strength,公式就得修改下,下面是我的修改版公式:
看看结果——
原图:
变形,strength=20:
变形,strength=120:
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;
}