openMVG source code learning main_SfMInit_ImageListing

Posted by cesarcesar on Wed, 09 Feb 2022 00:03:10 +0100

openMVG source code learning (I) main_SfMInit_ImageListing

This study note will use openmvg as the third-party library, the method of restoring motion structure recommended in the official documents, the meaning of the code and the experience I found in my study. Welcome to discuss!

Code and platform included

ubuntu18.04
The installation of openMVG will not be repeated here. Just refer to the official documents

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

main_SfMInit_ImageListing.cpp

Then let's officially start!
First, create a new project and paste the source code.
We need to configure the engineering environment ourselves:

cmakelist.txt

Let's start with cmakelist How to write 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()

But explain more

Preparation before code running

Delete cmd related things. Anyway, there will be an error when running in the clion. Deleting cmd related things will not affect the integrity of the code. After deletion, the parameter passed by the main function is int main()

Then, sort out the structure of the code to make it easier for you to understand the logical arrangement.

Data preparation

Well, let's go into the explanation of the code,

Firstly, the main function of the cpp is to read the picture, obtain the path, and the internal parameters of the camera to generate sfm_data.json file is the preparation file for later feature matching and reconstruction, which should be used in subsequent processes.

The beginning of the mian function defines the folder path of the input and output of the program

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

Picture folder path

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

Output file folder

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

Camera internal parameter matrix, read in the string, and obtain the f, ppx, ppy data by the checkIntrinsicStringValidity function

std::string sfileDatabase = "";	

Read the data database file from the file and read it into the vector < < datasheet >
Datasheet stores the database structure of camera model and sensor size
There are two members std::string model_; And double sensorSize_;

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

Priority of predefined rotation

int i_User_camera_model = PINHOLE_CAMERA_RADIAL3;

Defines the type of camera model

enum EINTRINSIC
{
  PINHOLE_CAMERA_START = 0,
  PINHOLE_CAMERA,         //No distortion
  PINHOLE_CAMERA_RADIAL1, // Radial distortion K1
  PINHOLE_CAMERA_RADIAL3, // Radial distortion K1,K2,K3
  PINHOLE_CAMERA_BROWN, //Radial distortion K1, K2, K3, tangential distortion T1, T2
  PINHOLE_CAMERA_FISHEYE, //A simple fisheye distortion model with four distortion coefficients
  PINHOLE_CAMERA_END,
  CAMERA_SPHERICAL = PINHOLE_CAMERA_END + 1
};

This is the camera model given by the source code

bool b_Group_camera_model = true;

If necessary, cameras with the same characteristics can be combined together (resulting in a faster and more stable BA).

 int i_GPS_XYZ_method = 0;

In the function checkGPS, 1 is UTM, that is (function lla_to_utm):
Convert WGS84 lon, lat and alt data into UTM data (universal horizontal axis Mercator).
0 is wgs84 (function lla_to_ecef)
Convert WGS84 lon, lat and alt data into ECEF data (geocentric fixation).
And the function ecef_to_lla
Convert the ECEF (XYZ) of WGS84 ellipsoid to lon, lat and alt values.

data fetch

So with these data:

Image file
 Output folder
 Camera internal parameter matrix
 Sensor model and size
 Coordinate Datum
 Distortion model

We can do sfm_data.json has been established. The inspection of file data and internal parameter matrix will not be repeated here.

After reading various data:

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

The internal parameters defined to represent each picture are checked and read in through the checkIntrinsicStringValidity function

const auto e_User_camera_model = EINTRINSIC(i_User_camera_model);

Defined distortion model

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

It is defined as the vector of each picture path and sorted by name

std::vector<Datasheet> vec_database;

The sensor type and size are stored and read in using parseDatabase

Read in and generate sfm_data.json

First, let's take a look at SfM_Data class, which is used to define the general SfM data container, storage structure and camera attributes
Include members:

Views

 It's a Hash_Map<IndexT, std::shared_ptr<View>>,storage View type
View A view defines an image through a string and a unique index of the view, camera, and pose
  // 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>;      //Define pose collection (indexed by View::id_Pose)
Direction matrix and rotation center Mat3 rotation_;   Vec3 center_;			Eigen type

Intrinsics

using Intrinsics = Hash_Map<IndexT, std::shared_ptr<cameras::IntrinsicBase>>;
Is the internal parameter attribute of the camera

Landmarks

Defined by TrackId Indexed landmark collection, Landmark Contains two members,
3d Points and their corresponding coordinates on the image,
Because the coordinates in a world can be observed by multiple cameras.
Landmarks Points are divided into points obtained by triangulation (for BA)And ground control points (for GCP)

std::string s_root_path;
The root directory path of the picture

Next, let's take a look at how this program is arranged for reading
Create an SfM_Data, read the root directory
VEC_ Iterator of image to loop C_Progress_display displays progress on the console

Take a cycle as an example:

Initialize internal parameters

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

Merge folder with file name (basename.extension)

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

Gets the file name - that is, the file name without the folder part but with the extension

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

Detect whether it is an image

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 is a constant used to indicate the non-existent location
//Determine whether it is a mask image
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;
}
//Skip this loop in both cases


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;

Consider providing focus manually

if (sKmatrix.size() > 0) // Known user calibration K matrix
{
  if (!checkIntrinsicStringValidity(sKmatrix, focal, ppx, ppy))
    focal = -1.0;
}
else //User supplied focal length value
  if (focal_pixels != -1 )
    focal = focal_pixels;

// If not provided manually or incorrectly
if (focal == -1)
{
  std::unique_ptr<Exif_IO> exifReader(new Exif_IO_EasyExif);
  //Open the file for inspection and analysis, and return bool
  exifReader->open( sImageFilename );
  //Verify whether the file has metadata and the model of the obtained camera is not empty
  const bool bHaveValidExifMetadata =
    exifReader->doesHaveExifInfo()
    && !exifReader->getModel().empty();
//If there is an error, an error will be reported, and if there is a right, it will be referenced
  if (bHaveValidExifMetadata) // If image contains meta data
  {
    const std::string sCamModel = exifReader->getModel();
    // Handle the case where the focal length is equal to 0
    if (exifReader->getFocal() == 0.0f)
    {
      error_report_stream
        << stlplus::basename_part(sImageFilename) << ": Focal length is missing." << "\n";
      focal = -1.0;
    }
    else
    // Create an image entry in a list file
    {
      Datasheet datasheet;
      if ( getInfo( sCamModel, vec_database, datasheet ))
      {
        // The camera model is found in the database, so we can calculate its approximate focal length
        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";
      }
    }
  }
}

Obtain the camera model and construct the internal parameters related to the view

std::shared_ptr<IntrinsicBase> intrinsic;
if (focal > 0 && ppx > 0 && ppy > 0 && width > 0 && height > 0)
{
  // Create the required camera type, as described earlier
  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;
  }
}

Build the view corresponding to the image. If there is gps weight (it needs to be defined as priority rotation)

const std::pair<bool, Vec3> gps_info = checkGPS(sImageFilename, i_GPS_XYZ_method);
if (gps_info.first)
{
//The subclass of Views. You can choose whether to preferentially rotate or adjust the position
  ViewPriors v(*iter_image, views.size(), views.size(), views.size(), width, height);

  // Add internal files related to the image (if any)
  if (intrinsic == nullptr)
  {
//Because the view has invalid internal data
//(export view with invalid intrinsic field value)
    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;

  //Previous weights
  if (prior_w_info.first == true)
  {
    v.center_weight_ = prior_w_info.second;
  }
  //Add view to sfm container
  views[v.id_view] = std::make_shared<ViewPriors>(v);
}

When there is no gps information

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);
}

Error reporting and preservation

Report mistakes without telling
Save SFM_ Name the data of data and save it to the output file

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

Examples

This example is very simple. Read a picture. Because this process does not make gps constraints, there is no need to add an advanced pose

Topics: C++ Ubuntu