首页 > 编程语言 >RANSAC算法在图像拼接上的应用的实现

RANSAC算法在图像拼接上的应用的实现

时间:2022-12-25 13:00:24浏览次数:71  
标签:RANSAC Mat int cb 算法 拼接 m1 m2 CV

    关于算法原理请参考《基于SURF特征的图像与视频拼接技术的研究》。

一、问题提出


    


        为了提高识别的效率,前辈对算法进行了不懈的研究和提升,目前看来,用于图像拼接的RANSAC算法应该如下:

    


RANSAC算法在图像拼接上的应用的实现_ide




RANSAC算法在图像拼接上的应用的实现_特征点_02


及其改进算法:



RANSAC算法在图像拼接上的应用的实现_数据_03



RANSAC算法在图像拼接上的应用的实现_ide_04


二、算法实现


       a)数据准备


某大学图片,很明显的有视场变化



RANSAC算法在图像拼接上的应用的实现_数据_05

RANSAC算法在图像拼接上的应用的实现_数据_06


       b)算法分析,参考《


       现在思考,RANSAC算法其实是”基于统计的配对算法“,在进入RANSAC算法流程之前,已经计算出来图1和图2上的特征点值了。我们不仅需要根据这些点值去预测模型,而且需要去检测模型。这个模型也不是凭空随便找的,而是以”透视变换“作为基础的(关于透视变化请参考我前面的博文)。


        寻找的方法是首先找到符合某一模型的”内点集“,而后根据这一”内点集“,创建变换模型。


        寻找”内点集“的方法就是直接从现有的数据中找出一部分数据计算出一个模型,而后根据这个模型计算所有点的误差,迭代多次,得到最小误差的情况(和对应的点集),这个时候的模型就是接近正确的模型。


        这个误差的计算方法也是设计出来的(很可能还是统计值)。


        所以RANSAC很像是基于统计的一种计算可行解的模式。很多时候你不是需要从很多的数据中找出一个模型来吗?比如马尔萨斯模型?这个模型可能有函数,还有参数。你猜测的是函数,但是参数就可以通过这种模式来进行计算。


        如果有比较好的评价函数, 最后你还可以比较几种函数的选择。所以RANSAC就是一种单模型下基于离散数据计算模型的方法。(其实也是直观的、基础的、简洁的、有效的)


       这样我想起之前研究过的一种叫做”交叉检验“(cross check /cross validation)的东西。


       定义:在给定的建模样本中,拿出大部分样本进行模型建立,留小部分对建立的模型进行预报,并将这小部分进行误差预报,计算平方加和。(然后当然是选取误差最小的模型和)


       相比较RANSAC和CROSS VALIDATION,有两点不同。一个是模型的建立,RANSAC是选择很少量的数据建立模型(比如圆、线、透视变换),而后大量数据做验证;而CROSS需要较多的数据建立模型(比如MLP,神网),较少的数据进行验证(它也只有较少的数据了)


       c)解析


       为了实现图像的特征点的匹配,并且最后实现图像拼接,在OPENCV中实现了RANSAC算法及其改进算法


       c.1 调用方法

//-- Step 3: 匹配



FlannBasedMatcher matcher;//BFMatcher为强制匹配



std::vector< DMatch > matches;



matcher.match( descriptors_1, descriptors_2, matches );



//取最大最小距离



double max_dist = 0; double min_dist = 100;



for( int i = 0; i < descriptors_1.rows; i++ )



{



double dist = matches[i].distance;



if( dist < min_dist ) min_dist = dist;



if( dist > max_dist ) max_dist = dist;



}



std::vector< DMatch > good_matches;



for( int i = 0; i < descriptors_1.rows; i++ )



{



if( matches[i].distance <= 3*min_dist )//这里的阈值选择了3倍的min_dist



{



good_matches.push_back( matches[i]);



}



}



//画出"good match"



Mat img_matches;



drawMatches( img_1, keypoints_1, img_2, keypoints_2,



good_matches, img_matches, Scalar::all(-1), Scalar::all(-1),



vector<char>(), DrawMatchesFlags::NOT_DRAW_SINGLE_POINTS );



//-- Localize the object from img_1 in img_2



std::vector<Point2f> obj;



std::vector<Point2f> scene;



for( int i = 0; i < (int)good_matches.size(); i++ )



{



obj.push_back( keypoints_1[ good_matches[i].queryIdx ].pt );



scene.push_back( keypoints_2[ good_matches[i].trainIdx ].pt );



}



//直接调用ransac,计算单应矩阵

Mat H = findHomography( obj, scene, CV_RANSAC );


c.2 原理分析


opencv中已经封装的很好了,注意的是在实际使用中还可以对识别出来的结果做进一步的处理。


参数分为points1和points2,参数3是method,然后是threshold(一般为3),最后为mask


​​:     :Mat cv     :     :findHomography( InputArray _points1, InputArray _points2,     
int method, double ransacReprojThreshold, OutputArray _mask )
{
const double confidence = 0. 995;
const int maxIters = 2000;
const double defaultRANSACReprojThreshold = 3;
bool result = false;

Mat points1 = _points1.getMat(), points2 = _points2.getMat();
Mat src, dst, H, tempMask;
int npoints = - 1;

for( int i = 1; i < = 2; i
++ )

{
Mat & p = i == 1 ? points1
: points2;

Mat & m = i == 1 ? src
: dst;

npoints = p.checkVector( 2, - 1, false);
if( npoints < 0 )
{
npoints = p.checkVector( 3, - 1, false);
if( npoints < 0 )
CV_Error(Error : :StsBadArg, "The input arrays should be 2D or 3D point sets");
if( npoints == 0 )
return Mat();
convertPointsFromHomogeneous(p, p);
}
p.reshape( 2, npoints).convertTo(m, CV_32F);
}

CV_Assert( src.checkVector( 2) == dst.checkVector( 2) );

if( ransacReprojThreshold < = 0 )
ransacReprojThreshold = defaultRANSACReprojThreshold;

Ptr <PointSetRegistrator : :Callback > cb = makePtr
<HomographyEstimatorCallback
>();


if( method == 0 || npoints == 4 )

{
tempMask = Mat : :ones(npoints, 1, CV_8U);
result = cb - >runKernel(src, dst, H) > 0;

}
else if( method == RANSAC )
result = createRANSACPointSetRegistrator(cb, 4, ransacReprojThreshold, confidence, maxIters) - >run(src, dst, H, tempMask);
else if( method == LMEDS )
result = createLMeDSPointSetRegistrator(cb, 4, confidence, maxIters) - >run(src, dst, H, tempMask);
else

CV_Error(Error : :StsBadArg, "Unknown estimation method");

if( result && npoints > 4 )
{
compressPoints( src.ptr <Point2f >(), tempMask.ptr <uchar >(), 1, npoints );

npoints = compressPoints( dst.ptr <Point2f >(), tempMask.ptr <uchar >(),
1, npoints );

if( npoints > 0 )
{
Mat src1 = src.rowRange( 0, npoints);
Mat dst1 = dst.rowRange( 0, npoints);
src = src1;
dst = dst1;
if( method == RANSAC || method == LMEDS )
cb - >runKernel( src, dst, H );
Mat H8( 8, 1, CV_64F, H.ptr < double
>());

createLMSolver(makePtr <HomographyRefineCallback >(src, dst), 10) - >run(H8);

}
}

if( result )
{
if( _mask.needed() )
tempMask.copyTo(_mask);
}
else

H.release();

return H;
} ​​



       和RANSAC相关的是


​​<PointSetRegistrator       > createRANSACPointSetRegistrator(       const Ptr       <PointSetRegistrator       :       :Callback       >       & _cb,

int _modelPoints, double _threshold,
double _confidence, int _maxIters)
{
CV_Assert( !RANSACPointSetRegistrator_info_auto.name().empty() );
return Ptr <PointSetRegistrator >(
new RANSACPointSetRegistrator(_cb, _modelPoints, _threshold, _confidence, _maxIters));
} ​​


class RANSACPointSetRegistrator : public

​​
{

public
:

RANSACPointSetRegistrator( const Ptr <PointSetRegistrator : :Callback > & _cb
=Ptr
<PointSetRegistrator
:
:Callback
>(),

int _modelPoints = 0, double _threshold = 0, double _confidence = 0. 99, int _maxIters = 1000)
: cb(_cb), modelPoints(_modelPoints), threshold(_threshold), confidence(_confidence), maxIters(_maxIters)
{
checkPartialSubsets = true;
}

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;
}

bool getSubset( const Mat & m1, const Mat & m2,
Mat & ms1, Mat & ms2, RNG & rng,
int maxAttempts = 1000 ) const

{
cv : :AutoBuffer < int
> _idx(modelPoints);

int
* idx
= _idx;

int i = 0, j, k, iters = 0;
int esz1 = ( int)m1.elemSize(), esz2 = ( int)m2.elemSize();
int d1 = m1.channels() > 1 ? m1.channels() : m1.cols;

int d2 = m2.channels() > 1 ? m2.channels() : m2.cols;

int count = m1.checkVector(d1), count2 = m2.checkVector(d2);
const int *m1ptr = m1.ptr < int
>(),
*m2ptr
= m2.ptr
<
int
>();


ms1.create(modelPoints, 1, CV_MAKETYPE(m1.depth(), d1));
ms2.create(modelPoints, 1, CV_MAKETYPE(m2.depth(), d2));

int *ms1ptr = ms1.ptr < int
>(),
*ms2ptr
= ms2.ptr
<
int
>();


CV_Assert( count > = modelPoints && count == count2 );
CV_Assert( (esz1 % sizeof( int)) == 0 && (esz2 % sizeof( int)) == 0 );
esz1 /= sizeof( int);
esz2 /= sizeof( int);

for(; iters < maxAttempts; iters ++)
{
for( i = 0; i < modelPoints && iters < maxAttempts; )

{
int idx_i = 0;
for(;;)
{
idx_i = idx[i] = rng.uniform( 0, count);
for( j = 0; j < i; j ++ )
if( idx_i == idx[j] )
break;
if( j == i )
break;
}
for( k = 0; k < esz1; k ++ )
ms1ptr[i *esz1 + k] = m1ptr[idx_i *esz1 + k];

for( k = 0; k < esz2; k ++ )
ms2ptr[i *esz2 + k] = m2ptr[idx_i *esz2 + k];

if( checkPartialSubsets && !cb - >checkSubset( ms1, ms2, i +
1 ))

{
iters ++;
continue;
}
i ++;
}
if( !checkPartialSubsets && i == modelPoints && !cb
-
>checkSubset(ms1, ms2, i))

continue;
break;
}

return i == modelPoints && iters < maxAttempts;
}

bool run(InputArray _m1, InputArray _m2, OutputArray _model, OutputArray _mask) const

{
bool result = false;
Mat m1 = _m1.getMat(), m2 = _m2.getMat();
Mat err, mask, model, bestModel, ms1, ms2;

int iter, niters = MAX(maxIters, 1);
int d1 = m1.channels() > 1 ? m1.channels() : m1.cols;

int d2 = m2.channels() > 1 ? m2.channels() : m2.cols;

int count = m1.checkVector(d1), count2 = m2.checkVector(d2), maxGoodCount = 0;

RNG rng((uint64) - 1);

CV_Assert( cb );
CV_Assert( confidence > 0 && confidence < 1 );


CV_Assert( count > = 0 && count2 == count );

if( count < modelPoints )
return false;

Mat bestMask0, bestMask;

if( _mask.needed() )
{
_mask.create(count, 1, CV_8U, - 1, true);
bestMask0 = bestMask = _mask.getMat();
CV_Assert( (bestMask.cols == 1 || bestMask.rows == 1)
&& (
int)bestMask.total() == count );
}
else

{
bestMask.create(count, 1, CV_8U);
bestMask0 = bestMask;
}

if( count == modelPoints )
{
if( cb - >runKernel(m1, m2, bestModel) < = 0 )

return false;
bestModel.copyTo(_model);
bestMask.setTo(Scalar : :all( 1));
return true;
}

for( iter = 0; iter < niters; iter ++ )
{
int i, goodCount, nmodels;
if( count > modelPoints )
{
bool found = getSubset( m1, m2, ms1, ms2, rng );
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 );

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 );
}
}
}

if( maxGoodCount > 0 )
{
if( bestMask.data != bestMask0.data )
{
if( bestMask.size() == bestMask0.size() )
bestMask.copyTo(bestMask0);
else

transpose(bestMask, bestMask0);
}
bestModel.copyTo(_model);
result = true;
}
else

_model.release();

return result;
}

void setCallback( const Ptr <PointSetRegistrator : :Callback > & _cb) { cb
= _cb; }


AlgorithmInfo * info() const;

Ptr <PointSetRegistrator : :Callback > cb;
int modelPoints;
bool checkPartialSubsets;
double threshold;
double confidence;
int maxIters;
}; ​​


       d)如何复用OPENCV中的实现于数据的统计研究


       


四、反思小结


       即使是这样一个原理来说比较清楚的算法,如果想从零开始进行实现,还是很不容易的。所以积累算法资源、提高算法实现能力,可能都是很重要的事情。


和cross validation算法比较,

标签:RANSAC,Mat,int,cb,算法,拼接,m1,m2,CV
From: https://blog.51cto.com/jsxyhelu2017/5968125

相关文章