您的位置:首页 > 其它

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

2014-04-18 21:37 513 查看


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

转自http://blog.sina.com.cn/s/blog_4298002e01013w9a.html

在新版的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一文中所述方法提取特征点并匹配,然后用上面的程序计算基本矩阵,并显示内点匹配。如图:

初始匹配:





RANSAC过后的内点匹配:



内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: