OpenCV中的solvePnPRansac函数和findHomography函数都具有RANSAC特性,该特性使算法对少量的错误数据鲁棒。
这两个函数利用RANSACPointSetRegistrator
类实现RANSAC算法,但这个类并没有对外开放,因此只能通过阅读OpenCV源代码学习RANSAC算法的实现和使用。
类的实现在ptsetreg.cpp中,可通过调用precomp.hpp文件中的createRANSACPointSetRegistrator
函数使用。此外,该文件还提供了createLMeDSPointSetRegistrator
函数调用最小中值算法。
关于RANSAC的介绍详见这篇博客
createRANSACPointSetRegistrator
函数的原型为
Ptr createRANSACPointSetRegistrator(const Ptr& cb,
int modelPoints, double threshold,
double confidence = 0.99, int maxIters = 1000);
参数cb
类型为PointSetRegistrator::Callback
,是要传入的被包装的估计算法对象,被RANSAC类调用以进行试探的估计过程;参数modelPoints
设定计算模型需要的最少数据点;参数threshold
设定区分内点和外点的阈值;参数confidence
设定返回正确值的概率(决定抽样次数);参数maxIters
设定最大走样次数。后三个参数与solvePnPRansac
函数中的意义相同。
其中PointSetRegistrator::Callback
类的声明为
class CV_EXPORTS Callback
{
public:
virtual ~Callback() {}
virtual int runKernel(InputArray m1, InputArray m2, OutputArray model) const = 0;
virtual void computeError(InputArray m1, InputArray m2, InputArray model, OutputArray err) const = 0;
virtual bool checkSubset(InputArray, InputArray, int) const { return true; }
};
使用时,需要继承该类,并至少实现其中的前两个函数。
runKernel
函数需要根据输入的数据集m1 m2
,计算并输出模型model
,函数返回输出模型的个数,一般都返回1。左右数据均按行排列。为什么这里有两个输入参数呢,因为OpenCV中的RANSAC算法最初设计是用来求解2D-3D变换的,所以这两个参数分别对应源数据点集和目标数据点集。即便某算法只需要一个参数m1
,也必须传入同样大小的m2
以避免运行错误。
computerError
函数需要根据输入的数据集m1 m2
,模型model
,计算并输出各数据点的残差err
。输出残差为列向量,每行对应一个输入数据点, 由于RANSACPointSetRegistratorerr
内部的实现,err的类型必须为float否则出现指针错误。
checkSubset
(非必须)函数需要根据输入的数据,初步验证其是否为可行的子集。若不可行,返回false,这样可以提前剔除一些错误的子集。
由于OpenCV中并没有开放RANSACPointSetRegistrator
的头文件,故需要自己编写以下头文件包含在工程中,方可使用createRANSACPointSetRegistrator
函数
//cvRANSAC.h
#include
#include
namespace cv
{
class CV_EXPORTS PointSetRegistrator : public Algorithm
{
public:
class CV_EXPORTS Callback
{
public:
virtual ~Callback() {}
virtual int runKernel(InputArray m1, InputArray m2, OutputArray model) const = 0;
virtual void computeError(InputArray m1, InputArray m2, InputArray model, OutputArray err) const = 0;
virtual bool checkSubset(InputArray, InputArray, int) const { return true; }
};
virtual void setCallback(const Ptr& cb) = 0;
virtual bool run(InputArray m1, InputArray m2, OutputArray model, OutputArray mask) const = 0;
};
CV_EXPORTS Ptr createRANSACPointSetRegistrator(const Ptr& cb,
int modelPoints, double threshold,
double confidence = 0.99, int maxIters = 1000);
CV_EXPORTS Ptr createLMeDSPointSetRegistrator(const Ptr& cb,
int modelPoints, double confidence = 0.99, int maxIters = 1000);
}
使用时,调用bool result = createRANSACPointSetRegistrator(cb, modelPoints, threshold, confidence, maxIters)->run(m1, m2, model, mask);
即可运行RANSAC算法,返回是否成功。
###RANSACPointSetRegistrator类的实现
RANSACPointSetRegistrator
类实现了RANSAC算法,主要由以下成员函数组成
//在给定数据集``m1 m2``中,利用随机数发生器``rng``,生成样本集``ms1 ms2``
bool getSubset(const Mat& m1, const Mat& m2, Mat& ms1, Mat& ms2, RNG& rng, int maxAttempts = 1000) const
//根据已拟合出的模型model,给定的thresh阈值,调用computeError函数计算残差err,并返回内外点掩码mask向量,返回内点的个数
int findInliers(const Mat& m1, const Mat& m2, const Mat& model, Mat& err, Mat& mask, double thresh) const
//根据期望正确概率和内点比例,确定迭代次数
int RANSACUpdateNumIters(double p, double ep, int modelPoints, int maxIters);
//运行RANSAC估计 先调用getSubset采样样本,然后调用runKernel执行拟合,调用findInliers判断内点数量,取最大,并调用RANSACUpdateNumIters更新采样次数。到达采样次数后,返回内点数最多的
bool run(InputArray _m1, InputArray _m2, OutputArray _model, OutputArray _mask) const