最近,在OpenCV上开发目标识别程序的时候遇到一个问题就是:在源图像和场景图像中分别做特征点检测之后,会发现有误匹配的情况出现,经过在上网查资料得知可以用随机抽样一致算法(简称:RANSAC)解决误匹配的问题。可是其中的数学背景对于一些视觉领域的新手而言看的让人有点搞不透,在这里我想整理下网上的一些资料和自己的理解,仅供有需要的人参考。
在讲解RANSAC算法之前,我们有必要了解几个数学概念。
1、齐次坐标
齐次坐标就是将一个原本是n维的向量用一个n+1维向量来表示,是指一个用于投影几何里的坐标系统。这里我以最常见的欧式平面上的笛卡尔坐标为基础讲解。给定欧氏平面上的一点(x,y),对任意非零实数 Z,三元组(xZ,yZ,Z)即称之为该点的齐次坐标。依据定义,将齐次坐标内的数值乘上同一个非零实数,可得到同一点的另一组齐次坐标。例如,笛卡尔坐标上的点 (1,2) 在齐次坐标中即可标示成 (1,2,1) 或 (2,4,2)。原来的笛卡儿坐标可透过将前两个数值除以第三个数值取回。因此,与笛卡儿坐标不同,一个点可以有无限多个齐次坐标表示法。这些点是“齐次的”,因为他们代表了笛卡尔坐标系里面的同一个点。换句话说,齐次坐标有规模不变性。在图像中主要进行仿射(线性)几何变换。
2、单应性矩阵
在计算机视觉中:平面的单应性被定义为从一个平面到另一个平面的投影映射。比如,一个二维平面上的点映射到摄像机成像仪上的映射就是平面单应性的例子。
两个不同视角的图像上的点对的齐次坐标可以用一个射影变换(projective transformation)表述,即:
x1 =H*x2
说明:X1表示原图像上的点,X2表示新图像平面的点,H表示变换矩阵。
3、代价函数
https://blog.csdn.net/quiet_girl/article/details/68544273
现在正式开始阐述对RANSAC算法的理解:
RANSAC的基本思想主要是随机选取几对匹配点,前提是每对匹配点之间的连线不共线,然后利用单应性矩阵的性质求出单应性矩阵H
图中原图像齐次坐标为(x,y),场景图像坐标为(x',y').求出H之后,利用这个模型测试所有数据,并计算满足这个模型的数据点与投影误差,此处用代价函数计算投影误差。符合模型的数据记为内点,否则为外点。如果此模型为最优模型,则代价函数最小。
基本实现步骤为:
1. 随机从数据集中随机抽出4个样本数据 ,计算出变换矩阵H,记为模型M;
2. 计算数据集中所有数据与模型M的投影误差,若误差小于阈值,加入内点集 I ;
3. 如果当前内点集 I 元素个数大于最优内点集 I_best , 则更新 I_best = I,同时更新迭代次数k ;
4. 如果迭代次数大于k,则退出 ; 否则迭代次数加1,并重复上述步骤;
例程:
//保存匹配点序号
vector queryIdxs(good_matches.size()), trainIdxs(good_matches.size());
for (size_t i = 0; i < good_matches.size(); i++)
{
queryIdxs[i] = good_matches[i].queryIdx;
trainIdxs[i] = good_matches[i].trainIdx;
}
//求透视变换矩阵
Mat H12;
vectorPoints1,Points2;
//从匹配成功相对较好的匹配对中获取关键点
for (unsigned int j = 0; j < good_matches.size(); j++)
{
Points1.push_back(keypoints_1[good_matches[j].queryIdx].pt);
Points2.push_back(keypoints_2[good_matches[j].trainIdx].pt);
}
int ransacReprojThreshold = 5; //拒绝阙值
H12 = findHomography(Mat(Points1), Mat(Points2), CV_RANSAC, ransacReprojThreshold);
vector matchesMask(good_matches.size(), 0); //用于存储内点
Mat Points1t;
perspectiveTransform(Mat(Points1), Points1t,H12);
for (size_t i1 = 0; i1 < Points1.size(); i1++)
{
if (norm(Points2[i1] - Points1t.at((int)i1, 0)) <= ransacReprojThreshold)
{
matchesMask[i1] = 1;
}
}
Mat img_matches2; //滤除‘外点’后
drawMatches(srcImage1, keypoints_1, srcImage2, keypoints_2,
good_matches, img_matches2, Scalar::all(-1), Scalar::all(-1), matchesMask);
下面贴出在使用RANSAC算法前后的效果图:
如果想更进一步了解RANSAC的数学原理以及实现过程,建议看一下OpenCV库中相关的源代码和API函数说明。