OpenCV中关于二维仿射变换函数estimateAffinePartial2D的源码分析

这篇具有很好参考价值的文章主要介绍了OpenCV中关于二维仿射变换函数estimateAffinePartial2D的源码分析。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

二维仿射变换及其接口

关于二维仿射变化的介绍: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;
}

代码分析

数据准备工作

  1. 先看第一段程序,将函数接收的参数_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。否则,它将返回矩阵中元素的数量。注意,一个元素可以有多个通道。

  1. 第二段程序则是负责将fromto的矩阵类型变为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();
}
  1. 接下来这一段有注释:转成 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;
}
  1. 以下代码自然是创建内点矩阵
Mat inliers;
if(_inliers.needed())
{
    _inliers.create(count, 1, CV_8U, -1, true);
    inliers = _inliers.getMat();
}

运行RANSAC算法

  1. 然后注意到下面这一行代码,创建了一个智能指针cb
Ptr<PointSetRegistrator::Callback> cb = makePtr<AffinePartial2DEstimatorCallback>();

这一行有点麻烦,接下来追根溯源一下,这一行代码中涉及两个类型:PointSetRegistrator::CallbackAffinePartial2DEstimatorCallback。其中,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>
  1. 智能指针cb会作为参数传入函数createRANSACPointSetRegistrator中,即若是以RANSAC算法解算变换矩阵,则进入代码。这一行会将所有的数据全部代入,包括:cbransacReprojThresholdconfidencemaxItersfromtoHinliers
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

首先分析一下参数,_m1from_m2to,也就是求解fromto的变换矩阵,_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

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

根据内点计算变换矩阵

  1. 再回到最初的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模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【OpenCV • c++】图像几何变换 | 图像仿射变换

    🚀 个人简介:CSDN「 博客新星 」TOP 10 , C/C++ 领域新星创作者 💟 作    者: 锡兰_CC ❣️ 📝 专    栏: 【OpenCV • c++】计算机视觉 🌈 若有帮助,还请 关注➕点赞➕收藏 ,不行的话我再努努力💪💪💪

    2024年02月16日
    浏览(52)
  • opencv仿射变换

    #include opencv2/opencv.hpp /* 功能:对一系列坐标点进行平移仿射变换 参数: srcPoints:输入点坐标 dstPoints:变换后的点坐标 x:x方向平移的距离 y:y方向平移的距离 */ void tranlatePoints(std::vectorcv::Point2f srcPoints, std::vectorcv::Point2f dstPoints,double x,double y) {     cv::Mat affineMatrix = (cv::Mat_double

    2024年01月16日
    浏览(41)
  • OpenCV(十一):图像仿射变换

    目录 1.图像仿射变换介绍  仿射变换: 仿射变换矩阵: 仿射变换公式: 2.仿射变换函数 仿射变换函数:warpAffine() 图像旋转:getRotationMatrix2D() 计算仿射变换矩阵:getAffineTransform()  3.demo 1.图像仿射变换介绍  仿射变换:        仿射变换是由平移、缩放、旋转、翻转和错切组

    2024年02月10日
    浏览(56)
  • 【OpenCV】-仿射变换

    1、认识仿射变换 仿射变换(Affine Map)又称仿射映射,是指在几何中,一个向量空间进行一次线性变换并接上一个平移,变换为另一个向量空间的过程。保持二维图形之间的相对位置保持不变,平行线依然是平行线,且直线上的点的位置顺序不变。 一个任意的仿射变换都可以

    2024年02月08日
    浏览(66)
  • 仿射变换代码opencv

    顺时针为正,左上角为默认坐标原点 CV_INSTRUMENT_REGION() 是一个OpenCV宏,用于在代码中进行性能分析和测量。它可以用于标记代码的某个区域,以便在运行时获取该区域的性能指标。(不太明白,先放一下)  

    2024年01月17日
    浏览(95)
  • 【C++ OpenCV】图像变换:连接、尺寸、翻转、旋转、仿射变换

    目录 图像缩放变换 图像翻转 图像拼接 纵向拼接 横向拼接 图像插值原理 作用 单线性插值 双线性插值的公式 双线性插值的例子 双线性插值的直观展示 意义 仿射变换 图像旋转 实操 一、实现图像旋转 二、根据定义的三个点实现仿射变换,并且求取仿射变换矩阵 源码 src -

    2024年01月18日
    浏览(196)
  • 【OpenCV】图像变换(缩放、平移、旋转、仿射)

    图像变换是指通过对图像进行缩放、平移、旋转、仿射、透视等变换来改变图像的形状和大小。在本篇博客中,我们将详细介绍OpenCV中的图像变换函数,并提供示例代码以帮助读者更好地理解这些函数的使用方法。 缩放变换是指通过改变图像的大小来改变图像的形状。在Op

    2024年02月07日
    浏览(61)
  • OPENCV C++(六)canny边缘检测+仿射变换+透射变换

    图像的缩放  输入图像 输出图像 大小变换 canny边缘算子的使用  必须先转化为灰度图,作为输入 超过100是真的边缘 低于40是确定不是边缘 在中间若连接边缘 则为边缘  普通旋转缩放变换(仿射变换) 获取仿射变换的矩阵 中心点 旋转角度 大小是否变换 -10是顺时针转 输入

    2024年02月14日
    浏览(51)
  • OpenCV图像的仿射变换、旋转和缩放

    以下是对代码的逐行解释:

    2024年02月13日
    浏览(53)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包