diff --git a/sophus/se3.hpp b/sophus/se3.hpp index c3c4290f7..96a666744 100644 --- a/sophus/se3.hpp +++ b/sophus/se3.hpp @@ -1014,6 +1014,53 @@ class SE3 : public SE3Base> { return upsilon_omega; } + template + SOPHUS_FUNC void setFromPointClouds( + Eigen::MatrixBase const& src, + Eigen::MatrixBase const& dst) { + using Eigen::Index; + using MatrixType = + typename Eigen::internal::plain_matrix_type::type; + const Index N = src.cols(); + + SOPHUS_ENSURE( + src.rows() == 3 && dst.rows() == 3 && N == dst.cols(), + "Point clouds are stored in 3xN matrices, one point per column. Source " + "and destination matrices are {}x{} and {}x{} respectively", + src.rows(), N, dst.rows(), dst.cols()); + + Scalar w(1. / N); + Vector3 const src_mean = src.rowwise().sum() * w; + Vector3 const dst_mean = dst.rowwise().sum() * w; + + MatrixType m1 = src.colwise() - src_mean; + MatrixType m2 = dst.colwise() - dst_mean; + + m1 += m2; + m2 = m1 - Scalar(2) * m2; + Matrix4 W = Matrix4::Zero(); + for (Index i(0); i < N; ++i) { + Matrix3 const S = SO3::hat(m1.col(i)); + Vector3 const v = m2.col(i); + Matrix4 V; + V << S, -v, v.transpose(), Scalar(0); + W += w * V.transpose() * V; + } + const Eigen::SelfAdjointEigenSolver> eig(W); + const Eigen::Quaternion rotation(eig.eigenvectors().real().col(0)); + so3_.setQuaternion(rotation); + translation_ = dst_mean - rotation * src_mean; + } + + template + SOPHUS_FUNC static SE3 FromPointClouds( + Eigen::MatrixBase const& src, + Eigen::MatrixBase const& dst) { + SE3 ret; + ret.setFromPointClouds(src, dst); + return ret; + } + protected: SO3Member so3_; TranslationMember translation_; diff --git a/test/core/test_se3.cpp b/test/core/test_se3.cpp index 5dffc5599..16ca9dd7f 100644 --- a/test/core/test_se3.cpp +++ b/test/core/test_se3.cpp @@ -57,6 +57,7 @@ class Tests { passed &= testMutatingAccessors(); passed &= testConstructors(); passed &= testFit(); + passed &= testPointCloudAlignment(); processTestResult(passed); } @@ -243,6 +244,49 @@ class Tests { return true; } + template + enable_if_t::value, bool> + testPointCloudAlignment() { + bool passed = true; + + using MatrixType = Eigen::Matrix; + + const int sz = point_vec_.size(); + Eigen::Map src(point_vec_.front().data(), 3, sz); + MatrixType dst(3, sz); + + for (const auto& it : se3_vec_) { + for (int i = 0; i < sz; ++i) { + dst.col(i) = it * src.col(i); + } + SE3Type result; + result.setFromPointClouds(src, dst); + SOPHUS_TEST_APPROX(passed, it.translation(), result.translation(), + Constants::epsilon(), + "Expected translation: {}; Got: {}", it.translation(), + result.translation()); + SOPHUS_TEST_APPROX( + passed, it.matrix(), result.matrix(), Constants::epsilon(), + "Expected rotation: {}; Got: {}", it.matrix(), result.matrix()); + + result = SE3Type::FromPointClouds(src, dst); + SOPHUS_TEST_APPROX(passed, it.translation(), result.translation(), + Constants::epsilon(), + "Expected translation: {}; Got: {}", it.translation(), + result.translation()); + SOPHUS_TEST_APPROX( + passed, it.matrix(), result.matrix(), Constants::epsilon(), + "Expected rotation: {}; Got: {}", it.matrix(), result.matrix()); + } + return passed; + } + + template + enable_if_t::value, bool> + testPointCloudAlignment() { + return true; + } + std::vector> se3_vec_; std::vector> tangent_vec_; std::vector> point_vec_;