圖像旋轉:本質上是對旋轉后的圖片中的每個像素計算在原圖的位置。
在opencv包里有自帶的旋轉函數,當你知道傾斜角度theta時:
用getRotationMatrix2D可得2X3的旋轉變換矩陣 M,在用warpaffine函數可得傾斜后的圖像dst。
很方便啊,為什么還要自己實現底層的圖像旋轉呢?因為有些地方你用這兩個函數就會出現問題,比如說:
當原圖的size是MXN,且圖像是完全填充的(因為如果有留白可能還不能將問題完全反映出來),現在你需要將它90°變換(為了形象說明),可是用前面兩個API會產生什么結果呢?如下:
噢?有一部分看不見了…我們來看一下上面兩個函數:
C++: Mat getRotationMatrix2D(Point2f center, double angle, double scale)
C++: void warpAffine(InputArray src, OutputArray dst, InputArray M, Size dsize, int flags=INTER_LINEAR, int borderMode=BORDER_CONSTANT, const Scalar& borderValue=Scalar())
一開始我以為是這兩個參數的設置問題。一般來說,center是設置在源圖像中心點進行翻轉的,現在有一部分看不見了,是不是中心點的問題呢?我換個中心點可不可以呢?實驗證明,分別設置左上、下;右上,下角點為中心點時,都會有部分圖像顯示不出來。我放棄center,改試dsize,改大一點,還是不行,只是顯示和黑色面積分別更大了,顯示的比例並沒有改變什么。這時候就不必再試了,有這個功夫我還不如自己寫個旋轉函數呢。為什么會這樣呢,因為原圖的面積不夠大啊,一旋轉就出去了啊,所以依然不想自己寫函數(不建議,因為寫的函數代碼很簡單啊)的童鞋,用copyMakeborder函數在原圖上添加邊界再旋轉吧。添加完邊界后問題又來了:邊界添加的小圖像還是會旋出去。邊界添加的大,圖片本身就很小了(就像置身於一片蒼茫的黑色的宇宙中,當然是誇張),接下來你要對原來的圖片中的像素進行什么處理就更困難了,有點小了。必要時還得去邊界,opencv里面好像可沒有現成的去邊界函數啊。
所以(前言說的太長),我們有必要自己寫個簡單方便的旋轉函數。先給出旋轉矩陣及其變換關系(別問我怎么來的,我是拿現成的老師教得,不過知道的朋友可以圖解一下)
代碼如下:
1 Mat angleRectify(Mat img,float angle) 2 { 3 Mat retMat = Mat::zeros(550,850, CV_8UC3); 4 float anglePI =(float) (angle * CV_PI / 180); 5 int xSm, ySm; 6 for(int i = 0; i < retMat.rows; i++) 7 for(int j = 0; j < retMat.cols; j++) 8 { 9 xSm = (int)((i-retMat.rows/2)*cos(anglePI) - (j-retMat.cols/2)*sin(anglePI) + 0.5); 10 ySm = (int)((i-retMat.rows/2)*sin(anglePI) + (j-retMat.cols/2)*cos(anglePI) + 0.5); 11 xSm += img.rows / 2; 12 ySm += img.cols / 2; 13 if(xSm >= img.rows || ySm >= img.cols || xSm <= 0 || ySm <= 0){ 14 retMat.at<Vec3b>(i, j) = Vec3b(0, 0); 15 } 16 else{ 17 retMat.at<Vec3b>(i, j) = img.at<Vec3b>(xSm, ySm); 18 } 19 } 20 21 return retMat; 22 }
這里有幾個問題需要說明:
1 本來需要變換后圖片乘以 原圖變換矩陣的逆矩陣 對應到原圖中坐標。但是因為y軸方向向下,所以變換后圖片乘以原圖變換矩陣(無需逆矩陣)即可對應到原圖中坐標
2 第9、10行需要 -retMat.rows/2和retMat.cols/2的原因在於,圖像是以(retMat.cols/2,retMat.rows/2)為坐標原點旋轉的,所以變換后圖片中的每個像素點(i; j),需要平移到相對旋轉中心的新坐標,即(i - Mat.rows/2; j - Mat.cols/2)。
- 第11,12行表示在計算完成之后,需要再次還原到相對左上角原點的舊坐標;
- 矩陣下標與原圖變換矩陣相乘之前,需要將矩陣下標兩值互換。相乘之后,需要再次互換下標值還原成矩陣下標。這句話是什么意思呢,我們可以看出
,但是實際 xSm = (int)((i-retMat.rows/2)*cos(anglePI) - (j-retMat.cols/2)*sin(anglePI) + 0.5); (i-retMat.rows/2)是我們認為的縱坐標。
- 旋轉角度整數代表順時針,負數代表逆時針(這點在opencv里的很多情況下都使用)
- 這里表示彩色圖的旋轉,Vec3b是彩色類型,如果是灰度圖,將<>里的類型換成uchar並將retMat.at<Vec3b>(i, j) = Vec3b(0, 0); =>retMat.at<uchar>(i, j) = uchar(0);
-
如果是要旋轉90°這種已知dst的size的情況下可以指定reMat的size從而不邊界,其他類型還是會有邊界的(但是比自己copyMakeBorder會小一些),因為你不知道reMat需要多大。
-
如果是從原圖* 變化矩陣算目標圖的坐標就會產生一定的黑色點,那是因為再取整時,略過了一些點的坐標,而保留了他的初始化的像素值0
文章參考:http://blog.csdn.net/ironyoung/article/details/41117039?utm_source=tuicool