139 lines
5.1 KiB
C++
139 lines
5.1 KiB
C++
/* ----------------------------------------------------------------------------
|
|
|
|
* GTSAM Copyright 2010, Georgia Tech Research Corporation,
|
|
* Atlanta, Georgia 30332-0415
|
|
* All Rights Reserved
|
|
* Authors: Frank Dellaert, et al. (see THANKS for the full author list)
|
|
|
|
* See LICENSE for the license information
|
|
* -------------------------------------------------------------------------- */
|
|
|
|
/**
|
|
* @file ShonanAveragingCLI.cpp
|
|
* @brief Run Shonan Rotation Averaging Algorithm on a file or example dataset
|
|
* @author Frank Dellaert
|
|
* @date August, 2020
|
|
*
|
|
* Example usage:
|
|
*
|
|
* Running without arguments will run on tiny 3D example pose3example-grid
|
|
* ./ShonanAveragingCLI
|
|
*
|
|
* Read 2D dataset w10000 (in examples/data) and output to w10000-rotations.g2o
|
|
* ./ShonanAveragingCLI -d 2 -n w10000 -o w10000-rotations.g2o
|
|
*
|
|
* Read 3D dataset sphere25000.txt and output to shonan.g2o (default)
|
|
* ./ShonanAveragingCLI -i spere2500.txt
|
|
*
|
|
* If you prefer using a robust Huber loss, you can add the option "-h true",
|
|
* for instance
|
|
* ./ShonanAveragingCLI -i spere2500.txt -h true
|
|
*/
|
|
|
|
#include <gtsam/base/timing.h>
|
|
#include <gtsam/sfm/ShonanAveraging.h>
|
|
#include <gtsam/slam/InitializePose.h>
|
|
#include <gtsam/slam/dataset.h>
|
|
|
|
#include <boost/program_options.hpp>
|
|
|
|
using namespace std;
|
|
using namespace gtsam;
|
|
namespace po = boost::program_options;
|
|
|
|
/* ************************************************************************* */
|
|
int main(int argc, char* argv[]) {
|
|
string datasetName;
|
|
string inputFile;
|
|
string outputFile;
|
|
int d, seed, pMin;
|
|
bool useHuberLoss;
|
|
po::options_description desc(
|
|
"Shonan Rotation Averaging CLI reads a *pose* graph, extracts the "
|
|
"rotation constraints, and runs the Shonan algorithm.");
|
|
desc.add_options()("help", "Print help message")(
|
|
"named_dataset,n",
|
|
po::value<string>(&datasetName)->default_value("pose3example-grid"),
|
|
"Find and read frome example dataset file")(
|
|
"input_file,i", po::value<string>(&inputFile)->default_value(""),
|
|
"Read pose constraints graph from the specified file")(
|
|
"output_file,o",
|
|
po::value<string>(&outputFile)->default_value("shonan.g2o"),
|
|
"Write solution to the specified file")(
|
|
"dimension,d", po::value<int>(&d)->default_value(3),
|
|
"Optimize over 2D or 3D rotations")(
|
|
"useHuberLoss,h", po::value<bool>(&useHuberLoss)->default_value(false),
|
|
"set True to use Huber loss")("pMin,p",
|
|
po::value<int>(&pMin)->default_value(3),
|
|
"set to use desired rank pMin")(
|
|
"seed,s", po::value<int>(&seed)->default_value(42),
|
|
"Random seed for initial estimate");
|
|
po::variables_map vm;
|
|
po::store(po::command_line_parser(argc, argv).options(desc).run(), vm);
|
|
po::notify(vm);
|
|
|
|
if (vm.count("help")) {
|
|
cout << desc << "\n";
|
|
return 1;
|
|
}
|
|
|
|
// Get input file
|
|
if (inputFile.empty()) {
|
|
if (datasetName.empty()) {
|
|
cout << "You must either specify a named dataset or an input file\n"
|
|
<< desc << endl;
|
|
return 1;
|
|
}
|
|
inputFile = findExampleDataFile(datasetName);
|
|
}
|
|
|
|
// Seed random number generator
|
|
static std::mt19937 rng(seed);
|
|
|
|
NonlinearFactorGraph::shared_ptr inputGraph;
|
|
Values::shared_ptr posesInFile;
|
|
Values poses;
|
|
auto lmParams = LevenbergMarquardtParams::CeresDefaults();
|
|
if (d == 2) {
|
|
cout << "Running Shonan averaging for SO(2) on " << inputFile << endl;
|
|
ShonanAveraging2::Parameters parameters(lmParams);
|
|
parameters.setUseHuber(useHuberLoss);
|
|
ShonanAveraging2 shonan(inputFile, parameters);
|
|
auto initial = shonan.initializeRandomly(rng);
|
|
auto result = shonan.run(initial, pMin);
|
|
|
|
// Parse file again to set up translation problem, adding a prior
|
|
std::tie(inputGraph, posesInFile) = load2D(inputFile);
|
|
auto priorModel = noiseModel::Unit::Create(3);
|
|
inputGraph->addPrior(0, posesInFile->at<Pose2>(0), priorModel);
|
|
|
|
cout << "recovering 2D translations" << endl;
|
|
auto poseGraph = initialize::buildPoseGraph<Pose2>(*inputGraph);
|
|
poses = initialize::computePoses<Pose2>(result.first, &poseGraph);
|
|
} else if (d == 3) {
|
|
cout << "Running Shonan averaging for SO(3) on " << inputFile << endl;
|
|
ShonanAveraging3::Parameters parameters(lmParams);
|
|
parameters.setUseHuber(useHuberLoss);
|
|
ShonanAveraging3 shonan(inputFile, parameters);
|
|
auto initial = shonan.initializeRandomly(rng);
|
|
auto result = shonan.run(initial, pMin);
|
|
|
|
// Parse file again to set up translation problem, adding a prior
|
|
std::tie(inputGraph, posesInFile) = load3D(inputFile);
|
|
auto priorModel = noiseModel::Unit::Create(6);
|
|
inputGraph->addPrior(0, posesInFile->at<Pose3>(0), priorModel);
|
|
|
|
cout << "recovering 3D translations" << endl;
|
|
auto poseGraph = initialize::buildPoseGraph<Pose3>(*inputGraph);
|
|
poses = initialize::computePoses<Pose3>(result.first, &poseGraph);
|
|
} else {
|
|
cout << "Can only run SO(2) or SO(3) averaging\n" << desc << endl;
|
|
return 1;
|
|
}
|
|
cout << "Writing result to " << outputFile << endl;
|
|
writeG2o(*inputGraph, poses, outputFile);
|
|
return 0;
|
|
}
|
|
|
|
/* ************************************************************************* */
|