commit
f5ff7aa49f
|
@ -18,7 +18,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <gtsam/geometry/OrientedPlane3.h>
|
#include <gtsam/geometry/OrientedPlane3.h>
|
||||||
#include <gtsam/geometry/Point2.h>
|
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
|
||||||
using namespace std;
|
using namespace std;
|
||||||
|
@ -28,11 +28,12 @@ namespace gtsam {
|
||||||
/* ************************************************************************* */
|
/* ************************************************************************* */
|
||||||
void OrientedPlane3::print(const string& s) const {
|
void OrientedPlane3::print(const string& s) const {
|
||||||
Vector4 coeffs = planeCoefficients();
|
Vector4 coeffs = planeCoefficients();
|
||||||
cout << s << " : " << coeffs << endl;
|
cout << s << " : " << coeffs.transpose() << endl;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ************************************************************************* */
|
/* ************************************************************************* */
|
||||||
OrientedPlane3 OrientedPlane3::transform(const Pose3& xr, OptionalJacobian<3, 3> Hp,
|
OrientedPlane3 OrientedPlane3::transform(const Pose3& xr,
|
||||||
|
OptionalJacobian<3, 3> Hp,
|
||||||
OptionalJacobian<3, 6> Hr) const {
|
OptionalJacobian<3, 6> Hr) const {
|
||||||
Matrix23 D_rotated_plane;
|
Matrix23 D_rotated_plane;
|
||||||
Matrix22 D_rotated_pose;
|
Matrix22 D_rotated_pose;
|
||||||
|
@ -42,15 +43,14 @@ OrientedPlane3 OrientedPlane3::transform(const Pose3& xr, OptionalJacobian<3, 3>
|
||||||
double pred_d = n_.unitVector().dot(xr.translation()) + d_;
|
double pred_d = n_.unitVector().dot(xr.translation()) + d_;
|
||||||
|
|
||||||
if (Hr) {
|
if (Hr) {
|
||||||
*Hr = Matrix::Zero(3,6);
|
Hr->setZero();
|
||||||
Hr->block<2, 3>(0, 0) = D_rotated_plane;
|
Hr->block<2, 3>(0, 0) = D_rotated_plane;
|
||||||
Hr->block<1, 3>(2, 3) = unit_vec;
|
Hr->block<1, 3>(2, 3) = unit_vec;
|
||||||
}
|
}
|
||||||
if (Hp) {
|
if (Hp) {
|
||||||
Vector2 hpp = n_.basis().transpose() * xr.translation();
|
Hp->setZero();
|
||||||
*Hp = Z_3x3;
|
|
||||||
Hp->block<2, 2>(0, 0) = D_rotated_pose;
|
Hp->block<2, 2>(0, 0) = D_rotated_pose;
|
||||||
Hp->block<1, 2>(2, 0) = hpp;
|
Hp->block<1, 2>(2, 0) = n_.basis().transpose() * xr.translation();
|
||||||
(*Hp)(2, 2) = 1;
|
(*Hp)(2, 2) = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -58,25 +58,20 @@ OrientedPlane3 OrientedPlane3::transform(const Pose3& xr, OptionalJacobian<3, 3>
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ************************************************************************* */
|
/* ************************************************************************* */
|
||||||
Vector3 OrientedPlane3::error(const OrientedPlane3& plane) const {
|
Vector3 OrientedPlane3::errorVector(const OrientedPlane3& other,
|
||||||
Vector2 n_error = -n_.localCoordinates(plane.n_);
|
OptionalJacobian<3, 3> H1,
|
||||||
return Vector3(n_error(0), n_error(1), d_ - plane.d_);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ************************************************************************* */
|
|
||||||
Vector3 OrientedPlane3::errorVector(const OrientedPlane3& other, OptionalJacobian<3, 3> H1,
|
|
||||||
OptionalJacobian<3, 3> H2) const {
|
OptionalJacobian<3, 3> H2) const {
|
||||||
Matrix22 H_n_error_this, H_n_error_other;
|
Matrix22 H_n_error_this, H_n_error_other;
|
||||||
Vector2 n_error = n_.errorVector(other.normal(), H1 ? &H_n_error_this : 0,
|
Vector2 n_error = n_.errorVector(other.n_, H1 ? &H_n_error_this : 0,
|
||||||
H2 ? &H_n_error_other : 0);
|
H2 ? &H_n_error_other : 0);
|
||||||
|
|
||||||
double d_error = d_ - other.d_;
|
double d_error = d_ - other.d_;
|
||||||
|
|
||||||
if (H1) {
|
if (H1) {
|
||||||
*H1 << H_n_error_this, Vector2::Zero(), 0, 0, 1;
|
*H1 << H_n_error_this, Z_2x1, 0, 0, 1;
|
||||||
}
|
}
|
||||||
if (H2) {
|
if (H2) {
|
||||||
*H2 << H_n_error_other, Vector2::Zero(), 0, 0, -1;
|
*H2 << H_n_error_other, Z_2x1, 0, 0, -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
return Vector3(n_error(0), n_error(1), d_error);
|
return Vector3(n_error(0), n_error(1), d_error);
|
||||||
|
@ -88,7 +83,7 @@ OrientedPlane3 OrientedPlane3::retract(const Vector3& v,
|
||||||
Matrix22 H_n;
|
Matrix22 H_n;
|
||||||
Unit3 n_retract(n_.retract(Vector2(v(0), v(1)), H? &H_n : nullptr));
|
Unit3 n_retract(n_.retract(Vector2(v(0), v(1)), H? &H_n : nullptr));
|
||||||
if (H) {
|
if (H) {
|
||||||
*H << H_n, Vector2::Zero(), 0, 0, 1;
|
*H << H_n, Z_2x1, 0, 0, 1;
|
||||||
}
|
}
|
||||||
return OrientedPlane3(n_retract, d_ + v(2));
|
return OrientedPlane3(n_retract, d_ + v(2));
|
||||||
}
|
}
|
||||||
|
@ -100,4 +95,4 @@ Vector3 OrientedPlane3::localCoordinates(const OrientedPlane3& y) const {
|
||||||
return Vector3(n_local(0), n_local(1), -d_local);
|
return Vector3(n_local(0), n_local(1), -d_local);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
} // namespace gtsam
|
||||||
|
|
|
@ -20,22 +20,21 @@
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <gtsam/geometry/Rot3.h>
|
|
||||||
#include <gtsam/geometry/Unit3.h>
|
#include <gtsam/geometry/Unit3.h>
|
||||||
#include <gtsam/geometry/Pose3.h>
|
#include <gtsam/geometry/Pose3.h>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
namespace gtsam {
|
namespace gtsam {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Represents an infinite plane in 3D, which is composed of a planar normal and its
|
* @brief Represents an infinite plane in 3D, which is composed of a planar
|
||||||
* perpendicular distance to the origin.
|
* normal and its perpendicular distance to the origin.
|
||||||
* Currently it provides a transform of the plane, and a norm 1 differencing of two planes.
|
* Currently it provides a transform of the plane, and a norm 1 differencing of
|
||||||
|
* two planes.
|
||||||
* Refer to Trevor12iros for more math details.
|
* Refer to Trevor12iros for more math details.
|
||||||
*/
|
*/
|
||||||
class GTSAM_EXPORT OrientedPlane3 {
|
class GTSAM_EXPORT OrientedPlane3 {
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
||||||
Unit3 n_; ///< The direction of the planar normal
|
Unit3 n_; ///< The direction of the planar normal
|
||||||
double d_; ///< The perpendicular distance to this plane
|
double d_; ///< The perpendicular distance to this plane
|
||||||
|
|
||||||
|
@ -53,19 +52,17 @@ public:
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Construct from a Unit3 and a distance
|
/// Construct from a Unit3 and a distance
|
||||||
OrientedPlane3(const Unit3& s, double d) :
|
OrientedPlane3(const Unit3& n, double d) :
|
||||||
n_(s), d_(d) {
|
n_(n), d_(d) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Construct from a vector of plane coefficients
|
/// Construct from a vector of plane coefficients
|
||||||
OrientedPlane3(const Vector4& vec) :
|
explicit OrientedPlane3(const Vector4& vec)
|
||||||
n_(vec(0), vec(1), vec(2)), d_(vec(3)) {
|
: n_(vec(0), vec(1), vec(2)), d_(vec(3)) {}
|
||||||
}
|
|
||||||
|
|
||||||
/// Construct from four numbers of plane coeffcients (a, b, c, d)
|
/// Construct from four numbers of plane coeffcients (a, b, c, d)
|
||||||
OrientedPlane3(double a, double b, double c, double d) {
|
OrientedPlane3(double a, double b, double c, double d) {
|
||||||
Point3 p(a, b, c);
|
n_ = Unit3(a, b, c);
|
||||||
n_ = Unit3(p);
|
|
||||||
d_ = d;
|
d_ = d;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -93,34 +90,15 @@ public:
|
||||||
OptionalJacobian<3, 3> Hp = boost::none,
|
OptionalJacobian<3, 3> Hp = boost::none,
|
||||||
OptionalJacobian<3, 6> Hr = boost::none) const;
|
OptionalJacobian<3, 6> Hr = boost::none) const;
|
||||||
|
|
||||||
/**
|
|
||||||
* @deprecated the static method has wrong Jacobian order,
|
|
||||||
* please use the member method transform()
|
|
||||||
* @param The raw plane
|
|
||||||
* @param xr a transformation in current coordiante
|
|
||||||
* @param Hr optional jacobian wrpt the pose transformation
|
|
||||||
* @param Hp optional Jacobian wrpt the destination plane
|
|
||||||
* @return the transformed plane
|
|
||||||
*/
|
|
||||||
static OrientedPlane3 Transform(const OrientedPlane3& plane,
|
|
||||||
const Pose3& xr, OptionalJacobian<3, 6> Hr = boost::none,
|
|
||||||
OptionalJacobian<3, 3> Hp = boost::none) {
|
|
||||||
return plane.transform(xr, Hp, Hr);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Computes the error between two planes.
|
|
||||||
* The error is a norm 1 difference in tangent space.
|
|
||||||
* @param the other plane
|
|
||||||
*/
|
|
||||||
Vector3 error(const OrientedPlane3& plane) const;
|
|
||||||
|
|
||||||
/** Computes the error between the two planes, with derivatives.
|
/** Computes the error between the two planes, with derivatives.
|
||||||
* This uses Unit3::errorVector, as opposed to the other .error() in this class, which uses
|
* This uses Unit3::errorVector, as opposed to the other .error() in this
|
||||||
* Unit3::localCoordinates. This one has correct derivatives.
|
* class, which uses Unit3::localCoordinates. This one has correct
|
||||||
|
* derivatives.
|
||||||
* NOTE(hayk): The derivatives are zero when normals are exactly orthogonal.
|
* NOTE(hayk): The derivatives are zero when normals are exactly orthogonal.
|
||||||
* @param the other plane
|
* @param other the other plane
|
||||||
*/
|
*/
|
||||||
Vector3 errorVector(const OrientedPlane3& other, OptionalJacobian<3, 3> H1 = boost::none, //
|
Vector3 errorVector(const OrientedPlane3& other,
|
||||||
|
OptionalJacobian<3, 3> H1 = boost::none,
|
||||||
OptionalJacobian<3, 3> H2 = boost::none) const;
|
OptionalJacobian<3, 3> H2 = boost::none) const;
|
||||||
|
|
||||||
/// Dimensionality of tangent space = 3 DOF
|
/// Dimensionality of tangent space = 3 DOF
|
||||||
|
@ -134,7 +112,8 @@ public:
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The retract function
|
/// The retract function
|
||||||
OrientedPlane3 retract(const Vector3& v, OptionalJacobian<3,3> H = boost::none) const;
|
OrientedPlane3 retract(const Vector3& v,
|
||||||
|
OptionalJacobian<3, 3> H = boost::none) const;
|
||||||
|
|
||||||
/// The local coordinates function
|
/// The local coordinates function
|
||||||
Vector3 localCoordinates(const OrientedPlane3& s) const;
|
Vector3 localCoordinates(const OrientedPlane3& s) const;
|
||||||
|
|
|
@ -50,10 +50,6 @@ TEST (OrientedPlane3, getMethods) {
|
||||||
|
|
||||||
|
|
||||||
//*******************************************************************************
|
//*******************************************************************************
|
||||||
OrientedPlane3 Transform_(const OrientedPlane3& plane, const Pose3& xr) {
|
|
||||||
return OrientedPlane3::Transform(plane, xr);
|
|
||||||
}
|
|
||||||
|
|
||||||
OrientedPlane3 transform_(const OrientedPlane3& plane, const Pose3& xr) {
|
OrientedPlane3 transform_(const OrientedPlane3& plane, const Pose3& xr) {
|
||||||
return plane.transform(xr);
|
return plane.transform(xr);
|
||||||
}
|
}
|
||||||
|
@ -63,32 +59,19 @@ TEST (OrientedPlane3, transform) {
|
||||||
gtsam::Point3(2.0, 3.0, 4.0));
|
gtsam::Point3(2.0, 3.0, 4.0));
|
||||||
OrientedPlane3 plane(-1, 0, 0, 5);
|
OrientedPlane3 plane(-1, 0, 0, 5);
|
||||||
OrientedPlane3 expectedPlane(-sqrt(2.0) / 2.0, -sqrt(2.0) / 2.0, 0.0, 3);
|
OrientedPlane3 expectedPlane(-sqrt(2.0) / 2.0, -sqrt(2.0) / 2.0, 0.0, 3);
|
||||||
OrientedPlane3 transformedPlane1 = OrientedPlane3::Transform(plane, pose,
|
OrientedPlane3 transformedPlane = plane.transform(pose, none, none);
|
||||||
none, none);
|
EXPECT(assert_equal(expectedPlane, transformedPlane, 1e-5));
|
||||||
OrientedPlane3 transformedPlane2 = plane.transform(pose, none, none);
|
|
||||||
EXPECT(assert_equal(expectedPlane, transformedPlane1, 1e-5));
|
|
||||||
EXPECT(assert_equal(expectedPlane, transformedPlane2, 1e-5));
|
|
||||||
|
|
||||||
// Test the jacobians of transform
|
// Test the jacobians of transform
|
||||||
Matrix actualH1, expectedH1, actualH2, expectedH2;
|
Matrix actualH1, expectedH1, actualH2, expectedH2;
|
||||||
{
|
|
||||||
// because the Transform class uses a wrong order of Jacobians in interface
|
|
||||||
OrientedPlane3::Transform(plane, pose, actualH1, none);
|
|
||||||
expectedH1 = numericalDerivative22(Transform_, plane, pose);
|
|
||||||
EXPECT(assert_equal(expectedH1, actualH1, 1e-5));
|
|
||||||
OrientedPlane3::Transform(plane, pose, none, actualH2);
|
|
||||||
expectedH2 = numericalDerivative21(Transform_, plane, pose);
|
|
||||||
EXPECT(assert_equal(expectedH2, actualH2, 1e-5));
|
|
||||||
}
|
|
||||||
{
|
|
||||||
plane.transform(pose, actualH1, none);
|
|
||||||
expectedH1 = numericalDerivative21(transform_, plane, pose);
|
expectedH1 = numericalDerivative21(transform_, plane, pose);
|
||||||
|
plane.transform(pose, actualH1, none);
|
||||||
EXPECT(assert_equal(expectedH1, actualH1, 1e-5));
|
EXPECT(assert_equal(expectedH1, actualH1, 1e-5));
|
||||||
|
|
||||||
|
expectedH2 = numericalDerivative22(transform_, plane, pose);
|
||||||
plane.transform(pose, none, actualH2);
|
plane.transform(pose, none, actualH2);
|
||||||
expectedH2 = numericalDerivative22(Transform_, plane, pose);
|
|
||||||
EXPECT(assert_equal(expectedH2, actualH2, 1e-5));
|
EXPECT(assert_equal(expectedH2, actualH2, 1e-5));
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
//*******************************************************************************
|
//*******************************************************************************
|
||||||
// Returns a any size random vector
|
// Returns a any size random vector
|
||||||
|
@ -109,7 +92,6 @@ inline static Vector randomVector(const Vector& minLimits,
|
||||||
|
|
||||||
//*******************************************************************************
|
//*******************************************************************************
|
||||||
TEST(OrientedPlane3, localCoordinates_retract) {
|
TEST(OrientedPlane3, localCoordinates_retract) {
|
||||||
|
|
||||||
size_t numIterations = 10000;
|
size_t numIterations = 10000;
|
||||||
Vector4 minPlaneLimit, maxPlaneLimit;
|
Vector4 minPlaneLimit, maxPlaneLimit;
|
||||||
minPlaneLimit << -1.0, -1.0, -1.0, 0.01;
|
minPlaneLimit << -1.0, -1.0, -1.0, 0.01;
|
||||||
|
@ -119,7 +101,6 @@ TEST(OrientedPlane3, localCoordinates_retract) {
|
||||||
minXiLimit << -M_PI, -M_PI, -10.0;
|
minXiLimit << -M_PI, -M_PI, -10.0;
|
||||||
maxXiLimit << M_PI, M_PI, 10.0;
|
maxXiLimit << M_PI, M_PI, 10.0;
|
||||||
for (size_t i = 0; i < numIterations; i++) {
|
for (size_t i = 0; i < numIterations; i++) {
|
||||||
|
|
||||||
// Create a Plane
|
// Create a Plane
|
||||||
OrientedPlane3 p1(randomVector(minPlaneLimit, maxPlaneLimit));
|
OrientedPlane3 p1(randomVector(minPlaneLimit, maxPlaneLimit));
|
||||||
Vector v12 = randomVector(minXiLimit, maxXiLimit);
|
Vector v12 = randomVector(minXiLimit, maxXiLimit);
|
||||||
|
@ -138,22 +119,24 @@ TEST(OrientedPlane3, localCoordinates_retract) {
|
||||||
}
|
}
|
||||||
|
|
||||||
//*******************************************************************************
|
//*******************************************************************************
|
||||||
TEST (OrientedPlane3, error2) {
|
TEST(OrientedPlane3, errorVector) {
|
||||||
OrientedPlane3 plane1(-1, 0.1, 0.2, 5);
|
OrientedPlane3 plane1(-1, 0.1, 0.2, 5);
|
||||||
OrientedPlane3 plane2(-1.1, 0.2, 0.3, 5.4);
|
OrientedPlane3 plane2(-1.1, 0.2, 0.3, 5.4);
|
||||||
|
|
||||||
// Hard-coded regression values, to ensure the result doesn't change.
|
// Hard-coded regression values, to ensure the result doesn't change.
|
||||||
EXPECT(assert_equal((Vector) Z_3x1, plane1.errorVector(plane1), 1e-8));
|
EXPECT(assert_equal((Vector) Z_3x1, plane1.errorVector(plane1), 1e-8));
|
||||||
EXPECT(assert_equal(Vector3(-0.0677674148, -0.0760543588, -0.4), plane1.errorVector(plane2), 1e-5));
|
EXPECT(assert_equal(Vector3(-0.0677674148, -0.0760543588, -0.4),
|
||||||
|
plane1.errorVector(plane2), 1e-5));
|
||||||
|
|
||||||
// Test the jacobians of transform
|
// Test the jacobians of transform
|
||||||
Matrix33 actualH1, expectedH1, actualH2, expectedH2;
|
Matrix33 actualH1, expectedH1, actualH2, expectedH2;
|
||||||
|
|
||||||
Vector3 actual = plane1.errorVector(plane2, actualH1, actualH2);
|
Vector3 actual = plane1.errorVector(plane2, actualH1, actualH2);
|
||||||
EXPECT(assert_equal(plane1.normal().errorVector(plane2.normal()), Vector2(actual[0], actual[1])));
|
EXPECT(assert_equal(plane1.normal().errorVector(plane2.normal()),
|
||||||
|
Vector2(actual[0], actual[1])));
|
||||||
EXPECT(assert_equal(plane1.distance() - plane2.distance(), actual[2]));
|
EXPECT(assert_equal(plane1.distance() - plane2.distance(), actual[2]));
|
||||||
|
|
||||||
boost::function<Vector3(const OrientedPlane3&, const OrientedPlane3&)> f = //
|
boost::function<Vector3(const OrientedPlane3&, const OrientedPlane3&)> f =
|
||||||
boost::bind(&OrientedPlane3::errorVector, _1, _2, boost::none, boost::none);
|
boost::bind(&OrientedPlane3::errorVector, _1, _2, boost::none, boost::none);
|
||||||
expectedH1 = numericalDerivative21(f, plane1, plane2);
|
expectedH1 = numericalDerivative21(f, plane1, plane2);
|
||||||
expectedH2 = numericalDerivative22(f, plane1, plane2);
|
expectedH2 = numericalDerivative22(f, plane1, plane2);
|
||||||
|
|
|
@ -501,6 +501,7 @@ TEST(actualH, Serialization) {
|
||||||
EXPECT(serializationTestHelpers::equalsBinary(p));
|
EXPECT(serializationTestHelpers::equalsBinary(p));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* ************************************************************************* */
|
/* ************************************************************************* */
|
||||||
int main() {
|
int main() {
|
||||||
srand(time(nullptr));
|
srand(time(nullptr));
|
||||||
|
|
|
@ -14,15 +14,42 @@ namespace gtsam {
|
||||||
//***************************************************************************
|
//***************************************************************************
|
||||||
void OrientedPlane3Factor::print(const string& s,
|
void OrientedPlane3Factor::print(const string& s,
|
||||||
const KeyFormatter& keyFormatter) const {
|
const KeyFormatter& keyFormatter) const {
|
||||||
cout << "OrientedPlane3Factor Factor on " << landmarkKey_ << "\n";
|
cout << s << (s == "" ? "" : "\n");
|
||||||
|
cout << "OrientedPlane3Factor Factor (" << keyFormatter(key1()) << ", "
|
||||||
|
<< keyFormatter(key2()) << ")\n";
|
||||||
measured_p_.print("Measured Plane");
|
measured_p_.print("Measured Plane");
|
||||||
this->noiseModel_->print(" noise model: ");
|
this->noiseModel_->print(" noise model: ");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//***************************************************************************
|
||||||
|
Vector OrientedPlane3Factor::evaluateError(const Pose3& pose,
|
||||||
|
const OrientedPlane3& plane, boost::optional<Matrix&> H1,
|
||||||
|
boost::optional<Matrix&> H2) const {
|
||||||
|
Matrix36 predicted_H_pose;
|
||||||
|
Matrix33 predicted_H_plane, error_H_predicted;
|
||||||
|
|
||||||
|
OrientedPlane3 predicted_plane = plane.transform(pose,
|
||||||
|
H2 ? &predicted_H_plane : nullptr, H1 ? &predicted_H_pose : nullptr);
|
||||||
|
|
||||||
|
Vector3 err = predicted_plane.errorVector(
|
||||||
|
measured_p_, (H1 || H2) ? &error_H_predicted : nullptr);
|
||||||
|
|
||||||
|
// Apply the chain rule to calculate the derivatives.
|
||||||
|
if (H1) {
|
||||||
|
*H1 = error_H_predicted * predicted_H_pose;
|
||||||
|
}
|
||||||
|
if (H2) {
|
||||||
|
*H2 = error_H_predicted * predicted_H_plane;
|
||||||
|
}
|
||||||
|
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
//***************************************************************************
|
//***************************************************************************
|
||||||
void OrientedPlane3DirectionPrior::print(const string& s,
|
void OrientedPlane3DirectionPrior::print(const string& s,
|
||||||
const KeyFormatter& keyFormatter) const {
|
const KeyFormatter& keyFormatter) const {
|
||||||
cout << "Prior Factor on " << landmarkKey_ << "\n";
|
cout << s << (s == "" ? "" : "\n");
|
||||||
|
cout << s << "Prior Factor on " << keyFormatter(key()) << "\n";
|
||||||
measured_p_.print("Measured Plane");
|
measured_p_.print("Measured Plane");
|
||||||
this->noiseModel_->print(" noise model: ");
|
this->noiseModel_->print(" noise model: ");
|
||||||
}
|
}
|
||||||
|
@ -36,26 +63,17 @@ bool OrientedPlane3DirectionPrior::equals(const NonlinearFactor& expected,
|
||||||
}
|
}
|
||||||
|
|
||||||
//***************************************************************************
|
//***************************************************************************
|
||||||
|
Vector OrientedPlane3DirectionPrior::evaluateError(
|
||||||
Vector OrientedPlane3DirectionPrior::evaluateError(const OrientedPlane3& plane,
|
const OrientedPlane3& plane, boost::optional<Matrix&> H) const {
|
||||||
boost::optional<Matrix&> H) const {
|
Unit3 n_hat_p = measured_p_.normal();
|
||||||
|
Unit3 n_hat_q = plane.normal();
|
||||||
|
Matrix2 H_p;
|
||||||
|
Vector e = n_hat_p.error(n_hat_q, H ? &H_p : nullptr);
|
||||||
if (H) {
|
if (H) {
|
||||||
Matrix H_p;
|
|
||||||
Unit3 n_hat_p = measured_p_.normal();
|
|
||||||
Unit3 n_hat_q = plane.normal();
|
|
||||||
Vector e = n_hat_p.error(n_hat_q, H_p);
|
|
||||||
H->resize(2, 3);
|
H->resize(2, 3);
|
||||||
H->block<2, 2>(0, 0) << H_p;
|
*H << H_p, Z_2x1;
|
||||||
H->block<2, 1>(0, 2) << Z_2x1;
|
}
|
||||||
return e;
|
|
||||||
} else {
|
|
||||||
Unit3 n_hat_p = measured_p_.normal();
|
|
||||||
Unit3 n_hat_q = plane.normal();
|
|
||||||
Vector e = n_hat_p.error(n_hat_q);
|
|
||||||
return e;
|
return e;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
} // namespace gtsam
|
||||||
}
|
|
||||||
|
|
||||||
|
|
|
@ -16,66 +16,54 @@ namespace gtsam {
|
||||||
* Factor to measure a planar landmark from a given pose
|
* Factor to measure a planar landmark from a given pose
|
||||||
*/
|
*/
|
||||||
class OrientedPlane3Factor: public NoiseModelFactor2<Pose3, OrientedPlane3> {
|
class OrientedPlane3Factor: public NoiseModelFactor2<Pose3, OrientedPlane3> {
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
Key poseKey_;
|
|
||||||
Key landmarkKey_;
|
|
||||||
Vector measured_coeffs_;
|
|
||||||
OrientedPlane3 measured_p_;
|
OrientedPlane3 measured_p_;
|
||||||
|
|
||||||
typedef NoiseModelFactor2<Pose3, OrientedPlane3> Base;
|
typedef NoiseModelFactor2<Pose3, OrientedPlane3> Base;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
/// Constructor
|
/// Constructor
|
||||||
OrientedPlane3Factor() {
|
OrientedPlane3Factor() {
|
||||||
}
|
}
|
||||||
~OrientedPlane3Factor() override {}
|
~OrientedPlane3Factor() override {}
|
||||||
|
|
||||||
/// Constructor with measured plane coefficients (a,b,c,d), noise model, pose symbol
|
/** Constructor with measured plane (a,b,c,d) coefficients
|
||||||
OrientedPlane3Factor(const Vector&z, const SharedGaussian& noiseModel,
|
* @param z measured plane (a,b,c,d) coefficients as 4D vector
|
||||||
const Key& pose, const Key& landmark) :
|
* @param noiseModel noiseModel Gaussian noise model
|
||||||
Base(noiseModel, pose, landmark), poseKey_(pose), landmarkKey_(landmark), measured_coeffs_(
|
* @param poseKey Key or symbol for unknown pose
|
||||||
z) {
|
* @param landmarkKey Key or symbol for unknown planar landmark
|
||||||
measured_p_ = OrientedPlane3(Unit3(z(0), z(1), z(2)), z(3));
|
* @return the transformed plane
|
||||||
}
|
*/
|
||||||
|
OrientedPlane3Factor(const Vector4& z, const SharedGaussian& noiseModel,
|
||||||
|
Key poseKey, Key landmarkKey)
|
||||||
|
: Base(noiseModel, poseKey, landmarkKey), measured_p_(z) {}
|
||||||
|
|
||||||
/// print
|
/// print
|
||||||
void print(const std::string& s = "OrientedPlane3Factor",
|
void print(const std::string& s = "OrientedPlane3Factor",
|
||||||
const KeyFormatter& keyFormatter = DefaultKeyFormatter) const override;
|
const KeyFormatter& keyFormatter = DefaultKeyFormatter) const override;
|
||||||
|
|
||||||
/// evaluateError
|
/// evaluateError
|
||||||
Vector evaluateError(const Pose3& pose, const OrientedPlane3& plane,
|
Vector evaluateError(
|
||||||
boost::optional<Matrix&> H1 = boost::none, boost::optional<Matrix&> H2 =
|
const Pose3& pose, const OrientedPlane3& plane,
|
||||||
boost::none) const override {
|
boost::optional<Matrix&> H1 = boost::none,
|
||||||
OrientedPlane3 predicted_plane = OrientedPlane3::Transform(plane, pose, H1,
|
boost::optional<Matrix&> H2 = boost::none) const override;
|
||||||
H2);
|
|
||||||
Vector err(3);
|
|
||||||
err << predicted_plane.error(measured_p_);
|
|
||||||
return (err);
|
|
||||||
}
|
|
||||||
;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// TODO: Convert this factor to dimension two, three dimensions is redundant for direction prior
|
// TODO: Convert this factor to dimension two, three dimensions is redundant for direction prior
|
||||||
class OrientedPlane3DirectionPrior : public NoiseModelFactor1<OrientedPlane3> {
|
class OrientedPlane3DirectionPrior : public NoiseModelFactor1<OrientedPlane3> {
|
||||||
protected:
|
protected:
|
||||||
OrientedPlane3 measured_p_; /// measured plane parameters
|
OrientedPlane3 measured_p_; /// measured plane parameters
|
||||||
Key landmarkKey_;
|
|
||||||
typedef NoiseModelFactor1<OrientedPlane3> Base;
|
typedef NoiseModelFactor1<OrientedPlane3> Base;
|
||||||
public:
|
|
||||||
|
|
||||||
|
public:
|
||||||
typedef OrientedPlane3DirectionPrior This;
|
typedef OrientedPlane3DirectionPrior This;
|
||||||
/// Constructor
|
/// Constructor
|
||||||
OrientedPlane3DirectionPrior() {
|
OrientedPlane3DirectionPrior() {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Constructor with measured plane coefficients (a,b,c,d), noise model, landmark symbol
|
/// Constructor with measured plane coefficients (a,b,c,d), noise model, landmark symbol
|
||||||
OrientedPlane3DirectionPrior(Key key, const Vector&z,
|
OrientedPlane3DirectionPrior(Key key, const Vector4& z,
|
||||||
const SharedGaussian& noiseModel) :
|
const SharedGaussian& noiseModel)
|
||||||
Base(noiseModel, key), landmarkKey_(key) {
|
: Base(noiseModel, key), measured_p_(z) {}
|
||||||
measured_p_ = OrientedPlane3(Unit3(z(0), z(1), z(2)), z(3));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// print
|
/// print
|
||||||
void print(const std::string& s = "OrientedPlane3DirectionPrior",
|
void print(const std::string& s = "OrientedPlane3DirectionPrior",
|
||||||
|
|
|
@ -10,20 +10,24 @@
|
||||||
* -------------------------------------------------------------------------- */
|
* -------------------------------------------------------------------------- */
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* @file testOrientedPlane3.cpp
|
* @file testOrientedPlane3Factor.cpp
|
||||||
* @date Dec 19, 2012
|
* @date Dec 19, 2012
|
||||||
* @author Alex Trevor
|
* @author Alex Trevor
|
||||||
* @brief Tests the OrientedPlane3Factor class
|
* @brief Tests the OrientedPlane3Factor class
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <gtsam/slam/OrientedPlane3Factor.h>
|
#include <gtsam/slam/OrientedPlane3Factor.h>
|
||||||
#include <gtsam/nonlinear/ISAM2.h>
|
|
||||||
#include <gtsam/inference/Symbol.h>
|
|
||||||
#include <gtsam/base/numericalDerivative.h>
|
#include <gtsam/base/numericalDerivative.h>
|
||||||
|
#include <gtsam/inference/Symbol.h>
|
||||||
|
#include <gtsam/nonlinear/GaussNewtonOptimizer.h>
|
||||||
|
#include <gtsam/nonlinear/ISAM2.h>
|
||||||
|
|
||||||
#include <CppUnitLite/TestHarness.h>
|
#include <CppUnitLite/TestHarness.h>
|
||||||
#include <boost/bind.hpp>
|
|
||||||
#include <boost/assign/std/vector.hpp>
|
#include <boost/assign/std/vector.hpp>
|
||||||
|
#include <boost/assign/std.hpp>
|
||||||
|
#include <boost/bind.hpp>
|
||||||
|
|
||||||
using namespace boost::assign;
|
using namespace boost::assign;
|
||||||
using namespace gtsam;
|
using namespace gtsam;
|
||||||
|
@ -32,46 +36,46 @@ using namespace std;
|
||||||
GTSAM_CONCEPT_TESTABLE_INST(OrientedPlane3)
|
GTSAM_CONCEPT_TESTABLE_INST(OrientedPlane3)
|
||||||
GTSAM_CONCEPT_MANIFOLD_INST(OrientedPlane3)
|
GTSAM_CONCEPT_MANIFOLD_INST(OrientedPlane3)
|
||||||
|
|
||||||
|
using symbol_shorthand::P; //< Planes
|
||||||
|
using symbol_shorthand::X; //< Pose3
|
||||||
|
|
||||||
// *************************************************************************
|
// *************************************************************************
|
||||||
TEST(OrientedPlane3Factor, lm_translation_error) {
|
TEST(OrientedPlane3Factor, lm_translation_error) {
|
||||||
// Tests one pose, two measurements of the landmark that differ in range only.
|
// Tests one pose, two measurements of the landmark that differ in range only.
|
||||||
// Normal along -x, 3m away
|
// Normal along -x, 3m away
|
||||||
Symbol lm_sym('p', 0);
|
|
||||||
OrientedPlane3 test_lm0(-1.0, 0.0, 0.0, 3.0);
|
OrientedPlane3 test_lm0(-1.0, 0.0, 0.0, 3.0);
|
||||||
|
|
||||||
ISAM2 isam2;
|
NonlinearFactorGraph graph;
|
||||||
Values new_values;
|
|
||||||
NonlinearFactorGraph new_graph;
|
|
||||||
|
|
||||||
// Init pose and prior. Pose Prior is needed since a single plane measurement does not fully constrain the pose
|
// Init pose and prior. Pose Prior is needed since a single plane measurement
|
||||||
Symbol init_sym('x', 0);
|
// does not fully constrain the pose
|
||||||
Pose3 init_pose(Rot3::Ypr(0.0, 0.0, 0.0), Point3(0.0, 0.0, 0.0));
|
Pose3 init_pose(Rot3::Ypr(0.0, 0.0, 0.0), Point3(0.0, 0.0, 0.0));
|
||||||
Vector sigmas(6);
|
Vector6 sigmas;
|
||||||
sigmas << 0.001, 0.001, 0.001, 0.001, 0.001, 0.001;
|
sigmas << 0.001, 0.001, 0.001, 0.001, 0.001, 0.001;
|
||||||
new_graph.addPrior(
|
graph.addPrior(X(0), init_pose, noiseModel::Diagonal::Sigmas(sigmas));
|
||||||
init_sym, init_pose, noiseModel::Diagonal::Sigmas(sigmas));
|
|
||||||
new_values.insert(init_sym, init_pose);
|
|
||||||
|
|
||||||
// Add two landmark measurements, differing in range
|
// Add two landmark measurements, differing in range
|
||||||
new_values.insert(lm_sym, test_lm0);
|
Vector3 sigmas3 {0.1, 0.1, 0.1};
|
||||||
Vector sigmas3(3);
|
Vector4 measurement0 {-1.0, 0.0, 0.0, 3.0};
|
||||||
sigmas3 << 0.1, 0.1, 0.1;
|
OrientedPlane3Factor factor0(
|
||||||
Vector test_meas0_mean(4);
|
measurement0, noiseModel::Diagonal::Sigmas(sigmas3), X(0), P(0));
|
||||||
test_meas0_mean << -1.0, 0.0, 0.0, 3.0;
|
graph.add(factor0);
|
||||||
OrientedPlane3Factor test_meas0(test_meas0_mean,
|
Vector4 measurement1 {-1.0, 0.0, 0.0, 1.0};
|
||||||
noiseModel::Diagonal::Sigmas(sigmas3), init_sym, lm_sym);
|
OrientedPlane3Factor factor1(
|
||||||
new_graph.add(test_meas0);
|
measurement1, noiseModel::Diagonal::Sigmas(sigmas3), X(0), P(0));
|
||||||
Vector test_meas1_mean(4);
|
graph.add(factor1);
|
||||||
test_meas1_mean << -1.0, 0.0, 0.0, 1.0;
|
|
||||||
OrientedPlane3Factor test_meas1(test_meas1_mean,
|
// Initial Estimate
|
||||||
noiseModel::Diagonal::Sigmas(sigmas3), init_sym, lm_sym);
|
Values values;
|
||||||
new_graph.add(test_meas1);
|
values.insert(X(0), init_pose);
|
||||||
|
values.insert(P(0), test_lm0);
|
||||||
|
|
||||||
// Optimize
|
// Optimize
|
||||||
ISAM2Result result = isam2.update(new_graph, new_values);
|
ISAM2 isam2;
|
||||||
|
ISAM2Result result = isam2.update(graph, values);
|
||||||
Values result_values = isam2.calculateEstimate();
|
Values result_values = isam2.calculateEstimate();
|
||||||
OrientedPlane3 optimized_plane_landmark = result_values.at<OrientedPlane3>(
|
OrientedPlane3 optimized_plane_landmark =
|
||||||
lm_sym);
|
result_values.at<OrientedPlane3>(P(0));
|
||||||
|
|
||||||
// Given two noisy measurements of equal weight, expect result between the two
|
// Given two noisy measurements of equal weight, expect result between the two
|
||||||
OrientedPlane3 expected_plane_landmark(-1.0, 0.0, 0.0, 2.0);
|
OrientedPlane3 expected_plane_landmark(-1.0, 0.0, 0.0, 2.0);
|
||||||
|
@ -79,48 +83,80 @@ TEST (OrientedPlane3Factor, lm_translation_error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// *************************************************************************
|
// *************************************************************************
|
||||||
|
// TODO As described in PR #564 after correcting the derivatives in
|
||||||
|
// OrientedPlane3Factor this test fails. It should be debugged and re-enabled.
|
||||||
|
/*
|
||||||
TEST (OrientedPlane3Factor, lm_rotation_error) {
|
TEST (OrientedPlane3Factor, lm_rotation_error) {
|
||||||
// Tests one pose, two measurements of the landmark that differ in angle only.
|
// Tests one pose, two measurements of the landmark that differ in angle only.
|
||||||
// Normal along -x, 3m away
|
// Normal along -x, 3m away
|
||||||
Symbol lm_sym('p', 0);
|
OrientedPlane3 test_lm0(-1.0/sqrt(1.01), 0.1/sqrt(1.01), 0.0, 3.0);
|
||||||
OrientedPlane3 test_lm0(-1.0, 0.0, 0.0, 3.0);
|
|
||||||
|
|
||||||
ISAM2 isam2;
|
NonlinearFactorGraph graph;
|
||||||
Values new_values;
|
|
||||||
NonlinearFactorGraph new_graph;
|
|
||||||
|
|
||||||
// Init pose and prior. Pose Prior is needed since a single plane measurement does not fully constrain the pose
|
// Init pose and prior. Pose Prior is needed since a single plane measurement does not fully constrain the pose
|
||||||
Symbol init_sym('x', 0);
|
|
||||||
Pose3 init_pose(Rot3::Ypr(0.0, 0.0, 0.0), Point3(0.0, 0.0, 0.0));
|
Pose3 init_pose(Rot3::Ypr(0.0, 0.0, 0.0), Point3(0.0, 0.0, 0.0));
|
||||||
new_graph.addPrior(init_sym, init_pose,
|
graph.addPrior(X(0), init_pose,
|
||||||
noiseModel::Diagonal::Sigmas(
|
noiseModel::Diagonal::Sigmas(
|
||||||
(Vector(6) << 0.001, 0.001, 0.001, 0.001, 0.001, 0.001).finished()));
|
(Vector(6) << 0.001, 0.001, 0.001, 0.001, 0.001, 0.001).finished()));
|
||||||
new_values.insert(init_sym, init_pose);
|
|
||||||
|
|
||||||
// // Add two landmark measurements, differing in angle
|
// Add two landmark measurements, differing in angle
|
||||||
new_values.insert(lm_sym, test_lm0);
|
Vector4 measurement0 {-1.0, 0.0, 0.0, 3.0};
|
||||||
Vector test_meas0_mean(4);
|
OrientedPlane3Factor factor0(measurement0,
|
||||||
test_meas0_mean << -1.0, 0.0, 0.0, 3.0;
|
noiseModel::Diagonal::Sigmas(Vector3(0.1, 0.1, 0.1)), X(0), P(0));
|
||||||
OrientedPlane3Factor test_meas0(test_meas0_mean,
|
graph.add(factor0);
|
||||||
noiseModel::Diagonal::Sigmas(Vector3(0.1, 0.1, 0.1)), init_sym, lm_sym);
|
Vector4 measurement1 {0.0, -1.0, 0.0, 3.0};
|
||||||
new_graph.add(test_meas0);
|
OrientedPlane3Factor factor1(measurement1,
|
||||||
Vector test_meas1_mean(4);
|
noiseModel::Diagonal::Sigmas(Vector3(0.1, 0.1, 0.1)), X(0), P(0));
|
||||||
test_meas1_mean << 0.0, -1.0, 0.0, 3.0;
|
graph.add(factor1);
|
||||||
OrientedPlane3Factor test_meas1(test_meas1_mean,
|
|
||||||
noiseModel::Diagonal::Sigmas(Vector3(0.1, 0.1, 0.1)), init_sym, lm_sym);
|
// Initial Estimate
|
||||||
new_graph.add(test_meas1);
|
Values values;
|
||||||
|
values.insert(X(0), init_pose);
|
||||||
|
values.insert(P(0), test_lm0);
|
||||||
|
|
||||||
// Optimize
|
// Optimize
|
||||||
ISAM2Result result = isam2.update(new_graph, new_values);
|
ISAM2 isam2;
|
||||||
|
ISAM2Result result = isam2.update(graph, values);
|
||||||
Values result_values = isam2.calculateEstimate();
|
Values result_values = isam2.calculateEstimate();
|
||||||
OrientedPlane3 optimized_plane_landmark = result_values.at<OrientedPlane3>(
|
auto optimized_plane_landmark = result_values.at<OrientedPlane3>(P(0));
|
||||||
lm_sym);
|
|
||||||
|
|
||||||
// Given two noisy measurements of equal weight, expect result between the two
|
// Given two noisy measurements of equal weight, expect result between the two
|
||||||
OrientedPlane3 expected_plane_landmark(-sqrt(2.0) / 2.0, -sqrt(2.0) / 2.0,
|
OrientedPlane3 expected_plane_landmark(-sqrt(2.0) / 2.0, -sqrt(2.0) / 2.0,
|
||||||
0.0, 3.0);
|
0.0, 3.0);
|
||||||
EXPECT(assert_equal(optimized_plane_landmark, expected_plane_landmark));
|
EXPECT(assert_equal(optimized_plane_landmark, expected_plane_landmark));
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
// *************************************************************************
|
||||||
|
TEST( OrientedPlane3Factor, Derivatives ) {
|
||||||
|
// Measurement
|
||||||
|
OrientedPlane3 p(sqrt(2)/2, -sqrt(2)/2, 0, 5);
|
||||||
|
|
||||||
|
// Linearisation point
|
||||||
|
OrientedPlane3 pLin(sqrt(3)/3, -sqrt(3)/3, sqrt(3)/3, 7);
|
||||||
|
gtsam::Point3 pointLin = gtsam::Point3(1, 2, -4);
|
||||||
|
gtsam::Rot3 rotationLin = gtsam::Rot3::RzRyRx(0.5*M_PI, -0.3*M_PI, 1.4*M_PI);
|
||||||
|
Pose3 poseLin(rotationLin, pointLin);
|
||||||
|
|
||||||
|
// Factor
|
||||||
|
Key planeKey(1), poseKey(2);
|
||||||
|
SharedGaussian noise = noiseModel::Diagonal::Sigmas(Vector3(0.1, 0.1, 0.1));
|
||||||
|
OrientedPlane3Factor factor(p.planeCoefficients(), noise, poseKey, planeKey);
|
||||||
|
|
||||||
|
// Calculate numerical derivatives
|
||||||
|
boost::function<Vector(const Pose3&, const OrientedPlane3&)> f = boost::bind(
|
||||||
|
&OrientedPlane3Factor::evaluateError, factor, _1, _2, boost::none, boost::none);
|
||||||
|
Matrix numericalH1 = numericalDerivative21<Vector, Pose3, OrientedPlane3>(f, poseLin, pLin);
|
||||||
|
Matrix numericalH2 = numericalDerivative22<Vector, Pose3, OrientedPlane3>(f, poseLin, pLin);
|
||||||
|
|
||||||
|
// Use the factor to calculate the derivative
|
||||||
|
Matrix actualH1, actualH2;
|
||||||
|
factor.evaluateError(poseLin, pLin, actualH1, actualH2);
|
||||||
|
|
||||||
|
// Verify we get the expected error
|
||||||
|
EXPECT(assert_equal(numericalH1, actualH1, 1e-8));
|
||||||
|
EXPECT(assert_equal(numericalH2, actualH2, 1e-8));
|
||||||
|
}
|
||||||
|
|
||||||
// *************************************************************************
|
// *************************************************************************
|
||||||
TEST( OrientedPlane3DirectionPrior, Constructor ) {
|
TEST( OrientedPlane3DirectionPrior, Constructor ) {
|
||||||
|
@ -129,7 +165,7 @@ TEST( OrientedPlane3DirectionPrior, Constructor ) {
|
||||||
// If pitch and roll are zero for an aerospace frame,
|
// If pitch and roll are zero for an aerospace frame,
|
||||||
// that means Z is pointing down, i.e., direction of Z = (0,0,-1)
|
// that means Z is pointing down, i.e., direction of Z = (0,0,-1)
|
||||||
|
|
||||||
Vector planeOrientation = (Vector(4) << 0.0, 0.0, -1.0, 10.0).finished(); // all vertical planes directly facing the origin
|
Vector4 planeOrientation = (Vector(4) << 0.0, 0.0, -1.0, 10.0).finished(); // all vertical planes directly facing the origin
|
||||||
|
|
||||||
// Factor
|
// Factor
|
||||||
Key key(1);
|
Key key(1);
|
||||||
|
@ -137,9 +173,9 @@ TEST( OrientedPlane3DirectionPrior, Constructor ) {
|
||||||
OrientedPlane3DirectionPrior factor(key, planeOrientation, model);
|
OrientedPlane3DirectionPrior factor(key, planeOrientation, model);
|
||||||
|
|
||||||
// Create a linearization point at the zero-error point
|
// Create a linearization point at the zero-error point
|
||||||
Vector theta1 = Vector4(0.0, 0.02, -1.2, 10.0);
|
Vector4 theta1 {0.0, 0.02, -1.2, 10.0};
|
||||||
Vector theta2 = Vector4(0.0, 0.1, -0.8, 10.0);
|
Vector4 theta2 {0.0, 0.1, -0.8, 10.0};
|
||||||
Vector theta3 = Vector4(0.0, 0.2, -0.9, 10.0);
|
Vector4 theta3 {0.0, 0.2, -0.9, 10.0};
|
||||||
|
|
||||||
OrientedPlane3 T1(theta1);
|
OrientedPlane3 T1(theta1);
|
||||||
OrientedPlane3 T2(theta2);
|
OrientedPlane3 T2(theta2);
|
||||||
|
@ -170,6 +206,59 @@ TEST( OrientedPlane3DirectionPrior, Constructor ) {
|
||||||
EXPECT(assert_equal(expectedH3, actualH3, 1e-8));
|
EXPECT(assert_equal(expectedH3, actualH3, 1e-8));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ************************************************************************* */
|
||||||
|
// Simplified version of the test by Marco Camurri to debug issue #561
|
||||||
|
TEST(OrientedPlane3Factor, Issue561Simplified) {
|
||||||
|
// Typedefs
|
||||||
|
using Plane = OrientedPlane3;
|
||||||
|
|
||||||
|
NonlinearFactorGraph graph;
|
||||||
|
|
||||||
|
// Setup prior factors
|
||||||
|
// Note: If x0 is too far away from the origin (e.g. x=100) this test can fail.
|
||||||
|
Pose3 x0(Rot3::identity(), Vector3(10, -1, 1));
|
||||||
|
auto x0_noise = noiseModel::Isotropic::Sigma(6, 0.01);
|
||||||
|
graph.addPrior<Pose3>(X(0), x0, x0_noise);
|
||||||
|
|
||||||
|
// Two horizontal planes with different heights, in the world frame.
|
||||||
|
const Plane p1(0,0,1,1), p2(0,0,1,2);
|
||||||
|
auto p1_noise = noiseModel::Diagonal::Sigmas(Vector3{1, 1, 5});
|
||||||
|
auto p2_noise = noiseModel::Diagonal::Sigmas(Vector3{1, 1, 5});
|
||||||
|
graph.addPrior<Plane>(P(1), p1, p1_noise);
|
||||||
|
graph.addPrior<Plane>(P(2), p2, p2_noise);
|
||||||
|
|
||||||
|
// Plane factors
|
||||||
|
auto p1_measured_from_x0 = p1.transform(x0); // transform p1 to pose x0 as a measurement
|
||||||
|
auto p2_measured_from_x0 = p2.transform(x0); // transform p2 to pose x0 as a measurement
|
||||||
|
const auto x0_p1_noise = noiseModel::Isotropic::Sigma(3, 0.05);
|
||||||
|
const auto x0_p2_noise = noiseModel::Isotropic::Sigma(3, 0.05);
|
||||||
|
graph.emplace_shared<OrientedPlane3Factor>(
|
||||||
|
p1_measured_from_x0.planeCoefficients(), x0_p1_noise, X(0), P(1));
|
||||||
|
graph.emplace_shared<OrientedPlane3Factor>(
|
||||||
|
p2_measured_from_x0.planeCoefficients(), x0_p2_noise, X(0), P(2));
|
||||||
|
|
||||||
|
// Initial values
|
||||||
|
// Just offset the initial pose by 1m. This is what we are trying to optimize.
|
||||||
|
Values initialEstimate;
|
||||||
|
Pose3 x0_initial = x0.compose(Pose3(Rot3::identity(), Vector3(1,0,0)));
|
||||||
|
initialEstimate.insert(P(1), p1);
|
||||||
|
initialEstimate.insert(P(2), p2);
|
||||||
|
initialEstimate.insert(X(0), x0_initial);
|
||||||
|
|
||||||
|
// Optimize
|
||||||
|
try {
|
||||||
|
GaussNewtonOptimizer optimizer(graph, initialEstimate);
|
||||||
|
Values result = optimizer.optimize();
|
||||||
|
EXPECT_DOUBLES_EQUAL(0, graph.error(result), 0.1);
|
||||||
|
EXPECT(x0.equals(result.at<Pose3>(X(0))));
|
||||||
|
EXPECT(p1.equals(result.at<Plane>(P(1))));
|
||||||
|
EXPECT(p2.equals(result.at<Plane>(P(2))));
|
||||||
|
} catch (const IndeterminantLinearSystemException &e) {
|
||||||
|
std::cerr << "CAPTURED THE EXCEPTION: " << DefaultKeyFormatter(e.nearbyVariable()) << std::endl;
|
||||||
|
EXPECT(false); // fail if this happens
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* ************************************************************************* */
|
/* ************************************************************************* */
|
||||||
int main() {
|
int main() {
|
||||||
srand(time(nullptr));
|
srand(time(nullptr));
|
||||||
|
|
|
@ -0,0 +1,64 @@
|
||||||
|
/*
|
||||||
|
* LocalOrientedPlane3Factor.cpp
|
||||||
|
*
|
||||||
|
* Author: David Wisth
|
||||||
|
* Created on: February 12, 2021
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "LocalOrientedPlane3Factor.h"
|
||||||
|
|
||||||
|
using namespace std;
|
||||||
|
|
||||||
|
namespace gtsam {
|
||||||
|
|
||||||
|
//***************************************************************************
|
||||||
|
void LocalOrientedPlane3Factor::print(const string& s,
|
||||||
|
const KeyFormatter& keyFormatter) const {
|
||||||
|
cout << s << (s == "" ? "" : "\n");
|
||||||
|
cout << "LocalOrientedPlane3Factor Factor (" << keyFormatter(key1()) << ", "
|
||||||
|
<< keyFormatter(key2()) << ", " << keyFormatter(key3()) << ")\n";
|
||||||
|
measured_p_.print("Measured Plane");
|
||||||
|
this->noiseModel_->print(" noise model: ");
|
||||||
|
}
|
||||||
|
|
||||||
|
//***************************************************************************
|
||||||
|
Vector LocalOrientedPlane3Factor::evaluateError(const Pose3& wTwi,
|
||||||
|
const Pose3& wTwa, const OrientedPlane3& a_plane,
|
||||||
|
boost::optional<Matrix&> H1, boost::optional<Matrix&> H2,
|
||||||
|
boost::optional<Matrix&> H3) const {
|
||||||
|
|
||||||
|
Matrix66 aTai_H_wTwa, aTai_H_wTwi;
|
||||||
|
Matrix36 predicted_H_aTai;
|
||||||
|
Matrix33 predicted_H_plane, error_H_predicted;
|
||||||
|
|
||||||
|
// Find the relative transform from anchor to sensor frame.
|
||||||
|
const Pose3 aTai = wTwa.transformPoseTo(wTwi,
|
||||||
|
H2 ? &aTai_H_wTwa : nullptr,
|
||||||
|
H1 ? &aTai_H_wTwi : nullptr);
|
||||||
|
|
||||||
|
// Transform the plane measurement into sensor frame.
|
||||||
|
const OrientedPlane3 i_plane = a_plane.transform(aTai,
|
||||||
|
H2 ? &predicted_H_plane : nullptr,
|
||||||
|
(H1 || H3) ? &predicted_H_aTai : nullptr);
|
||||||
|
|
||||||
|
// Calculate the error between measured and estimated planes in sensor frame.
|
||||||
|
const Vector3 err = measured_p_.errorVector(i_plane,
|
||||||
|
boost::none, (H1 || H2 || H3) ? &error_H_predicted : nullptr);
|
||||||
|
|
||||||
|
// Apply the chain rule to calculate the derivatives.
|
||||||
|
if (H1) {
|
||||||
|
*H1 = error_H_predicted * predicted_H_aTai * aTai_H_wTwi;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (H2) {
|
||||||
|
*H2 = error_H_predicted * predicted_H_aTai * aTai_H_wTwa;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (H3) {
|
||||||
|
*H3 = error_H_predicted * predicted_H_plane;
|
||||||
|
}
|
||||||
|
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace gtsam
|
|
@ -0,0 +1,92 @@
|
||||||
|
/*
|
||||||
|
* @file LocalOrientedPlane3Factor.h
|
||||||
|
* @brief LocalOrientedPlane3 Factor class
|
||||||
|
* @author David Wisth
|
||||||
|
* @date February 12, 2021
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <gtsam/geometry/OrientedPlane3.h>
|
||||||
|
#include <gtsam/nonlinear/NonlinearFactor.h>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace gtsam {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Factor to measure a planar landmark from a given pose, with a given local
|
||||||
|
* linearization point.
|
||||||
|
*
|
||||||
|
* This factor is based on the relative plane factor formulation proposed in:
|
||||||
|
* Equation 25,
|
||||||
|
* M. Kaess, "Simultaneous Localization and Mapping with Infinite Planes",
|
||||||
|
* IEEE International Conference on Robotics and Automation, 2015.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* The main purpose of this factor is to improve the numerical stability of the
|
||||||
|
* optimization, especially compared to gtsam::OrientedPlane3Factor. This
|
||||||
|
* is especially relevant when the sensor is far from the origin (and thus
|
||||||
|
* the derivatives associated to transforming the plane are large).
|
||||||
|
*
|
||||||
|
* x0 is the current sensor pose, and x1 is the local "anchor pose" - i.e.
|
||||||
|
* a local linearisation point for the plane. The plane is representated and
|
||||||
|
* optimized in x1 frame in the optimization.
|
||||||
|
*/
|
||||||
|
class LocalOrientedPlane3Factor: public NoiseModelFactor3<Pose3, Pose3,
|
||||||
|
OrientedPlane3> {
|
||||||
|
protected:
|
||||||
|
OrientedPlane3 measured_p_;
|
||||||
|
typedef NoiseModelFactor3<Pose3, Pose3, OrientedPlane3> Base;
|
||||||
|
public:
|
||||||
|
/// Constructor
|
||||||
|
LocalOrientedPlane3Factor() {}
|
||||||
|
|
||||||
|
virtual ~LocalOrientedPlane3Factor() {}
|
||||||
|
|
||||||
|
/** Constructor with measured plane (a,b,c,d) coefficients
|
||||||
|
* @param z measured plane (a,b,c,d) coefficients as 4D vector
|
||||||
|
* @param noiseModel noiseModel Gaussian noise model
|
||||||
|
* @param poseKey Key or symbol for unknown pose
|
||||||
|
* @param anchorPoseKey Key or symbol for the plane's linearization point,
|
||||||
|
(called the "anchor pose").
|
||||||
|
* @param landmarkKey Key or symbol for unknown planar landmark
|
||||||
|
*
|
||||||
|
* Note: The anchorPoseKey can simply be chosen as the first pose a plane
|
||||||
|
* is observed.
|
||||||
|
*/
|
||||||
|
LocalOrientedPlane3Factor(const Vector4& z, const SharedGaussian& noiseModel,
|
||||||
|
Key poseKey, Key anchorPoseKey, Key landmarkKey)
|
||||||
|
: Base(noiseModel, poseKey, anchorPoseKey, landmarkKey), measured_p_(z) {}
|
||||||
|
|
||||||
|
LocalOrientedPlane3Factor(const OrientedPlane3& z,
|
||||||
|
const SharedGaussian& noiseModel,
|
||||||
|
Key poseKey, Key anchorPoseKey, Key landmarkKey)
|
||||||
|
: Base(noiseModel, poseKey, anchorPoseKey, landmarkKey), measured_p_(z) {}
|
||||||
|
|
||||||
|
/// print
|
||||||
|
void print(const std::string& s = "LocalOrientedPlane3Factor",
|
||||||
|
const KeyFormatter& keyFormatter = DefaultKeyFormatter) const override;
|
||||||
|
|
||||||
|
/***
|
||||||
|
* Vector of errors
|
||||||
|
* @brief Error = measured_plane_.error(a_plane.transform(inv(wTwa) * wTwi))
|
||||||
|
*
|
||||||
|
* This is the error of the measured and predicted plane in the current
|
||||||
|
* sensor frame, i. The plane is represented in the anchor pose, a.
|
||||||
|
*
|
||||||
|
* @param wTwi The pose of the sensor in world coordinates
|
||||||
|
* @param wTwa The pose of the anchor frame in world coordinates
|
||||||
|
* @param a_plane The estimated plane in anchor frame.
|
||||||
|
*
|
||||||
|
* Note: The optimized plane is represented in anchor frame, a, not the
|
||||||
|
* world frame.
|
||||||
|
*/
|
||||||
|
Vector evaluateError(const Pose3& wTwi, const Pose3& wTwa,
|
||||||
|
const OrientedPlane3& a_plane,
|
||||||
|
boost::optional<Matrix&> H1 = boost::none,
|
||||||
|
boost::optional<Matrix&> H2 = boost::none,
|
||||||
|
boost::optional<Matrix&> H3 = boost::none) const override;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace gtsam
|
||||||
|
|
|
@ -0,0 +1,243 @@
|
||||||
|
/* ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
* 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 testLocalOrientedPlane3Factor.cpp
|
||||||
|
* @date Feb 12, 2021
|
||||||
|
* @author David Wisth
|
||||||
|
* @brief Tests the LocalOrientedPlane3Factor class
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <gtsam_unstable/slam/LocalOrientedPlane3Factor.h>
|
||||||
|
|
||||||
|
#include <gtsam/base/numericalDerivative.h>
|
||||||
|
#include <gtsam/inference/Symbol.h>
|
||||||
|
#include <gtsam/nonlinear/ISAM2.h>
|
||||||
|
|
||||||
|
#include <CppUnitLite/TestHarness.h>
|
||||||
|
|
||||||
|
#include <boost/bind.hpp>
|
||||||
|
|
||||||
|
using namespace gtsam;
|
||||||
|
using namespace std;
|
||||||
|
|
||||||
|
GTSAM_CONCEPT_TESTABLE_INST(OrientedPlane3)
|
||||||
|
GTSAM_CONCEPT_MANIFOLD_INST(OrientedPlane3)
|
||||||
|
|
||||||
|
using symbol_shorthand::P; //< Planes
|
||||||
|
using symbol_shorthand::X; //< Pose3
|
||||||
|
|
||||||
|
// *************************************************************************
|
||||||
|
TEST(LocalOrientedPlane3Factor, lm_translation_error) {
|
||||||
|
// Tests one pose, two measurements of the landmark that differ in range only.
|
||||||
|
// Normal along -x, 3m away
|
||||||
|
OrientedPlane3 test_lm0(-1.0, 0.0, 0.0, 3.0);
|
||||||
|
|
||||||
|
NonlinearFactorGraph graph;
|
||||||
|
|
||||||
|
// Init pose and prior. Pose Prior is needed since a single plane measurement
|
||||||
|
// does not fully constrain the pose
|
||||||
|
Pose3 init_pose = Pose3::identity();
|
||||||
|
Pose3 anchor_pose = Pose3::identity();
|
||||||
|
graph.addPrior(X(0), init_pose, noiseModel::Isotropic::Sigma(6, 0.001));
|
||||||
|
graph.addPrior(X(1), anchor_pose, noiseModel::Isotropic::Sigma(6, 0.001));
|
||||||
|
|
||||||
|
// Add two landmark measurements, differing in range
|
||||||
|
Vector4 measurement0(-1.0, 0.0, 0.0, 3.0);
|
||||||
|
Vector4 measurement1(-1.0, 0.0, 0.0, 1.0);
|
||||||
|
LocalOrientedPlane3Factor factor0(
|
||||||
|
measurement0, noiseModel::Isotropic::Sigma(3, 0.1), X(0), X(1), P(0));
|
||||||
|
LocalOrientedPlane3Factor factor1(
|
||||||
|
measurement1, noiseModel::Isotropic::Sigma(3, 0.1), X(0), X(1), P(0));
|
||||||
|
graph.add(factor0);
|
||||||
|
graph.add(factor1);
|
||||||
|
|
||||||
|
// Initial Estimate
|
||||||
|
Values values;
|
||||||
|
values.insert(X(0), init_pose);
|
||||||
|
values.insert(X(1), anchor_pose);
|
||||||
|
values.insert(P(0), test_lm0);
|
||||||
|
|
||||||
|
// Optimize
|
||||||
|
ISAM2 isam2;
|
||||||
|
isam2.update(graph, values);
|
||||||
|
Values result_values = isam2.calculateEstimate();
|
||||||
|
auto optimized_plane_landmark = result_values.at<OrientedPlane3>(P(0));
|
||||||
|
|
||||||
|
// Given two noisy measurements of equal weight, expect result between the two
|
||||||
|
OrientedPlane3 expected_plane_landmark(-1.0, 0.0, 0.0, 2.0);
|
||||||
|
EXPECT(assert_equal(optimized_plane_landmark, expected_plane_landmark));
|
||||||
|
}
|
||||||
|
|
||||||
|
// *************************************************************************
|
||||||
|
// TODO As described in PR #564 after correcting the derivatives in
|
||||||
|
// OrientedPlane3Factor this test fails. It should be debugged and re-enabled.
|
||||||
|
/*
|
||||||
|
TEST (LocalOrientedPlane3Factor, lm_rotation_error) {
|
||||||
|
// Tests one pose, two measurements of the landmark that differ in angle only.
|
||||||
|
// Normal along -x, 3m away
|
||||||
|
OrientedPlane3 test_lm0(-1.0/sqrt(1.01), -0.1/sqrt(1.01), 0.0, 3.0);
|
||||||
|
|
||||||
|
NonlinearFactorGraph graph;
|
||||||
|
|
||||||
|
// Init pose and prior. Pose Prior is needed since a single plane measurement
|
||||||
|
// does not fully constrain the pose
|
||||||
|
Pose3 init_pose = Pose3::identity();
|
||||||
|
graph.addPrior(X(0), init_pose, noiseModel::Isotropic::Sigma(6, 0.001));
|
||||||
|
|
||||||
|
// Add two landmark measurements, differing in angle
|
||||||
|
Vector4 measurement0(-1.0, 0.0, 0.0, 3.0);
|
||||||
|
Vector4 measurement1(0.0, -1.0, 0.0, 3.0);
|
||||||
|
LocalOrientedPlane3Factor factor0(measurement0,
|
||||||
|
noiseModel::Isotropic::Sigma(3, 0.1), X(0), X(0), P(0));
|
||||||
|
LocalOrientedPlane3Factor factor1(measurement1,
|
||||||
|
noiseModel::Isotropic::Sigma(3, 0.1), X(0), X(0), P(0));
|
||||||
|
graph.add(factor0);
|
||||||
|
graph.add(factor1);
|
||||||
|
|
||||||
|
// Initial Estimate
|
||||||
|
Values values;
|
||||||
|
values.insert(X(0), init_pose);
|
||||||
|
values.insert(P(0), test_lm0);
|
||||||
|
|
||||||
|
// Optimize
|
||||||
|
ISAM2 isam2;
|
||||||
|
isam2.update(graph, values);
|
||||||
|
Values result_values = isam2.calculateEstimate();
|
||||||
|
isam2.getDelta().print();
|
||||||
|
|
||||||
|
auto optimized_plane_landmark = result_values.at<OrientedPlane3>(P(0));
|
||||||
|
|
||||||
|
values.print();
|
||||||
|
result_values.print();
|
||||||
|
|
||||||
|
// Given two noisy measurements of equal weight, expect result between the two
|
||||||
|
OrientedPlane3 expected_plane_landmark(-sqrt(2.0) / 2.0, -sqrt(2.0) / 2.0,
|
||||||
|
0.0, 3.0);
|
||||||
|
EXPECT(assert_equal(optimized_plane_landmark, expected_plane_landmark));
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
// *************************************************************************
|
||||||
|
TEST(LocalOrientedPlane3Factor, Derivatives) {
|
||||||
|
// Measurement
|
||||||
|
OrientedPlane3 p(sqrt(2)/2, -sqrt(2)/2, 0, 5);
|
||||||
|
|
||||||
|
// Linearisation point
|
||||||
|
OrientedPlane3 pLin(sqrt(3)/3, -sqrt(3)/3, sqrt(3)/3, 7);
|
||||||
|
Pose3 poseLin(Rot3::RzRyRx(0.5*M_PI, -0.3*M_PI, 1.4*M_PI), Point3(1, 2, -4));
|
||||||
|
Pose3 anchorPoseLin(Rot3::RzRyRx(-0.1*M_PI, 0.2*M_PI, 1.0), Point3(-5, 0, 1));
|
||||||
|
|
||||||
|
// Factor
|
||||||
|
Key planeKey(1), poseKey(2), anchorPoseKey(3);
|
||||||
|
SharedGaussian noise = noiseModel::Isotropic::Sigma(3, 0.1);
|
||||||
|
LocalOrientedPlane3Factor factor(p, noise, poseKey, anchorPoseKey, planeKey);
|
||||||
|
|
||||||
|
// Calculate numerical derivatives
|
||||||
|
auto f = boost::bind(&LocalOrientedPlane3Factor::evaluateError, factor,
|
||||||
|
_1, _2, _3, boost::none, boost::none, boost::none);
|
||||||
|
Matrix numericalH1 = numericalDerivative31<Vector3, Pose3, Pose3,
|
||||||
|
OrientedPlane3>(f, poseLin, anchorPoseLin, pLin);
|
||||||
|
Matrix numericalH2 = numericalDerivative32<Vector3, Pose3, Pose3,
|
||||||
|
OrientedPlane3>(f, poseLin, anchorPoseLin, pLin);
|
||||||
|
Matrix numericalH3 = numericalDerivative33<Vector3, Pose3, Pose3,
|
||||||
|
OrientedPlane3>(f, poseLin, anchorPoseLin, pLin);
|
||||||
|
|
||||||
|
// Use the factor to calculate the derivative
|
||||||
|
Matrix actualH1, actualH2, actualH3;
|
||||||
|
factor.evaluateError(poseLin, anchorPoseLin, pLin, actualH1, actualH2,
|
||||||
|
actualH3);
|
||||||
|
|
||||||
|
// Verify we get the expected error
|
||||||
|
EXPECT(assert_equal(numericalH1, actualH1, 1e-8));
|
||||||
|
EXPECT(assert_equal(numericalH2, actualH2, 1e-8));
|
||||||
|
EXPECT(assert_equal(numericalH3, actualH3, 1e-8));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* ************************************************************************* */
|
||||||
|
// Simplified version of the test by Marco Camurri to debug issue #561
|
||||||
|
//
|
||||||
|
// This test provides an example of how LocalOrientedPlane3Factor works.
|
||||||
|
// x0 is the current sensor pose, and x1 is the local "anchor pose" - i.e.
|
||||||
|
// a local linearisation point for the plane. The plane is representated and
|
||||||
|
// optimized in x1 frame in the optimization. This greatly improves numerical
|
||||||
|
// stability when the pose is far from the origin.
|
||||||
|
//
|
||||||
|
TEST(LocalOrientedPlane3Factor, Issue561Simplified) {
|
||||||
|
// Typedefs
|
||||||
|
using Plane = OrientedPlane3;
|
||||||
|
|
||||||
|
NonlinearFactorGraph graph;
|
||||||
|
|
||||||
|
// Setup prior factors
|
||||||
|
Pose3 x0(Rot3::identity(), Vector3(100, 30, 10)); // the "sensor pose"
|
||||||
|
Pose3 x1(Rot3::identity(), Vector3(90, 40, 5) ); // the "anchor pose"
|
||||||
|
|
||||||
|
auto x0_noise = noiseModel::Isotropic::Sigma(6, 0.01);
|
||||||
|
auto x1_noise = noiseModel::Isotropic::Sigma(6, 0.01);
|
||||||
|
graph.addPrior<Pose3>(X(0), x0, x0_noise);
|
||||||
|
graph.addPrior<Pose3>(X(1), x1, x1_noise);
|
||||||
|
|
||||||
|
// Two horizontal planes with different heights, in the world frame.
|
||||||
|
const Plane p1(0, 0, 1, 1), p2(0, 0, 1, 2);
|
||||||
|
// Transform to x1, the "anchor frame" (i.e. local frame)
|
||||||
|
auto p1_in_x1 = p1.transform(x1);
|
||||||
|
auto p2_in_x1 = p2.transform(x1);
|
||||||
|
auto p1_noise = noiseModel::Diagonal::Sigmas(Vector3{1, 1, 5});
|
||||||
|
auto p2_noise = noiseModel::Diagonal::Sigmas(Vector3{1, 1, 5});
|
||||||
|
graph.addPrior<Plane>(P(1), p1_in_x1, p1_noise);
|
||||||
|
graph.addPrior<Plane>(P(2), p2_in_x1, p2_noise);
|
||||||
|
|
||||||
|
// Add plane factors, with a local linearization point.
|
||||||
|
// transform p1 to pose x0 as a measurement
|
||||||
|
auto p1_measured_from_x0 = p1.transform(x0);
|
||||||
|
// transform p2 to pose x0 as a measurement
|
||||||
|
auto p2_measured_from_x0 = p2.transform(x0);
|
||||||
|
const auto x0_p1_noise = noiseModel::Isotropic::Sigma(3, 0.05);
|
||||||
|
const auto x0_p2_noise = noiseModel::Isotropic::Sigma(3, 0.05);
|
||||||
|
graph.emplace_shared<LocalOrientedPlane3Factor>(
|
||||||
|
p1_measured_from_x0.planeCoefficients(), x0_p1_noise, X(0), X(1), P(1));
|
||||||
|
graph.emplace_shared<LocalOrientedPlane3Factor>(
|
||||||
|
p2_measured_from_x0.planeCoefficients(), x0_p2_noise, X(0), X(1), P(2));
|
||||||
|
|
||||||
|
// Initial values
|
||||||
|
// Just offset the initial pose by 1m. This is what we are trying to optimize.
|
||||||
|
Values initialEstimate;
|
||||||
|
Pose3 x0_initial = x0.compose(Pose3(Rot3::identity(), Vector3(1, 0, 0)));
|
||||||
|
initialEstimate.insert(P(1), p1_in_x1);
|
||||||
|
initialEstimate.insert(P(2), p2_in_x1);
|
||||||
|
initialEstimate.insert(X(0), x0_initial);
|
||||||
|
initialEstimate.insert(X(1), x1);
|
||||||
|
|
||||||
|
// Optimize
|
||||||
|
try {
|
||||||
|
ISAM2 isam2;
|
||||||
|
isam2.update(graph, initialEstimate);
|
||||||
|
Values result = isam2.calculateEstimate();
|
||||||
|
EXPECT_DOUBLES_EQUAL(0, graph.error(result), 0.1);
|
||||||
|
EXPECT(x0.equals(result.at<Pose3>(X(0))));
|
||||||
|
EXPECT(p1_in_x1.equals(result.at<Plane>(P(1))));
|
||||||
|
EXPECT(p2_in_x1.equals(result.at<Plane>(P(2))));
|
||||||
|
} catch (const IndeterminantLinearSystemException &e) {
|
||||||
|
cerr << "CAPTURED THE EXCEPTION: "
|
||||||
|
<< DefaultKeyFormatter(e.nearbyVariable()) << endl;
|
||||||
|
EXPECT(false); // fail if this happens
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ************************************************************************* */
|
||||||
|
int main() {
|
||||||
|
srand(time(nullptr));
|
||||||
|
TestResult tr;
|
||||||
|
return TestRegistry::runAllTests(tr);
|
||||||
|
}
|
||||||
|
/* ************************************************************************* */
|
Loading…
Reference in New Issue