使用cv::findFundamentalMat要注意的几点

本文详细探讨了在新版OpenCV中计算基本矩阵时遇到的问题,包括RANSAC方法中的迭代次数不收敛bug以及使用cv::findFundamentalMat函数时的注意事项。重点介绍了如何正确构建Mat矩阵和设置数据类型,避免运行时崩溃,并通过实例展示了从匹配特征点计算基本矩阵的过程。此外,文章还提供了内点匹配的显示方法。

    在新版的OpenCV中,很多C代码都被重新封装成了C++代码,相应的其调用接口也发生了改变,而文档中的叙述也越来越不清楚,往往导致使用过程中出现各种问题。

    在处理立体图像对的时候经常会用到对极几何的知识,计算基础矩阵也是很常见的事。OpenCV实现了基本矩阵的算法。对于老版本的C代码,计算基本矩阵的RANSAC方法中有一个迭代次数不收敛的bug,可能导致每次计算的采样次数都达到最大限制的次数,其根源在于计算采样可信度的公式有误,新版本的代码还没有仔细看过,不敢确定是否已经修正了这个bug。但是这个bug并不会对最后的结果造成多大影响,只是会影响计算的效率——原本几次采样即可结束迭代,在这个bug的影响下可能要采样数百次。

    新版本的计算基本矩阵的函数的使用也有一些问题,下面来看看cv::findFundamentalMat函数:

//! the algorithm for finding fundamental matrix
enum
{
    FM_7POINT = CV_FM_7POINT, //!< 7-point algorithm
    FM_8POINT = CV_FM_8POINT, //!< 8-point algorithm
    FM_LMEDS = CV_FM_LMEDS,  //!< least-median algorithm
    FM_RANSAC = CV_FM_RANSAC  //!< RANSAC algorithm
};

//! finds fundamental matrix from a set of corresponding 2D points
CV_EXPORTS Mat findFundamentalMat( const Mat& points1, const Mat& points2,
                                     CV_OUT vector<uchar>& mask, int method=FM_RANSAC,
                                     double param1=3., double param2=0.99 );

//! finds fundamental matrix from a set of corresponding 2D points
CV_EXPORTS_W Mat findFundamentalMat( const Mat& points1, const Mat& points2,
                                     int method=FM_RANSAC,
                                     double param1=3., double param2=0.99 );

 

    上面是OpenCV计算基本矩阵的C++接口,其内部实现仍然是调用的C代码函数,仅仅是在上层进行了一次封装。网上有些文档中说const Mat& points1和points2这两个参数可以直接由vector<Point2f>类型作为传入参数,其实这是错误的。直接用vector<Point2f>传递,在编译时可能不会报错,但是运行时会直接崩溃。因为cv::Mat的构造函数并不会把Point2f转化为两个浮点数存于Mat的两个元素中,而是仍然以Point2f存于Mat的一个元素中,于是findFundamentalMat一读取这个Mat就会出错。

    因此我们最好老老实实地去构建Mat points1和Mat points2。该矩阵的维度可以是2xN,也可以是Nx2的,其中N是特征点的数目。另一个要注意的地方就是该矩阵的类型不能是CV_64F,也就是说findFundamentalMat内部解析points1和points2时是按照float类型去解析的,设为CV_64F将导致读取数据失败,程序崩溃。最好设为CV_32F。下面是从匹配好的特征点计算F的代码示例:

 

// vector<KeyPoint> m_LeftKey;
// vector<KeyPoint> m_RightKey;

// vector<DMatch> m_Matches;

// 以上三个变量已经被计算出来,分别是提取的关键点及其匹配,下面直接计算F

 

// 分配空间

int ptCount = (int)m_Matches.size();
Mat p1(ptCount, 2, CV_32F);
Mat p2(ptCount, 2, CV_32F);

 

// 把Keypoint转换为Mat

Point2f pt;
for (int i=0; i<ptCount; i++)
{
     pt = m_LeftKey[m_Matches[i].queryIdx].pt;
     p1.at<float>(i, 0) = pt.x;
     p1.at<float>(i, 1) = pt.y;
  
     pt = m_RightKey[m_Matches[i].trainIdx].pt;
     p2.at<float>(i, 0) = pt.x;
     p2.at<float>(i, 1) = pt.y;
}

 

// 用RANSAC方法计算F

// Mat m_Fundamental;

// 上面这个变量是基本矩阵

// vector<uchar> m_RANSACStatus;

// 上面这个变量已经定义过,用于存储RANSAC后每个点的状态

m_Fundamental = findFundamentalMat(p1, p2, m_RANSACStatus, FM_RANSAC);

 

// 计算野点个数

int OutlinerCount = 0;
for (int i=0; i<ptCount; i++)
{
     if (m_RANSACStatus[i] == 0) // 状态为0表示野点
     {
          OutlinerCount++;
     }
}

 

// 计算内点

// vector<Point2f> m_LeftInlier;
// vector<Point2f> m_RightInlier;
// vector<DMatch> m_InlierMatches;

// 上面三个变量用于保存内点和匹配关系

int InlinerCount = ptCount - OutlinerCount;
m_InlierMatches.resize(InlinerCount);
m_LeftInlier.resize(InlinerCount);
m_RightInlier.resize(InlinerCount);
InlinerCount = 0;
for (int i=0; i<ptCount; i++)
{
     if (m_RANSACStatus[i] != 0)
     {
          m_LeftInlier[InlinerCount].x = p1.at<float>(i, 0);
          m_LeftInlier[InlinerCount].y = p1.at<float>(i, 1);
          m_RightInlier[InlinerCount].x = p2.at<float>(i, 0);
          m_RightInlier[InlinerCount].y = p2.at<float>(i, 1);
          m_InlierMatches[InlinerCount].queryIdx = InlinerCount;
          m_InlierMatches[InlinerCount].trainIdx = InlinerCount;
          InlinerCount++;
     }
}

 

// 把内点转换为drawMatches可以使用的格式

vector<KeyPoint> key1(InlinerCount);
vector<KeyPoint> key2(InlinerCount);
KeyPoint::convert(m_LeftInlier, key1);
KeyPoint::convert(m_RightInlier, key2);

 

// 显示计算F过后的内点匹配

// Mat m_matLeftImage;
// Mat m_matRightImage;

// 以上两个变量保存的是左右两幅图像

Mat OutImage;
drawMatches(m_matLeftImage, key1, m_matRightImage, key2, m_InlierMatches, OutImage);
cvNamedWindow( "Match features", 1);
cvShowImage("Match features", &(IplImage(OutImage)));
cvWaitKey( 0 );
cvDestroyWindow( "Match features" );

    以上就是核心代码。载入左右两幅图像后,先用http://blog.sina.com.cn/s/blog_4298002e01013w4z.html一文中所述方法提取特征点并匹配,然后用上面的程序计算基本矩阵,并显示内点匹配。如图:

 

初始匹配:

使用cv::findFundamentalMat要注意的几点

 

RANSAC过后的内点匹配:

使用cv::findFundamentalMat要注意的几点


<think>我们正在讨论RANSAC算法在计算机视觉中的经典应用案例。用户已经要求提供与图像处理或几何模型估计相关的详细示例。根据引用[1][2][3],我们可以总结出RANSAC在计算机视觉中的几个经典应用场景。同时,结合我们之前关于道路检测的讨论,我们可以扩展更多案例。 经典应用案例: 1. **直线和几何形状拟合**(如道路检测、建筑物轮廓提取)[^1]: - 在存在噪声和离群点的边缘点集中,RANSAC可以鲁棒地拟合直线、圆或其他几何形状。例如,在道路图像中,我们使用RANSAC拟合车道线(如之前示例)。 2. **单应性矩阵估计**(图像拼接)[^3]: - 在图像拼接中,需要估计两幅图像之间的单应性矩阵(Homography)。RANSAC用于从匹配的特征点对(可能包含误匹配)中鲁棒地估计单应性矩阵。 3. **基础矩阵和本质矩阵估计**(立体视觉)[^3]: - 在立体视觉中,通过两幅图像中的对应点估计基础矩阵(Fundamental Matrix)或本质矩阵(Essential Matrix),用于恢复相机运动和三维结构。RANSAC可以有效地处理匹配点中的外点。 4. **平面检测**(如三维点云处理)[^3]: - 在三维点云中,RANSAC可用于检测平面(例如,室内场景中的墙面、地面)。平面模型由三个点定义,通过RANSAC迭代选择三个点拟合平面,并统计内点(点到平面的距离小于阈值)。 5. **运动估计**(视觉里程计)[^2]: - 在视觉里程计中,通过连续帧间的特征匹配估计相机的运动。RANSAC用于从匹配点中鲁棒地估计相机运动(例如,通过PnP问题求解相机位姿),并剔除误匹配。 下面,我们以图像拼接中的单应性矩阵估计为例,详细说明RANSAC的应用步骤和代码解析。 ### 案例:图像拼接中的单应性矩阵估计 **问题描述**:给定两幅有重叠区域的图像,通过特征匹配找到对应点,然后使用RANSAC估计单应性矩阵,最后将两幅图像拼接在一起。 **单应性矩阵**:是一个3x3的矩阵$H$,满足: $$ \begin{bmatrix} x' \\ y' \\ 1 \end{bmatrix} = H \begin{bmatrix} x \\ y \\ 1 \end{bmatrix} $$ 其中$(x,y)$和$(x',y')$分别是两幅图像中的对应点(齐次坐标)。 **步骤**: 1. **特征提取与匹配**:使用SIFT、SURF或ORB等算法提取特征点并匹配,得到初始匹配点对(可能包含误匹配)。 2. **RANSAC迭代**: - 随机选择4组匹配点(因为单应性矩阵有8个自由度,至少需要4对点求解)。 - 用这4个点计算单应性矩阵$H$(使用直接线性变换DLT算法)。 - 对于每一个匹配点对,计算投影误差:将第一幅图像中的点通过$H$投影到第二幅图像,计算与第二幅图像中对应点的欧氏距离。 - 统计投影误差小于阈值的内点数量。 3. **选择最优模型**:记录内点数量最多的单应性矩阵。 4. **精炼模型**:使用所有内点,通过最小二乘法重新估计单应性矩阵(提高精度)。 5. **图像变换与融合**:使用估计出的单应性矩阵将第二幅图像变换到第一幅图像的坐标系,然后进行融合。 **Python代码示例**(使用OpenCV): ```python import cv2 import numpy as np import matplotlib.pyplot as plt # 读取两幅图像 img1 = cv2.imread('image1.jpg') img2 = cv2.imread('image2.jpg') # 转换为灰度图 gray1 = cv2.cvtColor(img1, cv2.COLOR_BGR2GRAY) gray2 = cv2.cvtColor(img2, cv2.COLOR_BGR2GRAY) # 创建SIFT检测器 sift = cv2.SIFT_create() # 检测特征点和描述子 kp1, des1 = sift.detectAndCompute(gray1, None) kp2, des2 = sift.detectAndCompute(gray2, None) # 使用FLANN匹配器 FLANN_INDEX_KDTREE = 1 index_params = dict(algorithm=FLANN_INDEX_KDTREE, trees=5) search_params = dict(checks=50) flann = cv2.FlannBasedMatcher(index_params, search_params) matches = flann.knnMatch(des1, des2, k=2) # 筛选匹配点(Lowe's ratio test) good = [] for m, n in matches: if m.distance < 0.7 * n.distance: good.append(m) # 获取匹配点的坐标 src_pts = np.float32([kp1[m.queryIdx].pt for m in good]).reshape(-1, 1, 2) dst_pts = np.float32([kp2[m.trainIdx].pt for m in good]).reshape(-1, 1, 2) # 使用RANSAC估计单应性矩阵 H, mask = cv2.findHomography(src_pts, dst_pts, cv2.RANSAC, ransacReprojThreshold=5.0, maxIters=2000) # 应用单应性矩阵进行图像变换 height, width = img1.shape[:2] warped_img = cv2.warpPerspective(img2, H, (width*2, height)) # 将第一幅图像放在变换后的图像上 warped_img[0:height, 0:width] = img1 # 显示结果 plt.figure(figsize=(15,10)) plt.imshow(cv2.cvtColor(warped_img, cv2.COLOR_BGR2RGB)) plt.title('拼接结果') plt.show() ``` **代码解析**: 1. **特征匹配**:使用SIFT特征和FLANN匹配器进行特征匹配,并通过Lowe's比率测试筛选出较好的匹配点。 2. **RANSAC参数**:`cv2.findHomography`函数中,`method=cv2.RANSAC`表示使用RANSAC算法,`ransacReprojThreshold`是投影误差阈值(单位:像素),`maxIters`是最大迭代次数。 3. **单应性矩阵估计**:RANSAC迭代过程中,每次随机选择4个匹配点计算单应性矩阵,然后评估内点数量。最终返回内点对应的掩码(mask)和最优单应性矩阵$H$。 4. **图像变换**:使用`cv2.warpPerspective`将第二幅图像根据单应性矩阵$H$进行透视变换,然后与第一幅图像拼接。 该案例展示了RANSAC在图像拼接中的关键作用:从包含误匹配的特征点对中鲁棒地估计出准确的单应性矩阵。 ### 其他经典案例简要说明 **基础矩阵估计**(立体匹配): 在立体视觉中,基础矩阵$F$满足: $$ \mathbf{x}'^T F \mathbf{x} = 0 $$ 其中$\mathbf{x}$和$\mathbf{x}'$是两幅图像中的对应点。使用RANSAC估计基础矩阵的步骤与单应性矩阵类似,但模型不同。OpenCV中可以使用`cv2.findFundamentalMat`函数,并指定RANSAC方法。 **平面检测**(点云处理): 在三维点云中,平面模型可以表示为: $$ ax + by + cz + d = 0 $$ RANSAC步骤: 1. 随机选择3个点计算平面参数。 2. 计算所有点到平面的距离,统计内点(距离小于阈值)。 3. 选择内点最多的平面模型。 4. 用所有内点重新拟合平面(最小二乘法)。 这个应用在点云分割(如地面检测)中非常常见。 ### 总结 RANSAC算法因其鲁棒性,成为计算机视觉中几何模型估计的基石。它的核心思想是通过随机采样和迭代选择最优模型,能够有效处理数据中的大量离群点。在实际应用中,需要根据具体问题调整迭代次数、距离阈值等参数,以平衡计算效率和精度。 §§ 相关问题 §§ 1. RANSAC算法在估计基础矩阵时与估计单应性矩阵有什么不同? 2. 如何确定RANSAC算法的迭代次数? 3. RANSAC算法在处理高维模型(如三维平面)时有哪些挑战? 4. 除了RANSAC,还有哪些鲁棒估计方法可用于计算机视觉?</think>### 计算机视觉中RANSAC算法的经典应用案例 RANSAC(Random Sample Consensus)算法是计算机视觉中处理噪声数据和离群点的核心技术,特别适用于几何模型估计任务。以下是其在图像处理和几何模型估计中的经典应用案例: --- #### 1. **单应性矩阵估计(图像拼接)** 在图像拼接任务中,RANSAC用于从特征匹配点中鲁棒地估计单应性矩阵$H$: $$ \begin{bmatrix} x' \\ y' \\ 1 \end{bmatrix} = H \begin{bmatrix} x \\ y \\ 1 \end{bmatrix} ,\quad H = \begin{bmatrix} h_{11} & h_{12} & h_{13} \\ h_{21} & h_{22} & h_{23} \\ h_{31} & h_{32} & h_{33} \end{bmatrix} $$ **工作流程**: 1. 提取SIFT/SURF特征点并初步匹配 2. 随机选择4组匹配点(求解$H$的最小样本集) 3. 计算单应性矩阵$H$ 4. 用重投影误差$\|x' - Hx\|$评估内点 5. 保留内点最多的$H$,并用所有内点精炼估计 **代码实现**(OpenCV): ```python import cv2 import numpy as np # 读取图像并提取特征 img1 = cv2.imread('img1.jpg') img2 = cv2.imread('img2.jpg') sift = cv2.SIFT_create() kp1, des1 = sift.detectAndCompute(img1, None) kp2, des2 = sift.detectAndCompute(img2, None) # 特征匹配 bf = cv2.BFMatcher() matches = bf.knnMatch(des1, des2, k=2) # 筛选初步匹配 good = [] for m, n in matches: if m.distance < 0.75 * n.distance: good.append(m) # 转换为坐标点 src_pts = np.float32([kp1[m.queryIdx].pt for m in good]).reshape(-1, 1, 2) dst_pts = np.float32([kp2[m.trainIdx].pt for m in good]).reshape(-1, 1, 2) # RANSAC估计单应性矩阵 H, mask = cv2.findHomography(src_pts, dst_pts, cv2.RANSAC, 5.0) print(f"单应性矩阵:\n{H}") ``` 此方法能有效处理高达30%的误匹配率[^1],广泛应用于全景图像拼接和增强现实。 --- #### 2. **基础矩阵估计(立体视觉)** 在双目立体视觉中,RANSAC用于估计基础矩阵$F$,满足极线约束: $$ \mathbf{x}'^T F \mathbf{x} = 0 $$ **应用场景**: - 三维场景重建 - 相机运动估计 - 深度图生成 **实现关键**: ```python # 使用RANSAC估计基础矩阵 F, mask = cv2.findFundamentalMat(points1, points2, cv2.FM_RANSAC) inliers = points1[mask.ravel() == 1] # 提取内点 ``` 基础矩阵估计能鲁棒处理立体匹配中的误对应问题,是视觉里程计的核心组件[^2]。 --- #### 3. **平面检测(点云处理)** 在三维点云中,RANSAC可检测平面模型: $$ ax + by + cz + d = 0 $$ **算法流程**: 1. 随机选择3个点确定平面 2. 计算点云到平面的距离 3. 统计距离阈值内的内点 4. 迭代优化最大内点集 **伪代码**: ``` best_plane = None while iterations < max_iter: sample = random_select(points, 3) plane = fit_plane(sample) inliers = points_within_threshold(plane, points) if len(inliers) > best_inliers_count: best_plane = refine_plane(inliers) ``` 此方法在室内场景理解中用于检测墙面、地面等平面结构[^3]。 --- #### 4. **圆检测(工业检测)** RANSAC可鲁棒拟合圆模型: $$ (x - a)^2 + (y - b)^2 = r^2 $$ **优势**: - 处理部分遮挡的圆 - 抵抗噪声干扰 - 适应不完整边缘 **实现对比**: | 方法 | 噪声鲁棒性 | 计算效率 | 部分遮挡处理 | |------|------------|----------|--------------| | 霍夫变换 | 中 | 低 | 差 | | RANSAC | **高** | **高** | **优** | --- #### 5. **相机位姿估计(PnP问题)** 求解3D-2D点对应的相机位姿: $$ s \begin{bmatrix} u \\ v \\ 1 \end{bmatrix} = K [R|t] \begin{bmatrix} X \\ Y \\ Z \\ 1 \end{bmatrix} $$ **RANSAC流程**: 1. 随机选择3对3D-2D点 2. 用PnP算法求解位姿$[R|t]$ 3. 计算重投影误差 4. 统计内点数量 **OpenCV实现**: ```python retval, rvec, tvec, inliers = cv2.solvePnPRansac( objectPoints, imagePoints, cameraMatrix, distCoeffs, iterationsCount=1000, reprojectionError=8.0 ) ``` 此方法在增强现实和机器人定位中至关重要[^2]。 --- ### 算法性能优化策略 1. **自适应迭代次数**:根据内点比例动态调整迭代次数 $$ N = \frac{\log(1-p)}{\log(1-(1-\epsilon)^s)} $$ 其中$p$为置信度,$\epsilon$为离群率,$s$为样本大小 2. **PROSAC优化**:优先选择高质量样本加速收敛 3. **局部优化**:对候选模型进行局部迭代精炼 4. **并行化**:利用GPU加速采样评估过程 RANSAC通过其独特的随机采样机制,在存在大量离群点时仍能保持鲁棒性,使其成为计算机视觉几何估计的基石算法[^1][^3]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值