CVPR:A Two-point Method for PTZ Camera Calibration in Sports的C++程序分析(6)

本文深入分析了CVPR论文中提出的PTZ摄像机标定方法的C++实现过程。重点介绍了如何通过迭代减少假设集,并最终优化摄像机参数。详细解释了利用Eigen库与Levenberg-Marquardt算法进行非线性优化的过程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

CVPR:A Two-point Method for PTZ Camera Calibration in Sports的C++程序分析(6)

好的,今天就来分析一下这个循环的第一部分,

                // number of inliers is larger than minimum configure
                if (hypotheses[i].inlier_indices_.size() > 4) {
                    vector<Eigen::Vector2d> inlier_image_pts;
                    vector<Eigen::Vector2d> inlier_pan_tilt;
                    for (int j = 0; j < hypotheses[i].inlier_indices_.size(); j++) {
                        int index = hypotheses[i].inlier_indices_[j];
                        int pan_tilt_index = hypotheses[i].inlier_candidate_pan_tilt_indices_[j];
                        inlier_image_pts.push_back(image_points[index]);
                        inlier_pan_tilt.push_back(candidate_pan_tilt[index][pan_tilt_index]);
                    }

来看这个循环的第一个if语句,只有在hypotheses[i].inlier_indices_.size() >= 5 的时候,才会refine the camera

这是什么意思呢?第一次迭代,剩下512个假设集,第二次则有256,第三次则有128,第四次则有64,第五次则有32。也就是说,当假设集只有32个时候,开始refine the camera

好啦,经过层层筛选,假设集只有32个啦,现在要收集这32个假设集的信息:candidate_pan_tilt以及image_points的信息,它们分别用inlier_image_pts和inlier_pan_tilt收集

然后进行优化处理,

 Eigen::Vector3d opt_ptz;
 double reprojection_error = cvx_pgl::optimizePTZ(pp, inlier_pan_tilt, inlier_image_pts, hypotheses[i].ptz_, opt_ptz); 

需要搞清楚optimizePTZ函数的功能

    double optimizePTZ(const Eigen::Vector2d & pp,
                     const vector<Eigen::Vector2d> & pan_tilt,
                     const vector<Eigen::Vector2d> & image_point,
                     const Vector3d& init_ptz,
                     Vector3d & opt_ptz)
    {
        assert(pan_tilt.size() == image_point.size());
        
        // optimize pan, tilt and focal length
        SphericalPanTiltFunctor opt_functor(pp, pan_tilt, image_point);
        Eigen::NumericalDiff<SphericalPanTiltFunctor> dif_functor(opt_functor);
        Eigen::LevenbergMarquardt<Eigen::NumericalDiff<SphericalPanTiltFunctor>, double> lm(dif_functor);
        lm.parameters.ftol = 1e-6;
        lm.parameters.xtol = 1e-6;
        lm.parameters.maxfev = 500;
        
        Eigen::VectorXd x(3);
        x[0] = init_ptz[0];
        x[1] = init_ptz[1];
        x[2] = init_ptz[2];
        
        double error = opt_functor.meanReprojectionError(x);
        //printf("reprojection error after: %lf\n", error);
        Eigen::LevenbergMarquardtSpace::Status status = lm.minimize(x);
        //printf("LMQ status %d\n", status);
        opt_ptz[0] = x[0];
        opt_ptz[1] = x[1];
        opt_ptz[2] = x[2];
        error = opt_functor.meanReprojectionError(x);
        //printf("reprojection error before: %lf\n", error);
        
        return error;
    }

这段代码不难理解,但是有三点需要注意,

(1)SphericalPanTiltFunctor类变量opt_functor

(2)Eigen自带的LM算法求解器lm,看起来似乎非常好用,其运用格式值得借鉴(很多的非线性优化求解器都需要定义仿函数,这一点是值得学习的,比如ceres这个第三方库)

(3)meanReprojectionError需要查看

首先,要了解一下SphericalPanTiltFunctor这个类,其中functor指仿函数,百度上有这样的定义:

在我们写代码时有时会发现有些功能实现的代码,会不断的在不同的成员函数中用到,但是又不好将这些代码独立出来成为一个类的一个成员函数。但是又很想复用这些代码。写一个公共的函数,可以,这是一个解决方法,不过函数用到的一些变量,就可能成为公共的全局变量,再说为了复用这么一片代码,就要单立出一个函数,也不是很好维护。这时就可以用仿函数了,写一个简单类,除了那些维护一个类的成员函数外,就只是实现一个operator(),在类实例化时,就将要用的,非参数的元素传入类中。这样就免去了对一些公共变量的全局化的维护了。又可以使那些代码独立出来,以便下次复用。而且这些仿函数,还可以用关联,聚合,依赖的类之间的关系,与用到他们的类组合在一起,这样有利于资源的管理(这点可能是它相对于函数最显著的优点了)。

    struct SphericalPanTiltFunctor
    {
        typedef double Scalar;
        
        typedef Eigen::VectorXd InputType;
        typedef Eigen::VectorXd ValueType;
        typedef Eigen::Matrix<Scalar,Eigen::Dynamic,Eigen::Dynamic> JacobianType;
        
        enum {
            InputsAtCompileTime = Eigen::Dynamic,
            ValuesAtCompileTime = Eigen::Dynamic
        };
        
        const Eigen::Vector2d pp_;
        const vector<Eigen::Vector2d> pan_tilt_; // spherical space
        const vector<Eigen::Vector2d> image_point_;
        
        int m_inputs;
        int m_values;
        
        SphericalPanTiltFunctor(const Eigen::Vector2d & pp,
                                const vector<Eigen::Vector2d> & pan_tilt,
                                const vector<Eigen::Vector2d> & image_point):
        pp_(pp), pan_tilt_(pan_tilt), image_point_(image_point)
        {
            assert(pan_tilt_.size() == image_point_.size());
            m_inputs = 3;
            m_values = (int)pan_tilt.size() * 2;
            assert(m_values >= 4);
        }
        
        
        int operator()(const Eigen::VectorXd &x, Eigen::VectorXd &fx) const
        {
            double pan  = x[0];
            double tilt = x[1];
            double fl = x[2];
            Eigen::Vector3d ptz(pan, tilt, fl);
            
            // loop each points
            for (int i = 0; i<pan_tilt_.size(); i++) {
                Vector2d cur_pan_tilt = pan_tilt_[i];
                // projection from spherical space to image space
                Vector2d projected_pan_tilt = panTilt2Point(pp_, ptz, cur_pan_tilt);
                fx[2*i + 0] = image_point_[i].x() - projected_pan_tilt.x();
                fx[2*i + 1] = image_point_[i].y() - projected_pan_tilt.y();
            }
            return 0;
        }
        
        double meanReprojectionError(const Eigen::VectorXd& x)
        {
            double pan  = x[0];
            double tilt = x[1];
            double fl = x[2];
            Eigen::Vector3d ptz(pan, tilt, fl);
            
            // loop each points
            double avg_dist = 0.0;
            for (int i = 0; i<pan_tilt_.size(); i++) {
                Vector2d cur_pan_tilt = pan_tilt_[i];
                // projection from spherical space to image space
                Vector2d projected_pan_tilt = panTilt2Point(pp_, ptz, cur_pan_tilt);
                double dist = (image_point_[i] - projected_pan_tilt).norm();
                avg_dist += dist;
            }
            return avg_dist/pan_tilt_.size();
        }

        
        int inputs() const { return m_inputs; }// inputs is the dimension of x.
        int values() const { return m_values; } // "values" is the number of f_i and
    };

第一次接触到仿函数,不打算做全面的分析,主要看其中的几点:

(1)对于用Eigen调用LM优化算法,这一套仿函数的格式需要注意

(2)SphericalPanTiltFunctor的初始化

(3)SphericalPanTiltFunctor的operator(),即误差函数

(4)SphericalPanTiltFunctor的meanReprojectionError(),即平均重投影误差

(5)仿函数建立后进行自动求导以及建立LM求解器的操作,

        SphericalPanTiltFunctor opt_functor(pp, pan_tilt, image_point);
        Eigen::NumericalDiff<SphericalPanTiltFunctor> dif_functor(opt_functor);
        Eigen::LevenbergMarquardt<Eigen::NumericalDiff<SphericalPanTiltFunctor>, double> lm(dif_functor);

LM非线性优化后,又进行了哪些操作,下次再说













评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值