diff --git a/gtsam/geometry/CameraSet.h b/gtsam/geometry/CameraSet.h index 7e9062a8d..dd58f8e69 100644 --- a/gtsam/geometry/CameraSet.h +++ b/gtsam/geometry/CameraSet.h @@ -21,6 +21,8 @@ #include #include // for Cheirality exception #include +#include +#include #include namespace gtsam { @@ -39,8 +41,8 @@ protected: */ typedef typename CAMERA::Measurement Z; + static const int D = traits::dimension; ///< Camera dimension static const int ZDim = traits::dimension; ///< Measurement dimension - static const int Dim = traits::dimension; ///< Camera dimension /// Make a vector of re-projection errors static Vector ErrorVector(const std::vector& predicted, @@ -63,7 +65,7 @@ protected: public: /// Definitions for blocks of F - typedef Eigen::Matrix MatrixZD; // F + typedef Eigen::Matrix MatrixZD; typedef std::vector FBlocks; /** @@ -97,7 +99,7 @@ public: * throws CheiralityException */ std::vector project2(const Point3& point, // - boost::optional F = boost::none, // + boost::optional Fs = boost::none, // boost::optional E = boost::none) const { // Allocate result @@ -110,11 +112,11 @@ public: // Project and fill derivatives for (size_t i = 0; i < m; i++) { - Eigen::Matrix Fi; + MatrixZD Fi; Eigen::Matrix Ei; - z[i] = this->at(i).project2(point, F ? &Fi : 0, E ? &Ei : 0); - if (F) - F->push_back(Fi); + z[i] = this->at(i).project2(point, Fs ? &Fi : 0, E ? &Ei : 0); + if (Fs) + Fs->push_back(Fi); if (E) E->block(ZDim * i, 0) = Ei; } @@ -141,9 +143,9 @@ public: /// Calculate vector of re-projection errors Vector reprojectionError(const Point3& point, const std::vector& measured, - boost::optional F = boost::none, // + boost::optional Fs = boost::none, // boost::optional E = boost::none) const { - return ErrorVector(project2(point, F, E), measured); + return ErrorVector(project2(point, Fs, E), measured); } /// Calculate vector of re-projection errors, from point at infinity @@ -153,6 +155,127 @@ public: return ErrorVector(projectAtInfinity(point), measured); } + /** + * Do Schur complement, given Jacobian as Fs,E,P, return SymmetricBlockMatrix + * G = F' * F - F' * E * P * E' * F + * g = F' * (b - E * P * E' * b) + */ + static SymmetricBlockMatrix SchurComplement(const FBlocks& Fs, + const Matrix& E, const Matrix3& P, const Vector& b) { + + // a single point is observed in m cameras + int m = Fs.size(); + + // Create a SymmetricBlockMatrix + size_t M1 = D * m + 1; + std::vector dims(m + 1); // this also includes the b term + std::fill(dims.begin(), dims.end() - 1, D); + dims.back() = 1; + SymmetricBlockMatrix augmentedHessian(dims, Matrix::Zero(M1, M1)); + + // Blockwise Schur complement + for (size_t i = 0; i < m; i++) { // for each camera + + const MatrixZD& Fi = Fs[i]; + const Matrix23 Ei_P = E.block(ZDim * i, 0) * P; + + // D = (Dx2) * ZDim + augmentedHessian(i, m) = Fi.transpose() * b.segment(ZDim * i) // F' * b + - Fi.transpose() * (Ei_P * (E.transpose() * b)); // D = (DxZDim) * (ZDimx3) * (3*ZDimm) * (ZDimm x 1) + + // (DxD) = (DxZDim) * ( (ZDimxD) - (ZDimx3) * (3xZDim) * (ZDimxD) ) + augmentedHessian(i, i) = Fi.transpose() + * (Fi - Ei_P * E.block(ZDim * i, 0).transpose() * Fi); + + // upper triangular part of the hessian + for (size_t j = i + 1; j < m; j++) { // for each camera + const MatrixZD& Fj = Fs[j]; + + // (DxD) = (Dx2) * ( (2x2) * (2xD) ) + augmentedHessian(i, j) = -Fi.transpose() + * (Ei_P * E.block(ZDim * j, 0).transpose() * Fj); + } + } // end of for over cameras + + augmentedHessian(m, m)(0, 0) += b.squaredNorm(); + return augmentedHessian; + } + + /** + * Applies Schur complement (exploiting block structure) to get a smart factor on cameras, + * and adds the contribution of the smart factor to a pre-allocated augmented Hessian. + */ + static void UpdateSchurComplement(const FBlocks& Fs, const Matrix& E, + const Matrix3& P /*Point Covariance*/, const Vector& b, + const FastVector& allKeys, const FastVector& keys, + /*output ->*/SymmetricBlockMatrix& augmentedHessian) { + + assert(keys.size()==Fs.size()); + assert(keys.size()<=allKeys.size()); + + FastMap KeySlotMap; + for (size_t slot = 0; slot < allKeys.size(); slot++) + KeySlotMap.insert(std::make_pair(allKeys[slot], slot)); + + // Schur complement trick + // G = F' * F - F' * E * P * E' * F + // g = F' * (b - E * P * E' * b) + + Eigen::Matrix matrixBlock; + typedef SymmetricBlockMatrix::Block Block; ///< A block from the Hessian matrix + + // a single point is observed in m cameras + size_t m = Fs.size(); // cameras observing current point + size_t M = (augmentedHessian.rows() - 1) / D; // all cameras in the group + assert(allKeys.size()==M); + + // Blockwise Schur complement + for (size_t i = 0; i < m; i++) { // for each camera in the current factor + + const MatrixZD& Fi = Fs[i]; + const Matrix23 Ei_P = E.block(ZDim * i, 0) * P; + + // D = (DxZDim) * (ZDim) + // allKeys are the list of all camera keys in the group, e.g, (1,3,4,5,7) + // we should map those to a slot in the local (grouped) hessian (0,1,2,3,4) + // Key cameraKey_i = this->keys_[i]; + DenseIndex aug_i = KeySlotMap.at(keys[i]); + + // information vector - store previous vector + // vectorBlock = augmentedHessian(aug_i, aug_m).knownOffDiagonal(); + // add contribution of current factor + augmentedHessian(aug_i, M) = augmentedHessian(aug_i, M).knownOffDiagonal() + + Fi.transpose() * b.segment(ZDim * i) // F' * b + - Fi.transpose() * (Ei_P * (E.transpose() * b)); // D = (DxZDim) * (ZDimx3) * (3*ZDimm) * (ZDimm x 1) + + // (DxD) = (DxZDim) * ( (ZDimxD) - (ZDimx3) * (3xZDim) * (ZDimxD) ) + // main block diagonal - store previous block + matrixBlock = augmentedHessian(aug_i, aug_i); + // add contribution of current factor + augmentedHessian(aug_i, aug_i) = matrixBlock + + (Fi.transpose() + * (Fi - Ei_P * E.block(ZDim * i, 0).transpose() * Fi)); + + // upper triangular part of the hessian + for (size_t j = i + 1; j < m; j++) { // for each camera + const MatrixZD& Fj = Fs[j]; + + DenseIndex aug_j = KeySlotMap.at(keys[j]); + + // (DxD) = (DxZDim) * ( (ZDimxZDim) * (ZDimxD) ) + // off diagonal block - store previous block + // matrixBlock = augmentedHessian(aug_i, aug_j).knownOffDiagonal(); + // add contribution of current factor + augmentedHessian(aug_i, aug_j) = + augmentedHessian(aug_i, aug_j).knownOffDiagonal() + - Fi.transpose() + * (Ei_P * E.block(ZDim * j, 0).transpose() * Fj); + } + } // end of for over cameras + + augmentedHessian(M, M)(0, 0) += b.squaredNorm(); + } + private: /// Serialization function @@ -163,6 +286,9 @@ private: } }; +template +const int CameraSet::D; + template const int CameraSet::ZDim; diff --git a/gtsam/geometry/tests/testCameraSet.cpp b/gtsam/geometry/tests/testCameraSet.cpp index 25a6da5b2..05ffc275c 100644 --- a/gtsam/geometry/tests/testCameraSet.cpp +++ b/gtsam/geometry/tests/testCameraSet.cpp @@ -31,14 +31,15 @@ using namespace gtsam; #include TEST(CameraSet, Pinhole) { typedef PinholeCamera Camera; + typedef CameraSet Set; typedef vector ZZ; - CameraSet set; + Set set; Camera camera; set.push_back(camera); set.push_back(camera); Point3 p(0, 0, 1); EXPECT(assert_equal(set, set)); - CameraSet set2 = set; + Set set2 = set; set2.push_back(camera); EXPECT(!set.equals(set2)); @@ -50,7 +51,7 @@ TEST(CameraSet, Pinhole) { // Calculate expected derivatives using Pinhole Matrix43 actualE; - Matrix F1; + Matrix29 F1; { Matrix23 E1; Matrix23 H1; @@ -59,12 +60,12 @@ TEST(CameraSet, Pinhole) { } // Check computed derivatives - CameraSet::FBlocks F; - Matrix E, H; - set.project2(p, F, E); - LONGS_EQUAL(2,F.size()); - EXPECT(assert_equal(F1, F[0])); - EXPECT(assert_equal(F1, F[1])); + Set::FBlocks Fs; + Matrix E; + set.project2(p, Fs, E); + LONGS_EQUAL(2, Fs.size()); + EXPECT(assert_equal(F1, Fs[0])); + EXPECT(assert_equal(F1, Fs[1])); EXPECT(assert_equal(actualE, E)); // Check errors @@ -78,6 +79,40 @@ TEST(CameraSet, Pinhole) { Vector actualV = set.reprojectionError(p, measured); EXPECT(assert_equal(expectedV, actualV)); + // Check Schur complement + Matrix F(4, 18); + F << F1, Matrix29::Zero(), Matrix29::Zero(), F1; + Matrix Ft = F.transpose(); + Matrix34 Et = E.transpose(); + Matrix3 P = Et * E; + Matrix schur(19, 19); + Vector4 b = actualV; + Vector v = Ft * (b - E * P * Et * b); + schur << Ft * F - Ft * E * P * Et * F, v, v.transpose(), 30; + SymmetricBlockMatrix actualReduced = Set::SchurComplement(Fs, E, P, b); + EXPECT(assert_equal(schur, actualReduced.matrix())); + + // Check Schur complement update, same order, should just double + FastVector allKeys, keys; + allKeys.push_back(1); + allKeys.push_back(2); + keys.push_back(1); + keys.push_back(2); + Set::UpdateSchurComplement(Fs, E, P, b, allKeys, keys, actualReduced); + EXPECT(assert_equal((Matrix )(2.0 * schur), actualReduced.matrix())); + + // Check Schur complement update, keys reversed + FastVector keys2; + keys2.push_back(2); + keys2.push_back(1); + Set::UpdateSchurComplement(Fs, E, P, b, allKeys, keys2, actualReduced); + Vector4 reverse_b; + reverse_b << b.tail<2>(), b.head<2>(); + Vector reverse_v = Ft * (reverse_b - E * P * Et * reverse_b); + Matrix A(19, 19); + A << Ft * F - Ft * E * P * Et * F, reverse_v, reverse_v.transpose(), 30; + EXPECT(assert_equal((Matrix )(2.0 * schur + A), actualReduced.matrix())); + // reprojectionErrorAtInfinity EXPECT( assert_equal(Point3(0, 0, 1), @@ -113,12 +148,12 @@ TEST(CameraSet, Stereo) { } // Check computed derivatives - CameraSet::FBlocks F; + CameraSet::FBlocks Fs; Matrix E; - set.project2(p, F, E); - LONGS_EQUAL(2,F.size()); - EXPECT(assert_equal(F1, F[0])); - EXPECT(assert_equal(F1, F[1])); + set.project2(p, Fs, E); + LONGS_EQUAL(2, Fs.size()); + EXPECT(assert_equal(F1, Fs[0])); + EXPECT(assert_equal(F1, Fs[1])); EXPECT(assert_equal(actualE, E)); }