CVPR:A Two-point Method for PTZ Camera Calibration in Sports的C++程序分析(4)
接着之前的讨论,开始看函数panTilt2Point(),
Eigen::Vector2d panTilt2Point(const Eigen::Vector2d& pp, const Eigen::Vector3d& ptz, const Eigen::Vector2d& point_pan_tilt) { double delta_pan = (point_pan_tilt[0] - ptz[0]) * M_PI/180.0; double delta_tilt = (point_pan_tilt[1] - ptz[1]) * M_PI/180.0; double fl = ptz[2]; double delta_x = fl * tan(delta_pan); double delta_y = fl * tan(delta_tilt); Eigen::Vector2d point(pp.x() + delta_x, pp.y() - delta_y); // oppositive direction of y return point; }
这个函数主要是根据预测的PT角来来求解变量rp,变量rp的定义在这篇论文的Eq.(5)。如果不读论文,姑且接受这个变量好了。
那么,对每一个特征点的每一个预测值,都可以产生一个rp,而rp是一个2×1的列向量。可能不好理解。
举个例子,如果这幅图像有8个特征点,并且每个特征点都有5个预测值,而每个预测值都可以产生一个rp,所以一共有8×5=40个rp,记为rp_i_j,i=1,2,3,...,8,j=1,2,3..,5
其实,对于真值Pan_true,和Tilt_true,在这幅图像的8个特征点,分别生成rp_true_1, rp_true_2,..., rp_true_8.
看到最后,就会发现:这个找最优解的问题其实就是一个最近邻问题,别慌,慢慢来
好了,对于这幅图像的第一个特征点,现在有rp_1_1, ..., rp_1_8 这八个值,它们哪一个跟 rp_true_1最近呢?因为rp是2×1的列向量,所以这里的距离指的是欧氏距离。经过排序,前三名分别是rp_1_3, rp_1_8, rp_1_4。这说明rp_1_3相关联的PT_1_3接近真值。
这只是第一个特征点。还有7个需要查看。每一次都需要排序,找到前三名。我们只要盯着预测值中上榜率最高的就行啦。原理是这样的。
讨论完这个辅助函数之后,就直接看这个工程中最为核心的函数preemptiveRANSACOneToMany(),它的函数原型是,
bool preemptiveRANSACOneToMany(const vector<Eigen::Vector2d> & image_points, const vector<vector<Eigen::Vector2d> > & candidate_pan_tilt, const Eigen::Vector2d& pp, const PTZPreemptiveRANSACParameter & param, Eigen::Vector3d & ptz, bool verbose)
至于变量candidate_pan_tilt为什么是vector<vector<Eigen::vector2d> >这我就不多说了,前面已经介绍了。pp是相机的中心点,如果相机分辨率是1024×768,那么pp等于(512, 384), param 存放RANSAC,关于迭代以及阈值的变量,都写在这里了,
Eigen::Vector2d pp(1280.0/2.0, 720.0/2.0); ptz_pose_opt::PTZPreemptiveRANSACParameter param; param.reprojection_error_threshold_ = reprojection_error_threshold; param.sample_number_ = sample_number;
而vector2d型变量ptz是输出结果
来看这个函数的第一部分,
assert(image_points.size() == candidate_pan_tilt.size()); if (image_points.size() <= 12) { return false; } const int num_iteration = 1024; const int K = 512; const int N = (int)image_points.size(); const int B = param.sample_number_; double threshold = param.reprojection_error_threshold_;
除了一些常规赋值,报警外,可以发现:一副图像至少需要12个点,用来做RANSAC,否则就会报错; 变量N指代这一帧图像点的数量
再往下看,一个小循环的第一部分
// step 1: sample hyperthesis vector<Hypothesis> hypotheses; for (int i = 0; i<num_iteration; i++) { int k1 = 0; int k2 = 0; do{ k1 = rand()%N; k2 = rand()%N; }while (k1 == k2); // not end.... }
看注释可以发现,这是在sample hypothesis,中文就是“假设抽样”,白话一点就是,随机地抽取一些假设。可能还是听不太明白。可以这样讲。假设一副图像用SIFT采集到20个特征点,相当于袋子里放了二十个球,每次从这里面拿两个出来; 然后把这两个球再放进去,再随机拿出两个,就这样重复num_iteration次,这里num_iteration等于1024
现在,从放有N个球的袋子里随机拿两个后,得以初始化一些变量,
const Eigen::Vector2d pan_tilt1 = candidate_pan_tilt[k1][0]; const Eigen::Vector2d pan_tilt2 = candidate_pan_tilt[k2][0]; const Eigen::Vector2d point1 = image_points[k1]; const Eigen::Vector2d point2 = image_points[k2]; Eigen::Vector3d ptz;
然后,对这一次抽样进行一次判断,如果合格,就把它存起来,
bool is_valid = EigenX::ptzFromTwoPoints(pan_tilt1, pan_tilt2, point1, point2, pp, ptz); if (is_valid) { Hypothesis hp; hp.ptz_ = ptz; hypotheses.push_back(hp); } else { if (verbose) { printf("warning: estimate ptz from two points failed.\n"); } } if (hypotheses.size() > K) { if (verbose) { printf("initialization repeat %d times\n", i); } break; } }
这样的操作重复num_iteration次,这里num_iteration等于1024,因此一共抽取了1024个假设抽样。
在进行sample hypotheses之后,进行下一步,计算pan角,tilt角以及focal
// step 2: optimize pan, tilt, focal length while (hypotheses.size() > 1) { //........... }
先看step2,这个大循环的第一部分,
// sample random set vector<Eigen::Vector2d> sampled_image_pts; vector<vector<Eigen::Vector2d> > sampled_pan_tilt; // one camera point may have multiple pan, tilt correspondences vector<int> sampled_indices; for (int i =0; i<B; i++) { int index = rand()%N; sampled_image_pts.push_back(image_points[index]); sampled_pan_tilt.push_back(candidate_pan_tilt[index]); sampled_indices.push_back(index); }
这个注释的意思前面已经讲过,就是在一帧图像中,一个特征点对应多个随机森里预测值。B = param.sample_number_,那么sample_number_在头文件中这样定义,它等于32
即假设一帧图像有40个特征点,随机抽取32次(不排除重复,这应该是一个bug),每次抽取之后,记录它的像素坐标,相应的随机森林多个预测值,以及该特征点的index(即它是这40个特征点的第多少多少位)
下一次,再分析后面的代码