SfM平台
过去一两年尝试自己搭个SfM的小平台,主要采用Global SfM的流程,所以在OpenMVG和Theia上二次开发花了不少时间。这篇文章先写下基于GlobalSfM流程下OpenMVG二次开发的主要接口,希望能对大家有些帮助。SfM的各类平台可以参考如下:
Name | Download | Type | Open Source |
---|---|---|---|
Bundler | http://www.cs.cornell.edu/~snavely/bundler/ | Incremental SfM | Yes |
Visual SFM | http://www.ccwu.me/vsfm/ | Incremental SfM | Yes |
1DSfM | https://github.com/wilsonkl/SfM_Init | Global SfM | Yes |
DISCO | http://vision.soic.indiana.edu/projects/disco/ | Global SfM | Yes |
OpenMVG | https://github.com/openMVG/openMVG | Global & Incremental SfM | Yes |
Theia | https://github.com/sweeneychris/TheiaSfM | Global & Incremental SfM | Yes |
OpenSfM | https://github.com/mapillary/OpenSfM | Incremental SfM | Yes |
SAMANTHA | https://www.3dflow.net/technology/3df-samantha/ | Hierarchical SfM | No |
OpenMVG配置
- github 地址:https://github.com/openMVG/openMVG
- doc 地址:https://openmvg.readthedocs.io/en/latest/
- 说明:Linux下配置很方便,可以参考官方配置说明。win下配置可以参考这篇文章
OpenMVG使用与评估
- 评估数据集: 在Global SfM相关论文中经常比较Strecha这个数据集,从论文上给的地址已经404,所幸在OpenMVG的repos中还是可以找到:https://github.com/openMVG/SfM_quality_evaluation
- 使用: 下载数据集之后,可以按照数据集中Readme的提示进行测试,但是会出现错误:
(1): 安装OpenMVG
(2):python EvaluationLauncher.py [OpenMVG构建的可执行文件夹对应的绝对路径] [数据集路径] [输出路径]例如:
python EvaluationLauncher.py /home/user/openMVG_Build/Linux-x86_64-RELEASE ./Benchmarking_Camera_Calibration_2008 ./Benchmarking_Camera_Calibration_2008_out
- 错误说明:下载数据集之后,按照数据集中Readme的提示进行测试, 应该会出现无法评估的错误。这是因为评估脚本EvaluationLauncher.py 源码中的问题。可以对EvaluationLauncher.py 文件中修改下:
gt_camera_dir = os.path.join(input_eval_dir, directory, "gt_dense_cameras")
修改为
gt_camera_dir = os.path.join(input_eval_dir, directory)
- 评估结果:以castle-P19为例,位姿的精度评估最终结果如下,我这里把参数稍微调整了下,所以评估精度会比默认参数更高一点。
OpenMVG 二次开发之 BAL格式文件接口
- 起因: 康奈尔大学课题组和Google-Ceres采用的都是BAL格式的观测文件,而OpenMVG本身不支持BAL格式读入或者写出. 因此,在某些需求下,需要进行拓展BAL接口.
- BAL文件格式:可以参考这个网站: http://grail.cs.washington.edu/projects/bal/
- 源码分析 ( 以openMVG_main_GlobalSfM为例):
1.1 读写流程分析:
Note: openMVG Global SfM的pipeline可以参考博文https://blog.csdn.net/weixin_41109672/article/details/107908290, 这篇文章的博主提到angle_axis是什么,这个是3x1的旋转向量, 模值表示旋转角度, 方向向量表示旋转轴方向
1.2 这里提供Writing 部分的WriteBALfile函数, 仅供参考. Loading类似, 只需要按照Flowchart, 填充Features_Provider & Matches_Provider & SfM_Data的结构体变量即可. 这里,我把WriteBALfile作为成员函数添加进GlobalSfMReconstructionEngine_RelativeMotions类.
void GlobalSfMReconstructionEngine_RelativeMotions::WriteBALfile(
const char* fname){
{
IndexT num_obs(0);
for ( int i = 0; i < sfm_data_.structure.size(); ++i) {
auto tmp_observations = sfm_data_.structure[i].obs;
for ( int j =0; j< tmp_observations.size(); ++j) {
num_obs++;
}
}
//PO added code segment: export observations into bal file format
{
std::fstream of(fname,std::ios::out);
// store header info
int bal_num_views, bal_num_pts, bal_num_obs;
bal_num_views =sfm_data_.poses.size();
bal_num_pts = sfm_data_.structure.size();
bal_num_obs = num_obs;
of<<bal_num_views<<" "<<bal_num_pts<<" "<<bal_num_obs<<std::endl;
// store track info
for ( int i = 0; i < sfm_data_.structure.size(); ++i) {
auto tmp_observations = sfm_data_.structure[i].obs;
for ( auto it = tmp_observations.begin(); it != tmp_observations.end(); ++it){
const size_t imaIndex = it->first;
auto & pt = it->second.x;
Eigen::Vector3d normalized_coord;
normalized_coord<<pt.x(),pt.y(),1;
// std::shared_ptr<cameras::Pinhole_Intrinsic> tmp_intrinsic;
auto tmp_intrinsic = (this->sfm_data_.intrinsics[0]);
IntrinsicBase *tmp_intrinsic1=tmp_intrinsic.get();
cameras::Pinhole_Intrinsic *tmp_pinhole_intrinsic=(cameras::Pinhole_Intrinsic*)tmp_intrinsic1;
auto Kinv = tmp_pinhole_intrinsic->Kinv();
normalized_coord = -Kinv*normalized_coord;
of<<imaIndex<<" "<<i<<" "<<normalized_coord(0)<<" "<<normalized_coord(1)<<std::endl;
}
}
// store pose info
SfM_Data& tmp_data =this->sfm_data_;
Poses& tmp_poses = tmp_data.poses;
for (int i = 0; i < bal_num_views; ++i) {
geometry::Pose3& tmp_pose=tmp_data.poses[i];
double ang_axis[3];
ceres::RotationMatrixToAngleAxis(tmp_pose.rotation().data(),ang_axis);
of<<ang_axis[0]<<std::endl;
of<<ang_axis[1]<<std::endl;
of<<ang_axis[2]<<std::endl;
of<<tmp_pose.translation()[0]<<std::endl;
of<<tmp_pose.translation()[1]<<std::endl;
of<<tmp_pose.translation()[2]<<std::endl;
of<<1<<std::endl;
of<<0<<std::endl;
of<<0<<std::endl;
}
// store 3D info
auto tmp_structure = tmp_data.structure;
for ( int i = 0; i < bal_num_pts; ++i) {
Landmark& landmark = tmp_structure[i];
of<<landmark.X(0)<<std::endl;
of<<landmark.X(1)<<std::endl;
of<<landmark.X(2)<<std::endl;
}
of.close();
}
//PO end *************************************************
}
}
1.3 SfM_Data结构体: OpenMVG的核心结构体. 以下是我对其做的注释.
struct SfM_Data
{
/// Considered views
Views views;
// using Views = Hash_Map<IndexT, std::shared_ptr<View>>;
//其中View结构体的成员变量:
//(1) 图像的路径: std::string s_Img_path;
//(2) 图像的id: IndexT id_view;`在这里插入代码片`
//(3) 内参数和位姿的id: IndexT id_intrinsic, id_pose;
//(4) 图像的大小: IndexT ui_width, ui_height;
/// Considered poses (indexed by view.id_pose)
Poses poses;
//using Poses = Hash_Map<IndexT, geometry::Pose3>;
//其中Pose3的成员变量:
//实际上Pose3就是相机3x3旋转矩阵变量和一个3x1的相机位置变量
/// Considered camera intrinsics (indexed by view.id_intrinsic)
Intrinsics intrinsics;
//using Intrinsics = Hash_Map<IndexT, std::shared_ptr<cameras::IntrinsicBase>>;
//IntrinsicBase的成员变量:
//实际上IntrinsicBase结构中只有width和height变量
/// Structure (3D points with their 2D observations)
Landmarks structure;
//using Landmarks = Hash_Map<IndexT, Landmark>;
//其中Landmark的成员变量:
//(1)3d points: Vec3 X;
//(2)3D点对应的观测图像特征点:Observations obs;//using Observations = Hash_Map<IndexT, Observation>;
//其中IndexT为ImageID, Observation的成员变量:
//(1)2d image points: Vec2 x;
//(2)特征点的id: IndexT id_feat;
/// Controls points (stored as Landmarks (id_feat has no meaning here))
Landmarks control_points;
/// Root Views path
std::string s_root_path;
};
暂时先写这么多,有什么需求可以提, 然后我再更新对应部分, 后续有空会更新theia vision的bal接口, distort_bal_flie等部分, ceres开发的坑以及GlobalSfM 到目前的发展情况.