图像仿射变换之旋转变换
需要对图像进行旋转变换,以为利用opencv会很简单,只需要调用cvGetQuadrangleSubPix函数或者cvWarpAffine函数即可。
但是,经过实验发现:牛逼的人都是相似的,苦逼的人各有各的苦逼!!!!
实验过程如下:
首先从网上找了奔跑的兔子的程序,原文:opencv 任意角度旋转图像
首先利用文章中的方法一进行实验,可惜程序报错,原来cvGetQuadrangleSubPix已不支持5个参数,变成了3个参数,也就是说插值方法和插值像素值都不再是你我能控制的了的了。
其代码为:
int main()
{
IplImage *src,*dst, *img_tmp;
double angle=60;
int method =1;
if(!(src=cvLoadImage("src.jpg",1/*CV_LOAD_IMAGE_GRAYSCALE*/)) )
{
return -1;
}
dst = cvCloneImage(src);
dst->origin = src->origin;
cvZero(dst);
cvNamedWindow("at",1);
cvShowImage("at",src);
cvWaitKey(0);
double anglerad = (CV_PI* (angle/180)) ;
int newheight =int (fabs(( sin(anglerad)*src->width )) + fabs(( cos(anglerad)*src->height )) );
int newwidth =int (fabs(( sin(anglerad)*src->height)) + fabs(( cos(anglerad)*src->width)) );
img_tmp = cvCreateImage(cvSize(newwidth,newheight), IPL_DEPTH_8U, 3);
cvFillImage(img_tmp,0);//目的图像 使用扩展的大小
float m[6];
CvMat M = cvMat( 2, 3, CV_32F, m );
if(1==method)
{
//方法一 提取象素四边形,使用子象素精度
int w = src->width;
int h = src->height;
m[0] = (float)(cos(angle*CV_PI/180.));
m[1] = (float)(sin(angle*CV_PI/180.));
m[2] = w*0.5f;
m[3] = -m[1];
m[4] = m[0];
m[5] = h*0.5f;
cvGetQuadrangleSubPix( src, dst, &M);//图像大小不变
cvGetQuadrangleSubPix( src, img_tmp, &M);//+CV_WARP_FILL_OUTLIERS 改变图像大小
//方法一 提取象素四边形,使用子象素精度
}
cvShowImage("at",dst);
cvWaitKey(0);
cvShowImage("at",img_tmp);
cvWaitKey(0);
return 0;
}
图像原图是
dst输出结果是:
img_tmp的输出结果:
从输出图像能够发现特别让人蛋疼的事情就是插值的效果!!!没办法,用cvWarpAffine方法试试。
当然奔跑的兔子方法二有问题,但是思路可以借鉴:
他的代码如下,我没有试:
if(2==method)
{
//方法二 使用 二维旋转的仿射变换矩阵 存在问题 要求输入和输出图像一样大 旋转中心不对
CvPoint2D32f center;
center.x=float (Img_old->width/2.0+0.5);//float (Img_tmp->width/2.0+0.5);
center.y=float (Img_old->height/2.0+0.5);//float (Img_tmp->height/2.0+0.5);
cv2DRotationMatrix( center, angle,1, &M);
cvWarpAffine( Img_old, dst, &M,CV_INTER_LINEAR,cvScalarAll(0) );//小图
//小目标图像
//对图像进行扩展
// 只能一定角度以内 不同象限的不同对待
int dx=int((newwidth -Img_old->width )/2+0.5);
int dy=int((newheight-Img_old->height)/2+0.5);
uchar* old_ptr,*temp_ptr;
for( int y=0 ; y<Img_old->height; y++) //为了不越界
{
for (int x=0 ; x< Img_old->width; x++)
{
old_ptr = &((uchar*)(Img_old->imageData + Img_old->widthStep*y))[(x)*3];
temp_ptr = &((uchar*)(Img_tmp->imageData + Img_tmp->widthStep*(y+dy)))[(x+dx)*3];
temp_ptr[0]=old_ptr[0]; //green
temp_ptr[1]=old_ptr[1]; //blue
temp_ptr[2]=old_ptr[2]; //Red
}
}
center.x=float (Img_tmp->width/2.0+0.5);
center.y=float (Img_tmp->height/2.0+0.5);
cv2DRotationMatrix( center, angle,1, &M);
IplImage* temp = cvCloneImage( Img_tmp );//生成输出图像
cvWarpAffine( Img_tmp, temp , &M,CV_INTER_LINEAR+CV_WARP_FILL_OUTLIERS,cvScalarAll(0) );//大图
Img_tmp=cvCloneImage( temp );
//问题
//cvWarpAffine( Img_tmp, Img_tmp, &M,CV_INTER_LINEAR+CV_WARP_FILL_OUTLIERS,cvScalarAll(0) );//大图
//方法二 使用 二维旋转的仿射变换矩阵
}
发现这次的方法依旧不好。旋转中心的确定很让人头疼,一气之下,找了另一篇文章:图像变换——计算机视觉图像处理
看过之后,若有所悟,于是大刀阔斧,进行了实验。
不用旋转center了,我自己造一个m矩阵,应该也可以实现旋转功能。根据文章提到的内容,好确定矩阵的四个参数,但是平移还是不好确定。当然肯定是和旋转点或者图像原点有关,经过实验,发现了巨大的秘密,终于成功了。
实验思路是,首先根据图像的旋转角度,确定出xmin,xmax,ymin,ymax,这样就可以计算出新图像的高度和宽度。
计算方法是:
xmin = xmax = ymin = ymax = 0;
bound(nx-1,0,ca,sa,&xmin,&xmax,&ymin,&ymax);
bound(0,ny-1,ca,sa,&xmin,&xmax,&ymin,&ymax);
bound(nx-1,ny-1,ca,sa,&xmin,&xmax,&ymin,&ymax);
其中,ca 是旋转角度的余弦值,sa是旋转角度的正旋值。nx是原图像的宽度,ny是原图像的高度。
bound函数的功能就是确定xmin xmax ymin ymax的值。
函数体为:
void bound(int x, int y, float ca, float sa, int *xmin, int *xmax, int *ymin, int *ymax)
/* int x,y;
float ca,sa;
int *xmin,*xmax,*ymin,*ymax;*/
{
int rx,ry;
// 顺时针旋转
rx = (int)floor(ca*(float)x+sa*(float)y);
ry = (int)floor(-sa*(float)x+ca*(float)y);
if (rx<*xmin) *xmin=rx; if (rx>*xmax) *xmax=rx;
if (ry<*ymin) *ymin=ry; if (ry>*ymax) *ymax=ry;
}
相信大家都会看明白。
确定了边界之后,就可以计算图像高度和宽度了:
sx = xmax-xmin+1;
sy = ymax-ymin+1;
然后自己设置M矩阵的值:
m[0] = ca;
m[1] = sa;
m[2] =-(float)xmin;
m[3] =-m[1];
m[4] = m[0];
m[5] =-(float)ymin;
当真是费了九牛二虎之力才找出来平移的值。其实很简单,就是将左上方的图像点,平移值图像原点。
设置之后,调用方法cvWarpAffine( src, newImage, &M,CV_INTER_LINEAR+CV_WARP_FILL_OUTLIERS,cvScalarAll(0) );一直正常了。
完整代码为:
int main()
{
IplImage *src,*dst, *img_tmp;
double angle=60;
int method =2;
if(!(src=cvLoadImage("src.jpg",1/*CV_LOAD_IMAGE_GRAYSCALE*/)) )
{
return -1;
}
dst = cvCloneImage(src);
dst->origin = src->origin;
cvZero(dst);
cvNamedWindow("at",1);
cvShowImage("at",src);
cvWaitKey(0);
double anglerad = (CV_PI* (angle/180)) ;
int newheight =int (fabs(( sin(anglerad)*src->width )) + fabs(( cos(anglerad)*src->height )) );
int newwidth =int (fabs(( sin(anglerad)*src->height)) + fabs(( cos(anglerad)*src->width)) );
img_tmp = cvCreateImage(cvSize(newwidth,newheight), IPL_DEPTH_8U, 3);
cvFillImage(img_tmp,0);//目的图像 使用扩展的大小
float m[6];
CvMat M = cvMat( 2, 3, CV_32F, m );
if(1==method)
{
//方法一 提取象素四边形,使用子象素精度
int w = src->width;
int h = src->height;
m[0] = (float)(cos(angle*CV_PI/180.));
m[1] = (float)(sin(angle*CV_PI/180.));
m[2] = w*0.5f;
m[3] = -m[1];
m[4] = m[0];
m[5] = h*0.5f;
cvGetQuadrangleSubPix( src, dst, &M);//图像大小不变
cvGetQuadrangleSubPix( src, img_tmp, &M);//+CV_WARP_FILL_OUTLIERS 改变图像大小
cvShowImage("at",dst);
cvWaitKey(0);
cvShowImage("at",img_tmp);
//方法一 提取象素四边形,使用子象素精度
}
if (2==method)
{
int nx,ny;
float ca,sa;
int xmin,xmax,ymin,ymax,sx,sy;
ca = (float)cos((double)(angle)*CV_PI/180.0);
sa = (float)sin((double)(angle)*CV_PI/180.0);
nx = src->width;
ny=src->height;
xmin = xmax = ymin = ymax = 0;
bound(nx-1,0,ca,sa,&xmin,&xmax,&ymin,&ymax);
bound(0,ny-1,ca,sa,&xmin,&xmax,&ymin,&ymax);
bound(nx-1,ny-1,ca,sa,&xmin,&xmax,&ymin,&ymax);
sx = xmax-xmin+1;
sy = ymax-ymin+1;
IplImage* newImage;
newImage=cvCreateImage(cvSize(sx,sy),src->depth,src->nChannels);
m[0] = ca;
m[1] = sa;
m[2] =-(float)xmin;
m[3] =-m[1];
m[4] = m[0];
m[5] =-(float)ymin;
cvWarpAffine( src, newImage, &M,CV_INTER_LINEAR+CV_WARP_FILL_OUTLIERS,cvScalarAll(0) );
cvNamedWindow("newImage");
cvShowImage("newImage",newImage);
cvWaitKey(0);
}
cvWaitKey(0);
return 0;
}
方法二效果图为:
方法三,处理的是灰度单通道图像。刚开始,将彩色图像处理成灰度图像,但仍然是三通道图像,因此实验总是不成功,为此抑郁了好几天。今天终于开窍了。但,遇到的问题是,取设置图像时,好像只能用uchar类型,用float int 等,出错,这点不理想。
请看方法三代码:
if(3==method)
{
gray = cvCreateImage(cvSize(src->width,src->height),8,1);
if (src->nChannels!=1)
{
cvCvtColor(src,gray,CV_BGR2GRAY);
src = gray;
}
int x,y, x1,y1,adr,nx,ny;
float ca,sa,xp,yp,a11,a12,a21,a22,ux,uy,xtrans,ytrans;
int tx1,ty1,tx2,ty2,xmin,xmax,ymin,ymax,sx,sy;
ca = (float)cos((double)(angle)*CV_PI/180.0);
sa = (float)sin((double)(angle)*CV_PI/180.0);
nx = src->width;
ny=src->height;
xmin = xmax = ymin = ymax = 0;
bound(nx-1,0,ca,sa,&xmin,&xmax,&ymin,&ymax);
bound(0,ny-1,ca,sa,&xmin,&xmax,&ymin,&ymax);
bound(nx-1,ny-1,ca,sa,&xmin,&xmax,&ymin,&ymax);
sx = xmax-xmin+1;
sy = ymax-ymin+1;
xtrans = ytrans = 0.0;
IplImage* method3Img;
method3Img=cvCreateImage(cvSize(sx,sy),IPL_DEPTH_8U,1);
for (x=xmin;x<=xmax;x++)
for (y=ymin;y<=ymax;y++) {
xp = ca*(float)x-sa*(float)y + xtrans;
yp = sa*(float)x+ca*(float)y + ytrans;
x1 = (int)floor(xp);
y1 = (int)floor(yp);
ux = xp-(float)x1;
uy = yp-(float)y1;
/*CV_IMAGE_ELEM(image, uchar, yp,xp);*/
/*adr = y1*nx+x1;*/
tx1 = (x1>=0 && x1<nx);
tx2 = (x1+1>=0 && x1+1<nx);
ty1 = (y1>=0 && y1<ny);
ty2 = (y1+1>=0 && y1+1<ny);
/*v_11f = ( (float*)(src->imageData + src->widthStep*y1) )[x1];
v_12f = ( (float*)(src->imageData + src->widthStep*(y1+1)) )[x1];
v_21f = ( (float*)(src->imageData + src->widthStep*y1) )[x1+1];
v_22f = ( (float*)(src->imageData + src->widthStep*(y1+1)) )[x1+1];*/
a11 = (tx1 && ty1? CV_IMAGE_ELEM(src,uchar,y1,x1):255);
a12 = (tx1 && ty2? CV_IMAGE_ELEM(src,uchar,y1+1,x1):255);
a21 = (tx2 && ty1? CV_IMAGE_ELEM(src,uchar,y1,x1+1):255);
a22 = (tx2 && ty2? CV_IMAGE_ELEM(src,uchar,y1+1,x1+1):255);
if(a11<255.0)
{
float tmp = CV_IMAGE_ELEM(src,uchar,y1,x1);
int x = a11;
}
/*out[(y-ymin)*sx+x-xmin] =
(1.0-uy)*((1.0-ux)*a11+ux*a21)+uy*((1.0-ux)*a12+ux*a22);*/
float value = (1.0-uy)*((1.0-ux)*a11+ux*a21)+uy*((1.0-ux)*a12+ux*a22);
CV_IMAGE_ELEM(method3Img, uchar, y-ymin, x-xmin ) =cvFloor(value);
}
cvNamedWindow("newImage3");
cvShowImage("newImage3",method3Img);
cvWaitKey(0);
}
效果图是: