---恢复内容开始---
在openmvg库中,定义了一个CmdLine类来定义例程的输入参数格式。源文件为.\openMVG\src\third_party\cmdLine\cmdLine.h。
先举个例子来说明一般输入参数格式,选用.\openMVG\src\software\SfM\main_SfMInit_ImageListing.cpp作为例程,对应的可执行文件为openMVG_main_SfMInit_ImageListing,在官网上(https://openmvg.readthedocs.io/en/latest/software/SfM/SfM/#)有该例程用法的介绍:
// Example(3种参数输入方式) $openMVG_main_SfMInit_ImageListing --imageDirectory images --sensorWidthDatabase images/sensor_width_camera_database.txt --outputDirectory matches
$openMVG_main_SfMInit_ImageListing -i images -d images/sensor_width_camera_database.txt -o matches
$openMVG_main_SfMInit_ImageListing --imageDirectory=images --sensorWidthDatabase=images/sensor_width_camera_database.txt --outputDirectory=matches
其中第一种“--”后面接着的是长名标识符(longName如imageDirectory),images为对应参数
第二种“-”后面是短标识符(c如i,d,o等)
下面来分析与这3种输入参数格式相关的源代码。
我们先来看其中的一个类Option,它用于存储参数标识符(上面的-d,-i,-o等)与对应的长标识名(imageDirectory等),比如某个Option类种存储着-d和sensorWidthDatabase ,另一个Option类存储着-i和imageDirectory 。类的定义如下:
/// 类中包含3个数据成员,该类为父类,有两个子类(后面介绍) class Option { public: char c; ///短标识符:i,d,o等 bool used; ///用于说明该参数是否已经被检测到,在解析输入参数时使用 std::string longName; /// 长名标识符:imageDirectory等 Option(char d, std::string name) //构造函数 : c(d), used(false), longName(name) {} virtual ~Option() = default; virtual bool check(int& argc, char* argv[]) = 0; ///检查是否存在有对应的符合格式的参数存在,并解析,具体解析见子类 virtual Option* clone() const = 0; ///< Copy };
OptionField类,是Option的一个子类
template <class T> class OptionField : public Option { public: //构造函数c,name分别对应父类Option种的c,longName;field对应参数比如例子中的输入参数images/sensor_width_camera_database.txt等 //因为输入参数类型不固定,可能是字符串也可能是数字,所以采用模板类 OptionField(char c, T& field, std::string name = "") : Option(c, name), _field(field) {} /// 查找正确的输入参数 bool check(int& argc, char* argv[]) override { //这里的argc,argv形参,并不与主程序中的argc,argv等同 std::string param; int arg = 0; if (std::string("-") + c == argv[0] || (!longName.empty() && std::string("--") + longName == argv[0])) { //对应输入参数格式为:-i ImageDataset_SceauxCastle/images if (argc <= 1) // 或:--imageDirectory ImageDataset_SceauxCastle/images throw std::string("Option ") + argv[0] + " requires argument"; param = argv[1]; arg = 2; //将ImageDataset_SceauxCastle/images赋值給param } else if (std::string(argv[0]).find(std::string("-") + c) == 0) { //对应输入参数格式为:-iImageDataset_SceauxCastle/images param = argv[0] + 2; arg = 1; //将ImageDataset_SceauxCastle/images赋值給param } else if (!longName.empty() && //对应输入参数格式为:--imageDirectory=ImageDataset_SceauxCastle/images std::string(argv[0]).find(std::string("--") + longName + '=') == 0) { size_t size = (std::string("--") + longName + '=').size(); param = std::string(argv[0]).substr(size); arg = 1; //将ImageDataset_SceauxCastle/images赋值給param } if (arg>0) { //arg>0代表检查到了符合格式的输入参数 if (!read_param(param)) //读入检测到的参数,如果不成功,抛出异常 throw std::string("Unable to interpret ") + param + " as argument of " + argv[0]; used = true; //该option对象对应的参数已经检测到 std::rotate(argv, argv + arg, argv + argc); //把从参数argv到argv + argc之间的参数序列看成一个圆,对他们进行旋转,旋转后的圆的第一个元素为argv + arg //作用就是将检测到的参数放在输入参数序列后面 //rotate函数的用法详细介绍见:http://c.biancheng.net/view/609.html /* 比如原始序列为:openMVG_main_SfMInit_ImageListing -i images -d images/sensor_width_camera_database.txt -o matches 假如argv对应-i argv + argc对应images/sensor_width_camera_database.txt arg=2 -i images -d images/sensor_width_camera_database.txt构成待旋转的圆,argv + arg对应的-d为旋转后圆的第一个元素 那么rotate后的新序列为openMVG_main_SfMInit_ImageListing -d images/sensor_width_camera_database.txt -i images -o matches */ argc -= arg; return true; } return false; } /// 将读到的参数param赋值给_field;param为string型,_field类型在OptionField对象中预先定义了,见于make_option函数,也在cmdLine.h文件中 bool read_param(const std::string& param) { std::stringstream str(param); char unused; return !((str >> _field).fail() || !(str >> unused).fail()); } /// Copy Option* clone() const override { return new OptionField<T>(c, _field, longName); } private: T& _field; };
另一个类OptionSwitch用的少,用于是否选用某种方法或者模式,比如:输入参数中如果有-u或者--use_sift参数,就使用sift方法,如果没有就不适用,只有是否两种情况;具体类定义就不做介绍了
Option类check用于匹配单个输入参数,CmdLine类实现所有的输入参数与所有的Option对象进行匹配
/// Command line parsing class CmdLine { std::vector<Option*> opts; //opts中包含所有的必须的或者可选的参数对应的标识符;即该程序需要输入哪些参数 public: /// Destructor ~CmdLine() { std::vector<Option*>::iterator it = opts.begin(); for (; it != opts.end(); ++it) delete *it; } /// Add an option void add(const Option& opt) { //在Option对象向量中添加Option对象 opts.push_back(opt.clone()); } /// ************主要的成员函数************ /// 实现输入参数与程序需求的参数进行匹配 void process(int& argc, char* argv[]) { std::vector<Option*>::iterator it = opts.begin(); for (; it != opts.end(); ++it) (*it)->used = false; for (int i = 1; i<argc;) { if (std::string("--") == argv[i]) { // "--" 为参数结尾标志,不知道这个有什么用 std::rotate(argv + i, argv + i + 1, argv + argc); //旋转,使"--"为序列结尾 --argc; break; } bool found = false; // Find option for (it = opts.begin(); it != opts.end(); ++it) { //将预先定义的Option类的各个对象分别与输入参数进行匹配 int n = argc - i; found = (*it)->check(n, argv + i); //argv + i(argv[1])对应的参数与当前Option对象匹配 if (found) { argc = n + i; //这行也不知道有什么用 break; } } if (!found) { //如果没找到合适的匹配,但是输入的参数开头为"_",则抛出异常未识别标识符 if (std::string(argv[i]).size()>1 && argv[i][0] == '-') { std::istringstream str(argv[i]); float v; if (!(str >> v).eof()) throw std::string("Unrecognized option ") + argv[i]; } ++i; } } } /// Was the option used in last parsing? bool used(char c) const { std::vector<Option*>::const_iterator it = opts.begin(); for (; it != opts.end(); ++it) if ((*it)->c == c) return (*it)->used; assert(false); // Called with non-existent option, probably a bug return false; } };
在openmvg库中例程读入参数的主要流程如下:
#include"cmdLine.h" #include <Eigen/Core> //#include <Eigen/Dense> //#include <Eigen/SparseCore> //#include <Eigen/StdVector> using namespace std; using namespace Eigen; 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 Fish-eye distortion model with 4 distortion coefficients PINHOLE_CAMERA_END, CAMERA_SPHERICAL = PINHOLE_CAMERA_END + 1 }; using Vec3 = Eigen::Vector3d; inline bool split ( const std::string & rhs, const char delim, std::vector<std::string> & items ) { items.clear(); std::stringstream ss(rhs); std::string item; while (std::getline(ss, item, delim)) { items.emplace_back(item); } // return true if the delimiter is present in the input string return rhs.find(delim) != std::string::npos; } /// Check that Kmatrix is a string like "f;0;ppx;0;f;ppy;0;0;1" /// With f,ppx,ppy as valid numerical value bool checkIntrinsicStringValidity(const std::string & Kmatrix, double & focal, double & ppx, double & ppy) { std::vector<std::string> vec_str; split(Kmatrix, ';', vec_str); if (vec_str.size() != 9) { std::cerr << "\n Missing ';' character" << std::endl; return false; } // Check that all K matrix value are valid numbers 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)) { std::cerr << "\n Used an invalid not a number character" << std::endl; return false; } if (i == 0) focal = readvalue; if (i == 2) ppx = readvalue; if (i == 5) ppy = readvalue; } return true; } int main() { int argc = 5; char* argv[] = { "cmdline_study","-iimages", "-dsensor_width_camera_database.txt", "-ocmdline_study_outputdir", "-k2905.88; 0; 1416; \ 0; 2905.88; 1064; \ 0; 0; 1" }; //1.定义一个CmdLine类 CmdLine cmd; std::string sImageDir, //2.定义输入参数对应数据类型,有的为string,有的为double等 sfileDatabase, sOutputDir, sKmatrix; std::string sPriorWeights; std::pair<bool, Vec3> prior_w_info(false, Vec3(1.0, 1.0, 1.0)); 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; //3.在CmdLine类中添加option对象(即需要输入的参数标识符) 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")); //4.进行输入参数与标识符匹配(CmdLine::process()) try { if (argc == 1) throw std::string("Invalid command line parameter."); cmd.process(argc, argv); } catch (const std::string& s) { std::cerr << "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\n" << std::endl; std::cerr << s << std::endl; return EXIT_FAILURE; } system("pause"); return 0; }