1275 lines
42 KiB
C++
1275 lines
42 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 dataset.cpp
|
|
* @date Jan 22, 2010
|
|
* @author Kai Ni
|
|
* @author Luca Carlone
|
|
* @author Frank Dellaert
|
|
* @author Varun Agrawal
|
|
* @brief utility functions for loading datasets
|
|
*/
|
|
|
|
#include <gtsam/sam/BearingRangeFactor.h>
|
|
#include <gtsam/slam/BetweenFactor.h>
|
|
#include <gtsam/slam/dataset.h>
|
|
|
|
#include <gtsam/geometry/Point3.h>
|
|
#include <gtsam/geometry/Pose2.h>
|
|
#include <gtsam/geometry/Rot3.h>
|
|
|
|
#include <gtsam/nonlinear/NonlinearFactor.h>
|
|
#include <gtsam/nonlinear/Values-inl.h>
|
|
|
|
#include <gtsam/linear/Sampler.h>
|
|
|
|
#include <gtsam/inference/FactorGraph.h>
|
|
#include <gtsam/inference/Symbol.h>
|
|
|
|
#include <gtsam/base/GenericValue.h>
|
|
#include <gtsam/base/Lie.h>
|
|
#include <gtsam/base/Matrix.h>
|
|
#include <gtsam/base/Value.h>
|
|
#include <gtsam/base/Vector.h>
|
|
#include <gtsam/base/types.h>
|
|
|
|
#include <boost/assign/list_inserter.hpp>
|
|
#include <boost/filesystem/operations.hpp>
|
|
#include <boost/filesystem/path.hpp>
|
|
#include <boost/optional.hpp>
|
|
|
|
#include <cmath>
|
|
#include <fstream>
|
|
#include <iostream>
|
|
#include <stdexcept>
|
|
|
|
using namespace std;
|
|
namespace fs = boost::filesystem;
|
|
using namespace gtsam::symbol_shorthand;
|
|
|
|
#define LINESIZE 81920
|
|
|
|
namespace gtsam {
|
|
|
|
/* ************************************************************************* */
|
|
string findExampleDataFile(const string &name) {
|
|
// Search source tree and installed location
|
|
vector<string> rootsToSearch;
|
|
|
|
// Constants below are defined by CMake, see gtsam/gtsam/CMakeLists.txt
|
|
rootsToSearch.push_back(GTSAM_SOURCE_TREE_DATASET_DIR);
|
|
rootsToSearch.push_back(GTSAM_INSTALLED_DATASET_DIR);
|
|
|
|
// Search for filename as given, and with .graph and .txt extensions
|
|
vector<string> namesToSearch;
|
|
namesToSearch.push_back(name);
|
|
namesToSearch.push_back(name + ".graph");
|
|
namesToSearch.push_back(name + ".txt");
|
|
namesToSearch.push_back(name + ".out");
|
|
namesToSearch.push_back(name + ".xml");
|
|
|
|
// Find first name that exists
|
|
for (const fs::path root : rootsToSearch) {
|
|
for (const fs::path name : namesToSearch) {
|
|
if (fs::is_regular_file(root / name))
|
|
return (root / name).string();
|
|
}
|
|
}
|
|
|
|
// If we did not return already, then we did not find the file
|
|
throw invalid_argument("gtsam::findExampleDataFile could not find a matching "
|
|
"file in\n" GTSAM_SOURCE_TREE_DATASET_DIR
|
|
" or\n" GTSAM_INSTALLED_DATASET_DIR " named\n" +
|
|
name + ", " + name + ".graph, or " + name + ".txt");
|
|
}
|
|
|
|
/* ************************************************************************* */
|
|
string createRewrittenFileName(const string &name) {
|
|
// Search source tree and installed location
|
|
if (!exists(fs::path(name))) {
|
|
throw invalid_argument(
|
|
"gtsam::createRewrittenFileName could not find a matching file in\n" +
|
|
name);
|
|
}
|
|
|
|
fs::path p(name);
|
|
fs::path newpath = fs::path(p.parent_path().string()) /
|
|
fs::path(p.stem().string() + "-rewritten.txt");
|
|
|
|
return newpath.string();
|
|
}
|
|
|
|
/* ************************************************************************* */
|
|
// Parse a file by calling the parse(is, tag) function for every line.
|
|
template <typename T>
|
|
using Parser =
|
|
std::function<boost::optional<T>(istream &is, const string &tag)>;
|
|
|
|
template <typename T>
|
|
static void parseLines(const string &filename, Parser<T> parse) {
|
|
ifstream is(filename.c_str());
|
|
if (!is)
|
|
throw invalid_argument("parse: can not find file " + filename);
|
|
while (!is.eof()) {
|
|
string tag;
|
|
is >> tag;
|
|
parse(is, tag); // ignore return value
|
|
is.ignore(LINESIZE, '\n');
|
|
}
|
|
}
|
|
|
|
/* ************************************************************************* */
|
|
// Parse types T into a Key-indexed map
|
|
template <typename T>
|
|
map<Key, T> parseToMap(const string &filename, Parser<pair<Key, T>> parse,
|
|
Key maxKey) {
|
|
ifstream is(filename.c_str());
|
|
if (!is)
|
|
throw invalid_argument("parse: can not find file " + filename);
|
|
|
|
map<Key, T> result;
|
|
Parser<pair<Key, T>> emplace = [&result, parse](istream &is,
|
|
const string &tag) {
|
|
if (auto t = parse(is, tag))
|
|
result.emplace(*t);
|
|
return boost::none;
|
|
};
|
|
parseLines(filename, emplace);
|
|
return result;
|
|
}
|
|
|
|
/* ************************************************************************* */
|
|
// Parse a file and push results on a vector
|
|
// TODO(frank): do we need this *and* parseMeasurements?
|
|
template <typename T>
|
|
static vector<T> parseToVector(const string &filename, Parser<T> parse) {
|
|
vector<T> result;
|
|
Parser<T> add = [&result, parse](istream &is, const string &tag) {
|
|
if (auto t = parse(is, tag))
|
|
result.push_back(*t);
|
|
return boost::none;
|
|
};
|
|
parseLines(filename, add);
|
|
return result;
|
|
}
|
|
|
|
/* ************************************************************************* */
|
|
boost::optional<IndexedPose> parseVertexPose(istream &is, const string &tag) {
|
|
if ((tag == "VERTEX2") || (tag == "VERTEX_SE2") || (tag == "VERTEX")) {
|
|
Key id;
|
|
double x, y, yaw;
|
|
is >> id >> x >> y >> yaw;
|
|
return IndexedPose(id, Pose2(x, y, yaw));
|
|
} else {
|
|
return boost::none;
|
|
}
|
|
}
|
|
|
|
template <>
|
|
std::map<Key, Pose2> parseVariables<Pose2>(const std::string &filename,
|
|
Key maxKey) {
|
|
return parseToMap<Pose2>(filename, parseVertexPose, maxKey);
|
|
}
|
|
|
|
/* ************************************************************************* */
|
|
boost::optional<IndexedLandmark> parseVertexLandmark(istream &is,
|
|
const string &tag) {
|
|
if (tag == "VERTEX_XY") {
|
|
Key id;
|
|
double x, y;
|
|
is >> id >> x >> y;
|
|
return IndexedLandmark(id, Point2(x, y));
|
|
} else {
|
|
return boost::none;
|
|
}
|
|
}
|
|
|
|
template <>
|
|
std::map<Key, Point2> parseVariables<Point2>(const std::string &filename,
|
|
Key maxKey) {
|
|
return parseToMap<Point2>(filename, parseVertexLandmark, maxKey);
|
|
}
|
|
|
|
/* ************************************************************************* */
|
|
// Interpret noise parameters according to flags
|
|
static SharedNoiseModel
|
|
createNoiseModel(const Vector6 v, bool smart, NoiseFormat noiseFormat,
|
|
KernelFunctionType kernelFunctionType) {
|
|
if (noiseFormat == NoiseFormatAUTO) {
|
|
// Try to guess covariance matrix layout
|
|
if (v(0) != 0.0 && v(1) == 0.0 && v(2) != 0.0 && //
|
|
v(3) != 0.0 && v(4) == 0.0 && v(5) == 0.0) {
|
|
noiseFormat = NoiseFormatGRAPH;
|
|
} else if (v(0) != 0.0 && v(1) == 0.0 && v(2) == 0.0 && //
|
|
v(3) != 0.0 && v(4) == 0.0 && v(5) != 0.0) {
|
|
noiseFormat = NoiseFormatCOV;
|
|
} else {
|
|
throw std::invalid_argument(
|
|
"load2D: unrecognized covariance matrix format in dataset file. "
|
|
"Please specify the noise format.");
|
|
}
|
|
}
|
|
|
|
// Read matrix and check that diagonal entries are non-zero
|
|
Matrix3 M;
|
|
switch (noiseFormat) {
|
|
case NoiseFormatG2O:
|
|
case NoiseFormatCOV:
|
|
// i.e., [v(0) v(1) v(2);
|
|
// v(1)' v(3) v(4);
|
|
// v(2)' v(4)' v(5) ]
|
|
if (v(0) == 0.0 || v(3) == 0.0 || v(5) == 0.0)
|
|
throw runtime_error(
|
|
"load2D::readNoiseModel looks like this is not G2O matrix order");
|
|
M << v(0), v(1), v(2), v(1), v(3), v(4), v(2), v(4), v(5);
|
|
break;
|
|
case NoiseFormatTORO:
|
|
case NoiseFormatGRAPH:
|
|
// http://www.openslam.org/toro.html
|
|
// inf_ff inf_fs inf_ss inf_rr inf_fr inf_sr
|
|
// i.e., [v(0) v(1) v(4);
|
|
// v(1)' v(2) v(5);
|
|
// v(4)' v(5)' v(3) ]
|
|
if (v(0) == 0.0 || v(2) == 0.0 || v(3) == 0.0)
|
|
throw invalid_argument(
|
|
"load2D::readNoiseModel looks like this is not TORO matrix order");
|
|
M << v(0), v(1), v(4), v(1), v(2), v(5), v(4), v(5), v(3);
|
|
break;
|
|
default:
|
|
throw runtime_error("load2D: invalid noise format");
|
|
}
|
|
|
|
// Now, create a Gaussian noise model
|
|
// The smart flag will try to detect a simpler model, e.g., unit
|
|
SharedNoiseModel model;
|
|
switch (noiseFormat) {
|
|
case NoiseFormatG2O:
|
|
case NoiseFormatTORO:
|
|
// In both cases, what is stored in file is the information matrix
|
|
model = noiseModel::Gaussian::Information(M, smart);
|
|
break;
|
|
case NoiseFormatGRAPH:
|
|
case NoiseFormatCOV:
|
|
// These cases expect covariance matrix
|
|
model = noiseModel::Gaussian::Covariance(M, smart);
|
|
break;
|
|
default:
|
|
throw invalid_argument("load2D: invalid noise format");
|
|
}
|
|
|
|
switch (kernelFunctionType) {
|
|
case KernelFunctionTypeNONE:
|
|
return model;
|
|
break;
|
|
case KernelFunctionTypeHUBER:
|
|
return noiseModel::Robust::Create(
|
|
noiseModel::mEstimator::Huber::Create(1.345), model);
|
|
break;
|
|
case KernelFunctionTypeTUKEY:
|
|
return noiseModel::Robust::Create(
|
|
noiseModel::mEstimator::Tukey::Create(4.6851), model);
|
|
break;
|
|
default:
|
|
throw invalid_argument("load2D: invalid kernel function type");
|
|
}
|
|
}
|
|
|
|
/* ************************************************************************* */
|
|
boost::optional<IndexedEdge> parseEdge(istream &is, const string &tag) {
|
|
if ((tag == "EDGE2") || (tag == "EDGE") || (tag == "EDGE_SE2") ||
|
|
(tag == "ODOMETRY")) {
|
|
|
|
Key id1, id2;
|
|
double x, y, yaw;
|
|
is >> id1 >> id2 >> x >> y >> yaw;
|
|
return IndexedEdge(pair<Key, Key>(id1, id2), Pose2(x, y, yaw));
|
|
} else {
|
|
return boost::none;
|
|
}
|
|
}
|
|
|
|
/* ************************************************************************* */
|
|
// Measurement parsers are implemented as a functor, as they has several options
|
|
// to filter and create the measurement model.
|
|
template <typename T> struct ParseMeasurement;
|
|
|
|
/* ************************************************************************* */
|
|
// Converting from Measurement to BetweenFactor is generic
|
|
template <typename T> struct ParseFactor : ParseMeasurement<T> {
|
|
ParseFactor(const ParseMeasurement<T> &parent)
|
|
: ParseMeasurement<T>(parent) {}
|
|
|
|
// We parse a measurement then convert
|
|
typename boost::optional<typename BetweenFactor<T>::shared_ptr>
|
|
operator()(istream &is, const string &tag) {
|
|
if (auto m = ParseMeasurement<T>::operator()(is, tag))
|
|
return boost::make_shared<BetweenFactor<T>>(
|
|
m->key1(), m->key2(), m->measured(), m->noiseModel());
|
|
else
|
|
return boost::none;
|
|
}
|
|
};
|
|
|
|
/* ************************************************************************* */
|
|
// Pose2 measurement parser
|
|
template <> struct ParseMeasurement<Pose2> {
|
|
// The arguments
|
|
boost::shared_ptr<Sampler> sampler;
|
|
Key maxKey;
|
|
|
|
// For noise model creation
|
|
bool smart;
|
|
NoiseFormat noiseFormat;
|
|
KernelFunctionType kernelFunctionType;
|
|
|
|
// If this is not null, will use instead of parsed model:
|
|
SharedNoiseModel model;
|
|
|
|
// The actual parser
|
|
boost::optional<BinaryMeasurement<Pose2>> operator()(istream &is,
|
|
const string &tag) {
|
|
auto edge = parseEdge(is, tag);
|
|
if (!edge)
|
|
return boost::none;
|
|
|
|
// parse noise model
|
|
Vector6 v;
|
|
is >> v(0) >> v(1) >> v(2) >> v(3) >> v(4) >> v(5);
|
|
|
|
// optional filter
|
|
Key id1, id2;
|
|
tie(id1, id2) = edge->first;
|
|
if (maxKey && (id1 >= maxKey || id2 >= maxKey))
|
|
return boost::none;
|
|
|
|
// Get pose and optionally add noise
|
|
Pose2 &pose = edge->second;
|
|
if (sampler)
|
|
pose = pose.retract(sampler->sample());
|
|
|
|
// emplace measurement
|
|
auto modelFromFile =
|
|
createNoiseModel(v, smart, noiseFormat, kernelFunctionType);
|
|
return BinaryMeasurement<Pose2>(id1, id2, pose,
|
|
model ? model : modelFromFile);
|
|
}
|
|
};
|
|
|
|
/* ************************************************************************* */
|
|
// Create a sampler to corrupt a measurement
|
|
boost::shared_ptr<Sampler> createSampler(const SharedNoiseModel &model) {
|
|
auto noise = boost::dynamic_pointer_cast<noiseModel::Diagonal>(model);
|
|
if (!noise)
|
|
throw invalid_argument("gtsam::load: invalid noise model for adding noise"
|
|
"(current version assumes diagonal noise model)!");
|
|
return boost::shared_ptr<Sampler>(new Sampler(noise));
|
|
}
|
|
|
|
/* ************************************************************************* */
|
|
// Implementation of parseMeasurements for Pose2
|
|
template <>
|
|
std::vector<BinaryMeasurement<Pose2>>
|
|
parseMeasurements(const std::string &filename,
|
|
const noiseModel::Diagonal::shared_ptr &model, Key maxKey) {
|
|
ParseMeasurement<Pose2> parse{model ? createSampler(model) : nullptr, maxKey,
|
|
true, NoiseFormatAUTO, KernelFunctionTypeNONE};
|
|
return parseToVector<BinaryMeasurement<Pose2>>(filename, parse);
|
|
}
|
|
|
|
/* ************************************************************************* */
|
|
// Implementation of parseMeasurements for Rot2, which converts from Pose2
|
|
|
|
// Extract Rot2 measurement from Pose2 measurement
|
|
static BinaryMeasurement<Rot2> convert(const BinaryMeasurement<Pose2> &p) {
|
|
auto gaussian =
|
|
boost::dynamic_pointer_cast<noiseModel::Gaussian>(p.noiseModel());
|
|
if (!gaussian)
|
|
throw std::invalid_argument(
|
|
"parseMeasurements<Rot2> can only convert Pose2 measurements "
|
|
"with Gaussian noise models.");
|
|
const Matrix3 M = gaussian->covariance();
|
|
return BinaryMeasurement<Rot2>(p.key1(), p.key2(), p.measured().rotation(),
|
|
noiseModel::Isotropic::Variance(1, M(2, 2)));
|
|
}
|
|
|
|
template <>
|
|
std::vector<BinaryMeasurement<Rot2>>
|
|
parseMeasurements(const std::string &filename,
|
|
const noiseModel::Diagonal::shared_ptr &model, Key maxKey) {
|
|
auto poses = parseMeasurements<Pose2>(filename, model, maxKey);
|
|
std::vector<BinaryMeasurement<Rot2>> result;
|
|
result.reserve(poses.size());
|
|
for (const auto &p : poses)
|
|
result.push_back(convert(p));
|
|
return result;
|
|
}
|
|
|
|
/* ************************************************************************* */
|
|
// Implementation of parseFactors for Pose2
|
|
template <>
|
|
std::vector<BetweenFactor<Pose2>::shared_ptr>
|
|
parseFactors<Pose2>(const std::string &filename,
|
|
const noiseModel::Diagonal::shared_ptr &model, Key maxKey) {
|
|
ParseFactor<Pose2> parse({model ? createSampler(model) : nullptr, maxKey,
|
|
true, NoiseFormatAUTO, KernelFunctionTypeNONE,
|
|
nullptr});
|
|
return parseToVector<BetweenFactor<Pose2>::shared_ptr>(filename, parse);
|
|
}
|
|
|
|
/* ************************************************************************* */
|
|
using BearingRange2D = BearingRange<Pose2, Point2>;
|
|
|
|
template <> struct ParseMeasurement<BearingRange2D> {
|
|
// arguments
|
|
Key maxKey;
|
|
|
|
// The actual parser
|
|
boost::optional<BinaryMeasurement<BearingRange2D>>
|
|
operator()(istream &is, const string &tag) {
|
|
if (tag != "BR" && tag != "LANDMARK")
|
|
return boost::none;
|
|
|
|
Key id1, id2;
|
|
is >> id1 >> id2;
|
|
double bearing, range, bearing_std, range_std;
|
|
|
|
if (tag == "BR") {
|
|
is >> bearing >> range >> bearing_std >> range_std;
|
|
}
|
|
|
|
// A landmark measurement, converted to bearing-range
|
|
if (tag == "LANDMARK") {
|
|
double lmx, lmy;
|
|
double v1, v2, v3;
|
|
|
|
is >> lmx >> lmy >> v1 >> v2 >> v3;
|
|
|
|
// Convert x,y to bearing,range
|
|
bearing = atan2(lmy, lmx);
|
|
range = sqrt(lmx * lmx + lmy * lmy);
|
|
|
|
// In our experience, the x-y covariance on landmark sightings is not
|
|
// very good, so assume it describes the uncertainty at a range of 10m,
|
|
// and convert that to bearing/range uncertainty.
|
|
if (std::abs(v1 - v3) < 1e-4) {
|
|
bearing_std = sqrt(v1 / 10.0);
|
|
range_std = sqrt(v1);
|
|
} else {
|
|
// TODO(frank): we are ignoring the non-uniform covariance
|
|
bearing_std = 1;
|
|
range_std = 1;
|
|
}
|
|
}
|
|
|
|
// optional filter
|
|
if (maxKey && id1 >= maxKey)
|
|
return boost::none;
|
|
|
|
// Create noise model
|
|
auto measurementNoise = noiseModel::Diagonal::Sigmas(
|
|
(Vector(2) << bearing_std, range_std).finished());
|
|
|
|
return BinaryMeasurement<BearingRange2D>(
|
|
id1, L(id2), BearingRange2D(bearing, range), measurementNoise);
|
|
}
|
|
};
|
|
|
|
/* ************************************************************************* */
|
|
GraphAndValues load2D(const string &filename, SharedNoiseModel model,
|
|
Key maxKey, bool addNoise, bool smart,
|
|
NoiseFormat noiseFormat,
|
|
KernelFunctionType kernelFunctionType) {
|
|
|
|
auto graph = boost::make_shared<NonlinearFactorGraph>();
|
|
auto initial = boost::make_shared<Values>();
|
|
|
|
// TODO(frank): do a single pass
|
|
const auto poses = parseVariables<Pose2>(filename, maxKey);
|
|
for (const auto &key_pose : poses) {
|
|
initial->insert(key_pose.first, key_pose.second);
|
|
}
|
|
|
|
const auto landmarks = parseVariables<Point2>(filename, maxKey);
|
|
for (const auto &key_landmark : landmarks) {
|
|
initial->insert(L(key_landmark.first), key_landmark.second);
|
|
}
|
|
|
|
// Create parser for Pose2 and bearing/range
|
|
ParseFactor<Pose2> parseBetweenFactor(
|
|
{addNoise ? createSampler(model) : nullptr, maxKey, smart, noiseFormat,
|
|
kernelFunctionType, model});
|
|
ParseMeasurement<BearingRange2D> parseBearingRange{maxKey};
|
|
|
|
Parser<int> parse = [&](istream &is, const string &tag) {
|
|
if (auto f = parseBetweenFactor(is, tag)) {
|
|
graph->push_back(*f);
|
|
|
|
// Insert vertices if pure odometry file
|
|
Key key1 = (*f)->key1(), key2 = (*f)->key2();
|
|
if (!initial->exists(key1))
|
|
initial->insert(key1, Pose2());
|
|
if (!initial->exists(key2))
|
|
initial->insert(key2, initial->at<Pose2>(key1) * (*f)->measured());
|
|
} else if (auto m = parseBearingRange(is, tag)) {
|
|
Key key1 = m->key1(), key2 = m->key2();
|
|
BearingRange2D br = m->measured();
|
|
graph->emplace_shared<BearingRangeFactor<Pose2, Point2>>(key1, key2, br,
|
|
m->noiseModel());
|
|
|
|
// Insert poses or points if they do not exist yet
|
|
if (!initial->exists(key1))
|
|
initial->insert(key1, Pose2());
|
|
if (!initial->exists(key2)) {
|
|
Pose2 pose = initial->at<Pose2>(key1);
|
|
Point2 local = br.bearing() * Point2(br.range(), 0);
|
|
Point2 global = pose.transformFrom(local);
|
|
initial->insert(key2, global);
|
|
}
|
|
}
|
|
return 0;
|
|
};
|
|
|
|
parseLines<int>(filename, parse);
|
|
|
|
return make_pair(graph, initial);
|
|
}
|
|
|
|
/* ************************************************************************* */
|
|
GraphAndValues load2D(pair<string, SharedNoiseModel> dataset, Key maxKey,
|
|
bool addNoise, bool smart, NoiseFormat noiseFormat,
|
|
KernelFunctionType kernelFunctionType) {
|
|
return load2D(dataset.first, dataset.second, maxKey, addNoise, smart,
|
|
noiseFormat, kernelFunctionType);
|
|
}
|
|
|
|
/* ************************************************************************* */
|
|
GraphAndValues load2D_robust(const string &filename,
|
|
noiseModel::Base::shared_ptr &model, Key maxKey) {
|
|
return load2D(filename, model, maxKey);
|
|
}
|
|
|
|
/* ************************************************************************* */
|
|
void save2D(const NonlinearFactorGraph &graph, const Values &config,
|
|
const noiseModel::Diagonal::shared_ptr model,
|
|
const string &filename) {
|
|
|
|
fstream stream(filename.c_str(), fstream::out);
|
|
|
|
// save poses
|
|
for (const Values::ConstKeyValuePair key_value : config) {
|
|
const Pose2 &pose = key_value.value.cast<Pose2>();
|
|
stream << "VERTEX2 " << key_value.key << " " << pose.x() << " " << pose.y()
|
|
<< " " << pose.theta() << endl;
|
|
}
|
|
|
|
// save edges
|
|
// TODO(frank): why don't we use model in factor?
|
|
Matrix3 R = model->R();
|
|
Matrix3 RR = R.transpose() * R;
|
|
for (auto f : graph) {
|
|
auto factor = boost::dynamic_pointer_cast<BetweenFactor<Pose2>>(f);
|
|
if (!factor)
|
|
continue;
|
|
|
|
const Pose2 pose = factor->measured().inverse();
|
|
stream << "EDGE2 " << factor->key2() << " " << factor->key1() << " "
|
|
<< pose.x() << " " << pose.y() << " " << pose.theta() << " "
|
|
<< RR(0, 0) << " " << RR(0, 1) << " " << RR(1, 1) << " " << RR(2, 2)
|
|
<< " " << RR(0, 2) << " " << RR(1, 2) << endl;
|
|
}
|
|
|
|
stream.close();
|
|
}
|
|
|
|
/* ************************************************************************* */
|
|
GraphAndValues readG2o(const string &g2oFile, const bool is3D,
|
|
KernelFunctionType kernelFunctionType) {
|
|
if (is3D) {
|
|
return load3D(g2oFile);
|
|
} else {
|
|
// just call load2D
|
|
Key maxKey = 0;
|
|
bool addNoise = false;
|
|
bool smart = true;
|
|
return load2D(g2oFile, SharedNoiseModel(), maxKey, addNoise, smart,
|
|
NoiseFormatG2O, kernelFunctionType);
|
|
}
|
|
}
|
|
|
|
/* ************************************************************************* */
|
|
void writeG2o(const NonlinearFactorGraph &graph, const Values &estimate,
|
|
const string &filename) {
|
|
fstream stream(filename.c_str(), fstream::out);
|
|
|
|
// save 2D poses
|
|
for (const auto key_value : estimate) {
|
|
auto p = dynamic_cast<const GenericValue<Pose2> *>(&key_value.value);
|
|
if (!p)
|
|
continue;
|
|
const Pose2 &pose = p->value();
|
|
stream << "VERTEX_SE2 " << key_value.key << " " << pose.x() << " "
|
|
<< pose.y() << " " << pose.theta() << endl;
|
|
}
|
|
|
|
// save 3D poses
|
|
for (const auto key_value : estimate) {
|
|
auto p = dynamic_cast<const GenericValue<Pose3> *>(&key_value.value);
|
|
if (!p)
|
|
continue;
|
|
const Pose3 &pose = p->value();
|
|
const Point3 t = pose.translation();
|
|
const auto q = pose.rotation().toQuaternion();
|
|
stream << "VERTEX_SE3:QUAT " << key_value.key << " " << t.x() << " "
|
|
<< t.y() << " " << t.z() << " " << q.x() << " " << q.y() << " "
|
|
<< q.z() << " " << q.w() << endl;
|
|
}
|
|
|
|
// save 2D landmarks
|
|
for (const auto key_value : estimate) {
|
|
auto p = dynamic_cast<const GenericValue<Point2> *>(&key_value.value);
|
|
if (!p)
|
|
continue;
|
|
const Point2 &point = p->value();
|
|
stream << "VERTEX_XY " << key_value.key << " " << point.x() << " "
|
|
<< point.y() << endl;
|
|
}
|
|
|
|
// save 3D landmarks
|
|
for (const auto key_value : estimate) {
|
|
auto p = dynamic_cast<const GenericValue<Point3> *>(&key_value.value);
|
|
if (!p)
|
|
continue;
|
|
const Point3 &point = p->value();
|
|
stream << "VERTEX_TRACKXYZ " << key_value.key << " " << point.x() << " "
|
|
<< point.y() << " " << point.z() << endl;
|
|
}
|
|
|
|
// save edges (2D or 3D)
|
|
for (const auto &factor_ : graph) {
|
|
boost::shared_ptr<BetweenFactor<Pose2>> factor =
|
|
boost::dynamic_pointer_cast<BetweenFactor<Pose2>>(factor_);
|
|
if (factor) {
|
|
SharedNoiseModel model = factor->noiseModel();
|
|
boost::shared_ptr<noiseModel::Gaussian> gaussianModel =
|
|
boost::dynamic_pointer_cast<noiseModel::Gaussian>(model);
|
|
if (!gaussianModel) {
|
|
model->print("model\n");
|
|
throw invalid_argument("writeG2o: invalid noise model!");
|
|
}
|
|
Matrix3 Info = gaussianModel->R().transpose() * gaussianModel->R();
|
|
Pose2 pose = factor->measured(); //.inverse();
|
|
stream << "EDGE_SE2 " << factor->key1() << " " << factor->key2() << " "
|
|
<< pose.x() << " " << pose.y() << " " << pose.theta();
|
|
for (size_t i = 0; i < 3; i++) {
|
|
for (size_t j = i; j < 3; j++) {
|
|
stream << " " << Info(i, j);
|
|
}
|
|
}
|
|
stream << endl;
|
|
}
|
|
|
|
boost::shared_ptr<BetweenFactor<Pose3>> factor3D =
|
|
boost::dynamic_pointer_cast<BetweenFactor<Pose3>>(factor_);
|
|
|
|
if (factor3D) {
|
|
SharedNoiseModel model = factor3D->noiseModel();
|
|
|
|
boost::shared_ptr<noiseModel::Gaussian> gaussianModel =
|
|
boost::dynamic_pointer_cast<noiseModel::Gaussian>(model);
|
|
if (!gaussianModel) {
|
|
model->print("model\n");
|
|
throw invalid_argument("writeG2o: invalid noise model!");
|
|
}
|
|
Matrix6 Info = gaussianModel->R().transpose() * gaussianModel->R();
|
|
const Pose3 pose3D = factor3D->measured();
|
|
const Point3 p = pose3D.translation();
|
|
const auto q = pose3D.rotation().toQuaternion();
|
|
stream << "EDGE_SE3:QUAT " << factor3D->key1() << " " << factor3D->key2()
|
|
<< " " << p.x() << " " << p.y() << " " << p.z() << " " << q.x()
|
|
<< " " << q.y() << " " << q.z() << " " << q.w();
|
|
|
|
Matrix6 InfoG2o = I_6x6;
|
|
InfoG2o.block<3, 3>(0, 0) = Info.block<3, 3>(3, 3); // cov translation
|
|
InfoG2o.block<3, 3>(3, 3) = Info.block<3, 3>(0, 0); // cov rotation
|
|
InfoG2o.block<3, 3>(0, 3) = Info.block<3, 3>(0, 3); // off diagonal
|
|
InfoG2o.block<3, 3>(3, 0) = Info.block<3, 3>(3, 0); // off diagonal
|
|
|
|
for (size_t i = 0; i < 6; i++) {
|
|
for (size_t j = i; j < 6; j++) {
|
|
stream << " " << InfoG2o(i, j);
|
|
}
|
|
}
|
|
stream << endl;
|
|
}
|
|
}
|
|
stream.close();
|
|
}
|
|
|
|
/* ************************************************************************* */
|
|
// parse quaternion in x,y,z,w order, and normalize to unit length
|
|
istream &operator>>(istream &is, Quaternion &q) {
|
|
double x, y, z, w;
|
|
is >> x >> y >> z >> w;
|
|
const double norm = sqrt(w * w + x * x + y * y + z * z), f = 1.0 / norm;
|
|
q = Quaternion(f * w, f * x, f * y, f * z);
|
|
return is;
|
|
}
|
|
|
|
/* ************************************************************************* */
|
|
// parse Rot3 from roll, pitch, yaw
|
|
istream &operator>>(istream &is, Rot3 &R) {
|
|
double yaw, pitch, roll;
|
|
is >> roll >> pitch >> yaw; // notice order !
|
|
R = Rot3::Ypr(yaw, pitch, roll);
|
|
return is;
|
|
}
|
|
|
|
/* ************************************************************************* */
|
|
boost::optional<pair<Key, Pose3>> parseVertexPose3(istream &is,
|
|
const string &tag) {
|
|
if (tag == "VERTEX3") {
|
|
Key id;
|
|
double x, y, z;
|
|
Rot3 R;
|
|
is >> id >> x >> y >> z >> R;
|
|
return make_pair(id, Pose3(R, {x, y, z}));
|
|
} else if (tag == "VERTEX_SE3:QUAT") {
|
|
Key id;
|
|
double x, y, z;
|
|
Quaternion q;
|
|
is >> id >> x >> y >> z >> q;
|
|
return make_pair(id, Pose3(q, {x, y, z}));
|
|
} else
|
|
return boost::none;
|
|
}
|
|
|
|
template <>
|
|
std::map<Key, Pose3> parseVariables<Pose3>(const std::string &filename,
|
|
Key maxKey) {
|
|
return parseToMap<Pose3>(filename, parseVertexPose3, maxKey);
|
|
}
|
|
|
|
/* ************************************************************************* */
|
|
boost::optional<pair<Key, Point3>> parseVertexPoint3(istream &is,
|
|
const string &tag) {
|
|
if (tag == "VERTEX_TRACKXYZ") {
|
|
Key id;
|
|
double x, y, z;
|
|
is >> id >> x >> y >> z;
|
|
return make_pair(id, Point3(x, y, z));
|
|
} else
|
|
return boost::none;
|
|
}
|
|
|
|
template <>
|
|
std::map<Key, Point3> parseVariables<Point3>(const std::string &filename,
|
|
Key maxKey) {
|
|
return parseToMap<Point3>(filename, parseVertexPoint3, maxKey);
|
|
}
|
|
|
|
/* ************************************************************************* */
|
|
// Parse a symmetric covariance matrix (onlyupper-triangular part is stored)
|
|
istream &operator>>(istream &is, Matrix6 &m) {
|
|
for (size_t i = 0; i < 6; i++)
|
|
for (size_t j = i; j < 6; j++) {
|
|
is >> m(i, j);
|
|
m(j, i) = m(i, j);
|
|
}
|
|
return is;
|
|
}
|
|
|
|
/* ************************************************************************* */
|
|
// Pose3 measurement parser
|
|
template <> struct ParseMeasurement<Pose3> {
|
|
// The arguments
|
|
boost::shared_ptr<Sampler> sampler;
|
|
Key maxKey;
|
|
|
|
// The actual parser
|
|
boost::optional<BinaryMeasurement<Pose3>> operator()(istream &is,
|
|
const string &tag) {
|
|
if (tag != "EDGE3" && tag != "EDGE_SE3:QUAT")
|
|
return boost::none;
|
|
|
|
// parse ids and optionally filter
|
|
Key id1, id2;
|
|
is >> id1 >> id2;
|
|
if (maxKey && (id1 >= maxKey || id2 >= maxKey))
|
|
return boost::none;
|
|
|
|
Matrix6 m;
|
|
if (tag == "EDGE3") {
|
|
double x, y, z;
|
|
Rot3 R;
|
|
is >> x >> y >> z >> R >> m;
|
|
Pose3 T12(R, {x, y, z});
|
|
// optionally add noise
|
|
if (sampler)
|
|
T12 = T12.retract(sampler->sample());
|
|
|
|
return BinaryMeasurement<Pose3>(id1, id2, T12,
|
|
noiseModel::Gaussian::Information(m));
|
|
} else if (tag == "EDGE_SE3:QUAT") {
|
|
double x, y, z;
|
|
Quaternion q;
|
|
is >> x >> y >> z >> q >> m;
|
|
|
|
Pose3 T12(q, {x, y, z});
|
|
// optionally add noise
|
|
if (sampler)
|
|
T12 = T12.retract(sampler->sample());
|
|
|
|
// EDGE_SE3:QUAT stores covariance in t,R order, unlike GTSAM:
|
|
Matrix6 mgtsam;
|
|
mgtsam.block<3, 3>(0, 0) = m.block<3, 3>(3, 3); // cov rotation
|
|
mgtsam.block<3, 3>(3, 3) = m.block<3, 3>(0, 0); // cov translation
|
|
mgtsam.block<3, 3>(0, 3) = m.block<3, 3>(0, 3); // off diagonal
|
|
mgtsam.block<3, 3>(3, 0) = m.block<3, 3>(3, 0); // off diagonal
|
|
SharedNoiseModel model = noiseModel::Gaussian::Information(mgtsam);
|
|
|
|
return BinaryMeasurement<Pose3>(
|
|
id1, id2, T12, noiseModel::Gaussian::Information(mgtsam));
|
|
} else
|
|
return boost::none;
|
|
}
|
|
};
|
|
|
|
/* ************************************************************************* */
|
|
// Implementation of parseMeasurements for Pose3
|
|
template <>
|
|
std::vector<BinaryMeasurement<Pose3>>
|
|
parseMeasurements(const std::string &filename,
|
|
const noiseModel::Diagonal::shared_ptr &model, Key maxKey) {
|
|
ParseMeasurement<Pose3> parse{model ? createSampler(model) : nullptr, maxKey};
|
|
return parseToVector<BinaryMeasurement<Pose3>>(filename, parse);
|
|
}
|
|
|
|
/* ************************************************************************* */
|
|
// Implementation of parseMeasurements for Rot3, which converts from Pose3
|
|
|
|
// Extract Rot3 measurement from Pose3 measurement
|
|
static BinaryMeasurement<Rot3> convert(const BinaryMeasurement<Pose3> &p) {
|
|
auto gaussian =
|
|
boost::dynamic_pointer_cast<noiseModel::Gaussian>(p.noiseModel());
|
|
if (!gaussian)
|
|
throw std::invalid_argument(
|
|
"parseMeasurements<Rot3> can only convert Pose3 measurements "
|
|
"with Gaussian noise models.");
|
|
const Matrix6 M = gaussian->covariance();
|
|
return BinaryMeasurement<Rot3>(
|
|
p.key1(), p.key2(), p.measured().rotation(),
|
|
noiseModel::Gaussian::Covariance(M.block<3, 3>(3, 3), true));
|
|
}
|
|
|
|
template <>
|
|
std::vector<BinaryMeasurement<Rot3>>
|
|
parseMeasurements(const std::string &filename,
|
|
const noiseModel::Diagonal::shared_ptr &model, Key maxKey) {
|
|
auto poses = parseMeasurements<Pose3>(filename, model, maxKey);
|
|
std::vector<BinaryMeasurement<Rot3>> result;
|
|
result.reserve(poses.size());
|
|
for (const auto &p : poses)
|
|
result.push_back(convert(p));
|
|
return result;
|
|
}
|
|
|
|
/* ************************************************************************* */
|
|
// Implementation of parseFactors for Pose3
|
|
template <>
|
|
std::vector<BetweenFactor<Pose3>::shared_ptr>
|
|
parseFactors<Pose3>(const std::string &filename,
|
|
const noiseModel::Diagonal::shared_ptr &model, Key maxKey) {
|
|
ParseFactor<Pose3> parse({model ? createSampler(model) : nullptr, maxKey});
|
|
return parseToVector<BetweenFactor<Pose3>::shared_ptr>(filename, parse);
|
|
}
|
|
|
|
/* ************************************************************************* */
|
|
GraphAndValues load3D(const string &filename) {
|
|
auto graph = boost::make_shared<NonlinearFactorGraph>();
|
|
auto initial = boost::make_shared<Values>();
|
|
|
|
// TODO(frank): do a single pass
|
|
const auto factors = parseFactors<Pose3>(filename);
|
|
for (const auto &factor : factors) {
|
|
graph->push_back(factor);
|
|
}
|
|
|
|
const auto poses = parseVariables<Pose3>(filename);
|
|
for (const auto &key_pose : poses) {
|
|
initial->insert(key_pose.first, key_pose.second);
|
|
}
|
|
|
|
const auto landmarks = parseVariables<Point3>(filename);
|
|
for (const auto &key_landmark : landmarks) {
|
|
initial->insert(key_landmark.first, key_landmark.second);
|
|
}
|
|
|
|
return make_pair(graph, initial);
|
|
}
|
|
|
|
/* ************************************************************************* */
|
|
Rot3 openGLFixedRotation() { // this is due to different convention for
|
|
// cameras in gtsam and openGL
|
|
/* R = [ 1 0 0
|
|
* 0 -1 0
|
|
* 0 0 -1]
|
|
*/
|
|
Matrix3 R_mat = Matrix3::Zero(3, 3);
|
|
R_mat(0, 0) = 1.0;
|
|
R_mat(1, 1) = -1.0;
|
|
R_mat(2, 2) = -1.0;
|
|
return Rot3(R_mat);
|
|
}
|
|
|
|
/* ************************************************************************* */
|
|
Pose3 openGL2gtsam(const Rot3 &R, double tx, double ty, double tz) {
|
|
Rot3 R90 = openGLFixedRotation();
|
|
Rot3 wRc = (R.inverse()).compose(R90);
|
|
|
|
// Our camera-to-world translation wTc = -R'*t
|
|
return Pose3(wRc, R.unrotate(Point3(-tx, -ty, -tz)));
|
|
}
|
|
|
|
/* ************************************************************************* */
|
|
Pose3 gtsam2openGL(const Rot3 &R, double tx, double ty, double tz) {
|
|
Rot3 R90 = openGLFixedRotation();
|
|
Rot3 cRw_openGL = R90.compose(R.inverse());
|
|
Point3 t_openGL = cRw_openGL.rotate(Point3(-tx, -ty, -tz));
|
|
return Pose3(cRw_openGL, t_openGL);
|
|
}
|
|
|
|
/* ************************************************************************* */
|
|
Pose3 gtsam2openGL(const Pose3 &PoseGTSAM) {
|
|
return gtsam2openGL(PoseGTSAM.rotation(), PoseGTSAM.x(), PoseGTSAM.y(),
|
|
PoseGTSAM.z());
|
|
}
|
|
|
|
/* ************************************************************************* */
|
|
bool readBundler(const string &filename, SfmData &data) {
|
|
// Load the data file
|
|
ifstream is(filename.c_str(), ifstream::in);
|
|
if (!is) {
|
|
cout << "Error in readBundler: can not find the file!!" << endl;
|
|
return false;
|
|
}
|
|
|
|
// Ignore the first line
|
|
char aux[500];
|
|
is.getline(aux, 500);
|
|
|
|
// Get the number of camera poses and 3D points
|
|
size_t nrPoses, nrPoints;
|
|
is >> nrPoses >> nrPoints;
|
|
|
|
// Get the information for the camera poses
|
|
for (size_t i = 0; i < nrPoses; i++) {
|
|
// Get the focal length and the radial distortion parameters
|
|
float f, k1, k2;
|
|
is >> f >> k1 >> k2;
|
|
Cal3Bundler K(f, k1, k2);
|
|
|
|
// Get the rotation matrix
|
|
float r11, r12, r13;
|
|
float r21, r22, r23;
|
|
float r31, r32, r33;
|
|
is >> r11 >> r12 >> r13 >> r21 >> r22 >> r23 >> r31 >> r32 >> r33;
|
|
|
|
// Bundler-OpenGL rotation matrix
|
|
Rot3 R(r11, r12, r13, r21, r22, r23, r31, r32, r33);
|
|
|
|
// Check for all-zero R, in which case quit
|
|
if (r11 == 0 && r12 == 0 && r13 == 0) {
|
|
cout << "Error in readBundler: zero rotation matrix for pose " << i
|
|
<< endl;
|
|
return false;
|
|
}
|
|
|
|
// Get the translation vector
|
|
float tx, ty, tz;
|
|
is >> tx >> ty >> tz;
|
|
|
|
Pose3 pose = openGL2gtsam(R, tx, ty, tz);
|
|
|
|
data.cameras.emplace_back(pose, K);
|
|
}
|
|
|
|
// Get the information for the 3D points
|
|
data.tracks.reserve(nrPoints);
|
|
for (size_t j = 0; j < nrPoints; j++) {
|
|
SfmTrack track;
|
|
|
|
// Get the 3D position
|
|
float x, y, z;
|
|
is >> x >> y >> z;
|
|
track.p = Point3(x, y, z);
|
|
|
|
// Get the color information
|
|
float r, g, b;
|
|
is >> r >> g >> b;
|
|
track.r = r / 255.f;
|
|
track.g = g / 255.f;
|
|
track.b = b / 255.f;
|
|
|
|
// Now get the visibility information
|
|
size_t nvisible = 0;
|
|
is >> nvisible;
|
|
|
|
track.measurements.reserve(nvisible);
|
|
track.siftIndices.reserve(nvisible);
|
|
for (size_t k = 0; k < nvisible; k++) {
|
|
size_t cam_idx = 0, point_idx = 0;
|
|
float u, v;
|
|
is >> cam_idx >> point_idx >> u >> v;
|
|
track.measurements.emplace_back(cam_idx, Point2(u, -v));
|
|
track.siftIndices.emplace_back(cam_idx, point_idx);
|
|
}
|
|
|
|
data.tracks.push_back(track);
|
|
}
|
|
|
|
is.close();
|
|
return true;
|
|
}
|
|
|
|
/* ************************************************************************* */
|
|
bool readBAL(const string &filename, SfmData &data) {
|
|
// Load the data file
|
|
ifstream is(filename.c_str(), ifstream::in);
|
|
if (!is) {
|
|
cout << "Error in readBAL: can not find the file!!" << endl;
|
|
return false;
|
|
}
|
|
|
|
// Get the number of camera poses and 3D points
|
|
size_t nrPoses, nrPoints, nrObservations;
|
|
is >> nrPoses >> nrPoints >> nrObservations;
|
|
|
|
data.tracks.resize(nrPoints);
|
|
|
|
// Get the information for the observations
|
|
for (size_t k = 0; k < nrObservations; k++) {
|
|
size_t i = 0, j = 0;
|
|
float u, v;
|
|
is >> i >> j >> u >> v;
|
|
data.tracks[j].measurements.emplace_back(i, Point2(u, -v));
|
|
}
|
|
|
|
// Get the information for the camera poses
|
|
for (size_t i = 0; i < nrPoses; i++) {
|
|
// Get the Rodrigues vector
|
|
float wx, wy, wz;
|
|
is >> wx >> wy >> wz;
|
|
Rot3 R = Rot3::Rodrigues(wx, wy, wz); // BAL-OpenGL rotation matrix
|
|
|
|
// Get the translation vector
|
|
float tx, ty, tz;
|
|
is >> tx >> ty >> tz;
|
|
|
|
Pose3 pose = openGL2gtsam(R, tx, ty, tz);
|
|
|
|
// Get the focal length and the radial distortion parameters
|
|
float f, k1, k2;
|
|
is >> f >> k1 >> k2;
|
|
Cal3Bundler K(f, k1, k2);
|
|
|
|
data.cameras.emplace_back(pose, K);
|
|
}
|
|
|
|
// Get the information for the 3D points
|
|
for (size_t j = 0; j < nrPoints; j++) {
|
|
// Get the 3D position
|
|
float x, y, z;
|
|
is >> x >> y >> z;
|
|
SfmTrack &track = data.tracks[j];
|
|
track.p = Point3(x, y, z);
|
|
track.r = 0.4f;
|
|
track.g = 0.4f;
|
|
track.b = 0.4f;
|
|
}
|
|
|
|
is.close();
|
|
return true;
|
|
}
|
|
|
|
/* ************************************************************************* */
|
|
bool writeBAL(const string &filename, SfmData &data) {
|
|
// Open the output file
|
|
ofstream os;
|
|
os.open(filename.c_str());
|
|
os.precision(20);
|
|
if (!os.is_open()) {
|
|
cout << "Error in writeBAL: can not open the file!!" << endl;
|
|
return false;
|
|
}
|
|
|
|
// Write the number of camera poses and 3D points
|
|
size_t nrObservations = 0;
|
|
for (size_t j = 0; j < data.number_tracks(); j++) {
|
|
nrObservations += data.tracks[j].number_measurements();
|
|
}
|
|
|
|
// Write observations
|
|
os << data.number_cameras() << " " << data.number_tracks() << " "
|
|
<< nrObservations << endl;
|
|
os << endl;
|
|
|
|
for (size_t j = 0; j < data.number_tracks(); j++) { // for each 3D point j
|
|
const SfmTrack &track = data.tracks[j];
|
|
|
|
for (size_t k = 0; k < track.number_measurements();
|
|
k++) { // for each observation of the 3D point j
|
|
size_t i = track.measurements[k].first; // camera id
|
|
double u0 = data.cameras[i].calibration().u0();
|
|
double v0 = data.cameras[i].calibration().v0();
|
|
|
|
if (u0 != 0 || v0 != 0) {
|
|
cout << "writeBAL has not been tested for calibration with nonzero "
|
|
"(u0,v0)"
|
|
<< endl;
|
|
}
|
|
|
|
double pixelBALx = track.measurements[k].second.x() -
|
|
u0; // center of image is the origin
|
|
double pixelBALy = -(track.measurements[k].second.y() -
|
|
v0); // center of image is the origin
|
|
Point2 pixelMeasurement(pixelBALx, pixelBALy);
|
|
os << i /*camera id*/ << " " << j /*point id*/ << " "
|
|
<< pixelMeasurement.x() /*u of the pixel*/ << " "
|
|
<< pixelMeasurement.y() /*v of the pixel*/ << endl;
|
|
}
|
|
}
|
|
os << endl;
|
|
|
|
// Write cameras
|
|
for (size_t i = 0; i < data.number_cameras(); i++) { // for each camera
|
|
Pose3 poseGTSAM = data.cameras[i].pose();
|
|
Cal3Bundler cameraCalibration = data.cameras[i].calibration();
|
|
Pose3 poseOpenGL = gtsam2openGL(poseGTSAM);
|
|
os << Rot3::Logmap(poseOpenGL.rotation()) << endl;
|
|
os << poseOpenGL.translation().x() << endl;
|
|
os << poseOpenGL.translation().y() << endl;
|
|
os << poseOpenGL.translation().z() << endl;
|
|
os << cameraCalibration.fx() << endl;
|
|
os << cameraCalibration.k1() << endl;
|
|
os << cameraCalibration.k2() << endl;
|
|
os << endl;
|
|
}
|
|
|
|
// Write the points
|
|
for (size_t j = 0; j < data.number_tracks(); j++) { // for each 3D point j
|
|
Point3 point = data.tracks[j].p;
|
|
os << point.x() << endl;
|
|
os << point.y() << endl;
|
|
os << point.z() << endl;
|
|
os << endl;
|
|
}
|
|
|
|
os.close();
|
|
return true;
|
|
}
|
|
|
|
bool writeBALfromValues(const string &filename, const SfmData &data,
|
|
Values &values) {
|
|
using Camera = PinholeCamera<Cal3Bundler>;
|
|
SfmData dataValues = data;
|
|
|
|
// Store poses or cameras in SfmData
|
|
size_t nrPoses = values.count<Pose3>();
|
|
if (nrPoses ==
|
|
dataValues.number_cameras()) { // we only estimated camera poses
|
|
for (size_t i = 0; i < dataValues.number_cameras();
|
|
i++) { // for each camera
|
|
Key poseKey = symbol('x', i);
|
|
Pose3 pose = values.at<Pose3>(poseKey);
|
|
Cal3Bundler K = dataValues.cameras[i].calibration();
|
|
Camera camera(pose, K);
|
|
dataValues.cameras[i] = camera;
|
|
}
|
|
} else {
|
|
size_t nrCameras = values.count<Camera>();
|
|
if (nrCameras == dataValues.number_cameras()) { // we only estimated camera
|
|
// poses and calibration
|
|
for (size_t i = 0; i < nrCameras; i++) { // for each camera
|
|
Key cameraKey = i; // symbol('c',i);
|
|
Camera camera = values.at<Camera>(cameraKey);
|
|
dataValues.cameras[i] = camera;
|
|
}
|
|
} else {
|
|
cout << "writeBALfromValues: different number of cameras in "
|
|
"SfM_dataValues (#cameras "
|
|
<< dataValues.number_cameras() << ") and values (#cameras "
|
|
<< nrPoses << ", #poses " << nrCameras << ")!!" << endl;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Store 3D points in SfmData
|
|
size_t nrPoints = values.count<Point3>(),
|
|
nrTracks = dataValues.number_tracks();
|
|
if (nrPoints != nrTracks) {
|
|
cout << "writeBALfromValues: different number of points in "
|
|
"SfM_dataValues (#points= "
|
|
<< nrTracks << ") and values (#points " << nrPoints << ")!!" << endl;
|
|
}
|
|
|
|
for (size_t j = 0; j < nrTracks; j++) { // for each point
|
|
Key pointKey = P(j);
|
|
if (values.exists(pointKey)) {
|
|
Point3 point = values.at<Point3>(pointKey);
|
|
dataValues.tracks[j].p = point;
|
|
} else {
|
|
dataValues.tracks[j].r = 1.0;
|
|
dataValues.tracks[j].g = 0.0;
|
|
dataValues.tracks[j].b = 0.0;
|
|
dataValues.tracks[j].p = Point3(0, 0, 0);
|
|
}
|
|
}
|
|
|
|
// Write SfmData to file
|
|
return writeBAL(filename, dataValues);
|
|
}
|
|
|
|
Values initialCamerasEstimate(const SfmData &db) {
|
|
Values initial;
|
|
size_t i = 0; // NO POINTS: j = 0;
|
|
for (const SfmCamera &camera : db.cameras)
|
|
initial.insert(i++, camera);
|
|
return initial;
|
|
}
|
|
|
|
Values initialCamerasAndPointsEstimate(const SfmData &db) {
|
|
Values initial;
|
|
size_t i = 0, j = 0;
|
|
for (const SfmCamera &camera : db.cameras)
|
|
initial.insert((i++), camera);
|
|
for (const SfmTrack &track : db.tracks)
|
|
initial.insert(P(j++), track.p);
|
|
return initial;
|
|
}
|
|
|
|
#ifdef GTSAM_ALLOW_DEPRECATED_SINCE_V41
|
|
std::map<Key, Pose3> parse3DPoses(const std::string &filename, Key maxKey) {
|
|
return parseVariables<Pose3>(filename, maxKey);
|
|
}
|
|
|
|
std::map<Key, Point3> parse3DLandmarks(const std::string &filename,
|
|
Key maxKey) {
|
|
return parseVariables<Point3>(filename, maxKey);
|
|
}
|
|
|
|
BetweenFactorPose3s
|
|
parse3DFactors(const std::string &filename,
|
|
const noiseModel::Diagonal::shared_ptr &model, Key maxKey) {
|
|
return parseFactors<Pose3>(filename, model, maxKey);
|
|
}
|
|
#endif
|
|
} // namespace gtsam
|