基於c++和opencv底層的圖像旋轉


圖像旋轉:本質上是對旋轉后的圖片中的每個像素計算在原圖的位置。

在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 第910行需要 -retMat.rows/2retMat.cols/2的原因在於,圖像是以(retMat.cols/2,retMat.rows/2)為坐標原點旋轉的,所以變換后圖片中的每個像素點(i; j),需要平移到相對旋轉中心的新坐標,即(i - Mat.rows/2; j - Mat.cols/2)

  1. 1112行表示在計算完成之后,需要再次還原到相對左上角原點的舊坐標;
  2. 矩陣下標與原圖變換矩陣相乘之前,需要將矩陣下標兩值互換。相乘之后,需要再次互換下標值還原成矩陣下標。這句話是什么意思呢,我們可以看出,但是實際 xSm = (int)((i-retMat.rows/2)*cos(anglePI) - (j-retMat.cols/2)*sin(anglePI) + 0.5);  (i-retMat.rows/2)是我們認為的縱坐標。
  3. 旋轉角度整數代表順時針,負數代表逆時針(這點在opencv里的很多情況下都使用)
  4. 這里表示彩色圖的旋轉,Vec3b是彩色類型,如果是灰度圖,將<>里的類型換成uchar並將retMat.at<Vec3b>(i, j) = Vec3b(0, 0); =>retMat.at<uchar>(i, j) = uchar(0);
  5. 如果是要旋轉90°這種已知dstsize的情況下可以指定reMatsize從而不邊界,其他類型還是會有邊界的(但是比自己copyMakeBorder會小一些),因為你不知道reMat需要多大。
  6.  

    如果是從原圖* 變化矩陣算目標圖的坐標就會產生一定的黑色點,那是因為再取整時,略過了一些點的坐標,而保留了他的初始化的像素值0 

  

 

 

文章參考:http://blog.csdn.net/ironyoung/article/details/41117039?utm_source=tuicool

 


免責聲明!

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



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