头文件
#include "openMVG/cameras/cameras.hpp"
#include "openMVG/exif/exif_IO_EasyExif.hpp"
#include "openMVG/exif/sensor_width_database/ParseDatabase.hpp"
#include "openMVG/geodesy/geodesy.hpp"
#include "openMVG/image/image_io.hpp"
#include "openMVG/numeric/eigen_alias_definition.hpp"
#include "openMVG/sfm/sfm_data.hpp"
#include "openMVG/sfm/sfm_data_io.hpp"
#include "openMVG/sfm/sfm_data_utils.hpp"
#include "openMVG/sfm/sfm_view.hpp"
#include "openMVG/sfm/sfm_view_priors.hpp"
#include "openMVG/system/loggerprogress.hpp"
#include "openMVG/types.hpp"
#include "third_party/cmdLine/cmdLine.h"
#include "third_party/stlplus3/filesystemSimplified/file_system.hpp"
#include <fstream>
#include <memory>
#include <string>
#include <utility>
cameras.hpp
- 定义和实现了多种相机模型,例如针孔相机模型、鱼眼相机模型等。这些模型用于描述从三维世界到二维图像的投影过程。
exif_IO_EasyExif.hpp
- 提供了读取和写入EXIF(Exchangeable Image File Format)信息的功能。EXIF信息通常嵌入在JPEG和TIFF等图像文件中,包含了关于图像的元数据,如拍摄时间、相机型号、焦距等。
ParseDatabase.hpp
- 用于解析传感器宽度数据库,这些数据库通常包含了各种相机型号的传感器宽度信息。
geodesy.hpp
- 提供了与大地测量相关的功能,如WGS84坐标系的转换、地球模型的近似等。它在MVG中的主要应用于坐标转换。
image_io.hpp
- 提供了图像读取和写入的功能,支持多种图像格式,如JPEG、PNG、TIFF等。
eigen_alias_definition.hpp
- 为Eigen库(一个高级C++库,用于线性代数、矩阵和向量操作、数值分析和相关的数学算法)定义了别名和类型。
sfm_data.hpp
- 定义了用于表示SFM问题的数据结构。这包括相机参数、三维点、图像之间的对应关系等。
sfm_data_utils.hpp
- 提供了一系列用于操作和处理SfM数据的实用工具函数。如在数据清理方面可以移除不稳定的或低质量的相机三维点,合并来自不同场景或不同数据集的SfM数据等等...
sfm_view.hpp
- 定义了与图像视图(即SfM问题中的一个图像)相关的数据结构和函数。这包括视图之间的匹配、视图的选择等。
sfm_view_priors.hpp
- 提供了与视图先验(priors)相关的信息。在SfM问题中,先验信息可能来自图像之间的时间顺序、空间位置关系等,这些信息可以帮助提高重建的准确性和鲁棒性。
loggerprogress.hpp
- 提供了日志和进度记录的功能。这允许OpenMVG库在运行时输出有关其操作的信息,并允许用户跟踪其进度。
types.hpp
- 定义了OpenMVG库中使用的各种基本类型和常量。这有助于确保代码的一致性和可读性。
cmdLine.h / file_system.hpp
- cmdLine.h 用于从命令行读取参数并解析。
- file_system.hpp 提供了一些简化文件操作(如读取、写入、目录遍历等)的接口 f
fstream / memory / string /utility
fstream
:包含C++标准库中的文件流库,它允许你通过流对象(如std::ifstream
和std::ofstream
)来读取和写入文件。memory:
包含C++标准库中的内存管理库,它提供了智能指针(如std::unique_ptr
和std::shared_ptr
)和其他内存管理工具。string:
包含C++标准库中的字符串库,它提供了std::string
类和其他与字符串相关的实用程序。utility:
包含C++标准库中的实用程序库,它提供了许多有用的模板类和函数,如std::pair
和std::move
。
// 该函数用于检查一个表示相机内参矩阵的字符串(Kmatrix)是否有效,并提取焦距(focal)、主点x坐标(ppx)和主点y坐标(ppy)。
bool checkIntrinsicStringValidity(const std::string & Kmatrix, double & focal, double & ppx, double & ppy)
{
// 用于存储从Kmatrix中分割出来的字符串
std::vector<std::string> vec_str;
stl::split(Kmatrix, ';', vec_str);
// 内参矩阵是3*3的,如果分出来不是9个,那说明k错误
if (vec_str.size() != 9) {
OPENMVG_LOG_ERROR << "\n Missing ';' character";
return false;
}
// 遍历,使用stringstream判断字符串是不是有效数字
for (size_t i = 0; i < vec_str.size(); ++i) {
double readvalue = 0.0;
std::stringstream ss;
ss.str(vec_str[i]);
if (! (ss >> readvalue) ) {
OPENMVG_LOG_ERROR << "\n Used an invalid not a number character";
return false;
}
// 提取焦距,像主点横坐标,像主点纵坐标
if (i==0) focal = readvalue;
if (i==2) ppx = readvalue;
if (i==5) ppy = readvalue;
}
return true;
}
//提取GPS坐标信息,并根据指定的方法将其转换为三维空间中的坐标信息
bool getGPS
(
const std::string & filename,
const int & GPS_to_XYZ_method,
Vec3 & pose_center
)
{
std::unique_ptr<Exif_IO> exifReader(new Exif_IO_EasyExif);
if (exifReader)
{
// 尝试获取EXIF信息
if ( exifReader->open( filename ) && exifReader->doesHaveExifInfo() )
{
// 检查GPS信息
double latitude, longitude, altitude;
if ( exifReader->GPSLatitude( &latitude ) &&
exifReader->GPSLongitude( &longitude ) &&
exifReader->GPSAltitude( &altitude ) )
{
// 转换坐标并添加
switch (GPS_to_XYZ_method)
{
case 1:
pose_center = lla_to_utm( latitude, longitude, altitude );
break;
case 0:
default:
pose_center = lla_to_ecef( latitude, longitude, altitude );
break;
}
return true;
}
}
}
return false;
}
// 用于检查sWeights 是否包含三个有效的权重值,并将这些权重值存储在Vec3 类型的对象中
std::pair<bool, Vec3> checkPriorWeightsString
(
const std::string &sWeights
)
{
std::pair<bool, Vec3> val(true, Vec3::Zero());
std::vector<std::string> vec_str;
// 分割字符串
stl::split(sWeights, ';', vec_str);
// 如果 vec_str 的大小不等于 3,表示权重字符串中缺少分号
if (vec_str.size() != 3)
{
OPENMVG_LOG_ERROR << "Missing ';' character in prior weights";
// 表示权重字符串无效
val.first = false;
}
// 检查每个字符串是否为有效数字
for (size_t i = 0; i < vec_str.size(); ++i)
{
double readvalue = 0.0;
std::stringstream ss;
ss.str(vec_str[i]);
if (! (ss >> readvalue) ) {
OPENMVG_LOG_ERROR << "Used an invalid not a number character in local frame origin";
val.first = false;
}
val.second[i] = readvalue;
}
return val;
}
接下来就进入到主函数了
int main(int argc, char **argv)
{
CmdLine cmd;
// 输入图像目录路径,传感器宽度数据库路径,输出路径,内参矩阵的字符串表示
std::string sImageDir,
sfileDatabase = "",
sOutputDir = "",
sKmatrix;
std::string sPriorWeights = "1.0;1.0;1.0";
std::pair<bool, Vec3> prior_w_info(false, Vec3());
// 默认相机模型为PINHOLE_CAMERA_RADIAL3
int i_User_camera_model = PINHOLE_CAMERA_RADIAL3;
bool b_Group_camera_model = true;
int i_GPS_XYZ_method = 0;
double focal_pixels = -1.0;
// 添加命令行选项,这里就不多说了
cmd.add( make_option('i', sImageDir, "imageDirectory") );
cmd.add( make_option('d', sfileDatabase, "sensorWidthDatabase") );
cmd.add( make_option('o', sOutputDir, "outputDirectory") );
cmd.add( make_option('f', focal_pixels, "focal") );
cmd.add( make_option('k', sKmatrix, "intrinsics") );
cmd.add( make_option('c', i_User_camera_model, "camera_model") );
cmd.add( make_option('g', b_Group_camera_model, "group_camera_model") );
cmd.add( make_switch('P', "use_pose_prior") );
cmd.add( make_option('W', sPriorWeights, "prior_weights"));
cmd.add( make_option('m', i_GPS_XYZ_method, "gps_to_xyz_method") );
// 如果给的参数不对,抛出异常,显示日志信息
try {
if (argc == 1) throw std::string("Invalid command line parameter.");
cmd.process(argc, argv);
} catch (const std::string& s) {
OPENMVG_LOG_INFO << "Usage: " << argv[0] << '\n'
<< "[-i|--imageDirectory]\n"
<< "[-d|--sensorWidthDatabase]\n"
<< "[-o|--outputDirectory]\n"
<< "[-f|--focal] (pixels)\n"
<< "[-k|--intrinsics] Kmatrix: \"f;0;ppx;0;f;ppy;0;0;1\"\n"
<< "[-c|--camera_model] Camera model type:\n"
<< "\t" << static_cast<int>(PINHOLE_CAMERA) << ": Pinhole\n"
<< "\t" << static_cast<int>(PINHOLE_CAMERA_RADIAL1) << ": Pinhole radial 1\n"
<< "\t" << static_cast<int>(PINHOLE_CAMERA_RADIAL3) << ": Pinhole radial 3 (default)\n"
<< "\t" << static_cast<int>(PINHOLE_CAMERA_BROWN) << ": Pinhole brown 2\n"
<< "\t" << static_cast<int>(PINHOLE_CAMERA_FISHEYE) << ": Pinhole with a simple Fish-eye distortion\n"
<< "\t" << static_cast<int>(CAMERA_SPHERICAL) << ": Spherical camera\n"
<< "[-g|--group_camera_model]\n"
<< "\t 0-> each view have it's own camera intrinsic parameters,\n"
<< "\t 1-> (default) view can share some camera intrinsic parameters\n"
<< "\n"
<< "[-P|--use_pose_prior] Use pose prior if GPS EXIF pose is available"
<< "[-W|--prior_weights] \"x;y;z;\" of weights for each dimension of the prior (default: 1.0)\n"
<< "[-m|--gps_to_xyz_method] XZY Coordinate system:\n"
<< "\t 0: ECEF (default)\n"
<< "\t 1: UTM";
OPENMVG_LOG_ERROR << s;
return EXIT_FAILURE;
}
// 如果命令行参数'P'被使用,则返回true,否则返回false
const bool b_Use_pose_prior = cmd.used('P');
// 打印一些指定的参数到控制台
OPENMVG_LOG_INFO << " You called : " << argv[0]
<< "\n--imageDirectory " << sImageDir
<< "\n--sensorWidthDatabase " << sfileDatabase
<< "\n--outputDirectory " << sOutputDir
<< "\n--focal " << focal_pixels
<< "\n--intrinsics " << sKmatrix
<< "\n--camera_model " << i_User_camera_model
<< "\n--group_camera_model " << b_Group_camera_model
<< "\n--use_pose_prior " << b_Use_pose_prior
<< "\n--prior_weights " << sPriorWeights
<< "\n--gps_to_xyz_method " << i_GPS_XYZ_method;
// 用于存储图像的宽度和高度和相机的内参
double width = -1, height = -1, focal = -1, ppx = -1, ppy = -1;
const EINTRINSIC e_User_camera_model = EINTRINSIC(i_User_camera_model);
// 判断输入目录是否有效
if ( !stlplus::folder_exists( sImageDir ) )
{
OPENMVG_LOG_ERROR << "The input directory doesn't exist";
return EXIT_FAILURE;
}
// 判断是否给出了输出目录
if (sOutputDir.empty())
{
OPENMVG_LOG_ERROR << "Invalid output directory";
return EXIT_FAILURE;
}
// 检查指定的输出目录是否存在,如果不存在,则尝试创建它。
if ( !stlplus::folder_exists( sOutputDir ) )
{
if ( !stlplus::folder_create( sOutputDir ))
{
OPENMVG_LOG_ERROR << "Cannot create output directory";
return EXIT_FAILURE;
}
}
// 内参数据无效
if (sKmatrix.size() > 0 &&
!checkIntrinsicStringValidity(sKmatrix, focal, ppx, ppy) )
{
OPENMVG_LOG_ERROR << "Invalid K matrix input";
return EXIT_FAILURE;
}
// 如果用户同时提供了内参矩阵和焦距则报错
// 内参矩阵中已经含有焦距,若在单独提供有潜在风险
if (sKmatrix.size() > 0 && focal_pixels != -1.0)
{
OPENMVG_LOG_ERROR << "Cannot combine -f and -k options";
return EXIT_FAILURE;
}
// 解析传感器宽度数据库
std::vector<Datasheet> vec_database;
if (!sfileDatabase.empty())
{
if ( !parseDatabase( sfileDatabase, vec_database ) )
{
OPENMVG_LOG_ERROR
<< "Invalid input database: " << sfileDatabase
<< ", please specify a valid file.";
return EXIT_FAILURE;
}
}
// 判断用户提供的EXIF信息是否有效
if (b_Use_pose_prior)
{
prior_w_info = checkPriorWeightsString(sPriorWeights);
}
// 读取图像
std::vector<std::string> vec_image = stlplus::folder_files( sImageDir );
std::sort(vec_image.begin(), vec_image.end());
// 设置场景的根目录、视图(Views)和它们对应的相机参数(Intrinsics)
SfM_Data sfm_data;
sfm_data.s_root_path = sImageDir; // Setup main image root_path
Views & views = sfm_data.views;
Intrinsics & intrinsics = sfm_data.intrinsics;
system::LoggerProgress my_progress_bar(vec_image.size(), "- Listing images -" );
std::ostringstream error_report_stream;
// 处理每张图像
for ( std::vector<std::string>::const_iterator iter_image = vec_image.begin();
iter_image != vec_image.end();
++iter_image, ++my_progress_bar )
{
// Read meta data to fill camera parameter (w,h,focal,ppx,ppy) fields.
width = height = ppx = ppy = focal = -1.0;
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
}
// 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;
// 如果提供了不正确的内参矩阵,则使用提供的focal值
if (sKmatrix.size() > 0) // Known user calibration K matrix
{
if (!checkIntrinsicStringValidity(sKmatrix, focal, ppx, ppy))
focal = -1.0;
}
else
if (focal_pixels != -1 )
focal = focal_pixels;
// 如果都没有提供focal
if (focal == -1)
{
// 创建EXIF读取器,检查是否有有效的EXIF元数据
std::unique_ptr<Exif_IO> exifReader(new Exif_IO_EasyExif);
exifReader->open( sImageFilename );
const bool bHaveValidExifMetadata =
exifReader->doesHaveExifInfo()
&& !exifReader->getModel().empty()
&& !exifReader->getBrand().empty();
if (bHaveValidExifMetadata) // 如果包含EXIF
{
// 但是如果EXIF中丢失focal值
if (exifReader->getFocal() == 0.0f)
{
error_report_stream
<< stlplus::basename_part(sImageFilename) << ": Focal length is missing." << "\n";
focal = -1.0;
}
else
// 在数据库中查找相机模型
{
const std::string sCamModel = exifReader->getBrand() + " " + exifReader->getModel();
Datasheet datasheet;
if ( getInfo( sCamModel, vec_database, datasheet ))
{
// The camera model was found in the database so we can compute it's approximated focal length
const double ccdw = datasheet.sensorSize_;
// 使用数据库中存储的传感器尺寸(datasheet.sensorSize_)和图像的较大维度(std::max(width, height))以及EXIF数据中的焦距来计算近似焦距。
focal = std::max ( width, height ) * exifReader->getFocal() / ccdw;
}
else
{
error_report_stream
<< stlplus::basename_part(sImageFilename)
<< "\" model \"" << sCamModel << "\" doesn't exist in the database" << "\n"
<< "Please consider add your camera model and sensor width in the database." << "\n";
}
}
}
}
// Build intrinsic parameter related to the view
// 获取相机模型,构建与视图相关的内在参数
std::shared_ptr<IntrinsicBase> intrinsic;
if (focal > 0 && ppx > 0 && ppy > 0 && width > 0 && height > 0)
{
// Create the desired camera type
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:
OPENMVG_LOG_ERROR << "Error: unknown camera model: " << (int) e_User_camera_model;
return EXIT_FAILURE;
}
}
// Build the view corresponding to the image
// 构建视图 参数:文件名,GPS定位方法,是否使用姿态先验信息
Vec3 pose_center;
// 获取GPS位置
if (getGPS(sImageFilename, i_GPS_XYZ_method, pose_center) && b_Use_pose_prior)
{
// Views的子类,可以选择是否优先旋转,或者优先调整位置
ViewPriors v(*iter_image, views.size(), views.size(), views.size(), width, height);
// Add intrinsic related to the image (if any)
if (!intrinsic)
{
//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;
}
v.b_use_pose_center_ = true;
v.pose_center_ = pose_center;
// prior weights
if (prior_w_info.first == true)
{
v.center_weight_ = prior_w_info.second;
}
// 将处理好的视图保存到容器
views[v.id_view] = std::make_shared<ViewPriors>(v);
}
else
{
View v(*iter_image, views.size(), views.size(), views.size(), width, height);
// Add intrinsic related to the image (if any)
if (!intrinsic)
{
//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;
}
// 同理
views[v.id_view] = std::make_shared<View>(v);
}
}
// Display saved warning & error messages if any.
if (!error_report_stream.str().empty())
{
OPENMVG_LOG_WARNING
<< "Warning & Error messages:\n"
<< error_report_stream.str();
}
// 用于指示是否应该根据相机的共同属性(如内参)来分组相机,对于后续BA有帮助
if (b_Group_camera_model)
{
GroupSharedIntrinsics(sfm_data);
}
// 保存
if (!Save(
sfm_data,
stlplus::create_filespec( sOutputDir, "sfm_data.json" ).c_str(),
ESfM_Data(VIEWS|INTRINSICS)))
{
return EXIT_FAILURE;
}
OPENMVG_LOG_INFO
<< "SfMInit_ImageListing report:\n"
<< "listed #File(s): " << vec_image.size() << "\n"
<< "usable #File(s) listed in sfm_data: " << sfm_data.GetViews().size() << "\n"
<< "usable #Intrinsic(s) listed in sfm_data: " << sfm_data.GetIntrinsics().size();
return EXIT_SUCCESS;
}