ICode9

精准搜索请尝试: 精确搜索
首页 > 编程语言> 文章详细

openMVG源码学习(一)main_SfMInit_ImageListing

2021-05-25 10:31:19  阅读:624  来源: 互联网

标签:std SfMInit OpenMVG CAMERA openMVG 源码 FLAGS intrinsic


openMVG源码学习(一)main_SfMInit_ImageListing

这个学习笔记将使用openmvg作为第三方库,官方文档当中所推荐的恢复运动结构的方法,当中代码的含义以及我在学习当中所发现的经验。也欢迎大家来讨论!

包含的代码与平台

ubuntu18.04
openMVG的安装就不在此赘述了主要参照官方文档就好

main_SfMInit_ImageListing.cpp
main_ComputeFeatures.cpp
main_ComputeMatches.cpp
main_IncrementalSfM.cpp
main_GlobalSfM.cpp

main_SfMInit_ImageListing.cpp

那么我们正式开始!
首先新建一个工程,把源码粘贴过来。
我们需要自己配置工程环境:

cmakelist.txt

首先来看一下cmakelist.txt怎么写?

cmake_minimum_required(VERSION 3.0.0)
project(mvgtest1)
set(CMAKE_CXX_STANDARD 17)

add_executable(mvgtest1 img_list.cpp ComputeFeatures.cpp ComputeMatches.cpp Globalsfm.cpp)

#OpenMVG,OpenCV,Ceres

find_package(OpenCV REQUIRED)
find_package(OpenMVG REQUIRED)

find_package(Ceres REQUIRED PATHS "${CERES_PATH}/Thirdparty/ceres-solver")
include_directories(${CERES_INCLUDE_DIRS})
set(LIBS ${Ceres_LIBS_DIR}/libceres.a umfpack cxsparse glog gflags gomp
        ccolamd btf klu cholmod lapack blas camd amd pthread)
include_directories(${OPENMVG_INCLUDE_DIRS})

target_link_libraries(mvgtest1
        PRIVATE
        OpenMVG::openMVG_sfm
        OpenMVG::openMVG_matching
        OpenMVG::openMVG_camera
        OpenMVG::openMVG_exif
        OpenMVG::openMVG_features
        OpenMVG::openMVG_geodesy
        OpenMVG::openMVG_geometry
        OpenMVG::openMVG_graph
        OpenMVG::openMVG_image
        OpenMVG::openMVG_linearProgramming
        OpenMVG::openMVG_matching
        OpenMVG::openMVG_matching_image_collection
        OpenMVG::openMVG_multiview
        OpenMVG::openMVG_numeric
        OpenMVG::openMVG_robust_estimation
        OpenMVG::openMVG_sfm
        OpenMVG::openMVG_system
        ${OpenCV_LIBS}
        )
FIND_PACKAGE( OpenMP REQUIRED)
if(OPENMP_FOUND)
    message("OPENMP FOUND")
    set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${OpenMP_C_FLAGS}")
    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${OpenMP_CXX_FLAGS}")
    set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${OpenMP_EXE_LINKER_FLAGS}")
endif()

FIND_PACKAGE( OpenMP REQUIRED)
if(OPENMP_FOUND)
    message("OPENMP FOUND")
    set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${OpenMP_C_FLAGS}")
    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${OpenMP_CXX_FLAGS}")
endif()

不过多解释

代码运行前的准备

删除cmd相关的东西,反正这个在clion当中运行返回会出错,删除cmd的相关的东西也不会影响代码的完整性。删除之后main函数传参的参数,即int main()

之后,对代码的结构进行整理,让自己更加容易去看懂当中的逻辑安排。

数据准备

好了,罗嗦这么多,咱们进入代码的讲解,

首先,该cpp主要作用,是读取图片,获取路径,以及摄像机的内参参数,生成sfm_data.json文件为之后的特征匹配与重建的准备文件,之后的流程都要使用到该文件。

mian函数的开头,定义了程序的输入与输出的文件夹路径

std::string sImageDir = "../imgdata";

图片文件夹路径

std::string sOutputDir = "../output";		

输出文件文件夹

std::string sKmatrix = "fx;0;ppx;0;fy;ppy;0;0;1";	

相机内参矩阵,字符串读入,由checkIntrinsicStringValidity函数获取f,ppx,ppy数据

std::string sfileDatabase = "";	

从文件读取数据数据库文件,读入到vector< Datasheet >当中
其中Datasheet存储相机型号和传感器尺寸的数据库结构
有两个成员 std::string model_; 和double sensorSize_;

std::pair<bool, Vec3> prior_w_info(false, Vec3(1.0,1.0,1.0));

预先定义的旋转的优先级

int i_User_camera_model = PINHOLE_CAMERA_RADIAL3;

定义相机模型的类型

enum EINTRINSIC
{
  PINHOLE_CAMERA_START = 0,
  PINHOLE_CAMERA,         //无畸变
  PINHOLE_CAMERA_RADIAL1, // 径向畸变K1
  PINHOLE_CAMERA_RADIAL3, // 径向畸变K1,K2,K3
  PINHOLE_CAMERA_BROWN, //径向畸变K1,K2,K3,切向畸变T1,T2
  PINHOLE_CAMERA_FISHEYE, //具有4个畸变系数的简单鱼眼畸变模型
  PINHOLE_CAMERA_END,
  CAMERA_SPHERICAL = PINHOLE_CAMERA_END + 1
};

这是源码所给出的相机模型

bool b_Group_camera_model = true;

如果需要,可将具有相同特性的相机组合在一起(从而获得更快、更稳定的BA)。

 int i_GPS_XYZ_method = 0;

在函数checkGPS当中传入,1为utm即(函数lla_to_utm):
将WGS84 lon、lat、alt数据转换为UTM数据(通用横轴墨卡托)。
0为wgs84(函数lla_to_ecef)
将WGS84 lon、lat、alt数据转换为ECEF数据(地心固定)。
还有函数ecef_to_lla
将WGS84椭球体的ECEF(XYZ)转换为lon、lat、alt值。

数据读取

那么有了这些数据:

影像文件
输出文件夹
相机内参矩阵
传感器型号与尺寸
坐标基准
畸变模型

我们便可以对sfm_data.json进行建立了,这里对文件的数据与内参矩阵的检查不做赘述。

在读取各种数据之后:

 double width = -1, height = -1, focal = -1, ppx = -1,  ppy = -1;

被定义用于表示每张图片的内参,通过checkIntrinsicStringValidity函数检查并读入

const auto e_User_camera_model = EINTRINSIC(i_User_camera_model);

被定义畸变模型

  std::vector<std::string> vec_image = stlplus::folder_files( sImageDir );
  std::sort(vec_image.begin(), vec_image.end());	  

被定义为各张图片路径的vector,并且用名称排序

std::vector<Datasheet> vec_database;

存储传感器类型与尺寸,使用parseDatabase读入

读入并生成sfm_data.json

首先来看一下SfM_Data类,其用于定义通用SfM数据容器,存储结构和相机属性
包含成员:

Views

 是一个Hash_Map<IndexT, std::shared_ptr<View>>,存储View类型
View视图通过一个字符串和视图、相机和姿势的唯一索引来定义图像
  // image path on disk
  std::string s_Img_path;
  // Id of the view
  IndexT id_view;
  // Index of intrinsics and the pose
  IndexT id_intrinsic, id_pose;
  // image size
  IndexT ui_width, ui_height;

Poses

using Poses = Hash_Map<IndexT, geometry::Pose3>;      //定义姿势集合(按View::id_Pose索引)
方向矩阵和旋转中心 Mat3 rotation_;   Vec3 center_;			Eigen类型

Intrinsics

using Intrinsics = Hash_Map<IndexT, std::shared_ptr<cameras::IntrinsicBase>>;
为相机的内参属性

Landmarks

定义由TrackId索引的地标集合,Landmark包含两个成员,
3d点及其所对应于图像上的坐标的哈系表,
因为一个世界中的坐标可以被多张相机所观测到。
Landmarks点位又分为三角测量获得的点(用于BA)和地面控制点(用于GCP)

std::string s_root_path;
图片的根目录路径

接下来看看这个程序是如何编排进行读取的
创建一个SfM_Data,读取根目录
以vec_image的迭代器进行循环C_Progress_display在控制台显示进度

以一个循环为例:

初始化内参

width = height = ppx = ppy = focal = -1.0;

将文件夹与文件名(basename.extension)合并

const std::string sImageFilename = stlplus::create_filespec( sImageDir, *iter_image );

获取文件名-即不带文件夹部分但带有扩展名的文件名

const std::string sImFilenamePart = stlplus::filename_part(sImageFilename);

检测是否为图像

if (openMVG::image::GetFormat(sImageFilename.c_str()) == openMVG::image::Unknown)
{
  error_report_stream
      << sImFilenamePart << ": Unkown image file format." << "\n";
  continue; // image cannot be opened
}
//string::npos是一个常数,用来表示不存在的位置
//判断是否为遮罩图像
if (sImFilenamePart.find("mask.png") != std::string::npos
   || sImFilenamePart.find("_mask.png") != std::string::npos)
{
  error_report_stream
      << sImFilenamePart << " is a mask image" << "\n";
  continue;
}
//以上这两种情况跳过此循环


ImageHeader imgHeader;
if (!openMVG::image::ReadImageHeader(sImageFilename.c_str(), &imgHeader))
  continue; // image cannot be read

width = imgHeader.width;
height = imgHeader.height;
ppx = width / 2.0;
ppy = height / 2.0;

考虑手动提供焦点的情况

if (sKmatrix.size() > 0) // 已知用户校准K矩阵
{
  if (!checkIntrinsicStringValidity(sKmatrix, focal, ppx, ppy))
    focal = -1.0;
}
else //用户提供的焦距值
  if (focal_pixels != -1 )
    focal = focal_pixels;

// 如果不是手动提供或错误提供
if (focal == -1)
{
  std::unique_ptr<Exif_IO> exifReader(new Exif_IO_EasyExif);
  //打开文件进行检查和分析返回bool
  exifReader->open( sImageFilename );
  //验证文件是否有元数据并且获取相机的型号不为空
  const bool bHaveValidExifMetadata =
    exifReader->doesHaveExifInfo()
    && !exifReader->getModel().empty();
//错误则报错,对则引用
  if (bHaveValidExifMetadata) // If image contains meta data
  {
    const std::string sCamModel = exifReader->getModel();
    // 处理焦距等于0的情况
    if (exifReader->getFocal() == 0.0f)
    {
      error_report_stream
        << stlplus::basename_part(sImageFilename) << ": Focal length is missing." << "\n";
      focal = -1.0;
    }
    else
    // 在列表文件中创建图像条目
    {
      Datasheet datasheet;
      if ( getInfo( sCamModel, vec_database, datasheet ))
      {
        // 在数据库中找到了相机模型,所以我们可以计算出它的近似焦距
        const double ccdw = datasheet.sensorSize_;
        focal = std::max ( width, height ) * exifReader->getFocal() / ccdw;
      }
      else
      {
        error_report_stream
          << stlplus::basename_part(sImageFilename)
          << "\" model \"" << sCamModel << "\" doesn't exist in the dataelse		base" << "\n"
          << "Please consider add your camera model and sensor width in the database." << "\n";
      }
    }
  }
}

获取相机模型,构建与视图相关的内在参数

std::shared_ptr<IntrinsicBase> intrinsic;
if (focal > 0 && ppx > 0 && ppy > 0 && width > 0 && height > 0)
{
  // 创建所需的相机类型,这个前文中有描述
  switch (e_User_camera_model)
  {
    case PINHOLE_CAMERA:
      intrinsic = std::make_shared<Pinhole_Intrinsic>
        (width, height, focal, ppx, ppy);
    break;
    case PINHOLE_CAMERA_RADIAL1:
      intrinsic = std::make_shared<Pinhole_Intrinsic_Radial_K1>
        (width, height, focal, ppx, ppy, 0.0); // setup no distortion as initial guess
    break;
    case PINHOLE_CAMERA_RADIAL3:
      intrinsic = std::make_shared<Pinhole_Intrinsic_Radial_K3>
        (width, height, focal, ppx, ppy, 0.0, 0.0, 0.0);  // setup no distortion as initial guess
    break;
    case PINHOLE_CAMERA_BROWN:
      intrinsic = std::make_shared<Pinhole_Intrinsic_Brown_T2>
        (width, height, focal, ppx, ppy, 0.0, 0.0, 0.0, 0.0, 0.0); // setup no distortion as initial guess
    break;
    case PINHOLE_CAMERA_FISHEYE:
      intrinsic = std::make_shared<Pinhole_Intrinsic_Fisheye>
        (width, height, focal, ppx, ppy, 0.0, 0.0, 0.0, 0.0); // setup no distortion as initial guess
    break;
    case CAMERA_SPHERICAL:
       intrinsic = std::make_shared<Intrinsic_Spherical>
         (width, height);
    break;
    default:
      std::cerr << "Error: unknown camera model: " << (int) e_User_camera_model << std::endl;
      return EXIT_FAILURE;
  }
}

构建与图像对应的视图,若有gps权重时(就需要定义为优先旋转)

const std::pair<bool, Vec3> gps_info = checkGPS(sImageFilename, i_GPS_XYZ_method);
if (gps_info.first)
{
//Views的子类,可以选择是否优先旋转,或者优先调整位置
  ViewPriors v(*iter_image, views.size(), views.size(), views.size(), width, height);

  // 添加与图像相关的内部文件(如果有)
  if (intrinsic == nullptr)
  {
//因为视图具有无效的内部数据
//(使用无效的内在字段值导出视图)
    v.id_intrinsic = UndefinedIndexT;
  }
  else
  {
    // Add the defined intrinsic to the sfm_container
    intrinsics[v.id_intrinsic] = intrinsic;
  }

  v.b_use_pose_center_ = true;
  v.pose_center_ = gps_info.second;

  //先前的权重
  if (prior_w_info.first == true)
  {
    v.center_weight_ = prior_w_info.second;
  }
  //将视图添加到sfm容器
  views[v.id_view] = std::make_shared<ViewPriors>(v);
}

没有gps信息时

else		
{
  View v(*iter_image, views.size(), views.size(), views.size(), width, height);

  // Add intrinsic related to the image (if any)
  if (intrinsic == nullptr)
  {
    //Since the view have invalid intrinsic data
    // (export the view, with an invalid intrinsic field value)
    v.id_intrinsic = UndefinedIndexT;
  }
  else
  {
    // Add the defined intrinsic to the sfm_container
    intrinsics[v.id_intrinsic] = intrinsic;
  }

  // Add the view to the sfm_container
  views[v.id_view] = std::make_shared<View>(v);
}

报错与保存

报错不讲
保存则将sfm_data的数据命名并保存到output文件当中

Save(  sfm_data, stlplus::create_filespec( sOutputDir, "sfm_data.json" ).c_str(),  ESfM_Data(VIEWS|INTRINSICS))

示例

这个示例很简单,读取一段图片,因为本流程不做gps约束因此不需要增加提前的pose

标签:std,SfMInit,OpenMVG,CAMERA,openMVG,源码,FLAGS,intrinsic
来源: https://blog.csdn.net/qq_40084959/article/details/117234113

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有