透視變換


【圖像處理】透視變換 Perspective Transformation

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

u,v是原始圖片左邊,對應得到變換后的圖片坐標x,y,其中
變換矩陣可以拆成4部分,表示線性變換,比如scaling,shearing和ratotion。用於平移,產生透視變換。所以可以理解成仿射等是透視變換的特殊形式。經過透視變換之后的圖片通常不是平行四邊形(除非映射視平面和原來平面平行的情況)。

重寫之前的變換公式可以得到:

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

根據變換公式得到:

定義幾個輔助變量:

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

不為0時,得到:

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

看一段代碼:

 

[cpp]  view plain  copy
 
  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.   
  9. PerspectiveTransform PerspectiveTransform::quadrilateralToQuadrilateral(float x0, float y0, float x1, float y1,  
  10.     float x2, float y2, float x3, float y3, float x0p, float y0p, float x1p, float y1p, float x2p, float y2p,  
  11.     float x3p, float y3p) {  
  12.   PerspectiveTransform qToS = PerspectiveTransform::quadrilateralToSquare(x0, y0, x1, y1, x2, y2, x3, y3);  
  13.   PerspectiveTransform sToQ =  
  14.     PerspectiveTransform::squareToQuadrilateral(x0p, y0p, x1p, y1p, x2p, y2p, x3p, y3p);  
  15.   return sToQ.times(qToS);  
  16. }  
  17.   
  18. PerspectiveTransform PerspectiveTransform::squareToQuadrilateral(float x0, float y0, float x1, float y1, float x2,  
  19.     float y2, float x3, float y3) {  
  20.   float dx3 = x0 - x1 + x2 - x3;  
  21.   float dy3 = y0 - y1 + y2 - y3;  
  22.   if (dx3 == 0.0f && dy3 == 0.0f) {  
  23.     PerspectiveTransform result(PerspectiveTransform(x1 - x0, x2 - x1, x0, y1 - y0, y2 - y1, y0, 0.0f,  
  24.                                      0.0f, 1.0f));  
  25.     return result;  
  26.   } else {  
  27.     float dx1 = x1 - x2;  
  28.     float dx2 = x3 - x2;  
  29.     float dy1 = y1 - y2;  
  30.     float dy2 = y3 - y2;  
  31.     float denominator = dx1 * dy2 - dx2 * dy1;  
  32.     float a13 = (dx3 * dy2 - dx2 * dy3) / denominator;  
  33.     float a23 = (dx1 * dy3 - dx3 * dy1) / denominator;  
  34.     PerspectiveTransform result(PerspectiveTransform(x1 - x0 + a13 * x1, x3 - x0 + a23 * x3, x0, y1 - y0  
  35.                                      + a13 * y1, y3 - y0 + a23 * y3, y0, a13, a23, 1.0f));  
  36.     return result;  
  37.   }  
  38. }  
  39.   
  40. PerspectiveTransform PerspectiveTransform::quadrilateralToSquare(float x0, float y0, float x1, float y1, float x2,  
  41.     float y2, float x3, float y3) {  
  42.   // Here, the adjoint serves as the inverse:  
  43.   return squareToQuadrilateral(x0, y0, x1, y1, x2, y2, x3, y3).buildAdjoint();  
  44. }  
  45.   
  46. PerspectiveTransform PerspectiveTransform::buildAdjoint() {  
  47.   // Adjoint is the transpose of the cofactor matrix:  
  48.   PerspectiveTransform result(PerspectiveTransform(a22 * a33 - a23 * a32, a23 * a31 - a21 * a33, a21 * a32  
  49.                                    - a22 * a31, a13 * a32 - a12 * a33, a11 * a33 - a13 * a31, a12 * a31 - a11 * a32, a12 * a23 - a13 * a22,  
  50.                                    a13 * a21 - a11 * a23, a11 * a22 - a12 * a21));  
  51.   return result;  
  52. }  
  53.   
  54. PerspectiveTransform PerspectiveTransform::times(PerspectiveTransform other) {  
  55.   PerspectiveTransform result(PerspectiveTransform(a11 * other.a11 + a21 * other.a12 + a31 * other.a13,  
  56.                                    a11 * other.a21 + a21 * other.a22 + a31 * other.a23, a11 * other.a31 + a21 * other.a32 + a31  
  57.                                    * other.a33, a12 * other.a11 + a22 * other.a12 + a32 * other.a13, a12 * other.a21 + a22  
  58.                                    * other.a22 + a32 * other.a23, a12 * other.a31 + a22 * other.a32 + a32 * other.a33, a13  
  59.                                    * other.a11 + a23 * other.a12 + a33 * other.a13, a13 * other.a21 + a23 * other.a22 + a33  
  60.                                    * other.a23, a13 * other.a31 + a23 * other.a32 + a33 * other.a33));  
  61.   return result;  
  62. }  
  63.   
  64. void PerspectiveTransform::transformPoints(vector<float> &points) {  
  65.   int max = points.size();  
  66.   for (int i = 0; i < max; i += 2) {  
  67.     float x = points[i];  
  68.     float y = points[i + 1];  
  69.     float denominator = a13 * x + a23 * y + a33;  
  70.     points[i] = (a11 * x + a21 * y + a31) / denominator;  
  71.     points[i + 1] = (a12 * x + a22 * y + a32) / denominator;  
  72.   }  
  73. }  

對一張透視圖片變換回正面圖的效果:

 

 

[cpp]  view plain  copy
 
  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. }  

 

 
 
另外在OpenCV中也實現了基礎的透視變換操作,有關函數使用請見下一篇: 【OpenCV】透視變換 Perspective Transformation(續)
 
 
 

(轉載請注明作者和出處:http://blog.csdn.net/xiaowei_cqu 未經允許請勿用於商業用途)

 

透視變換的原理和矩陣求解請參見前一篇《透視變換 Perspective Transformation》。在OpenCV中也實現了透視變換的公式求解和變換函數。

求解變換公式的函數:

 

[cpp]  view plain  copy
 
  1. Mat getPerspectiveTransform(const Point2f src[], const Point2f dst[])  
輸入原始圖像和變換之后的圖像的對應4個點,便可以得到變換矩陣。之后用求解得到的矩陣輸入perspectiveTransform便可以對一組點進行變換:

 

 

[cpp]  view plain  copy
 
  1. void perspectiveTransform(InputArray src, OutputArray dst, InputArray m)  
注意這里src和dst的輸入並不是圖像,而是圖像對應的坐標。應用前一篇的例子,做個相反的變換:

 

 

[cpp]  view plain  copy
 
  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.   
  17.     Mat transform = getPerspectiveTransform(corners,corners_trans);  
  18.     cout<<transform<<endl;  
  19.     vector<Point2f> ponits, points_trans;  
  20.     for(int i=0;i<img_height;i++){  
  21.         for(int j=0;j<img_width;j++){  
  22.             ponits.push_back(Point2f(j,i));  
  23.         }  
  24.     }  
  25.   
  26.     perspectiveTransform( ponits, points_trans, transform);  
  27.     Mat img_trans = Mat::zeros(img_height,img_width,CV_8UC3);  
  28.     int count = 0;  
  29.     for(int i=0;i<img_height;i++){  
  30.         uchar* p = img.ptr<uchar>(i);  
  31.         for(int j=0;j<img_width;j++){  
  32.             int y = points_trans[count].y;  
  33.             int x = points_trans[count].x;  
  34.             uchar* t = img_trans.ptr<uchar>(y);  
  35.             t[x*3]  = p[j*3];  
  36.             t[x*3+1]  = p[j*3+1];  
  37.             t[x*3+2]  = p[j*3+2];  
  38.             count++;  
  39.         }  
  40.     }  
  41.     imwrite("boy_trans.png",img_trans);  
  42.   
  43.     return 0;  
  44. }  

得到變換之后的圖片:

 

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

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

 

[cpp]  view plain  copy
 
  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.   
  8.     //-- Step 1: Detect the keypoints using SURF Detector  
  9.     int minHessian = 400;  
  10.     SurfFeatureDetector detector( minHessian );  
  11.     std::vector<KeyPoint> keypoints_object, keypoints_scene;  
  12.     detector.detect( img_object, keypoints_object );  
  13.     detector.detect( img_scene, keypoints_scene );  
  14.   
  15.     //-- Step 2: Calculate descriptors (feature vectors)  
  16.     SurfDescriptorExtractor extractor;  
  17.     Mat descriptors_object, descriptors_scene;  
  18.     extractor.compute( img_object, keypoints_object, descriptors_object );  
  19.     extractor.compute( img_scene, keypoints_scene, descriptors_scene );  
  20.   
  21.     //-- Step 3: Matching descriptor vectors using FLANN matcher  
  22.     FlannBasedMatcher matcher;  
  23.     std::vector< DMatch > matches;  
  24.     matcher.match( descriptors_object, descriptors_scene, matches );  
  25.     double max_dist = 0; double min_dist = 100;  
  26.   
  27.     //-- Quick calculation of max and min distances between keypoints  
  28.     for( int i = 0; i < descriptors_object.rows; i++ )  
  29.     { double dist = matches[i].distance;  
  30.     if( dist < min_dist ) min_dist = dist;  
  31.     if( dist > max_dist ) max_dist = dist;  
  32.     }  
  33.   
  34.     printf("-- Max dist : %f \n", max_dist );  
  35.     printf("-- Min dist : %f \n", min_dist );  
  36.   
  37.     //-- Draw only "good" matches (i.e. whose distance is less than 3*min_dist )  
  38.     std::vector< DMatch > good_matches;  
  39.   
  40.     for( int i = 0; i < descriptors_object.rows; i++ )  
  41.     { if( matches[i].distance < 3*min_dist )  
  42.     { good_matches.push_back( matches[i]); }  
  43.     }  
  44.   
  45.     Mat img_matches;  
  46.     drawMatches( img_object, keypoints_object, img_scene, keypoints_scene,  
  47.         good_matches, img_matches, Scalar::all(-1), Scalar::all(-1),  
  48.         vector<char>(), DrawMatchesFlags::NOT_DRAW_SINGLE_POINTS );  
  49.   
  50.     //-- Localize the object from img_1 in img_2  
  51.     std::vector<Point2f> obj;  
  52.     std::vector<Point2f> scene;  
  53.   
  54.     for( size_t i = 0; i < good_matches.size(); i++ )  
  55.     {  
  56.         //-- Get the keypoints from the good matches  
  57.         obj.push_back( keypoints_object[ good_matches[i].queryIdx ].pt );  
  58.         scene.push_back( keypoints_scene[ good_matches[i].trainIdx ].pt );  
  59.     }  
  60.   
  61.     Mat H = findHomography( obj, scene, RANSAC );  
  62.   
  63.     //-- Get the corners from the image_1 ( the object to be "detected" )  
  64.     std::vector<Point2f> obj_corners(4);  
  65.     obj_corners[0] = Point(0,0); obj_corners[1] = Point( img_object.cols, 0 );  
  66.     obj_corners[2] = Point( img_object.cols, img_object.rows ); obj_corners[3] = Point( 0, img_object.rows );  
  67.     std::vector<Point2f> scene_corners(4);  
  68.     perspectiveTransform( obj_corners, scene_corners, H);  
  69.     //-- Draw lines between the corners (the mapped object in the scene - image_2 )  
  70.     Point2f offset( (float)img_object.cols, 0);  
  71.     line( img_matches, scene_corners[0] + offset, scene_corners[1] + offset, Scalar(0, 255, 0), 4 );  
  72.     line( img_matches, scene_corners[1] + offset, scene_corners[2] + offset, Scalar( 0, 255, 0), 4 );  
  73.     line( img_matches, scene_corners[2] + offset, scene_corners[3] + offset, Scalar( 0, 255, 0), 4 );  
  74.     line( img_matches, scene_corners[3] + offset, scene_corners[0] + offset, Scalar( 0, 255, 0), 4 );  
  75.   
  76.     //-- Show detected matches  
  77.     imshow( "Good Matches & Object detection", img_matches );  
  78.     waitKey(0);  
  79.     return 0;  
  80. }  

代碼運行效果:

 

 

findHomography()函數直接通過兩個平面上相匹配的特征點求出變換公式,之后代碼又對原圖的四個邊緣點進行變換,在右圖上畫出對應的矩形。這個圖也很好地解釋了所謂透視變換的“Viewing Plane”。

 

 

(轉載請注明作者和出處:http://blog.csdn.net/xiaowei_cqu 未經允許請勿用於商業用途)


免責聲明!

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



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