二维仿射变换及其接口
关于二维仿射变化的介绍:https://www.cnblogs.com/yinheyi/p/6148886.html
OpenCV3.4.1
中提供的接口为:estimateAffinePartial2D(),用于计算两个2D点集之间具有4个自由度的最优有限仿射变换。
其函数具体实现位于:./opencv/sources/modules/calib3d/src/ptsetreg.cpp
函数原型:
cv::Mat cv::estimateAffinePartial2D (
InputArray from,
InputArray to,
OutputArray inliers = noArray(),
int method = RANSAC,
double ransacReprojThreshold = 3,
size_t maxIters = 2000,
double confidence = 0.99,
size_t refineIters = 10
)
函数具体实现:
Mat estimateAffinePartial2D(InputArray _from, InputArray _to, OutputArray _inliers,
const int method, const double ransacReprojThreshold,
const size_t maxIters, const double confidence,
const size_t refineIters)
{
Mat from = _from.getMat(), to = _to.getMat();
const int count = from.checkVector(2);
bool result = false;
Mat H;
CV_Assert( count >= 0 && to.checkVector(2) == count );
if (from.type() != CV_32FC2 || to.type() != CV_32FC2)
{
Mat tmp1, tmp2;
from.convertTo(tmp1, CV_32FC2);
from = tmp1;
to.convertTo(tmp2, CV_32FC2);
to = tmp2;
}
else
{
// avoid changing of inputs in compressElems() call
from = from.clone();
to = to.clone();
}
// convert to N x 1 vectors
from = from.reshape(2, count);
to = to.reshape(2, count);
Mat inliers;
if(_inliers.needed())
{
_inliers.create(count, 1, CV_8U, -1, true);
inliers = _inliers.getMat();
}
// run robust estimation
Ptr<PointSetRegistrator::Callback> cb = makePtr<AffinePartial2DEstimatorCallback>();
if( method == RANSAC )
result = createRANSACPointSetRegistrator(cb, 2, ransacReprojThreshold, confidence, static_cast<int>(maxIters))->run(from, to, H, inliers);
else if( method == LMEDS )
result = createLMeDSPointSetRegistrator(cb, 2, confidence, static_cast<int>(maxIters))->run(from, to, H, inliers);
else
CV_Error(Error::StsBadArg, "Unknown or unsupported robust estimation method");
if(result && count > 2 && refineIters)
{
// reorder to start with inliers
compressElems(from.ptr<Point2f>(), inliers.ptr<uchar>(), 1, count);
int inliers_count = compressElems(to.ptr<Point2f>(), inliers.ptr<uchar>(), 1, count);
if(inliers_count > 0)
{
Mat src = from.rowRange(0, inliers_count);
Mat dst = to.rowRange(0, inliers_count);
// H is
// a -b tx
// b a ty
// Hvec model for LevMarq is
// (a, b, tx, ty)
double *Hptr = H.ptr<double>();
double Hvec_buf[4] = {Hptr[0], Hptr[3], Hptr[2], Hptr[5]};
Mat Hvec (4, 1, CV_64F, Hvec_buf);
createLMSolver(makePtr<AffinePartial2DRefineCallback>(src, dst), static_cast<int>(refineIters))->run(Hvec);
// update H with refined parameters
Hptr[0] = Hptr[4] = Hvec_buf[0];
Hptr[1] = -Hvec_buf[1];
Hptr[2] = Hvec_buf[2];
Hptr[3] = Hvec_buf[1];
Hptr[5] = Hvec_buf[3];
}
}
if (!result)
{
H.release();
if(_inliers.needed())
{
inliers = Mat::zeros(count, 1, CV_8U);
inliers.copyTo(_inliers);
}
}
return H;
}
代码分析
数据准备工作
- 先看第一段程序,将函数接收的参数
_from
与_to
变为Mat
数据类型
Mat from = _from.getMat(), to = _to.getMat();
const int count = from.checkVector(2);
bool result = false;
Mat H;
CV_Assert( count >= 0 && to.checkVector(2) == count );
checkVector():返回符合要求的矩阵中的元素个数
int cv::Mat::checkVector(
int elemChannels,
int depth = -1,
bool requireContinuous = true
)const
参数
- elemChannels:矩阵应具有的通道数或列数。对于二维矩阵,当矩阵只有1列时,它应该有elemChannels通道;当矩阵只有1个通道时,它应该有elemChannels列。对于三维矩阵,它应该只有一个通道。此外,如果平面的数目不是一,则每个平面内的行数必须是1;如果每个平面内的行数不是1,则平面数必须是1。
- depth:矩阵应有的深度
返回值:如果不满足要求,则为-1。否则,它将返回矩阵中元素的数量。注意,一个元素可以有多个通道。
- 第二段程序则是负责将
from
与to
的矩阵类型变为CV_32FC2
if (from.type() != CV_32FC2 || to.type() != CV_32FC2)
{
Mat tmp1, tmp2;
from.convertTo(tmp1, CV_32FC2);
from = tmp1;
to.convertTo(tmp2, CV_32FC2);
to = tmp2;
}
else
{
// avoid changing of inputs in compressElems() call
from = from.clone();
to = to.clone();
}
- 接下来这一段有注释:转成 N × 1 N\times1 N×1的向量
// convert to N x 1 vectors
from = from.reshape(2, count);
to = to.reshape(2, count);
reshape()函数原型
Mat cv::Mat::reshape(
int cn,
int rows = 0
)const
具体实现为:
Mat Mat::reshape(int new_cn, int new_rows) const
{
int cn = channels();
Mat hdr = *this;
if( dims > 2 )
{
if( new_rows == 0 && new_cn != 0 && size[dims-1]*cn % new_cn == 0 )
{
hdr.flags = (hdr.flags & ~CV_MAT_CN_MASK) | ((new_cn-1) << CV_CN_SHIFT);
hdr.step[dims-1] = CV_ELEM_SIZE(hdr.flags);
hdr.size[dims-1] = hdr.size[dims-1]*cn / new_cn;
return hdr;
}
if( new_rows > 0 )
{
int sz[] = { new_rows, (int)(total()/new_rows) };
return reshape(new_cn, 2, sz);
}
}
CV_Assert( dims <= 2 );
if( new_cn == 0 )
new_cn = cn;
int total_width = cols * cn;
if( (new_cn > total_width || total_width % new_cn != 0) && new_rows == 0 )
new_rows = rows * total_width / new_cn;
if( new_rows != 0 && new_rows != rows )
{
int total_size = total_width * rows;
if( !isContinuous() )
CV_Error( CV_BadStep,
"The matrix is not continuous, thus its number of rows can not be changed" );
if( (unsigned)new_rows > (unsigned)total_size )
CV_Error( CV_StsOutOfRange, "Bad new number of rows" );
total_width = total_size / new_rows;
if( total_width * new_rows != total_size )
CV_Error( CV_StsBadArg, "The total number of matrix elements "
"is not divisible by the new number of rows" );
hdr.rows = new_rows;
hdr.step[0] = total_width * elemSize1();
}
int new_width = total_width / new_cn;
if( new_width * new_cn != total_width )
CV_Error( CV_BadNumChannels,
"The total width is not divisible by the new number of channels" );
hdr.cols = new_width;
hdr.flags = (hdr.flags & ~CV_MAT_CN_MASK) | ((new_cn-1) << CV_CN_SHIFT);
hdr.step[1] = CV_ELEM_SIZE(hdr.flags);
return hdr;
}
- 以下代码自然是创建内点矩阵
Mat inliers;
if(_inliers.needed())
{
_inliers.create(count, 1, CV_8U, -1, true);
inliers = _inliers.getMat();
}
运行RANSAC算法
- 然后注意到下面这一行代码,创建了一个智能指针
cb
。
Ptr<PointSetRegistrator::Callback> cb = makePtr<AffinePartial2DEstimatorCallback>();
这一行有点麻烦,接下来追根溯源一下,这一行代码中涉及两个类型:PointSetRegistrator::Callback
、AffinePartial2DEstimatorCallback
。其中,PointSetRegistrator
是一个继承自Algorithm
的虚基类,定义在/opencv/sources/modules/calib3d/src/precomp.hpp
中。
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<PointSetRegistrator::Callback>& cb) = 0;
virtual bool run(InputArray m1, InputArray m2, OutputArray model, OutputArray mask) const = 0;
};
而AffinePartial2DEstimatorCallback
类继承自Affine2DEstimatorCallback
,具体实现如下
/*
* Compute
* x c -s X t1
* = * +
* y s c Y t2
*
* - every element in _m1 contains (X,Y), which are called source points
* - every element in _m2 contains (x,y), which are called destination points
* - _model is of size 2x3, which contains
* c -s t1
* s c t2
*/
class AffinePartial2DEstimatorCallback : public Affine2DEstimatorCallback
{
public:
int runKernel( InputArray _m1, InputArray _m2, OutputArray _model ) const CV_OVERRIDE
{
Mat m1 = _m1.getMat(), m2 = _m2.getMat();
const Point2f* from = m1.ptr<Point2f>();
const Point2f* to = m2.ptr<Point2f>();
_model.create(2, 3, CV_64F);
Mat M_mat = _model.getMat();
double *M = M_mat.ptr<double>();
// we need only 2 points to estimate transform
double x1 = from[0].x;
double y1 = from[0].y;
double x2 = from[1].x;
double y2 = from[1].y;
double X1 = to[0].x;
double Y1 = to[0].y;
double X2 = to[1].x;
double Y2 = to[1].y;
/*
we are solving AS = B
| x1 -y1 1 0 |
| y1 x1 0 1 |
A = | x2 -y2 1 0 |
| y2 x2 0 1 |
B = (X1, Y1, X2, Y2).t()
we solve that analytically
*/
double d = 1./((x1-x2)*(x1-x2) + (y1-y2)*(y1-y2));
// solution vector
double S0 = d * ( (X1-X2)*(x1-x2) + (Y1-Y2)*(y1-y2) );
double S1 = d * ( (Y1-Y2)*(x1-x2) - (X1-X2)*(y1-y2) );
double S2 = d * ( (Y1-Y2)*(x1*y2 - x2*y1) - (X1*y2 - X2*y1)*(y1-y2) - (X1*x2 - X2*x1)*(x1-x2) );
double S3 = d * (-(X1-X2)*(x1*y2 - x2*y1) - (Y1*x2 - Y2*x1)*(x1-x2) - (Y1*y2 - Y2*y1)*(y1-y2) );
// set model, rotation part is antisymmetric
M[0] = M[4] = S0;
M[1] = -S1;
M[2] = S2;
M[3] = S1;
M[5] = S3;
return 1;
}
};
AffinePartial2DEstimatorCallback
的父类Affine2DEstimatorCallback
继承自PointSetRegistrator::Callback
,具体实现如下。
/*
* Compute
* x a b X c
* = * +
* y d e Y f
*
* - every element in _m1 contains (X,Y), which are called source points
* - every element in _m2 contains (x,y), which are called destination points
* - _model is of size 2x3, which contains
* a b c
* d e f
*/
class Affine2DEstimatorCallback : public PointSetRegistrator::Callback
{
public:
int runKernel( InputArray _m1, InputArray _m2, OutputArray _model ) const CV_OVERRIDE
{
Mat m1 = _m1.getMat(), m2 = _m2.getMat();
const Point2f* from = m1.ptr<Point2f>();
const Point2f* to = m2.ptr<Point2f>();
_model.create(2, 3, CV_64F);
Mat M_mat = _model.getMat();
double *M = M_mat.ptr<double>();
// we need 3 points to estimate affine transform
double x1 = from[0].x;
double y1 = from[0].y;
double x2 = from[1].x;
double y2 = from[1].y;
double x3 = from[2].x;
double y3 = from[2].y;
double X1 = to[0].x;
double Y1 = to[0].y;
double X2 = to[1].x;
double Y2 = to[1].y;
double X3 = to[2].x;
double Y3 = to[2].y;
/*
We want to solve AX = B
| x1 y1 1 0 0 0 |
| 0 0 0 x1 y1 1 |
| x2 y2 1 0 0 0 |
A = | 0 0 0 x2 y2 1 |
| x3 y3 1 0 0 0 |
| 0 0 0 x3 y3 1 |
B = (X1, Y1, X2, Y2, X3, Y3).t()
X = (a, b, c, d, e, f).t()
As the estimate of (a, b, c) only depends on the Xi, and (d, e, f) only
depends on the Yi, we do the *trick* to solve each one analytically.
| X1 | | x1 y1 1 | | a |
| X2 | = | x2 y2 1 | * | b |
| X3 | | x3 y3 1 | | c |
| Y1 | | x1 y1 1 | | d |
| Y2 | = | x2 y2 1 | * | e |
| Y3 | | x3 y3 1 | | f |
*/
double d = 1. / ( x1*(y2-y3) + x2*(y3-y1) + x3*(y1-y2) );
M[0] = d * ( X1*(y2-y3) + X2*(y3-y1) + X3*(y1-y2) );
M[1] = d * ( X1*(x3-x2) + X2*(x1-x3) + X3*(x2-x1) );
M[2] = d * ( X1*(x2*y3 - x3*y2) + X2*(x3*y1 - x1*y3) + X3*(x1*y2 - x2*y1) );
M[3] = d * ( Y1*(y2-y3) + Y2*(y3-y1) + Y3*(y1-y2) );
M[4] = d * ( Y1*(x3-x2) + Y2*(x1-x3) + Y3*(x2-x1) );
M[5] = d * ( Y1*(x2*y3 - x3*y2) + Y2*(x3*y1 - x1*y3) + Y3*(x1*y2 - x2*y1) );
return 1;
}
void computeError( InputArray _m1, InputArray _m2, InputArray _model, OutputArray _err ) const CV_OVERRIDE
{
Mat m1 = _m1.getMat(), m2 = _m2.getMat(), model = _model.getMat();
const Point2f* from = m1.ptr<Point2f>();
const Point2f* to = m2.ptr<Point2f>();
const double* F = model.ptr<double>();
int count = m1.checkVector(2);
CV_Assert( count > 0 );
_err.create(count, 1, CV_32F);
Mat err = _err.getMat();
float* errptr = err.ptr<float>();
// transform matrix to floats
float F0 = (float)F[0], F1 = (float)F[1], F2 = (float)F[2];
float F3 = (float)F[3], F4 = (float)F[4], F5 = (float)F[5];
for(int i = 0; i < count; i++ )
{
const Point2f& f = from[i];
const Point2f& t = to[i];
float a = F0*f.x + F1*f.y + F2 - t.x;
float b = F3*f.x + F4*f.y + F5 - t.y;
errptr[i] = a*a + b*b;
}
}
bool checkSubset( InputArray _ms1, InputArray _ms2, int count ) const CV_OVERRIDE
{
Mat ms1 = _ms1.getMat();
Mat ms2 = _ms2.getMat();
// check collinearity and also check that points are too close
return !haveCollinearPoints(ms1, count) && !haveCollinearPoints(ms2, count);
}
};
因此,原来AffinePartial2DEstimatorCallback
也是自PointSetRegistrator::Callback
的子类派生而来的。
makePtr()
template<typename T>
Ptr<T> makePtr()
{
return Ptr<T>(new T());
}
Ptr:智能指针类型
template<typename _Tp >
using cv::Ptr = typedef std::shared_ptr<_Tp>
- 智能指针
cb
会作为参数传入函数createRANSACPointSetRegistrator
中,即若是以RANSAC
算法解算变换矩阵,则进入代码。这一行会将所有的数据全部代入,包括:cb
、ransacReprojThreshold
、confidence
、maxIters
、from
、to
、H
、inliers
。
result = createRANSACPointSetRegistrator(cb, 2, ransacReprojThreshold, confidence, static_cast<int>(maxIters))->run(from, to, H, inliers);
首先通过createRANSACPointSetRegistrator创建一个RANSAC
点集的注册器,其本质就是创建一个指向RANSACPointSetRegistrator
的匿名指针。
Ptr<PointSetRegistrator> createRANSACPointSetRegistrator(const Ptr<PointSetRegistrator::Callback>& _cb,
int _modelPoints, double _threshold,
double _confidence, int _maxIters)
{
return Ptr<PointSetRegistrator>(
new RANSACPointSetRegistrator(_cb, _modelPoints, _threshold, _confidence, _maxIters));
}
终于到了重要的一个类实现:RANSACPointSetRegistrator
,而RANSACPointSetRegistrator
正是继承自PointSetRegistrator
class RANSACPointSetRegistrator : public PointSetRegistrator
{
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) {}
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.data();
const int d1 = m1.channels() > 1 ? m1.channels() : m1.cols;
const int d2 = m2.channels() > 1 ? m2.channels() : m2.cols;
int esz1 = (int)m1.elemSize1() * d1;
int esz2 = (int)m2.elemSize1() * d2;
CV_Assert((esz1 % sizeof(int)) == 0 && (esz2 % sizeof(int)) == 0);
esz1 /= sizeof(int);
esz2 /= sizeof(int);
const int count = m1.checkVector(d1);
const int count2 = m2.checkVector(d2);
CV_Assert(count >= modelPoints && count == count2);
const int *m1ptr = m1.ptr<int>();
const 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>();
int *ms2ptr = ms2.ptr<int>();
for( int iters = 0; iters < maxAttempts; ++iters )
{
int i;
for( i = 0; i < modelPoints; ++i )
{
int idx_i;
for ( idx_i = rng.uniform(0, count);
std::find(idx, idx + i, idx_i) != idx + i;
idx_i = rng.uniform(0, count) )
{}
idx[i] = idx_i;
for( int k = 0; k < esz1; ++k )
ms1ptr[i*esz1 + k] = m1ptr[idx_i*esz1 + k];
for( int k = 0; k < esz2; ++k )
ms2ptr[i*esz2 + k] = m2ptr[idx_i*esz2 + k];
}
if( cb->checkSubset(ms1, ms2, i) )
return true;
}
return false;
}
bool run(InputArray _m1, InputArray _m2, OutputArray _model, OutputArray _mask) const CV_OVERRIDE
{
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, 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 );
}
}
}
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) CV_OVERRIDE { cb = _cb; }
Ptr<PointSetRegistrator::Callback> cb;
int modelPoints;
double threshold;
double confidence;
int maxIters;
};
因此,最终是通过RANSACPointSetRegistrator
类指针调用run
方法,从而筛选出数据的内点。而我们需要学习的正是这个run
方法,揭开它神秘的面纱。
bool run(InputArray _m1, InputArray _m2, OutputArray _model, OutputArray _mask) const CV_OVERRIDE
首先分析一下参数,_m1
是from
,_m2
是to
,也就是求解from
到to
的变换矩阵,_model
为变换矩阵H
,_mask
为内点inliers
(1)数据准备
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;
}
(2)count
等于modelPoints
,直接通过cb
调用runKernel
。文章来源:https://www.toymoban.com/news/detail-480846.html
if( count == modelPoints )
{
if( cb->runKernel(m1, m2, bestModel) <= 0 )
return false;
bestModel.copyTo(_model);
bestMask.setTo(Scalar::all(1));
return true;
}
(3)迭代计算文章来源地址https://www.toymoban.com/news/detail-480846.html
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 );
}
}
}
根据内点计算变换矩阵
- 再回到最初的
estimateAffinePartial2D
函数中,会发现后续正是利用内点来计算二维仿射变换矩阵。即如下代码
if(result && count > 2 && refineIters)
{
// reorder to start with inliers
compressElems(from.ptr<Point2f>(), inliers.ptr<uchar>(), 1, count);
int inliers_count = compressElems(to.ptr<Point2f>(), inliers.ptr<uchar>(), 1, count);
if(inliers_count > 0)
{
Mat src = from.rowRange(0, inliers_count);
Mat dst = to.rowRange(0, inliers_count);
// H is
// a -b tx
// b a ty
// Hvec model for LevMarq is
// (a, b, tx, ty)
double *Hptr = H.ptr<double>();
double Hvec_buf[4] = {Hptr[0], Hptr[3], Hptr[2], Hptr[5]};
Mat Hvec (4, 1, CV_64F, Hvec_buf);
createLMSolver(makePtr<AffinePartial2DRefineCallback>(src, dst), static_cast<int>(refineIters))->run(Hvec);
// update H with refined parameters
Hptr[0] = Hptr[4] = Hvec_buf[0];
Hptr[1] = -Hvec_buf[1];
Hptr[2] = Hvec_buf[2];
Hptr[3] = Hvec_buf[1];
Hptr[5] = Hvec_buf[3];
}
}
到了这里,关于OpenCV中关于二维仿射变换函数estimateAffinePartial2D的源码分析的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!