gtsam/gtsam_unstable/slam/SmartRangeFactor.h

188 lines
5.5 KiB
C++

/**
* @file SmartRangeFactor.h
*
* @brief A smart factor for range-only SLAM that does initialization and marginalization
*
* @date Sep 10, 2012
* @author Alex Cunningham
*/
#pragma once
#include <gtsam_unstable/dllexport.h>
#include <gtsam/nonlinear/NonlinearFactor.h>
#include <gtsam/inference/Key.h>
#include <gtsam/geometry/Pose2.h>
#include <list>
#include <map>
#include <stdexcept>
#include <string>
#include <vector>
namespace gtsam {
/**
* Smart factor for range SLAM
* @ingroup slam
*/
class SmartRangeFactor: public NoiseModelFactor {
protected:
struct Circle2 {
Circle2(const Point2& p, double r) :
center(p), radius(r) {
}
Point2 center;
double radius;
};
typedef SmartRangeFactor This;
std::vector<double> measurements_; ///< Range measurements
double variance_; ///< variance on noise
public:
// Provide access to the Matrix& version of unwhitenedError
using NoiseModelFactor::unwhitenedError;
/** Default constructor: don't use directly */
SmartRangeFactor() {
}
/**
* Constructor
* @param s standard deviation of range measurement noise
*/
explicit SmartRangeFactor(double s) :
NoiseModelFactor(noiseModel::Isotropic::Sigma(1, s)), variance_(s * s) {
}
~SmartRangeFactor() override {
}
/// Add a range measurement to a pose with given key.
void addRange(Key key, double measuredRange) {
if(std::find(keys_.begin(), keys_.end(), key) != keys_.end()) {
throw std::invalid_argument(
"SmartRangeFactor::addRange: adding duplicate measurement for key.");
}
keys_.push_back(key);
measurements_.push_back(measuredRange);
size_t n = keys_.size();
// Since we add the errors, the noise variance adds
noiseModel_ = noiseModel::Isotropic::Variance(1, n * variance_);
}
// Testable
/** print */
void print(const std::string& s = "",
const KeyFormatter& keyFormatter = DefaultKeyFormatter) const override {
std::cout << s << "SmartRangeFactor with " << size() << " measurements\n";
NoiseModelFactor::print(s);
}
/** Check if two factors are equal */
bool equals(const NonlinearFactor& f, double tol = 1e-9) const override {
return false;
}
// factor interface
/**
* Triangulate a point from at least three pose-range pairs
* Checks for best pair that includes first point
* Raise runtime_error if not well defined.
*/
Point2 triangulate(const Values& x) const {
// create n circles corresponding to measured range around each pose
std::list<Circle2> circles;
size_t n = size();
for (size_t j = 0; j < n; j++) {
const Pose2& pose = x.at<Pose2>(keys_[j]);
circles.push_back(Circle2(pose.translation(), measurements_[j]));
}
Circle2 circle1 = circles.front();
boost::optional<Point2> best_fh;
auto bestCircle2 = boost::make_optional(false, circle1); // fixes issue #38
// loop over all circles
for (const Circle2& it : circles) {
// distance between circle centers.
double d = distance2(circle1.center, it.center);
if (d < 1e-9)
continue; // skip circles that are in the same location
// Find f and h, the intersection points in normalized circles
boost::optional<Point2> fh = circleCircleIntersection(
circle1.radius / d, it.radius / d);
// Check if this pair is better by checking h = fh->y()
// if h is large, the intersections are well defined.
if (fh && (!best_fh || fh->y() > best_fh->y())) {
best_fh = fh;
bestCircle2 = it;
}
}
// use best fh to find actual intersection points
if (bestCircle2 && best_fh) {
auto bestCircleCenter = bestCircle2->center;
std::list<Point2> intersections =
circleCircleIntersection(circle1.center, bestCircleCenter, best_fh);
// pick winner based on other measurements
double error1 = 0, error2 = 0;
Point2 p1 = intersections.front(), p2 = intersections.back();
for (const Circle2& it : circles) {
error1 += distance2(it.center, p1);
error2 += distance2(it.center, p2);
}
return (error1 < error2) ? p1 : p2;
} else {
throw std::runtime_error("triangulate failed");
}
}
/**
* Error function *without* the NoiseModel, \f$ z-h(x) \f$.
*/
Vector unwhitenedError(const Values& x, OptionalMatrixVecType H = nullptr) const override {
size_t n = size();
if (n < 3) {
if (H) {
// set Jacobians to zero for n<3
for (size_t j = 0; j < n; j++)
(*H)[j] = Matrix::Zero(3, 1);
}
return Z_1x1;
} else {
Vector error = Z_1x1;
// triangulate to get the optimized point
// TODO(dellaert): Should we have a (better?) variant that does this in relative coordinates ?
Point2 optimizedPoint = triangulate(x);
// TODO(dellaert): triangulation should be followed by an optimization given poses
// now evaluate the errors between predicted and measured range
for (size_t j = 0; j < n; j++) {
const Pose2& pose = x.at<Pose2>(keys_[j]);
if (H)
// also calculate 1*3 derivative for each of the n poses
error[0] += pose.range(optimizedPoint, (*H)[j]) - measurements_[j];
else
error[0] += pose.range(optimizedPoint) - measurements_[j];
}
return error;
}
}
/// @return a deep copy of this factor
gtsam::NonlinearFactor::shared_ptr clone() const override {
return boost::static_pointer_cast<gtsam::NonlinearFactor>(
gtsam::NonlinearFactor::shared_ptr(new This(*this)));
}
};
} // \namespace gtsam