Skip to content

Commit d7f048b

Browse files
authored
Merge pull request #827 from borglab/feature/rollingShutterSmartFactors
Feature/rolling shutter smart factors
2 parents ff7ddf4 + 9798bfa commit d7f048b

13 files changed

+2641
-126
lines changed

gtsam/base/Lie.h

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
* @author Frank Dellaert
1818
* @author Mike Bosse
1919
* @author Duy Nguyen Ta
20+
* @author Yotam Stern
2021
*/
2122

2223

@@ -319,12 +320,28 @@ T expm(const Vector& x, int K=7) {
319320
}
320321

321322
/**
322-
* Linear interpolation between X and Y by coefficient t in [0, 1].
323+
* Linear interpolation between X and Y by coefficient t. Typically t \in [0,1],
324+
* but can also be used to extrapolate before pose X or after pose Y.
323325
*/
324326
template <typename T>
325-
T interpolate(const T& X, const T& Y, double t) {
326-
assert(t >= 0 && t <= 1);
327-
return traits<T>::Compose(X, traits<T>::Expmap(t * traits<T>::Logmap(traits<T>::Between(X, Y))));
327+
T interpolate(const T& X, const T& Y, double t,
328+
typename MakeOptionalJacobian<T, T>::type Hx = boost::none,
329+
typename MakeOptionalJacobian<T, T>::type Hy = boost::none) {
330+
if (Hx || Hy) {
331+
typename MakeJacobian<T, T>::type between_H_x, log_H, exp_H, compose_H_x;
332+
const T between =
333+
traits<T>::Between(X, Y, between_H_x); // between_H_y = identity
334+
typename traits<T>::TangentVector delta = traits<T>::Logmap(between, log_H);
335+
const T Delta = traits<T>::Expmap(t * delta, exp_H);
336+
const T result = traits<T>::Compose(
337+
X, Delta, compose_H_x); // compose_H_xinv_y = identity
338+
339+
if (Hx) *Hx = compose_H_x + t * exp_H * log_H * between_H_x;
340+
if (Hy) *Hy = t * exp_H * log_H;
341+
return result;
342+
}
343+
return traits<T>::Compose(
344+
X, traits<T>::Expmap(t * traits<T>::Logmap(traits<T>::Between(X, Y))));
328345
}
329346

330347
/**

gtsam/geometry/CameraSet.h

Lines changed: 139 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -147,51 +147,149 @@ class CameraSet : public std::vector<CAMERA, Eigen::aligned_allocator<CAMERA> >
147147
* G = F' * F - F' * E * P * E' * F
148148
* g = F' * (b - E * P * E' * b)
149149
* Fixed size version
150-
*/
151-
template<int N, int ND> // N = 2 or 3, ND is the camera dimension
152-
static SymmetricBlockMatrix SchurComplement(
153-
const std::vector< Eigen::Matrix<double, ZDim, ND>, Eigen::aligned_allocator< Eigen::Matrix<double, ZDim, ND> > >& Fs,
154-
const Matrix& E, const Eigen::Matrix<double, N, N>& P, const Vector& b) {
155-
156-
// a single point is observed in m cameras
157-
size_t m = Fs.size();
158-
159-
// Create a SymmetricBlockMatrix (augmented hessian, with extra row/column with info vector)
160-
size_t M1 = ND * m + 1;
161-
std::vector<DenseIndex> dims(m + 1); // this also includes the b term
162-
std::fill(dims.begin(), dims.end() - 1, ND);
163-
dims.back() = 1;
164-
SymmetricBlockMatrix augmentedHessian(dims, Matrix::Zero(M1, M1));
165-
166-
// Blockwise Schur complement
167-
for (size_t i = 0; i < m; i++) { // for each camera
168-
169-
const Eigen::Matrix<double, ZDim, ND>& Fi = Fs[i];
170-
const auto FiT = Fi.transpose();
171-
const Eigen::Matrix<double, ZDim, N> Ei_P = //
172-
E.block(ZDim * i, 0, ZDim, N) * P;
173-
174-
// D = (Dx2) * ZDim
175-
augmentedHessian.setOffDiagonalBlock(i, m, FiT * b.segment<ZDim>(ZDim * i) // F' * b
176-
- FiT * (Ei_P * (E.transpose() * b))); // D = (DxZDim) * (ZDimx3) * (N*ZDimm) * (ZDimm x 1)
150+
*/
151+
template <int N,
152+
int ND> // N = 2 or 3 (point dimension), ND is the camera dimension
153+
static SymmetricBlockMatrix SchurComplement(
154+
const std::vector<
155+
Eigen::Matrix<double, ZDim, ND>,
156+
Eigen::aligned_allocator<Eigen::Matrix<double, ZDim, ND>>>& Fs,
157+
const Matrix& E, const Eigen::Matrix<double, N, N>& P, const Vector& b) {
158+
// a single point is observed in m cameras
159+
size_t m = Fs.size();
160+
161+
// Create a SymmetricBlockMatrix (augmented hessian, with extra row/column with info vector)
162+
size_t M1 = ND * m + 1;
163+
std::vector<DenseIndex> dims(m + 1); // this also includes the b term
164+
std::fill(dims.begin(), dims.end() - 1, ND);
165+
dims.back() = 1;
166+
SymmetricBlockMatrix augmentedHessian(dims, Matrix::Zero(M1, M1));
167+
168+
// Blockwise Schur complement
169+
for (size_t i = 0; i < m; i++) { // for each camera
170+
171+
const Eigen::Matrix<double, ZDim, ND>& Fi = Fs[i];
172+
const auto FiT = Fi.transpose();
173+
const Eigen::Matrix<double, ZDim, N> Ei_P = //
174+
E.block(ZDim * i, 0, ZDim, N) * P;
175+
176+
// D = (Dx2) * ZDim
177+
augmentedHessian.setOffDiagonalBlock(i, m, FiT * b.segment<ZDim>(ZDim * i) // F' * b
178+
- FiT * (Ei_P * (E.transpose() * b))); // D = (DxZDim) * (ZDimx3) * (N*ZDimm) * (ZDimm x 1)
179+
180+
// (DxD) = (DxZDim) * ( (ZDimxD) - (ZDimx3) * (3xZDim) * (ZDimxD) )
181+
augmentedHessian.setDiagonalBlock(i, FiT
182+
* (Fi - Ei_P * E.block(ZDim * i, 0, ZDim, N).transpose() * Fi));
183+
184+
// upper triangular part of the hessian
185+
for (size_t j = i + 1; j < m; j++) { // for each camera
186+
const Eigen::Matrix<double, ZDim, ND>& Fj = Fs[j];
187+
188+
// (DxD) = (Dx2) * ( (2x2) * (2xD) )
189+
augmentedHessian.setOffDiagonalBlock(i, j, -FiT
190+
* (Ei_P * E.block(ZDim * j, 0, ZDim, N).transpose() * Fj));
191+
}
192+
} // end of for over cameras
177193

178-
// (DxD) = (DxZDim) * ( (ZDimxD) - (ZDimx3) * (3xZDim) * (ZDimxD) )
179-
augmentedHessian.setDiagonalBlock(i, FiT
180-
* (Fi - Ei_P * E.block(ZDim * i, 0, ZDim, N).transpose() * Fi));
194+
augmentedHessian.diagonalBlock(m)(0, 0) += b.squaredNorm();
195+
return augmentedHessian;
196+
}
181197

182-
// upper triangular part of the hessian
183-
for (size_t j = i + 1; j < m; j++) { // for each camera
184-
const Eigen::Matrix<double, ZDim, ND>& Fj = Fs[j];
198+
/**
199+
* Do Schur complement, given Jacobian as Fs,E,P, return SymmetricBlockMatrix
200+
* G = F' * F - F' * E * P * E' * F
201+
* g = F' * (b - E * P * E' * b)
202+
* In this version, we allow for the case where the keys in the Jacobian are
203+
* organized differently from the keys in the output SymmetricBlockMatrix In
204+
* particular: each diagonal block of the Jacobian F captures 2 poses (useful
205+
* for rolling shutter and extrinsic calibration) such that F keeps the block
206+
* structure that makes the Schur complement trick fast.
207+
*
208+
* N = 2 or 3 (point dimension), ND is the Jacobian block dimension, NDD is
209+
* the Hessian block dimension
210+
*/
211+
template <int N, int ND, int NDD>
212+
static SymmetricBlockMatrix SchurComplementAndRearrangeBlocks(
213+
const std::vector<
214+
Eigen::Matrix<double, ZDim, ND>,
215+
Eigen::aligned_allocator<Eigen::Matrix<double, ZDim, ND>>>& Fs,
216+
const Matrix& E, const Eigen::Matrix<double, N, N>& P, const Vector& b,
217+
const KeyVector& jacobianKeys, const KeyVector& hessianKeys) {
218+
size_t nrNonuniqueKeys = jacobianKeys.size();
219+
size_t nrUniqueKeys = hessianKeys.size();
220+
221+
// Marginalize point: note - we reuse the standard SchurComplement function.
222+
SymmetricBlockMatrix augmentedHessian = SchurComplement<N, ND>(Fs, E, P, b);
223+
224+
// Pack into an Hessian factor, allow space for b term.
225+
std::vector<DenseIndex> dims(nrUniqueKeys + 1);
226+
std::fill(dims.begin(), dims.end() - 1, NDD);
227+
dims.back() = 1;
228+
SymmetricBlockMatrix augmentedHessianUniqueKeys;
229+
230+
// Deal with the fact that some blocks may share the same keys.
231+
if (nrUniqueKeys == nrNonuniqueKeys) {
232+
// Case when there is 1 calibration key per camera:
233+
augmentedHessianUniqueKeys = SymmetricBlockMatrix(
234+
dims, Matrix(augmentedHessian.selfadjointView()));
235+
} else {
236+
// When multiple cameras share a calibration we have to rearrange
237+
// the results of the Schur complement matrix.
238+
std::vector<DenseIndex> nonuniqueDims(nrNonuniqueKeys + 1); // includes b
239+
std::fill(nonuniqueDims.begin(), nonuniqueDims.end() - 1, NDD);
240+
nonuniqueDims.back() = 1;
241+
augmentedHessian = SymmetricBlockMatrix(
242+
nonuniqueDims, Matrix(augmentedHessian.selfadjointView()));
243+
244+
// Get map from key to location in the new augmented Hessian matrix (the
245+
// one including only unique keys).
246+
std::map<Key, size_t> keyToSlotMap;
247+
for (size_t k = 0; k < nrUniqueKeys; k++) {
248+
keyToSlotMap[hessianKeys[k]] = k;
249+
}
185250

186-
// (DxD) = (Dx2) * ( (2x2) * (2xD) )
187-
augmentedHessian.setOffDiagonalBlock(i, j, -FiT
188-
* (Ei_P * E.block(ZDim * j, 0, ZDim, N).transpose() * Fj));
251+
// Initialize matrix to zero.
252+
augmentedHessianUniqueKeys = SymmetricBlockMatrix(
253+
dims, Matrix::Zero(NDD * nrUniqueKeys + 1, NDD * nrUniqueKeys + 1));
254+
255+
// Add contributions for each key: note this loops over the hessian with
256+
// nonUnique keys (augmentedHessian) and populates an Hessian that only
257+
// includes the unique keys (that is what we want to return).
258+
for (size_t i = 0; i < nrNonuniqueKeys; i++) { // rows
259+
Key key_i = jacobianKeys.at(i);
260+
261+
// Update information vector.
262+
augmentedHessianUniqueKeys.updateOffDiagonalBlock(
263+
keyToSlotMap[key_i], nrUniqueKeys,
264+
augmentedHessian.aboveDiagonalBlock(i, nrNonuniqueKeys));
265+
266+
// Update blocks.
267+
for (size_t j = i; j < nrNonuniqueKeys; j++) { // cols
268+
Key key_j = jacobianKeys.at(j);
269+
if (i == j) {
270+
augmentedHessianUniqueKeys.updateDiagonalBlock(
271+
keyToSlotMap[key_i], augmentedHessian.diagonalBlock(i));
272+
} else { // (i < j)
273+
if (keyToSlotMap[key_i] != keyToSlotMap[key_j]) {
274+
augmentedHessianUniqueKeys.updateOffDiagonalBlock(
275+
keyToSlotMap[key_i], keyToSlotMap[key_j],
276+
augmentedHessian.aboveDiagonalBlock(i, j));
277+
} else {
278+
augmentedHessianUniqueKeys.updateDiagonalBlock(
279+
keyToSlotMap[key_i],
280+
augmentedHessian.aboveDiagonalBlock(i, j) +
281+
augmentedHessian.aboveDiagonalBlock(i, j).transpose());
282+
}
283+
}
189284
}
190-
} // end of for over cameras
285+
}
191286

192-
augmentedHessian.diagonalBlock(m)(0, 0) += b.squaredNorm();
193-
return augmentedHessian;
287+
// Update bottom right element of the matrix.
288+
augmentedHessianUniqueKeys.updateDiagonalBlock(
289+
nrUniqueKeys, augmentedHessian.diagonalBlock(nrNonuniqueKeys));
194290
}
291+
return augmentedHessianUniqueKeys;
292+
}
195293

196294
/**
197295
* Do Schur complement, given Jacobian as Fs,E,P, return SymmetricBlockMatrix
@@ -206,7 +304,7 @@ class CameraSet : public std::vector<CAMERA, Eigen::aligned_allocator<CAMERA> >
206304
}
207305

208306
/// Computes Point Covariance P, with lambda parameter
209-
template<int N> // N = 2 or 3
307+
template<int N> // N = 2 or 3 (point dimension)
210308
static void ComputePointCovariance(Eigen::Matrix<double, N, N>& P,
211309
const Matrix& E, double lambda, bool diagonalDamping = false) {
212310

@@ -258,7 +356,7 @@ class CameraSet : public std::vector<CAMERA, Eigen::aligned_allocator<CAMERA> >
258356
* Applies Schur complement (exploiting block structure) to get a smart factor on cameras,
259357
* and adds the contribution of the smart factor to a pre-allocated augmented Hessian.
260358
*/
261-
template<int N> // N = 2 or 3
359+
template<int N> // N = 2 or 3 (point dimension)
262360
static void UpdateSchurComplement(const FBlocks& Fs, const Matrix& E,
263361
const Eigen::Matrix<double, N, N>& P, const Vector& b,
264362
const KeyVector& allKeys, const KeyVector& keys,

gtsam/geometry/tests/testCameraSet.cpp

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
*/
1818

1919
#include <gtsam/geometry/CameraSet.h>
20+
#include <gtsam/geometry/Cal3_S2.h>
2021
#include <gtsam/geometry/Pose3.h>
2122
#include <gtsam/base/numericalDerivative.h>
2223
#include <CppUnitLite/TestHarness.h>
@@ -125,6 +126,89 @@ TEST(CameraSet, Pinhole) {
125126
EXPECT(assert_equal(actualE, E));
126127
}
127128

129+
/* ************************************************************************* */
130+
TEST(CameraSet, SchurComplementAndRearrangeBlocks) {
131+
typedef PinholePose<Cal3Bundler> Camera;
132+
typedef CameraSet<Camera> Set;
133+
134+
// this is the (block) Jacobian with respect to the nonuniqueKeys
135+
std::vector<Eigen::Matrix<double, 2, 12>,
136+
Eigen::aligned_allocator<Eigen::Matrix<double, 2, 12> > > Fs;
137+
Fs.push_back(1 * Matrix::Ones(2, 12)); // corresponding to key pair (0,1)
138+
Fs.push_back(2 * Matrix::Ones(2, 12)); // corresponding to key pair (1,2)
139+
Fs.push_back(3 * Matrix::Ones(2, 12)); // corresponding to key pair (2,0)
140+
Matrix E = 4 * Matrix::Identity(6, 3) + Matrix::Ones(6, 3);
141+
E(0, 0) = 3;
142+
E(1, 1) = 2;
143+
E(2, 2) = 5;
144+
Matrix Et = E.transpose();
145+
Matrix P = (Et * E).inverse();
146+
Vector b = 5 * Vector::Ones(6);
147+
148+
{ // SchurComplement
149+
// Actual
150+
SymmetricBlockMatrix augmentedHessianBM = Set::SchurComplement<3, 12>(Fs, E,
151+
P, b);
152+
Matrix actualAugmentedHessian = augmentedHessianBM.selfadjointView();
153+
154+
// Expected
155+
Matrix F = Matrix::Zero(6, 3 * 12);
156+
F.block<2, 12>(0, 0) = 1 * Matrix::Ones(2, 12);
157+
F.block<2, 12>(2, 12) = 2 * Matrix::Ones(2, 12);
158+
F.block<2, 12>(4, 24) = 3 * Matrix::Ones(2, 12);
159+
160+
Matrix Ft = F.transpose();
161+
Matrix H = Ft * F - Ft * E * P * Et * F;
162+
Vector v = Ft * (b - E * P * Et * b);
163+
Matrix expectedAugmentedHessian = Matrix::Zero(3 * 12 + 1, 3 * 12 + 1);
164+
expectedAugmentedHessian.block<36, 36>(0, 0) = H;
165+
expectedAugmentedHessian.block<36, 1>(0, 36) = v;
166+
expectedAugmentedHessian.block<1, 36>(36, 0) = v.transpose();
167+
expectedAugmentedHessian(36, 36) = b.squaredNorm();
168+
169+
EXPECT(assert_equal(expectedAugmentedHessian, actualAugmentedHessian));
170+
}
171+
172+
{ // SchurComplementAndRearrangeBlocks
173+
KeyVector nonuniqueKeys;
174+
nonuniqueKeys.push_back(0);
175+
nonuniqueKeys.push_back(1);
176+
nonuniqueKeys.push_back(1);
177+
nonuniqueKeys.push_back(2);
178+
nonuniqueKeys.push_back(2);
179+
nonuniqueKeys.push_back(0);
180+
181+
KeyVector uniqueKeys;
182+
uniqueKeys.push_back(0);
183+
uniqueKeys.push_back(1);
184+
uniqueKeys.push_back(2);
185+
186+
// Actual
187+
SymmetricBlockMatrix augmentedHessianBM =
188+
Set::SchurComplementAndRearrangeBlocks<3, 12, 6>(
189+
Fs, E, P, b, nonuniqueKeys, uniqueKeys);
190+
Matrix actualAugmentedHessian = augmentedHessianBM.selfadjointView();
191+
192+
// Expected
193+
// we first need to build the Jacobian F according to unique keys
194+
Matrix F = Matrix::Zero(6, 18);
195+
F.block<2, 6>(0, 0) = Fs[0].block<2, 6>(0, 0);
196+
F.block<2, 6>(0, 6) = Fs[0].block<2, 6>(0, 6);
197+
F.block<2, 6>(2, 6) = Fs[1].block<2, 6>(0, 0);
198+
F.block<2, 6>(2, 12) = Fs[1].block<2, 6>(0, 6);
199+
F.block<2, 6>(4, 12) = Fs[2].block<2, 6>(0, 0);
200+
F.block<2, 6>(4, 0) = Fs[2].block<2, 6>(0, 6);
201+
202+
Matrix Ft = F.transpose();
203+
Vector v = Ft * (b - E * P * Et * b);
204+
Matrix H = Ft * F - Ft * E * P * Et * F;
205+
Matrix expectedAugmentedHessian(19, 19);
206+
expectedAugmentedHessian << H, v, v.transpose(), b.squaredNorm();
207+
208+
EXPECT(assert_equal(expectedAugmentedHessian, actualAugmentedHessian));
209+
}
210+
}
211+
128212
/* ************************************************************************* */
129213
#include <gtsam/geometry/StereoCamera.h>
130214
TEST(CameraSet, Stereo) {

0 commit comments

Comments
 (0)