OpenMVG中这部分讲的就是摄影测量中的空三部分
首先定义一个结构估算对象structure_estimator,然后让其调用structure_estimator.triangulate(sfm_data);即可实现。
原文代码如下:
首先了解一下triangulate函数的参数sfm_data表示的含义:sfm_data储存了所相关的视角Views有哪些,而Views是一个哈希图,定义为using Views = Hash_Map<IndexT, std::shared_ptr<View>>;也就是一系列的键值对,View包含影像的路径、影像索引、内参索引、姿态索引、影像大小信息。
sfm_data储存了每个视图的位姿Poses,而Poses也是一个哈希图using Poses = Hash_Map<IndexT, geometry::Pose3>;Pose3包含旋转矩阵。
sfm_data储存了每个视图的共同的一些内部参数Intrinsics,包含影像的宽和高
sfm_data储存了特征点structure的三维坐标及其对应的二维观察点坐标(像点坐标)以及地面控制点的三维坐标及对应观察点坐标,LandLmarks定义为using Landmarks = Hash_Map<IndexT, Landmark>;Landmark包含三维坐标和观察点的二维坐标和特征索引(对于控制点不用考虑这个特征索引)
第二步是遍历sfm_data中存储的特征点sfm_data.structure进行空三解算,如果解算成功就把成功的landmark赋值给这个特征点,否则就剔除这个特征点。原文代码入口如下:
第三步 空三解算(核心部分)
具体方案是使用RANSAC算法鲁棒估计最好的三维点,且这个三维点必须被大于所规定的最小要求局内点个数(min_required_inliers)的视角所观察到,用人话说就是需要选具有较高重叠度的点进行解算。
核心函数是bool SfM_Data_Structure_Computation_Robust::robust_triangulation
(const SfM_Data & sfm_data,const Observations & obs,Landmark & landmark // X & valid observations
),输入其实是某一个特征点对应的所有视图的像点坐标及其特征信息,输出是解算成功的landmark。
情况一:该点恰好只能被最小要求数量的视角看到,所有的相关视角都要参与解算
情况二:多张都能看到,进行鲁棒性空三估计
for (IndexT i = 0; i < nbIter; ++i) //迭代可视视角个数的2倍次
{
std::vector<uint32_t> samples;
robust::UniformSample(min_sample_index_, obs.size(), random_generator, &samples);
Vec3 X;
// Hypothesis generation
const auto minimal_sample = ObservationsSampler(obs, samples);
if (!track_triangulation(sfm_data, minimal_sample, X, etri_method_))
continue;
// Test validity of the hypothesis
if (!track_check_predicate(minimal_sample, sfm_data, X, predicate_binding))
continue;
std::deque<IndexT> inlier_set;
double current_error = 0.0;
// inlier/outlier classification according pixel residual errors.
for (const auto & obs_it : obs)
{
const View * view = sfm_data.views.at(obs_it.first).get();
if (!sfm_data.IsPoseAndIntrinsicDefined(view))
continue;
const IntrinsicBase & cam = *sfm_data.GetIntrinsics().at(view->id_intrinsic).get();
const Pose3 pose = sfm_data.GetPoseOrDie(view);
if (!CheiralityTest(cam(obs_it.second.x), pose, X))
continue;
const double residual_sq = cam.residual(pose(X), obs_it.second.x).squaredNorm();
if (residual_sq < dSquared_pixel_threshold)
{
inlier_set.push_front(obs_it.first);
current_error += residual_sq;
}
else
{
current_error += dSquared_pixel_threshold;
}
}
// Does the hypothesis:
// - is the best one we have seen so far.
// - has sufficient inliers.
if (current_error < best_error &&
inlier_set.size() >= min_required_inliers_)
{
best_model = X;
best_inlier_set = inlier_set;
best_error = current_error;
}
}
if (!best_inlier_set.empty() && best_inlier_set.size() >= min_required_inliers_)
{
// Update information (3D landmark position & valid observations)
landmark.X = best_model;
for (const IndexT & val : best_inlier_set)
{
landmark.obs[val] = obs.at(val);
}
}
return !best_inlier_set.empty();
}