Skip to content

Commit 33a5810

Browse files
committed
Batched version
1 parent e87ce14 commit 33a5810

File tree

3 files changed

+118
-35
lines changed

3 files changed

+118
-35
lines changed

examples/ViewGraphExample.cpp

+21-14
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
* @file ViewGraphExample.cpp
1414
* @brief View-graph calibration on a simulated dataset, a la Sweeney 2015
1515
* @author Frank Dellaert
16-
* @author October 2024
16+
* @date October 2024
1717
*/
1818

1919
#include <gtsam/geometry/Cal3_S2.h>
@@ -68,21 +68,28 @@ int main(int argc, char* argv[]) {
6868
// In this version, we only include triplets between 3 successive cameras.
6969
NonlinearFactorGraph graph;
7070
using Factor = TransferFactor<FundamentalMatrix>;
71+
7172
for (size_t a = 0; a < 4; ++a) {
7273
size_t b = (a + 1) % 4; // Next camera
7374
size_t c = (a + 2) % 4; // Camera after next
74-
for (size_t j = 0; j < 4; ++j) {
75-
// Add transfer factors between views a, b, and c. Note that the EdgeKeys
76-
// are crucial in performing the transfer in the right direction. We use
77-
// exactly 8 unique EdgeKeys, corresponding to 8 unknown fundamental
78-
// matrices we will optimize for.
79-
graph.emplace_shared<Factor>(EdgeKey(a, c), EdgeKey(b, c), p[a][j],
80-
p[b][j], p[c][j]);
81-
graph.emplace_shared<Factor>(EdgeKey(a, b), EdgeKey(b, c), p[a][j],
82-
p[c][j], p[b][j]);
83-
graph.emplace_shared<Factor>(EdgeKey(a, c), EdgeKey(a, b), p[c][j],
84-
p[b][j], p[a][j]);
75+
76+
// Vectors to collect tuples for each factor
77+
std::vector<std::tuple<Point2, Point2, Point2>> tuples1, tuples2, tuples3;
78+
79+
// Collect data for the three factors
80+
for (size_t j = 0; j < 8; ++j) {
81+
tuples1.emplace_back(p[a][j], p[b][j], p[c][j]);
82+
tuples2.emplace_back(p[a][j], p[c][j], p[b][j]);
83+
tuples3.emplace_back(p[c][j], p[b][j], p[a][j]);
8584
}
85+
86+
// Add transfer factors between views a, b, and c. Note that the EdgeKeys
87+
// are crucial in performing the transfer in the right direction. We use
88+
// exactly 8 unique EdgeKeys, corresponding to 8 unknown fundamental
89+
// matrices we will optimize for.
90+
graph.emplace_shared<Factor>(EdgeKey(a, c), EdgeKey(b, c), tuples1);
91+
graph.emplace_shared<Factor>(EdgeKey(a, b), EdgeKey(b, c), tuples2);
92+
graph.emplace_shared<Factor>(EdgeKey(a, c), EdgeKey(a, b), tuples3);
8693
}
8794

8895
auto formatter = [](Key key) {
@@ -96,7 +103,7 @@ int main(int argc, char* argv[]) {
96103
// We can't really go far before convergence becomes problematic :-(
97104
Vector7 delta;
98105
delta << 1, 2, 3, 4, 5, 6, 7;
99-
delta *= 5e-5;
106+
delta *= 1e-5;
100107

101108
// Create the data structure to hold the initial estimate to the solution
102109
Values initialEstimate;
@@ -107,7 +114,7 @@ int main(int argc, char* argv[]) {
107114
initialEstimate.insert(EdgeKey(a, c), F2.retract(delta));
108115
}
109116
initialEstimate.print("Initial Estimates:\n", formatter);
110-
// graph.printErrors(initialEstimate, "errors: ", formatter);
117+
graph.printErrors(initialEstimate, "errors: ", formatter);
111118

112119
/* Optimize the graph and print results */
113120
LevenbergMarquardtParams params;

gtsam/sfm/TransferFactor.h

+39-21
Original file line numberDiff line numberDiff line change
@@ -32,22 +32,14 @@ namespace gtsam {
3232
template <typename F>
3333
class TransferFactor : public NoiseModelFactorN<F, F> {
3434
EdgeKey key1_, key2_; ///< the two EdgeKeys
35-
Point2 pa, pb, pc; ///< The points in the three views
35+
std::vector<std::tuple<Point2, Point2, Point2>>
36+
triplets_; ///< Point triplets
3637

3738
public:
3839
/**
39-
* @brief Constructor for the TransferFactor class.
40+
* @brief Constructor for a single triplet of points
4041
*
41-
* Uses EdgeKeys to determine how to use the two fundamental matrix unknowns
42-
* F1 and F2, to transfer points pa and pb to the third view, and minimize the
43-
* difference with pc.
44-
*
45-
* The edge keys must represent valid edges for the transfer operation,
46-
* specifically one of the following configurations:
47-
* - (a, c) and (b, c)
48-
* - (a, c) and (c, b)
49-
* - (c, a) and (b, c)
50-
* - (c, a) and (c, b)
42+
* @note: batching all points for the same transfer will be much faster.
5143
*
5244
* @param key1 First EdgeKey specifying F1: (a, c) or (c, a).
5345
* @param key2 Second EdgeKey specifying F2: (b, c) or (c, b).
@@ -62,9 +54,25 @@ class TransferFactor : public NoiseModelFactorN<F, F> {
6254
: NoiseModelFactorN<F, F>(model, key1, key2),
6355
key1_(key1),
6456
key2_(key2),
65-
pa(pa),
66-
pb(pb),
67-
pc(pc) {}
57+
triplets_({std::make_tuple(pa, pb, pc)}) {}
58+
59+
/**
60+
* @brief Constructor that accepts a vector of point triplets.
61+
*
62+
* @param key1 First EdgeKey specifying F1: (a, c) or (c, a).
63+
* @param key2 Second EdgeKey specifying F2: (b, c) or (c, b).
64+
* @param triplets A vector of triplets containing (pa, pb, pc).
65+
* @param model An optional SharedNoiseModel that defines the noise model
66+
* for this factor. Defaults to nullptr.
67+
*/
68+
TransferFactor(
69+
EdgeKey key1, EdgeKey key2,
70+
const std::vector<std::tuple<Point2, Point2, Point2>>& triplets,
71+
const SharedNoiseModel& model = nullptr)
72+
: NoiseModelFactorN<F, F>(model, key1, key2),
73+
key1_(key1),
74+
key2_(key2),
75+
triplets_(triplets) {}
6876

6977
// Create Matrix3 objects based on EdgeKey configurations
7078
std::pair<Matrix3, Matrix3> getMatrices(const F& F1, const F& F2) const {
@@ -83,17 +91,27 @@ class TransferFactor : public NoiseModelFactorN<F, F> {
8391
}
8492
}
8593

86-
/// vector of errors returns 2D vector
94+
/// vector of errors returns 2*N vector
8795
Vector evaluateError(const F& F1, const F& F2,
8896
OptionalMatrixType H1 = nullptr,
8997
OptionalMatrixType H2 = nullptr) const override {
90-
std::function<Point2(F, F)> transfer = [&](const F& F1, const F& F2) {
98+
std::function<Vector(const F&, const F&)> transfer = [&](const F& F1,
99+
const F& F2) {
100+
Vector errors(2 * triplets_.size());
101+
size_t idx = 0;
91102
auto [Fca, Fcb] = getMatrices(F1, F2);
92-
return Transfer(Fca, pa, Fcb, pb);
103+
for (const auto& tuple : triplets_) {
104+
const auto& [pa, pb, pc] = tuple;
105+
Point2 transferredPoint = Transfer(Fca, pa, Fcb, pb);
106+
Vector2 error = transferredPoint - pc;
107+
errors.segment<2>(idx) = error;
108+
idx += 2;
109+
}
110+
return errors;
93111
};
94-
if (H1) *H1 = numericalDerivative21<Point2, F, F>(transfer, F1, F2);
95-
if (H2) *H2 = numericalDerivative22<Point2, F, F>(transfer, F1, F2);
96-
return transfer(F1, F2) - pc;
112+
if (H1) *H1 = numericalDerivative21<Vector, F, F>(transfer, F1, F2);
113+
if (H2) *H2 = numericalDerivative22<Vector, F, F>(transfer, F1, F2);
114+
return transfer(F1, F2);
97115
}
98116
};
99117

gtsam/sfm/tests/testTransferFactor.cpp

+58
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,64 @@ TEST(TransferFactor, Jacobians) {
8989
}
9090
}
9191

92+
//*************************************************************************
93+
// Test for TransferFactor with multiple tuples
94+
TEST(TransferFactor, MultipleTuples) {
95+
// Generate cameras on a circle
96+
std::array<Pose3, 3> cameraPoses = generateCameraPoses();
97+
auto triplet = generateTripleF(cameraPoses);
98+
99+
// Now project multiple points into the three cameras
100+
const size_t numPoints = 5; // Number of points to test
101+
std::vector<Point3> points3D;
102+
std::vector<std::array<Point2, 3>> projectedPoints;
103+
104+
const Cal3_S2 K(focalLength, focalLength, 0.0, //
105+
principalPoint.x(), principalPoint.y());
106+
107+
// Generate random 3D points and project them
108+
for (size_t n = 0; n < numPoints; ++n) {
109+
Point3 P(0.1 * n, 0.2 * n, 0.3 + 0.1 * n);
110+
points3D.push_back(P);
111+
112+
std::array<Point2, 3> p;
113+
for (size_t i = 0; i < 3; ++i) {
114+
// Project the point into each camera
115+
PinholeCameraCal3_S2 camera(cameraPoses[i], K);
116+
p[i] = camera.project(P);
117+
}
118+
projectedPoints.push_back(p);
119+
}
120+
121+
// Create a vector of tuples for the TransferFactor
122+
EdgeKey key01(0, 1), key12(1, 2), key20(2, 0);
123+
std::vector<std::tuple<Point2, Point2, Point2>> tuples0, tuples1, tuples2;
124+
125+
for (size_t n = 0; n < numPoints; ++n) {
126+
const auto& p = projectedPoints[n];
127+
tuples0.emplace_back(p[1], p[2], p[0]);
128+
}
129+
130+
// Create TransferFactors using the new constructor
131+
TransferFactor<SimpleFundamentalMatrix> factor{key01, key20, tuples0};
132+
133+
// Create Values with edge keys
134+
Values values;
135+
values.insert(key01, triplet.Fab);
136+
values.insert(key12, triplet.Fbc);
137+
values.insert(key20, triplet.Fca);
138+
139+
// Check error and Jacobians for multiple tuples
140+
Vector error = factor.unwhitenedError(values);
141+
// The error vector should be of size 2 * numPoints
142+
EXPECT_LONGS_EQUAL(2 * numPoints, error.size());
143+
// Since the points are perfectly matched, the error should be zero
144+
EXPECT(assert_equal<Vector>(Vector::Zero(2 * numPoints), error, 1e-9));
145+
146+
// Check the Jacobians
147+
EXPECT_CORRECT_FACTOR_JACOBIANS(factor, values, 1e-5, 1e-7);
148+
}
149+
92150
//*************************************************************************
93151
int main() {
94152
TestResult tr;

0 commit comments

Comments
 (0)