CVPR:A Two-point Method for PTZ Camera Calibration in Sports的C++程序分析(3)
接下来要分析一个循环。所谓的sample.size()指的是这一帧图像中的特征点大小
// predict from observation (descriptors)
double tt = clock();
for (int j = 0; j<samples.size(); j++) { // }
来看循环的第一个部分,
btdtr_ptz_util::PTZSample s = samples[j];
Eigen::VectorXf feat = s.descriptor_;
vector<Eigen::VectorXf> cur_predictions;
vector<float> cur_dists;
model.predict(feat, max_check, maxTreeNum, cur_predictions, cur_dists);
assert(cur_predictions.size() == cur_dists.size());
这一块代码容易理解,对这一帧图像的每一个特征点进行随机森林预测,预测结果放在cur_prediction中,预测偏差存放在cur_dists中。之前提及过,随机森林的预测值有多个,那么预测偏差也会有多个。例如这个特征点用随机森林预测可以得到8个结果,那么就有8个预测偏差(至于这个预测偏差是怎么定义的,还不清楚,但是应该不是和真值比较。)
这个循环的后一部分是,
//cout<<"minimum feature distance "<<cur_dists[0]<<endl;
if (cur_dists[0] < distance_threshold) {
image_points.push_back(Eigen::Vector2d(s.loc_.x(), s.loc_.y()));
vector<Eigen::Vector2d> cur_candidate;
for (int k = 0; k<cur_predictions.size(); k++) {
assert(cur_predictions[k].size() == 2);
if (cur_dists[k] < distance_threshold) {
cur_candidate.push_back(Eigen::Vector2d(cur_predictions[k][0], cur_predictions[k][1]));
}
}
candidate_pan_tilt.push_back(cur_candidate);
}
如果预测偏差小于阈值,那么认定这个特征点是正常的,把它存入image_points中。接着刚才的例子,如果8个特征点只有5个的预测偏差小于阈值,那么cur_candidate只记录那5个预测值。
分析完这个循环,接着向下走,
// estimate camera pose
bool is_opt = ptz_pose_opt::preemptiveRANSACOneToMany(image_points, candidate_pan_tilt, pp,
param, estimated_ptz, false);
printf("Prediction and camera pose estimation cost time: %f seconds.\n", (clock() - tt)/CLOCKS_PER_SEC);
if (is_opt) {
cout<<"ptz estimation error: "<<(ptz_gt - estimated_ptz).transpose()<<endl;
}
else {
printf("-------------------------------------------- Optimize PTZ failed.\n");
printf("valid feature number is %lu\n\n", image_points.size());
}
gt_ptz_all.row(index) = ptz_gt;
estimated_ptz_all.row(index) = estimated_ptz;
index++;
看到这行注释,就会明白下面的代码开始估计相机的pan角和tilt角啦。程序运行时间和结果输出的代码不必细说。主要分析函数preemptiveRANSACOneToMany()的作用。根据这个函数前面的定义域,可以迅速地找打它的源文件ptz_pose_estimation.cpp。这个源文件定义了一个类,叫做Hypothesis。
因此,在分析preemptiveRANSACOneToMany之前,必须分析类Hypothesis。先看类Hypothesis的成员变量,
struct Hypothesis
{
double loss_;
Eigen::Vector3d ptz_;
vector<int> inlier_indices_; // image coordinate index
vector<int> inlier_candidate_pan_tilt_indices_; // camera coordinate pan tilt index
// store all inliers from preemptive ransac
vector<Eigen::Vector2d> image_pts_;
vector<Eigen::Vector2d> camera_pan_tilt_;
//ignore functions.....
}
看注释,index的意思应该不会陌生。这段变量初始化的含义是给特征点和候选PT角分别建立索引。具体地讲,比如有5个特征点,inlier_indices_=1,2,3,4,5,对应这它的像素位置坐标。loss_指误差。
看完类Hypothesis的成员变量,再来看它的成员函数,
Hypothesis()
{
loss_ = INT_MAX;
}
Hypothesis(double loss)
{
loss_ = loss;
}
Hypothesis(const Hypothesis& other)
{
if (&other == this) {
return;
}
loss_ = other.loss_;
ptz_ = other.ptz_;
inlier_indices_ = other.inlier_indices_;
inlier_candidate_pan_tilt_indices_ = other.inlier_candidate_pan_tilt_indices_;
image_pts_ = other.image_pts_;
camera_pan_tilt_ = other.camera_pan_tilt_;
}
Hypothesis & operator = (const Hypothesis & other)
{
if (&other == this) {
return *this;
}
loss_ = other.loss_;
ptz_ = other.ptz_;
inlier_indices_ = other.inlier_indices_;
inlier_candidate_pan_tilt_indices_ = other.inlier_candidate_pan_tilt_indices_;
image_pts_ = other.image_pts_;
camera_pan_tilt_ = other.camera_pan_tilt_;
return *this;
}
bool operator < (const Hypothesis & other) const
{
return loss_ < other.loss_;
}
类Hypothesis中的函数Hypothesis()重载了两次,而后又重载定义了运算符号“=”和“+”。这是可以学习的。
类Hypothesis讲完之后,还得再讲一个辅助函数projectPanTilt(),
// @brief project pan, tilt ray to image space
static vector<vector<Eigen::Vector2d> > projectPanTilt(const Eigen::Vector3d& ptz,
const Eigen::Vector2d& pp,
const vector<vector<Eigen::Vector2d> > & input_pan_tilt)
{
vector<vector<Eigen::Vector2d> > image_pts(input_pan_tilt.size());
for (int i = 0; i<input_pan_tilt.size(); i++) {
for (int j = 0; j<input_pan_tilt[i].size(); j++) {
Eigen::Vector2d point = cvx_pgl::panTilt2Point(pp, ptz, input_pan_tilt[i][j]);
image_pts[i].push_back(point);
}
}
return image_pts;
}
理解这个函数,恐怕看注释也未必理解,必须得分析一下panTilt2Point()
那下回再作分析!