1. 程式人生 > >【影象處理】計算機視覺 透視變換 Perspective Transformation

【影象處理】計算機視覺 透視變換 Perspective Transformation

透視變換(Perspective Transformation)是指利用透視中心、像點、目標點三點共線的條件,按透視旋轉定律使承影面(透視面)繞跡線(透視軸)旋轉某一角度,破壞原有的投影光線束,仍能保持承影面上投影幾何圖形不變的變換。


透視變換(Perspective Transformation)是將圖片投影到一個新的視平面(Viewing Plane),也稱作投影對映(Projective Mapping)。通用的變換公式為:


u,v是原始圖片座標,對應得到變換後的圖片座標x,y,其中
變換矩陣可以拆成4部分,表示線性變換,比如scaling(縮放),shearing(錯切)和ratotion(翻轉)。

用於平移,產生透視變換。所以可以理解成仿射(線性變換+平移)等是透視變換的特殊形式。經過透視變換之後的圖片通常不是平行四邊形(除非對映視平面和原來平面平行的情況)。

重寫之前的變換公式可以得到(預設w為1?)


所以,已知變換對應的幾個點就可以求取變換公式,反之,特定的變換公式也能生成新的變換後的圖片。簡單的看一個正方形到四邊形的變換:
變換的4組對應點可以表示成:

根據變換公式得到(預設a33為1?)


定義幾個輔助變數:


都為0時變換平面與原來是平行的,可以得到:


不為0時,得到:


求解出的變換矩陣就可以將一個正方形變換到四邊形。反之,四邊形變換到正方形也是一樣的。於是,我們通過兩次變換:四邊形變換到正方形+正方形變換到四邊形就可以將任意一個四邊形變換到另一個四邊形。


看一段程式碼:

  1. PerspectiveTransform::PerspectiveTransform(float inA11, float inA21,   
  2.                                            float inA31, float inA12,   
  3.                                            float inA22, float inA32,   
  4.                                            float inA13, float inA23,   
  5.                                            float inA33) :   
  6.   a11(inA11), a12(inA12), a13(inA13), a21(inA21), a22(inA22), a23(inA23),  
  7.   a31(inA31), a32(inA32), a33(inA33) {}  
  8. PerspectiveTransform PerspectiveTransform::quadrilateralToQuadrilateral(float x0, float y0, float x1, float y1,  
  9.     float x2, float y2, float x3, float y3, float x0p, float y0p, float x1p, float y1p, float x2p, float y2p,  
  10.     float x3p, float y3p) {  
  11.   PerspectiveTransform qToS = PerspectiveTransform::quadrilateralToSquare(x0, y0, x1, y1, x2, y2, x3, y3);  
  12.   PerspectiveTransform sToQ =  
  13.     PerspectiveTransform::squareToQuadrilateral(x0p, y0p, x1p, y1p, x2p, y2p, x3p, y3p);  
  14.   return sToQ.times(qToS);  
  15. }  
  16. PerspectiveTransform PerspectiveTransform::squareToQuadrilateral(float x0, float y0, float x1, float y1, float x2,  
  17.     float y2, float x3, float y3) {  
  18.   float dx3 = x0 - x1 + x2 - x3;  
  19.   float dy3 = y0 - y1 + y2 - y3;  
  20.   if (dx3 == 0.0f && dy3 == 0.0f) {  
  21.     PerspectiveTransform result(PerspectiveTransform(x1 - x0, x2 - x1, x0, y1 - y0, y2 - y1, y0, 0.0f,  
  22.                                      0.0f, 1.0f));  
  23.     return result;  
  24.   } else {  
  25.     float dx1 = x1 - x2;  
  26.     float dx2 = x3 - x2;  
  27.     float dy1 = y1 - y2;  
  28.     float dy2 = y3 - y2;  
  29.     float denominator = dx1 * dy2 - dx2 * dy1;  
  30.     float a13 = (dx3 * dy2 - dx2 * dy3) / denominator;  
  31.     float a23 = (dx1 * dy3 - dx3 * dy1) / denominator;  
  32.     PerspectiveTransform result(PerspectiveTransform(x1 - x0 + a13 * x1, x3 - x0 + a23 * x3, x0, y1 - y0  
  33.                                      + a13 * y1, y3 - y0 + a23 * y3, y0, a13, a23, 1.0f));  
  34.     return result;  
  35.   }  
  36. }  
  37. PerspectiveTransform PerspectiveTransform::quadrilateralToSquare(float x0, float y0, float x1, float y1, float x2,  
  38.     float y2, float x3, float y3) {  
  39.   // Here, the adjoint serves as the inverse:
  40.   return squareToQuadrilateral(x0, y0, x1, y1, x2, y2, x3, y3).buildAdjoint();  
  41. }  
  42. PerspectiveTransform PerspectiveTransform::buildAdjoint() {  
  43.   // Adjoint is the transpose of the cofactor matrix:
  44.   PerspectiveTransform result(PerspectiveTransform(a22 * a33 - a23 * a32, a23 * a31 - a21 * a33, a21 * a32  
  45.                                    - a22 * a31, a13 * a32 - a12 * a33, a11 * a33 - a13 * a31, a12 * a31 - a11 * a32, a12 * a23 - a13 * a22,  
  46.                                    a13 * a21 - a11 * a23, a11 * a22 - a12 * a21));  
  47.   return result;  
  48. }  
  49. PerspectiveTransform PerspectiveTransform::times(PerspectiveTransform other) {  
  50.   PerspectiveTransform result(PerspectiveTransform(a11 * other.a11 + a21 * other.a12 + a31 * other.a13,  
  51.                                    a11 * other.a21 + a21 * other.a22 + a31 * other.a23, a11 * other.a31 + a21 * other.a32 + a31  
  52.                                    * other.a33, a12 * other.a11 + a22 * other.a12 + a32 * other.a13, a12 * other.a21 + a22  
  53.                                    * other.a22 + a32 * other.a23, a12 * other.a31 + a22 * other.a32 + a32 * other.a33, a13  
  54.                                    * other.a11 + a23 * other.a12 + a33 * other.a13, a13 * other.a21 + a23 * other.a22 + a33  
  55.                                    * other.a23, a13 * other.a31 + a23 * other.a32 + a33 * other.a33));  
  56.   return result;  
  57. }  
  58. void PerspectiveTransform::transformPoints(vector<float> &points) {  
  59.   int max = points.size();  
  60.   for (int i = 0; i < max; i += 2) {  
  61.     float x = points[i];  
  62.     float y = points[i + 1];  
  63.     float denominator = a13 * x + a23 * y + a33;  
  64.     points[i] = (a11 * x + a21 * y + a31) / denominator;  
  65.     points[i + 1] = (a12 * x + a22 * y + a32) / denominator;  
  66.   }  
  67. }  
對一張透檢視片變換回正面圖的效果:
  1. int main(){  
  2.     Mat img=imread("boy.png");  
  3.     int img_height = img.rows;  
  4.     int img_width = img.cols;  
  5.     Mat img_trans = Mat::zeros(img_height,img_width,CV_8UC3);  
  6.     PerspectiveTransform tansform = PerspectiveTransform::quadrilateralToQuadrilateral(  
  7.         0,0,  
  8.         img_width-1,0,  
  9.         0,img_height-1,  
  10.         img_width-1,img_height-1,  
  11.         150,250, // top left
  12.         771,0, // top right
  13.         0,1023,// bottom left
  14.         650,1023  
  15.         );  
  16.     vector<float> ponits;  
  17.     for(int i=0;i<img_height;i++){  
  18.         for(int j=0;j<img_width;j++){  
  19.             ponits.push_back(j);  
  20.             ponits.push_back(i);  
  21.         }  
  22.     }  
  23.     tansform.transformPoints(ponits);  
  24.     for(int i=0;i<img_height;i++){  
  25.         uchar*  t= img_trans.ptr<uchar>(i);  
  26.         for (int j=0;j<img_width;j++){  
  27.             int tmp = i*img_width+j;  
  28.             int x = ponits[tmp*2];  
  29.             int y = ponits[tmp*2+1];  
  30.             if(x<0||x>(img_width-1)||y<0||y>(img_height-1))  
  31.                 continue;  
  32.             uchar* p = img.ptr<uchar>(y);  
  33.             t[j*3] = p[x*3];  
  34.             t[j*3+1] = p[x*3+1];  
  35.             t[j*3+2] = p[x*3+2];  
  36.         }  
  37.     }  
  38.     imwrite("trans.png",img_trans);  
  39.     return 0;  
  40. }  


求解變換公式的函式:

  1. Mat getPerspectiveTransform(const Point2f src[], const Point2f dst[])  
輸入原始影象和變換之後的影象的對應4個點,便可以得到變換矩陣。之後用求解得到的矩陣輸入perspectiveTransform便可以對一組點進行變換:
  1. void perspectiveTransform(InputArray src, OutputArray dst, InputArray m)  
注意這裡src和dst的輸入並不是影象,而是影象對應的座標。應用前一篇的例子,做個相反的變換:
  1. int main( )  
  2. {  
  3.     Mat img=imread("boy.png");  
  4.     int img_height = img.rows;  
  5.     int img_width = img.cols;  
  6.     vector<Point2f> corners(4);  
  7.     corners[0] = Point2f(0,0);  
  8.     corners[1] = Point2f(img_width-1,0);  
  9.     corners[2] = Point2f(0,img_height-1);  
  10.     corners[3] = Point2f(img_width-1,img_height-1);  
  11.     vector<Point2f> corners_trans(4);  
  12.     corners_trans[0] = Point2f(150,250);  
  13.     corners_trans[1] = Point2f(771,0);  
  14.     corners_trans[2] = Point2f(0,img_height-1);  
  15.     corners_trans[3] = Point2f(650,img_height-1);  
  16.     Mat transform = getPerspectiveTransform(corners,corners_trans);  
  17.     cout<<transform<<endl;  
  18.     vector<Point2f> ponits, points_trans;  
  19.     for(int i=0;i<img_height;i++){  
  20.         for(int j=0;j<img_width;j++){  
  21.             ponits.push_back(Point2f(j,i));  
  22.         }  
  23.     }  
  24.     perspectiveTransform( ponits, points_trans, transform);  
  25.     Mat img_trans = Mat::zeros(img_height,img_width,CV_8UC3);  
  26.     int count = 0;  
  27.     for(int i=0;i<img_height;i++){  
  28.         uchar* p = img.ptr<uchar>(i);  
  29.         for(int j=0;j<img_width;j++){  
  30.             int y = points_trans[count].y;  
  31.             int x = points_trans[count].x;  
  32.             uchar* t = img_trans.ptr<uchar>(y);  
  33.             t[x*3]  = p[j*3];  
  34.             t[x*3+1]  = p[j*3+1];  
  35.             t[x*3+2]  = p[j*3+2];  
  36.             count++;  
  37.         }  
  38.     }  
  39.     imwrite("boy_trans.png",img_trans);  
  40.     return 0;  
  41. }  

得到變換之後的圖片:


注意這種將原圖變換到對應影象上的方式會有一些沒有被填充的點,也就是右圖中黑色的小點。解決這種問題一是用差值的方式,再一種比較簡單就是不用原圖的點變換後對應找新圖的座標,而是直接在新圖上找反向變換原圖的點。說起來有點繞口,具體見前一篇《透視變換 Perspective Transformation》的程式碼應該就能懂啦。

除了getPerspectiveTransform()函式,OpenCV還提供了findHomography()的函式,不是用點來找,而是直接用透視平面來找變換公式。這個函式在特徵匹配的經典例子中有用到,也非常直觀:

  1. int main( int argc, char** argv )  
  2. {  
  3.     Mat img_object = imread( argv[1], IMREAD_GRAYSCALE );  
  4.     Mat img_scene = imread( argv[2], IMREAD_GRAYSCALE );  
  5.     if( !img_object.data || !img_scene.data )  
  6.     { std::cout<< " --(!) Error reading images " << std::endl; return -1; }  
  7.     //-- Step 1: Detect the keypoints using SURF Detector
  8.     int minHessian = 400;  
  9.     SurfFeatureDetector detector( minHessian );  
  10.     std::vector<KeyPoint> keypoints_object, keypoints_scene;  
  11.     detector.detect( img_object, keypoints_object );  
  12.     detector.detect( img_scene, keypoints_scene );  
  13.     //-- Step 2: Calculate descriptors (feature vectors)
  14.     SurfDescriptorExtractor extractor;  
  15.     Mat descriptors_object, descriptors_scene;  
  16.     extractor.compute( img_object, keypoints_object, descriptors_object );  
  17.     extractor.compute( img_scene, keypoints_scene, descriptors_scene );  
  18.     //-- Step 3: Matching descriptor vectors using FLANN matcher
  19.     FlannBasedMatcher matcher;  
  20.     std::vector< DMatch > matches;  
  21.     matcher.match( descriptors_object, descriptors_scene, matches );  
  22.     double max_dist = 0; double min_dist = 100;  
  23.     //-- Quick calculation of max and min distances between keypoints
  24.     forint i = 0; i < descriptors_object.rows; i++ )  
  25.     { double dist = matches[i].distance;  
  26.     if( dist < min_dist ) min_dist = dist;  
  27.     if( dist > max_dist ) max_dist = dist;  
  28.     }  
  29.     printf("-- Max dist : %f \n", max_dist );  
  30.     printf("-- Min dist : %f \n", min_dist );  
  31.     //-- Draw only "good" matches (i.e. whose distance is less than 3*min_dist )
  32.     std::vector< DMatch > good_matches;  
  33.     forint i = 0; i < descriptors_object.rows; i