本文翻译自维基百科,英文原文地址是:http://en.wikipedia.org/wiki/ransac
RANSAC是“RANdom SAmple Consensus(随机抽样一致)”的缩写。它可以从一组包含“局外点”的观测数据集中,通过迭代方式估计数学模型的参数。它是一种不确定的算法——它有一定的概率得出一个合理的结果;为了提高概率必须提高迭代次数。该算法最早由Fischler和Bolles于1981年提出。他们使用该算法来解决3D重建中的位置确定问题(Location Determination Problem, LDP)。目前RANSAC算法被广泛用于计算机视觉领域中图像匹配、全景拼接等问题,比如从数对匹配的特征点中求得两幅图片之间的射影变换矩阵,OPENCV实现stitching类时即使用了该算法。
RANSAC算法与最小二乘法的不同之处主要有以下两点:
1. 最小二乘法总是使用所有的数据点来估计参数,而RANSAC算法仅使用局内点;
2. 最小二乘法是一种确定性算法,给定数据集,每一次所得到的模型参数都是相同的;而RANSAC算法是一种随机算法,受迭代次数等的影响,每一次得到的参数一般都不相同。
3. 一般而言,RANSAC算法先根据一定的准则筛选出局内点和局外点,然后对得到的局内点进行拟合,拟合方法可以是最小二乘法,也可以是其他优化算法,从这个角度来说,RANSAC算法是最小二乘法的扩展。
RANSAC的基本假设是:
(1)数据由“局内点”组成,例如:数据的分布可以用一些模型参数来解释;
(2)“局外点”是不能适应该模型的数据;
(3)除此之外的数据属于噪声。
局外点产生的原因有:噪声的极值;错误的测量方法;对数据的错误假设。
RANSAC也做了以下假设:给定一组(通常很小的)局内点,存在一个可以估计模型参数的过程;而该模型能够解释或者适用于局内点。
算法的求解过程如下:
- 首先从数据集中随机选出一组局内点(其数目要保证能够求解出模型的所有参数),计算出一套模型参数。
- 用得到的模型去测试其他所有的数据点,如果某点的误差在设定的误差阈值之内,就判定其为局内点,否则为局外点,只保留目前为止局内点数目最多的模型,将其记录为最佳模型。
- 重复执行1,2步足够的次数(即达到预设的迭代次数)后,使用最佳模型对应的局内点来最终求解模型参数,该步可以使用最小二乘法等优化算法。
- 最后可以通过估计局内点与模型的错误率来评估模型。
本文内容
1 示例
2 概述
3 part1 算法
3 part2算法实例
二、概述
RANSAC算法的输入是一组观测数据,一个可以解释或者适应于观测数据的参数化模型,一些可信的参数。
RANSAC通过反复选择数据中的一组随机子集来达成目标。被选取的子集被假设为局内点,并用下述方法进行验证:
1.有一个模型适应于假设的局内点,即所有的未知参数都能从假设的局内点计算得出。
2.用1中得到的模型去测试所有的其它数据,如果某个点适用于估计的模型,认为它也是局内点。
3.如果有足够多的点被归类为假设的局内点,那么估计的模型就足够合理。
4.然后,用所有假设的局内点去重新估计模型,因为它仅仅被初始的假设局内点估计过。
5.最后,通过估计局内点与模型的错误率来评估模型。
这个过程被重复执行固定的次数,每次产生的模型要么因为局内点太少而被舍弃,要么因为比现有的模型更好而被选用。
三、part one 算法
伪码形式的算法如下所示:
输入:
data —— 一组观测数据
model —— 适应于数据的模型
n —— 适用于模型的最少数据个数
k —— 算法的迭代次数
t —— 用于决定数据是否适应于模型的阀值
d —— 判定模型是否适用于数据集的数据数目
输出:
best_model —— 跟数据最匹配的模型参数(如果没有找到好的模型,返回null)
best_consensus_set —— 估计出模型的数据点
best_error —— 跟数据相关的估计出的模型错误
iterations = 0
best_model = null
best_consensus_set = null
best_error = 无穷大
while ( iterations < k )
maybe_inliers = 从数据集中随机选择n个点
maybe_model = 适合于maybe_inliers的模型参数
consensus_set = maybe_inliers
for ( 每个数据集中不属于maybe_inliers的点 )
if ( 如果点适合于maybe_model,且错误小于t )
将点添加到consensus_set
if ( consensus_set中的元素数目大于d )
已经找到了好的模型,现在测试该模型到底有多好
better_model = 适合于consensus_set中所有点的模型参数
this_error = better_model究竟如何适合这些点的度量
if ( this_error < best_error )
我们发现了比以前好的模型,保存该模型直到更好的模型出现
best_model = better_model
best_consensus_set = consensus_set
best_error = this_error
增加迭代次数
返回 best_model, best_consensus_set, best_error
RANSAC算法的可能变化包括以下几种:
(1)如果发现了一种足够好的模型(该模型有足够小的错误率),则跳出主循环。这样可能会节约计算额外参数的时间。
(2)直接从maybe_model计算this_error,而不从consensus_set重新估计模型。这样可能会节约比较两种模型错误的时间,但可能会对噪声更敏感。
三、part two算法实例
Ziv Yaniv以最简单的直线拟合为例,写过一版RANSAC算法的C++实现,没有依赖任何其他库,但该版代码对C++的依赖较重,使用了C++的一些高级数据结构,比如vector, set,在遍历时还使用了递归算法。详细代码可参见RANSAC代码示例
部分代码及相关注释如下:
//顶层变量定义
vector<double> lineParameters;//存储模型参数
LineParamEstimator lpEstimator(0.5);//误差阈值设置为0.5
vector pointData;//数据点集
int numForEstimate=2;//进行一次参数估计所需的最小样本点数,因为是直线拟合,所以可以直接设为2
//顶层函数的定义
/**
* Estimate the model parameters using the maximal consensus set by going over ALL possible
* subsets (brute force approach).
* Given: n - data.size()
* k - numForEstimate
* We go over all n choose k subsets n!
* ------------
* (n-k)! * k!
* @param parameters A vector which will contain the estimated parameters.
* If there is an error in the input then this vector will be empty.
* Errors are: 1. Less data objects than required for an exact fit.
* @param paramEstimator An object which can estimate the desired parameters using either an exact fit or a
* least squares fit.
* @param data The input from which the parameters will be estimated.
* @param numForEstimate The number of data objects required for an exact fit.
* @return Returns the percentage of data used in the least squares estimate.
*
* NOTE: This method should be used only when n choose k is small (i.e. k or (n-k) are approximatly equal to n)
*
*/
//T是数据的类型,该例子中是二维坐标点,作者自己定义了一个类Point2D来表示;S是参数的类型,此处为双精度double型
template <class T, class S>
double Ransac::compute(std::vector ¶meters,ParameterEsitmator *paramEstimator,
std::vector &data,
int numForEstimate)
{
std::vector leastSquaresEstimateData;
int numDataObjects = data.size();//数据集的大小,100
int numVotesForBest = -1;//最佳模型所对应的局内点数目初始化为-1
int *arr = new int[numForEstimate];//要进行一次计算所需的样本数:2
short *curVotes = new short[numDataObjects]; //one if data[i] agrees with the current model, otherwise zero
short *bestVotes = new short[numDataObjects]; //one if data[i] agrees with the best model, otherwise zero
//there are less data objects than the minimum required for an exact fit
if (numDataObjects < numForEstimate)
return 0;
//computeAllChoices函数寻找局内点数目最多的模型,并将局内点信息存储在bestVotes数组中,作为最终的模型
computeAllChoices(paramEstimator, data, numForEstimate,
bestVotes, curVotes, numVotesForBest, 0, data.size(), numForEstimate, 0, arr);
//将所有的局内点取出,存储在leastSquareEstimateData数组中
for (int j = 0; jif (bestVotes[j])
leastSquaresEstimateData.push_back(&(data[j]));
}
//利用所有局内点进行最小二乘参数估计,估计的结果存储在parameters数组中
paramEstimator->leastSquaresEstimate(leastSquaresEstimateData, parameters);
//释放动态数组
delete[] arr;
delete[] bestVotes;
delete[] curVotes;
//返回值为局内点占所有数据点的比值
return (double)leastSquaresEstimateData.size() / (double)numDataObjects;
}
//寻找最佳模型的函数定义如下:
//使用递归算法来对数据集进行n!/((n-k)!k!)次遍历
template<class T, class S>
void Ransac::computeAllChoices(ParameterEsitmator *paramEstimator, std::vector &data, int numForEstimate,
short *bestVotes, short *curVotes, int &numVotesForBest, int startIndex, int n, int k, int arrIndex, int *arr)
{
//we have a new choice of indexes
//每次k从2开始递减到0的时候,表示新取了2个数据点,可以进行一次参数估计
if (k == 0) {
estimate(paramEstimator, data, numForEstimate, bestVotes, curVotes, numVotesForBest, arr);
return;
}
//continue to recursivly generate the choice of indexes
int endIndex = n - k;
for (int i = startIndex; i <= endIndex; i++) {
arr[arrIndex] = i;
computeAllChoices(paramEstimator, data, numForEstimate, bestVotes, curVotes, numVotesForBest,
i + 1, n, k - 1, arrIndex + 1, arr);//递归调用
}
}
//进行参数估计,并根据情况更新当前最佳模型的函数,最佳模型的局内点信息存储在数组bestVotes中,而局内点的数目则是由numVotesForBest存储
//arr数组存储的是本轮两个样本点在data中的索引值
template<class T, class S>
void Ransac::estimate(ParameterEsitmator *paramEstimator, std::vector &data, int numForEstimate,
short *bestVotes, short *curVotes, int &numVotesForBest, int *arr)
{
std::vector exactEstimateData;
std::vector exactEstimateParameters;
int numDataObjects;
int numVotesForCur;//initalize with -1 so that the first computation will be set to best
int j;
numDataObjects = data.size();
memset(curVotes, '\0', numDataObjects * sizeof(short));//数组中的点全部初始化为局外点
numVotesForCur = 0;
for (j = 0; j// 取出两个数据的地址
paramEstimator->estimate(exactEstimateData, exactEstimateParameters);//用取出的两点来拟合出一组参数
for (j = 0; j//依次判断是否为局内点
if (paramEstimator->agree(exactEstimateParameters, data[j])) {
curVotes[j] = 1;
numVotesForCur++;
}
}
//如果当前模型inlier的数目大于目前最佳模型inlier的数目,则取代目前最佳模型,并更新信息
if (numVotesForCur > numVotesForBest) {
numVotesForBest = numVotesForCur;
memcpy(bestVotes, curVotes, numDataObjects * sizeof(short));
}
}
六、应用
RANSAC算法经常用于计算机视觉,例如同时求解相关问题与估计立体摄像机的基础矩阵。
七、参考文献
八、外部链接
由Fischer和Bolles在1981年的文章[1]中首先提出,简要的说经典RANSAC的目标是不断尝试不同的目标空间参数,使得目标函数 C 最大化的过程。这个过程是随机(Random)、数据驱动(data-driven)的过程。通过反复的随机选择数据集的子空间来产生一个模型估计,然后利用估计出来的模型,使用数据集剩余的点进行测试,获得一个得分,最终返回一个得分最高的模型估计作为整个数据集的模型。
在经典的RANSAC流程中,目标函数C 可以被看作:在第k次迭代过程中,在当前变换参数作用下,数据集中满足变换参数的点的个数,也就是在当前变换条件下类内点的个数,而RANSAC就是最大化 C 的的过程。而判断当前某个点是否为类内需要一个阈值t。
这里除了置信度外,m 为子集大小,ε 为类内点在中的比例,其中置信度一般设置为[0.95, 0.99]的范围内。然而在一般情况下,ε 显然是未知的,因此 ε 可以取最坏条件下类内点的比例,或者在初始状态下设置为最坏条件下的比例,然后随着迭代次数,不断更新为当前最大的类内点比例。
另外一种循环终止条件可以将选取的子集看做为“全部是类内点”或“不全部是类内点”这两种结果的二项分布,而前者的概率为。对于 p 足够小的情况下,可以将其看作为一种泊松分布,因此,在 k 次循环中,有 n个“子集全部是类内点”的概率可以表达为:
λ 表示在 k 次循环中,“子集全都是类内点”的选取次数的期望。例如在RANSAC中,我们希望在这k次循环中所选子集“没有一个全是类内点”的概率小于某个置信度,即:,以置信度为95%为例,λ约等于3,表示在95%置信度下,在 k 次循环中,平局可以选到3次“好”的子集。
经典RANSAC算法的流程如下图所示:
经典RANSAC有以下三个主要的局限性:
(1) 效率:经典方法效率与子集大小、类内点比例以及数据集大小有关,因此在某些场景下效率较低。
(2) 精度:经典方法计算参数时选取最小子集是从效率的角度考虑,往往得到的是非最佳参数,在应用产参数 之前还需要再经过细化处理。
(3) 退化:经典方法的目标函数求取最大化的过程基于一个假设:“选取的最小子集中如果有类外点,那么在这种情况下估计的参数获得的目标函数(数据集中点的个数)往往较少“但这种情况在退化发生时有可能是不对的。
针对经典方法的这几项局限性,有很多改进,在这里提出了一种全局RANSAC(Universal-RANSAC)的结构图,每一种改进方法都可以看做是这种USAC的特例,如下图所示。
输入数据集,含有 N 个点,在这一步中,SCRNMSAC[3],用一个一致性滤波器对初始的数据集进行滤波减少数量,然后将数据根据梯度排序。
对进行采样时,经典算法采用完全随机的方式,这种方式的前提是我们对数据的情况完全不知道,在实际应用中,很多情况对数据的先验知识是了解的,这对减少采样次数,尤其是类内点比例较低的数据集,有很大帮助,以下是几种在最小集采样当中对经典算法进行改进的方法。
Stage 1.a 采样
a. 中随机选取一个点 x ,设定一个半径 r,以 x 为中心 r 为半径建立超球面;
b. 超球面内包裹的点少于最小数据集的个数?返回 a,否则c
c. 均匀的从球体内取点,直至满足最小集中的个数。
这种方法对高维、类内点比例低的数据集效果明显,但是容易产生退化,且对于距离都很近的数据集效果差。
Stage 1.b 采样验证
Stage 2.a 模型计算
在这一步骤根据上一步选取的最小集计算参数,获得模型。
Stage 2.b 模型验证
还是利用先验知识,比如点集与圆形匹配,验证时候没必要将数据集中所有的点进行验证,而只是在得到模型(圆)的一个半径范围左右验证即可。
Stage 3.a 可能性验证
Stage 3.b 退化验证
数据退化的意思是无法提供足够的限制条件产生唯一解。传统RANSAC即没有这种安全的保障。
含有噪声的数据集有两个重要特点:1,即使子集中全都是类内点,产生的模型也并不一定适用于数据集中所有的类内点,这个现象增加了迭代的次数;2,最终收敛的RANSAC结果可能受到噪声未完全清理的影响,并不是全局最优的结果。
第一个影响往往被忽略,因为虽然增加了迭代次数,但是仍然返回的是一个准确的模型。而第二种影响就要增加一种模型细化的后处理。
Lo-RANSAC,局部最优RANSAC。在迭代过程中,出现当前最优解,进行Lo-RANSAC。一种方法是从返回结果的类内点中再进行采样计算模型,设置一个固定的迭代次数(10-20次)然后选取最优的局部结果作为改进的结果,另一种方法是设置一个优化参数K(2~3),选取结果中判断阈值(t)小于等于Kt 的结果作为优化结果,K 减小,直至减小至 t 终止。
思想与Lo-RANSAC一致,但是更为直接,因为初始的RANSAC结果产生自含有噪声的数据集,因此这个错误“传播”到了最终的模型,协方差可以看做是估计两个数据集关系是一种不确定的信息(而如上所述,判断阈值的计算是固定的)。具体方法参见文献[13]。
最终选取的结果如下图所示:
stage1: 最小集采样方法采用2.2.2节中的PROSAC。
stage3: 模型(参数)验证采用2.4.3的SPRT测试。
stage4: 产生最终模型,采用2.5.1介绍的Lo-RANSAC。
论文翻译自:文献[0]。
----------------------------参考文献---------------------------
[0] Raguram R, Chum O, Pollefeys M, et al. USAC: A Universal Framework for Random Sample Consensus[J]. Pattern Analysis & Machine Intelligence IEEE Transactions on, 2013, 35(8):2022-2038.
[1] M.A. Fischler and R.C. Bolles, “Random Sample Consensus: A Paradigm for Model Fitting with Applications to Image Analysis and Automated Cartography,” Comm. ACM, vol. 24, no. 6, pp. 381- 395,1981.
[2] R.I. Hartley and A. Zisserman, Multiple View Geometry in Computer Vision. Cambridge Univ. Press, 2000
[3] T. Sattler, B. Leibe, and L. Kobbelt, “SCRAMSAC: Improving RANSAC’s Efficiency with a Spatial Consistency Filter,” Proc. 12th IEEE Int’l Conf. Computer Vision, 2009.
[4] D.R. Myatt, P.H.S. Torr, S.J. Nasuto, J.M. Bishop, and R. Craddock,“NAPSAC: High Noise, High Dimensional Robust Estimation,”Proc. British Machine Vision Conf., pp. 458-467, 2002.
[5] O. Chum and J. Matas, “Matching with PROSAC—Progressive Sample Consensus,” Proc. IEEE Conf. Computer Vision and Pattern Recognition, 2005.
[6] K. Ni, H. Jin, and F. Dellaert, “GroupSAC: Efficient Consensus in the Presence of Groupings,” Proc. 12th IEEE Int’l Conf. Computer Vision, Oct. 2009.
[7] D. Capel, “An Effective Bail-Out Test for RANSAC Consensus Scoring,” Proc. British Machine Vision Conf., 2005.
[8] O. Chum and J. Matas, “Optimal Randomized RANSAC,” IEEE Trans. Pattern Analysis and Machine Intelligence, vol. 30, no. 8,pp. 1472-1482, Aug. 2008.
[9] J. Matas and O. Chum, “Randomized RANSAC with Sequential Probability Ratio Test,” Proc. 10th IEEE Int’l Conf. Computer Vision,vol. 2, pp. 1727-1732, Oct. 2005.
[10] D. Niste´r, “Preemptive RANSAC for Live Structure and Motion Estimation,” Proc. Ninth IEEE Int’l Conf. Computer Vision, 2003.
[11] R. Raguram, J.-M. Frahm, and M. Pollefeys, “A Comparative Analysis of RANSAC Techniques Leading to Adaptive Real-Time Random Sample Consensus,” Proc. European Conf. Computer Vision, pp. 500- 513. 2008,
[12] O. Chum, J. Matas, and J. Kittler, “Locally Optimized RANSAC,”Proc. DAGM-Symp. Pattern Recognition, pp. 236-243, 2003.
[13] R. Raguram, J.-M Frahm, and M. Pollefeys, “Exploiting Uncertainty in Random Sample Consensus,” Proc. 12th IEEE Int’l Conf. Computer Vision, Oct. 2009.