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非线性优化后,又进行了哪些操作,下次再说