圖像仿射變換之旋轉變換


需要對圖像進行旋轉變換,以為利用opencv會很簡單,只需要調用cvGetQuadrangleSubPix函數或者cvWarpAffine函數即可。

但是,經過實驗發現:牛逼的人都是相似的,苦逼的人各有各的苦逼!!!!

 實驗過程如下:

首先從網上找了奔跑的兔子的程序,原文:opencv 任意角度旋轉圖像

首先利用文章中的方法一進行實驗,可惜程序報錯,原來cvGetQuadrangleSubPix已不支持5個參數,變成了3個參數,也就是說插值方法和插值像素值都不再是你我能控制的了的了。

其代碼為:

View Code
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方法試試。

當然奔跑的兔子方法二有問題,但是思路可以借鑒:

他的代碼如下,我沒有試:

View Code
  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的值。

函數體為:

bound函數體
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);

}

效果圖是:


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM