RANSAC的经典步骤是
1. 随机采样
2. 计算模型参数
3. 阈值分类inlier outlier
4. 迭代2. 3.步直至获得符合阈值的最优解
5. 精确优化模型参数(LM算法迭代优化)
RANSAC的函数接口 参照opencv来说主要需要3-4个参数(第四个不是必须的)
1. 误差阈值ransacThreshold:区分inlier和outliner的依据
2. 置信度confidence:设置之后代表RANSAC采样n次过程中会出现(至少一次)采样点数据集中的点都为内点的概率
这个值设置的太大,会增加采样次数。太小,会使结果不太理想。一般取0.95-0.99
3. 最大采样迭代次数maxIters:为了防止一直在采样计算
4.*最大精细迭代次数refineIters:在采样之后,选取最优解。可以增加精确优化,比如使用LM算法获得更优解。
以上4个参数,有三个是经验值。其中最大采样迭代次数maxIters是可以有数值解的。
在RANSAC计算中,在已经采样计算之后,可以根据inlier num 和 outlier num求得一个更准确的maxIters
这里先列出一个等式:
1 − p = (1 − wn)k (1)
其中
p 表示置信度confidence
w 表示数据集中outlier占的比例
n 表示采样点数
k 表示需要迭代采样的最少次数
1 − wn 表示采样一次,n个点中至少有一个outlier的概率
(1 − wn)k 表示采样k次,n个点中至少有一个outlier的概率
因为p为采样k次,能有至少一次n个点都是inlier的概率
所以(1 − p)和(1 − wn)k相等时,k 为需要迭代采样的最少次数
下面是k的解析解:
(2)
关于这个部分的实现 opencv中代码如下
for( iter = 0; iter < niters; iter++ )
{
int i, nmodels;
if( count > modelPoints )
{
bool found = getSubset( m1, m2, ms1, ms2, rng, 10000 );
if( !found )
{
if( iter == 0 )
return false;
break;
}
}
nmodels = cb->runKernel( ms1, ms2, model );
if( nmodels <= 0 )
continue;
CV_Assert( model.rows % nmodels == 0 );
Size modelSize(model.cols, model.rows/nmodels);
for( i = 0; i < nmodels; i++ )
{
Mat model_i = model.rowRange( i*modelSize.height, (i+1)*modelSize.height );
int goodCount = findInliers( m1, m2, model_i, err, mask, threshold );
if( goodCount > MAX(maxGoodCount, modelPoints-1) )
{
std::swap(mask, bestMask);
model_i.copyTo(bestModel);
maxGoodCount = goodCount;
niters = RANSACUpdateNumIters( confidence, (double)(count - goodCount)/count, modelPoints, niters );
}
}
}
可以看到在采样迭代的环节中,最大迭代次数niters是动态更新的。在循环内,会被修改。
int RANSACUpdateNumIters( double p, double ep, int modelPoints, int maxIters )
{
if( modelPoints <= 0 )
CV_Error( Error::StsOutOfRange, "the number of model points should be positive" );
p = MAX(p, 0.);
p = MIN(p, 1.);
ep = MAX(ep, 0.);
ep = MIN(ep, 1.);
// avoid inf's & nan's
double num = MAX(1. - p, DBL_MIN);
double denom = 1. - std::pow(1. - ep, modelPoints);
if( denom < DBL_MIN )
return 0;
num = std::log(num);
denom = std::log(denom);
return denom >= 0 || -num >= maxIters*(-denom) ? maxIters : cvRound(num/denom);
}
这里的实现也是按照上面的公式(2)书写的。
末尾增加了一个功能,当求得的迭代次数比之前的大,就更新迭代次数。
LMedS的经典步骤是
1. 随机采样
2. 计算模型参数
3. 计算相对模型的点集偏差err,并求出偏差中值Med(err)
4. 迭代2. 3.步直至获得符合阈值的最优解:Med(err)最小
5. 精确优化模型参数(LM算法迭代优化)
LMedS的函数接口 参照opencv来说主要需要2-3个参数(第三个不是必须的)
1. 置信度confidence:设置之后代表RANSAC采样n次过程中会出现(至少一次)采样点数据集中的点都为内点的概率
这个值设置的太大,会增加采样次数。太小,会使结果不太理想。一般取0.95-0.99
2. 最大采样迭代次数maxIters:为了防止一直在采样计算
3.*最大精细迭代次数refineIters:在采样之后,选取最优解。可以增加精确优化,比如使用LM算法获得更优解。
注意: 相对于RANSAC,LMedS有一个优点:不需要指定 - 误差阈值ransacThreshold:区分inlier和outliner的依据
RANSAC的阈值在具有物理意义或者几何意义的时候比较容易确定,
但是当阈值不具有这些特征的时候,就成了一个不太好调整的参数了。
这时LMedS可以自适应迭代获得最优解。
这里要引用 Robust Regression Methods for Computer Vision_A Review【1】
这里先列出一个等式:
其中
n 表示点集的个数
p 表示计算模型一次采样的点个数
ri2 表示误差
med(ri2 ) 表示误差中值
筛选条件为
关于这个部分的实现 opencv中代码如下
double sigma = 2.5*1.4826*(1 + 5./(count - modelPoints))*std::sqrt(minMedian);
sigma = MAX( sigma, 0.001 );
count = findInliers( m1, m2, bestModel, err, mask, sigma );
findInliers的计算如下
int findInliers( const Mat& m1, const Mat& m2, const Mat& model, Mat& err, Mat& mask, double thresh ) const
{
cb->computeError( m1, m2, model, err );
mask.create(err.size(), CV_8U);
CV_Assert( err.isContinuous() && err.type() == CV_32F && mask.isContinuous() && mask.type() == CV_8U);
const float* errptr = err.ptr<float>();
uchar* maskptr = mask.ptr<uchar>();
float t = (float)(thresh*thresh);
int i, n = (int)err.total(), nz = 0;
for( i = 0; i < n; i++ )
{
int f = errptr[i] <= t;
maskptr[i] = (uchar)f;
nz += f;
}
return nz;
}
可以看到 里面用的是二阶范数,sigma输入到thresh参数入口之后会和err的计算一样,使用二阶范数来衡量
由于LMedS会需要对整个点集的err求中值,当点集很大的时候,求中值的过程会很消耗时间
文【1】中提到可以使用Monte Carlo方法,利用局部采样来模拟全局。
这里的式子和上面的式(1)是一样的 :1 − p = (1 − wn)k
使用多次采样,得到至少一次采样全是inlier的最少采样次数k。详细过程可以看论文。
【1】Robust Regression Methods for Computer Vision_A Review