【OpenCV】圖像幾何變換:旋轉,縮放,斜切

幾何變換

幾何變換能夠當作圖像中物體(或像素)空間位置改變,或者說是像素的移動。算法

幾何運算須要空間變換和灰度級差值兩個步驟的算法,像素經過變換映射到新的座標位置,新的位置多是在幾個像素之間,即不必定爲整數座標。這時就須要灰度級差值將映射的新座標匹配到輸出像素之間。最簡單的插值方法是最近鄰插值,就是令輸出像素的灰度值等於映射最近的位置像素,該方法可能會產生鋸齒。這種方法也叫零階插值,相應比較複雜的還有一階和高階插值。數組

插值算法感受只要瞭解就能夠了,圖像處理中比較須要理解的仍是空間變換。緩存

空間變換

空間變換對應矩陣的仿射變換。一個座標經過函數變換的新的座標位置:函數

因此在程序中咱們可使用一個2*3的數組結構來存儲變換矩陣:工具

以最簡單的平移變換爲例,平移(b1,b2)座標能夠表示爲:學習

所以,平移變換的變換矩陣及逆矩陣記爲:spa

縮放變換:將圖像橫座標放大(或縮小)sx倍,縱座標放大(或縮小)sy倍,變換矩陣及逆矩陣爲:.net

選擇變換:圖像繞原點逆時針旋轉a角,其變換矩陣及逆矩陣(順時針選擇)爲:code

OpenCV中的圖像變換函數

基本的放射變換函數:orm

void cvWarpAffine( 
    const CvArr* src,//輸入圖像
    CvArr* dst, //輸出圖像
    const CvMat* map_matrix,   //2*3的變換矩陣
    int flags=CV_INTER_LINEAR+CV_WARP_FILL_OUTLIERS,   //插值方法的組合
    CvScalar fillval=cvScalarAll(0)   //用來填充邊界外的值
);

另一個比較相似的函數是cvGetQuadrangleSubPix:

void cvGetQuadrangleSubPix( 
       const CvArr* src,  //輸入圖像 
       CvArr* dst,   // 提取的四邊形
       const CvMat* map_matrix //2*3的變換矩陣
);

這個函數用以提取輸入圖像中的四邊形,並經過map_matrix變換存儲到dst中,與WarpAffine變換意義相同,

 

即對應每一個點的變換:

WarpAffine與 GetQuadrangleSubPix 不一樣的在於cvWarpAffine 要求輸入和輸出圖像具備一樣的數據類型,有更大的資源開銷(所以對小圖像不太合適)並且輸出圖像的部分能夠保留不變。而 cvGetQuadrangleSubPix 能夠精確地從8位圖像中提取四邊形到浮點數緩存區中,具備比較小的系統開銷,並且老是所有改變輸出圖像的內容。

 

實踐:圖像旋轉變換(原尺寸)

首先用cvWarpAffine實驗將圖像逆時針旋轉degree角度。

//逆時針旋轉圖像degree角度(原尺寸)
void rotateImage(IplImage* img, IplImage *img_rotate,int degree)
{
	//旋轉中心爲圖像中心
	CvPoint2D32f center;  
	center.x=float (img->width/2.0+0.5);
	center.y=float (img->height/2.0+0.5);
	//計算二維旋轉的仿射變換矩陣
	float m[6];            
	CvMat M = cvMat( 2, 3, CV_32F, m );
	cv2DRotationMatrix( center, degree,1, &M);
	//變換圖像,並用黑色填充其他值
	cvWarpAffine(img,img_rotate, &M,CV_INTER_LINEAR+CV_WARP_FILL_OUTLIERS,cvScalarAll(0) );
}

逆時針旋轉30度結果:

這裏咱們將新的圖像還保留原來的圖像尺寸。這樣的效果顯然不太好,咱們經過計算相應放大圖像尺寸。

 

實踐:圖像旋轉變換(保留原圖內容,放大尺寸)

須要計算新圖的尺寸,示意圖以下:

因此新圖size爲(width*cos(a)+height*sin(a), height*cos(a)+width*sin(a))

//旋轉圖像內容不變,尺寸相應變大
IplImage* rotateImage1(IplImage* img,int degree){
	double angle = degree  * CV_PI / 180.; // 弧度  
	double a = sin(angle), b = cos(angle); 
	int width = img->width;  
	int height = img->height;  
	int width_rotate= int(height * fabs(a) + width * fabs(b));  
	int height_rotate=int(width * fabs(a) + height * fabs(b));  
	//旋轉數組map
	// [ m0  m1  m2 ] ===>  [ A11  A12   b1 ]
	// [ m3  m4  m5 ] ===>  [ A21  A22   b2 ]
	float map[6];
	CvMat map_matrix = cvMat(2, 3, CV_32F, map);  
	// 旋轉中心
	CvPoint2D32f center = cvPoint2D32f(width / 2, height / 2);  
	cv2DRotationMatrix(center, degree, 1.0, &map_matrix);  
	map[2] += (width_rotate - width) / 2;  
	map[5] += (height_rotate - height) / 2;  
	IplImage* img_rotate = cvCreateImage(cvSize(width_rotate, height_rotate), 8, 3); 
	//對圖像作仿射變換
	//CV_WARP_FILL_OUTLIERS - 填充全部輸出圖像的象素。
	//若是部分象素落在輸入圖像的邊界外,那麼它們的值設定爲 fillval.
	//CV_WARP_INVERSE_MAP - 指定 map_matrix 是輸出圖像到輸入圖像的反變換,
	cvWarpAffine( img,img_rotate, &map_matrix, CV_INTER_LINEAR | CV_WARP_FILL_OUTLIERS, cvScalarAll(0));  
	return img_rotate;
}

實踐:圖像旋轉變換(保留原圖內容,放大尺寸)-2

試一下用cvGetQuadrangleSubPix函數:

//旋轉圖像內容不變,尺寸相應變大
IplImage* rotateImage2(IplImage* img, int degree)  
{  
	double angle = degree  * CV_PI / 180.; 
	double a = sin(angle), b = cos(angle); 
	int width=img->width, height=img->height;
	//旋轉後的新圖尺寸
	int width_rotate= int(height * fabs(a) + width * fabs(b));  
	int height_rotate=int(width * fabs(a) + height * fabs(b));  
	IplImage* img_rotate = cvCreateImage(cvSize(width_rotate, height_rotate), img->depth, img->nChannels);  
	cvZero(img_rotate);  
	//保證原圖能夠任意角度旋轉的最小尺寸
	int tempLength = sqrt((double)width * width + (double)height *height) + 10;  
	int tempX = (tempLength + 1) / 2 - width / 2;  
	int tempY = (tempLength + 1) / 2 - height / 2;  
	IplImage* temp = cvCreateImage(cvSize(tempLength, tempLength), img->depth, img->nChannels);  
	cvZero(temp);  
	//將原圖複製到臨時圖像tmp中心
	cvSetImageROI(temp, cvRect(tempX, tempY, width, height));  
	cvCopy(img, temp, NULL);  
	cvResetImageROI(temp);  
	//旋轉數組map
	// [ m0  m1  m2 ] ===>  [ A11  A12   b1 ]
	// [ m3  m4  m5 ] ===>  [ A21  A22   b2 ]
	float m[6];  
	int w = temp->width;  
	int h = temp->height;  
	m[0] = b;  
	m[1] = a;  
	m[3] = -m[1];  
	m[4] = m[0];  
	// 將旋轉中心移至圖像中間  
	m[2] = w * 0.5f;  
	m[5] = h * 0.5f;  
	CvMat M = cvMat(2, 3, CV_32F, m);  
	cvGetQuadrangleSubPix(temp, img_rotate, &M);  
	cvReleaseImage(&temp);  
	return img_rotate;
}
//旋轉圖像內容不變,尺寸相應變大
IplImage* rotateImage2(IplImage* img, int degree)  
{  
	double angle = degree  * CV_PI / 180.; 
	double a = sin(angle), b = cos(angle); 
	int width=img->width, height=img->height;
	//旋轉後的新圖尺寸
	int width_rotate= int(height * fabs(a) + width * fabs(b));  
	int height_rotate=int(width * fabs(a) + height * fabs(b));  
	IplImage* img_rotate = cvCreateImage(cvSize(width_rotate, height_rotate), img->depth, img->nChannels);  
	cvZero(img_rotate);  
	//保證原圖能夠任意角度旋轉的最小尺寸
	int tempLength = sqrt((double)width * width + (double)height *height) + 10;  
	int tempX = (tempLength + 1) / 2 - width / 2;  
	int tempY = (tempLength + 1) / 2 - height / 2;  
	IplImage* temp = cvCreateImage(cvSize(tempLength, tempLength), img->depth, img->nChannels);  
	cvZero(temp);  
	//將原圖複製到臨時圖像tmp中心
	cvSetImageROI(temp, cvRect(tempX, tempY, width, height));  
	cvCopy(img, temp, NULL);  
	cvResetImageROI(temp);  
	//旋轉數組map
	// [ m0  m1  m2 ] ===>  [ A11  A12   b1 ]
	// [ m3  m4  m5 ] ===>  [ A21  A22   b2 ]
	float m[6];  
	int w = temp->width;  
	int h = temp->height;  
	m[0] = b;  
	m[1] = a;  
	m[3] = -m[1];  
	m[4] = m[0];  
	// 將旋轉中心移至圖像中間  
	m[2] = w * 0.5f;  
	m[5] = h * 0.5f;  
	CvMat M = cvMat(2, 3, CV_32F, m);  
	cvGetQuadrangleSubPix(temp, img_rotate, &M);  
	cvReleaseImage(&temp);  
	return img_rotate;
}

 

 

實踐:圖像放射變換(經過三點肯定變換矩陣)

在OpenCV 2.3的參考手冊中《opencv_tutorials》介紹了另外一種肯定變換矩陣的方法,經過三個點變換的幾何關係映射實現變換。

變換示意圖以下:

即經過三個點就能夠肯定一個變換矩陣。(矩形變換後必定爲平行四邊形)

如下是基於OpenCV 2.3的代碼(需至少2.0以上版本的支持)

int main( )
{
	Point2f srcTri[3];
	Point2f dstTri[3];
	Mat rot_mat( 2, 3, CV_32FC1 );
	Mat warp_mat( 2, 3, CV_32FC1 );
	Mat src, warp_dst, warp_rotate_dst;
	//讀入圖像
	src = imread( "baboon.jpg", 1 );
	warp_dst = Mat::zeros( src.rows, src.cols, src.type() );
	// 用3個點肯定A仿射變換
	srcTri[0] = Point2f( 0,0 );
	srcTri[1] = Point2f( src.cols - 1, 0 );
	srcTri[2] = Point2f( 0, src.rows - 1 );
	dstTri[0] = Point2f( src.cols*0.0, src.rows*0.33 );
	dstTri[1] = Point2f( src.cols*0.85, src.rows*0.25 );
	dstTri[2] = Point2f( src.cols*0.15, src.rows*0.7 );
	warp_mat = getAffineTransform( srcTri, dstTri );
	warpAffine( src, warp_dst, warp_mat, warp_dst.size() );
	/// 旋轉矩陣
	Point center = Point( warp_dst.cols/2, warp_dst.rows/2 );
	double angle = -50.0;
	double scale = 0.6;
	rot_mat = getRotationMatrix2D( center, angle, scale );
	warpAffine( warp_dst, warp_rotate_dst, rot_mat, warp_dst.size() );
	////OpenCV 1.0的形式
	//IplImage * img=cvLoadImage("baboon.jpg");
	//IplImage *img_rotate=cvCloneImage(img);
	//CvMat M =warp_mat;
	//cvWarpAffine(img,img_rotate, &M,CV_INTER_LINEAR+CV_WARP_FILL_OUTLIERS,cvScalarAll(0) );
	//cvShowImage("Wrap2",img_rotate);

	namedWindow( "Source", CV_WINDOW_AUTOSIZE );
	imshow( "Source", src );
	namedWindow( "Wrap", CV_WINDOW_AUTOSIZE );
	imshow( "Wrap", warp_dst );
	namedWindow("Wrap+Rotate", CV_WINDOW_AUTOSIZE );
	imshow( "Wrap+Rotate", warp_rotate_dst );
	waitKey(0);
	return 0;
}
int main( )
{
	Point2f srcTri[3];
	Point2f dstTri[3];
	Mat rot_mat( 2, 3, CV_32FC1 );
	Mat warp_mat( 2, 3, CV_32FC1 );
	Mat src, warp_dst, warp_rotate_dst;
	//讀入圖像
	src = imread( "baboon.jpg", 1 );
	warp_dst = Mat::zeros( src.rows, src.cols, src.type() );
	// 用3個點肯定A仿射變換
	srcTri[0] = Point2f( 0,0 );
	srcTri[1] = Point2f( src.cols - 1, 0 );
	srcTri[2] = Point2f( 0, src.rows - 1 );
	dstTri[0] = Point2f( src.cols*0.0, src.rows*0.33 );
	dstTri[1] = Point2f( src.cols*0.85, src.rows*0.25 );
	dstTri[2] = Point2f( src.cols*0.15, src.rows*0.7 );
	warp_mat = getAffineTransform( srcTri, dstTri );
	warpAffine( src, warp_dst, warp_mat, warp_dst.size() );
	/// 旋轉矩陣
	Point center = Point( warp_dst.cols/2, warp_dst.rows/2 );
	double angle = -50.0;
	double scale = 0.6;
	rot_mat = getRotationMatrix2D( center, angle, scale );
	warpAffine( warp_dst, warp_rotate_dst, rot_mat, warp_dst.size() );
	////OpenCV 1.0的形式
	//IplImage * img=cvLoadImage("baboon.jpg");
	//IplImage *img_rotate=cvCloneImage(img);
	//CvMat M =warp_mat;
	//cvWarpAffine(img,img_rotate, &M,CV_INTER_LINEAR+CV_WARP_FILL_OUTLIERS,cvScalarAll(0) );
	//cvShowImage("Wrap2",img_rotate);

	namedWindow( "Source", CV_WINDOW_AUTOSIZE );
	imshow( "Source", src );
	namedWindow( "Wrap", CV_WINDOW_AUTOSIZE );
	imshow( "Wrap", warp_dst );
	namedWindow("Wrap+Rotate", CV_WINDOW_AUTOSIZE );
	imshow( "Wrap+Rotate", warp_rotate_dst );
	waitKey(0);
	return 0;
}

變換結果:

 

轉載請註明出處:http://blog.csdn.net/xiaowei_cqu/article/details/7616044
實驗代碼下載:http://download.csdn.net/detail/xiaowei_cqu/4339856

寫在最後的一點點閒話

以前一直用的2.1的版本,後來裝了2.3,只是據說2.3很強大,但我剛開始學,用的也基礎,徹底沒感受出不一樣。直到今天突然看到了2.3的手冊,才發現從2.0開始函數和基本結構都有了很大的改變,而我一直仍是用的1.0風格的函數(好比cvMat,cvLoadImage)。個人兩個學習工具《Learnning OpenCV》和《OpenCV中文參考手冊》都是基於1.0的,這也是我到今天才看到Mat,而後直接被驚豔到了。

別人總結出來的東西能幫助咱們在一開始迅速入門,但要學深,學精,終歸仍是要本身去努力挖的。

相關文章
相關標籤/搜索