Homography,opencv,单应性矩阵的计算原理

本文介绍了一种基于SURF特征检测与匹配,结合Homography变换的方法来实现图像中的物体定位。通过计算关键点及描述符,并利用FLANN匹配器筛选出最佳匹配点对,最终应用RANSAC算法计算单应性矩阵完成定位。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Features2D + Homography to find a known object


[cpp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. #include <stdio.h>  
  2. #include <iostream>  
  3. #include "opencv2/core/core.hpp"  
  4. #include "opencv2/features2d/features2d.hpp"  
  5. #include "opencv2/highgui/highgui.hpp"  
  6. #include "opencv2/calib3d/calib3d.hpp"  
  7. #include "opencv2/nonfree/nonfree.hpp"  
  8.   
  9.   
  10. using namespace cv;  
  11.   
  12.   
  13. void readme();  
  14.   
  15.   
  16. /** @function main */  
  17. int main( int argc, char** argv )  
  18. {  
  19.     if( argc != 3 )  
  20.     { readme(); return -1; }  
  21.   
  22.   
  23.     Mat img_object = imread( argv[1], CV_LOAD_IMAGE_GRAYSCALE );  
  24.     Mat img_scene = imread( argv[2], CV_LOAD_IMAGE_GRAYSCALE );  
  25.   
  26.   
  27.     if( !img_object.data || !img_scene.data )  
  28.     { std::cout<< " --(!) Error reading images " << std::endl; return -1; }  
  29.   
  30.   
  31.     //-- Step 1: Detect the keypoints using SURF Detector  
  32.     int minHessian = 400;  
  33.   
  34.   
  35.     SurfFeatureDetector detector( minHessian );  
  36.   
  37.   
  38.     std::vector<KeyPoint> keypoints_object, keypoints_scene;  
  39.   
  40.   
  41.     detector.detect( img_object, keypoints_object );  
  42.     detector.detect( img_scene, keypoints_scene );  
  43.   
  44.   
  45.     //-- Step 2: Calculate descriptors (feature vectors)  
  46.     SurfDescriptorExtractor extractor;  
  47.   
  48.   
  49.     Mat descriptors_object, descriptors_scene;  
  50.   
  51.   
  52.     extractor.compute( img_object, keypoints_object, descriptors_object );  
  53.     extractor.compute( img_scene, keypoints_scene, descriptors_scene );  
  54.   
  55.   
  56.     //-- Step 3: Matching descriptor vectors using FLANN matcher  
  57.     FlannBasedMatcher matcher;  
  58.     std::vector< DMatch > matches;  
  59.     matcher.match( descriptors_object, descriptors_scene, matches );  
  60.   
  61.   
  62.     double max_dist = 0; double min_dist = 100;  
  63.   
  64.   
  65.     //-- Quick calculation of max and min distances between keypoints  
  66.     forint i = 0; i < descriptors_object.rows; i++ )  
  67.     { double dist = matches[i].distance;  
  68.     if( dist < min_dist ) min_dist = dist;  
  69.     if( dist > max_dist ) max_dist = dist;  
  70.     }  
  71.   
  72.   
  73.     printf("-- Max dist : %f \n", max_dist );  
  74.     printf("-- Min dist : %f \n", min_dist );  
  75.   
  76.   
  77.     //-- Draw only "good" matches (i.e. whose distance is less than 3*min_dist )  
  78.     std::vector< DMatch > good_matches;  
  79.   
  80.   
  81.     forint i = 0; i < descriptors_object.rows; i++ )  
  82.     { if( matches[i].distance < 3*min_dist )  
  83.     { good_matches.push_back( matches[i]); }  
  84.     }  
  85.   
  86.   
  87.     Mat img_matches;  
  88.     drawMatches( img_object, keypoints_object, img_scene, keypoints_scene,  
  89.         good_matches, img_matches, Scalar::all(-1), Scalar::all(-1),  
  90.         vector<char>(), DrawMatchesFlags::NOT_DRAW_SINGLE_POINTS );  
  91.   
  92.   
  93.     //-- Localize the object  
  94.     std::vector<Point2f> obj;  
  95.     std::vector<Point2f> scene;  
  96.   
  97.   
  98.     forint i = 0; i < good_matches.size(); i++ )  
  99.     {  
  100.         //-- Get the keypoints from the good matches  
  101.         obj.push_back( keypoints_object[ good_matches[i].queryIdx ].pt );  
  102.         scene.push_back( keypoints_scene[ good_matches[i].trainIdx ].pt );  
  103.     }  
  104.   
  105.   
  106.     Mat H = findHomography( obj, scene, CV_RANSAC );  
  107.   
  108.   
  109.     //-- Get the corners from the image_1 ( the object to be "detected" )  
  110.     std::vector<Point2f> obj_corners(4);  
  111.     obj_corners[0] = cvPoint(0,0); obj_corners[1] = cvPoint( img_object.cols, 0 );  
  112.     obj_corners[2] = cvPoint( img_object.cols, img_object.rows ); obj_corners[3] = cvPoint( 0, img_object.rows );  
  113.     std::vector<Point2f> scene_corners(4);  
  114.   
  115.   
  116.     perspectiveTransform( obj_corners, scene_corners, H);  
  117.   
  118.   
  119.     //-- Draw lines between the corners (the mapped object in the scene - image_2 )  
  120.     line( img_matches, scene_corners[0] + Point2f( img_object.cols, 0), scene_corners[1] + Point2f( img_object.cols, 0), Scalar(0, 255, 0), 4 );  
  121.     line( img_matches, scene_corners[1] + Point2f( img_object.cols, 0), scene_corners[2] + Point2f( img_object.cols, 0), Scalar( 0, 255, 0), 4 );  
  122.     line( img_matches, scene_corners[2] + Point2f( img_object.cols, 0), scene_corners[3] + Point2f( img_object.cols, 0), Scalar( 0, 255, 0), 4 );  
  123.     line( img_matches, scene_corners[3] + Point2f( img_object.cols, 0), scene_corners[0] + Point2f( img_object.cols, 0), Scalar( 0, 255, 0), 4 );  
  124.   
  125.   
  126.     //-- Show detected matches  
  127.     imshow( "Good Matches & Object detection", img_matches );  
  128.   
  129.   
  130.     waitKey(0);  
  131.     return 0;  
  132. }  
  133.   
  134.   
  135. /** @function readme */  
  136. void readme()  
  137. { std::cout << " Usage: ./SURF_descriptor <img1> <img2>" << std::endl; }  


Homography Estimation




Homography 理解

下文转载之:http://m.blog.csdn.NET/blog/xuluhui123/17115073

在计算机视觉中,平面的单应性被定义为一个平面到另外一个平面的投影映射。因此一个二维平面上的点映射到摄像机成像仪上的映射就是平面单应性的例子。如果点Q到成像仪上的点q的映射使用齐次坐标,这种映射可以用矩阵相乘的方式表示。若有一下定义:


则可以将单应性简单的表示为:


这里引入参数s,它是任意尺度的比例(目的是使得单应性定义到该尺度比例)。通常根据习惯放在H的外面


H有两部分组成:用于定位观察的物体平面的物理变换和使用摄像机内参数矩阵的投影。


物理变换部分是与观测到的图像平面相关的部分旋转R和部分平移t的影响之和,表示如下


这里R为3*3大小的矩阵,t表示一个一个3维的列矢量。

摄像机内参数矩阵用M表示,那么我们重写单应性如下:


我们知道单应性研究的是一个平面上到另外一个平面的映射,那么上述公式中的~Q,就可以简化为平面坐标中的~Q',即我们使Z=0。即物体平面上的点我们用x,y表示,相机平面上的点,我们也是用二维点表示。我们去掉了Z方向的坐标,那么相对于旋转矩阵R,R可以分解为R=[r1 r2 r3],那么r3也就不要了,参考下面的推导:


其中H为:


是一个3×3大小的矩阵.

 故最终的单应性矩阵可表示如下:


OpenCV就是利用上述公式来计算单应性矩阵。它使用同一物体的多个图像来计算每个视场的旋转和平移,同时也计算摄像机的内参数。我们知道旋转和平移共6个参数,摄像机内参数为4个参数。对于每一个视场有6个要求解的新参数和4个不变的相机内参数。对于平面物体如棋盘,能够提供8个方差,即映射一个正方形到四边形可以用4个(x,y)来描述。那么对于两个视场,我们就有8*2=16=2*6+4,即求解所有的参数,至少需要两个视场。

为什么正方形到四边形的四个点的映射可以确定8个方程呢,结果是显然的,我们假设物体平面上的正方形的一个顶点坐标为(u,v),成像仪与该点对应的点坐标为(x,y),我们假设它们之间的关系如下:

u=f(x,y);

v=g(x,y);

显然,我们把四点的对应坐标带入到上述公式可以得到8个方程。

这里我们会想物体平面上正方形的四个顶点坐标如何确定,其实我们就可以理解为角点的个数,对于尺度的话,我们有s进行控制。对于图像平面上的角点的位置,我们可以可以通过寻找角点来定位他们的位置。其实对于具体的操作,由于还没细读代码和相关原理,在这里只能大体猜测一下。等日后学习了,再来纠正。


单应性矩阵H把源图像平面上的点集位置与目标图像平面上(通常是成像仪平面)的点集位置联系起来:


OpenCV就是利用多个视场计算多个单应性矩阵的方法来求解摄像机内参数


OpenCV提供了一个方便的C函数cvFindHomography(),函数接口如下:

[cpp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. void cvFindHomography(  
  2. const CvMat* src_points,  
  3. const CvMat* dst_points,  
  4. CvMat* homography  
  5. );  

1、src_points,dst_points为N×2或者N×3的矩阵,N×2表示点是以像素坐标表示。N×3表示以齐次坐标表示。

2、homography,为3*3大小的矩阵,用来存储输出的结果。


C++函数的接口:

[cpp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. Mat findHomography( const Mat& srcPoints, const Mat& dstPoints,  
  2. Mat& status, int method=0,  
  3. double ransacReprojThreshold=3 );  
  4. Mat findHomography( const Mat& srcPoints, const Mat& dstPoints,  
  5. vector<uchar>& status, int method=0,  
  6. double ransacReprojThreshold=3 );  
  7. Mat findHomography( const Mat& srcPoints, const Mat& dstPoints,  
  8. int method=0, double ransacReprojThreshold=3 );  

1、srcPoints,dstPoints为CV_32FC2或者vector<Point2f>类型

2、method:0表示使用所有点的常规方法;CV_RANSAC 基于RANSAC鲁棒性的方法;CV_LMEDS 最小中值鲁棒性方法

3、ransacReprojThreshod 仅在RANSAC方法中使用,一个点对被认为是内层围值(非异常值)所允许的最大投影误差。即如果:


那么点i被认为是异常值。如果srcPoints和dstPoints单位是像素,通常意味着在某些情况下这个参数的范围在1到10之间。

4、status,可选的输出掩码,用在CV_RANSAC或者CV_LMEDS方法中。注意输入掩码将被忽略。


这个函数找到并且返回源图像平面和目的图像平面之间的透视变换矩阵H:


使得下面的返回投影误差(back-projection)最小:


如果参数method设置为默认值0,该函数使用一个简单的最小二乘方案来计算初始的单应性估计。

然而,如果不是所有的点对(srcPoints,dstPoints)都适应这个严格的透视变换。(也就是说,有一些异常值),这个初始估计值将很差。在这种情况下,我们可以使用两个鲁棒性算法中的一个。RANSCA和LMEDS这两个方法都尝试不同的随机的相对应点对的子集,每四对点集一组,使用这个子集和一个简单的最小二乘算法来估计单应性矩阵,然后计算得到单应性矩阵的质量quality/goodness。(对于RANSAC方法是内层围点的数量,对于LMeDs是中间的重投影误差)。然后最好的子集用来产生单应性矩阵的初始化估计和inliers/outliers的掩码。


忽略方法,鲁棒性与否,计算得到的单应性矩阵使用Levenberg-Marquardt方法来进一步减少重投影误差,从而进一步提纯。(对于鲁棒性的方法仅使用内围层点(inliers))。


RANSAC方法,几乎可以处理任含有何异常值比率的情况,但是它需要一个阈值用来区分inliers和outliers。LMeDS方法不需要任何阈值,但是它仅在inliers大于50%的情况下才能正确的工作。最后,如果你确信在你计算得到的特征点仅含一些小的噪声,但是没有异常值,默认的方法可能是最好的选择。(因此,在计算相机参数时,我们或许仅使用默认的方法

这个函数用来找到初始化内参数和外参数矩阵。单应性矩阵取决于一个尺度,那么通常归一化,以使得h33=1。


<think>好的,我现在需要帮助用户了解如何使用单应性矩阵进行图像变换,特别是用OpenCV的实现方法。用户可能已经看过一些资料,但需要具体的步骤和代码示例。首先,我得回忆一下单应性矩阵的基本概念和它在图像处理中的作用。根据引用[1],单应性矩阵是一个3x3的矩阵,用于将图像中的点从一个平面投影到另一个平面,这属于透视变换的一部分。 接下来,我需要确定用户的需求。用户提到“实现方法或代码示例”,所以应该提供具体的步骤和对应的代码。引用[3]中有一个OpenCV的示例代码,这可以作为参考。不过,我需要将代码分解并解释每一步的作用,让用户更容易理解。 首先,步骤一应该是准备输入图像。用户需要读取图像并确定源点和目标点。通常,源点是原始图像中的四个顶点,而目标点是变换后的位置。这里需要确保点的顺序一致,比如左上、右上、左下、右下。引用[3]中的例子使用了这样的点,但用户可能需要根据自己的图像调整坐标。 然后,步骤二是计算单应性矩阵。使用OpenCV的cv2.findHomography函数,传入源点和目标点,得到单应性矩阵。这里需要解释函数的参数和返回值,比如status用来检测是否有错误。不过,用户可能更关心如何正确使用这个函数,所以需要强调点的顺序和数量是否正确。 步骤三是应用变换。使用cv2.warpPerspective函数,传入原图、单应性矩阵和输出图像大小。这里要注意输出尺寸的设置,可能需要根据目标点来确定,或者保持原图尺寸。引用[3]中的代码使用了原图的尺寸,但用户可能需要调整,特别是当变换后的图像超出原图范围时。 最后,步骤四是显示和保存结果。这部分相对直接,但用户可能需要知道如何查看和保存变换后的图像。此外,代码示例中的plt.show()可能需要用户安装matplotlib,如果用户不熟悉,可能需要提醒或者改用OpenCV的显示方法。 在代码示例中,需要注意变量命名和注释的清晰性,让用户容易跟随。同时,要解释关键函数和参数的作用,比如为什么使用齐次坐标,单应性矩阵的形状(3x3)等。引用[2]中的资料提到透视变换原理,可能需要简要提及,但用户可能更关注实现。 另外,用户的问题可能涉及单应性矩阵的应用场景,比如图像校正、全景拼接等,可以在相关问题中提及。但根据用户当前的需求,重点放在实现方法上。 在检查过程中,确保代码语法正确,比如使用正确的函数名和参数顺序。例如,cv2.findHomography的正确用法是传入源点和目标点,可能还有其他参数如方法类型(默认是RANSAC)。如果有必要,可以说明这些参数的作用,但保持简洁。 最后,确保回答结构清晰,分步骤说明,每个步骤有对应的代码和解释。同时,生成的相关问题要围绕单应性矩阵的扩展应用和常见问题,帮助用户进一步学习。</think>### 单应性矩阵图像变换实现方法 #### 一、核心原理 单应性矩阵Homography Matrix)是一个 3x3 的线性变换矩阵,用于描述两个平面之间的投影映射关系,数学形式为: $$ \begin{bmatrix} x' \\ y' \\ w' \end{bmatrix} = \begin{bmatrix} h_{11} & h_{12} & h_{13} \\ h_{21} & h_{22} & h_{23} \\ h_{31} & h_{32} & h_{33} \end{bmatrix} \begin{bmatrix} x \\ y \\ 1 \end{bmatrix} $$ 其中$(x', y')$为目标图像坐标,$(x, y)$为原图像坐标[^1]。 #### 二、OpenCV实现步骤 1. **准备输入数据** - 定义原图像中物体**4个顶点坐标**(需按顺序排列,如左上→右上→左下→右下) - 定义目标图像中对应的**4个顶点坐标** 2. **计算单应性矩阵** 调用`cv2.findHomography()`函数,输入源点和目标点坐标: ```python homography, status = cv2.findHomography(pts_src, pts_dst) ``` 3. **应用透视变换** 使用`cv2.warpPerspective()`执行变换: ```python im_out = cv2.warpPerspective(im_src, homography, (output_width, output_height)) ``` #### 三、完整代码示例 ```python import cv2 import numpy as np # 读取图像 im_src = cv2.imread('input.jpg') h, w = im_src.shape[:2] # 定义源点坐标 (示例为A4纸四个角) pts_src = np.array([[56, 65], [368, 52], [28, 387], [389, 390]], dtype=np.float32) # 定义目标坐标 (校正后的矩形) pts_dst = np.array([[0, 0], [w, 0], [0, h], [w, h]], dtype=np.float32) # 计算单应性矩阵 H, _ = cv2.findHomography(pts_src, pts_dst) # 执行透视变换 result = cv2.warpPerspective(im_src, H, (w, h)) # 显示结果 cv2.imshow("Source", im_src) cv2.imshow("Transformed", result) cv2.waitKey(0) ``` #### 四、关键参数说明 | 参数/函数 | 作用说明 | |-------------------------|------------------------------------------------------------------------| | `cv2.findHomography()` | 使用RANSAC算法鲁棒估算应矩阵,返回矩阵和匹配状态掩码[^2] | | `cv2.warpPerspective()` | 执行实际变换,第三个参数控制输出图像尺寸(建议设置为目标区域实际大小) | #### 五、应用场景 - 文档校正(如倾斜的A4纸转正) - 车牌识别预处理 - 增强现实中的虚拟物体投影 - 全景图像拼接[^3]
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值