testTriangulation.cpp
Go to the documentation of this file.
1 /* ----------------------------------------------------------------------------
2 
3  * GTSAM Copyright 2010, Georgia Tech Research Corporation,
4  * Atlanta, Georgia 30332-0415
5  * All Rights Reserved
6  * Authors: Frank Dellaert, et al. (see THANKS for the full author list)
7 
8  * See LICENSE for the license information
9 
10  * -------------------------------------------------------------------------- */
11 
23 #include <gtsam/geometry/Cal3DS2.h>
32 
33 using namespace std;
34 using namespace gtsam;
35 
36 // Some common constants
37 
38 static const std::shared_ptr<Cal3_S2> kSharedCal = //
39  std::make_shared<Cal3_S2>(1500, 1200, 0.1, 640, 480);
40 
41 // Looking along X-axis, 1 meter above ground plane (x-y)
42 static const Rot3 upright = Rot3::Ypr(-M_PI / 2, 0., -M_PI / 2);
43 static const Pose3 kPose1 = Pose3(upright, gtsam::Point3(0, 0, 1));
45 
46 // create second camera 1 meter to the right of first camera
47 static const Pose3 kPose2 = kPose1 * Pose3(Rot3(), Point3(1, 0, 0));
49 
50 static const std::vector<Pose3> kPoses = {kPose1, kPose2};
51 
52 
53 // landmark ~5 meters in front of camera
54 static const Point3 kLandmark(5, 0.5, 1.2);
55 
56 // 1. Project two landmarks into two cameras and triangulate
57 static const Point2 kZ1 = kCamera1.project(kLandmark);
58 static const Point2 kZ2 = kCamera2.project(kLandmark);
60 
61 //******************************************************************************
62 // Simple test with a well-behaved two camera situation
63 TEST(triangulation, twoPoses) {
64  Point2Vector measurements = kMeasurements;
65 
66  double rank_tol = 1e-9;
67 
68  // 1. Test simple DLT, perfect in no noise situation
69  bool optimize = false;
70  std::optional<Point3> actual1 = //
71  triangulatePoint3<Cal3_S2>(kPoses, kSharedCal, measurements, rank_tol, optimize);
72  EXPECT(assert_equal(kLandmark, *actual1, 1e-7));
73 
74  // 2. test with optimization on, same answer
75  optimize = true;
76  std::optional<Point3> actual2 = //
77  triangulatePoint3<Cal3_S2>(kPoses, kSharedCal, measurements, rank_tol, optimize);
78  EXPECT(assert_equal(kLandmark, *actual2, 1e-7));
79 
80  // 3. Add some noise and try again: result should be ~ (4.995,
81  // 0.499167, 1.19814)
82  measurements.at(0) += Point2(0.1, 0.5);
83  measurements.at(1) += Point2(-0.2, 0.3);
84  optimize = false;
85  std::optional<Point3> actual3 = //
86  triangulatePoint3<Cal3_S2>(kPoses, kSharedCal, measurements, rank_tol, optimize);
87  EXPECT(assert_equal(Point3(4.995, 0.499167, 1.19814), *actual3, 1e-4));
88 
89  // 4. Now with optimization on
90  optimize = true;
91  std::optional<Point3> actual4 = //
92  triangulatePoint3<Cal3_S2>(kPoses, kSharedCal, measurements, rank_tol, optimize);
93  EXPECT(assert_equal(Point3(4.995, 0.499167, 1.19814), *actual4, 1e-4));
94 }
95 
96 TEST(triangulation, twoCamerasUsingLOST) {
98  cameras.push_back(kCamera1);
99  cameras.push_back(kCamera2);
100 
101  Point2Vector measurements = kMeasurements;
102  SharedNoiseModel measurementNoise = noiseModel::Isotropic::Sigma(2, 1e-4);
103  double rank_tol = 1e-9;
104 
105  // 1. Test simple triangulation, perfect in no noise situation
106  std::optional<Point3> actual1 = //
107  triangulatePoint3<PinholeCamera<Cal3_S2>>(cameras, measurements, rank_tol,
108  /*optimize=*/false,
109  measurementNoise,
110  /*useLOST=*/true);
111  EXPECT(assert_equal(kLandmark, *actual1, 1e-12));
112 
113  // 3. Add some noise and try again: result should be ~ (4.995,
114  // 0.499167, 1.19814)
115  measurements[0] += Point2(0.1, 0.5);
116  measurements[1] += Point2(-0.2, 0.3);
117  std::optional<Point3> actual2 = //
118  triangulatePoint3<PinholeCamera<Cal3_S2>>(
119  cameras, measurements, rank_tol,
120  /*optimize=*/false, measurementNoise,
121  /*use_lost_triangulation=*/true);
122  EXPECT(assert_equal(Point3(4.995, 0.499167, 1.19814), *actual2, 1e-4));
123 }
124 
125 TEST(triangulation, twoCamerasLOSTvsDLT) {
126  // LOST has been shown to preform better when the point is much closer to one
127  // camera compared to another. This unit test checks this configuration.
128  const Cal3_S2 identityK;
129  const Pose3 pose1;
130  const Pose3 pose2(Rot3(), Point3(5., 0., -5.));
132  cameras.emplace_back(pose1, identityK);
133  cameras.emplace_back(pose2, identityK);
134 
135  Point3 landmark(0, 0, 1);
136  Point2 x1_noisy = cameras[0].project(landmark) + Point2(0.00817, 0.00977);
137  Point2 x2_noisy = cameras[1].project(landmark) + Point2(-0.00610, 0.01969);
138  Point2Vector measurements = {x1_noisy, x2_noisy};
139 
140  const double rank_tol = 1e-9;
141  SharedNoiseModel measurementNoise = noiseModel::Isotropic::Sigma(2, 1e-2);
142 
143  std::optional<Point3> estimateLOST = //
144  triangulatePoint3<PinholeCamera<Cal3_S2>>(cameras, measurements, rank_tol,
145  /*optimize=*/false,
146  measurementNoise,
147  /*useLOST=*/true);
148 
149  // These values are from a MATLAB implementation.
150  EXPECT(assert_equal(Point3(0.007, 0.011, 0.945), *estimateLOST, 1e-3));
151 
152  std::optional<Point3> estimateDLT =
153  triangulatePoint3<Cal3_S2>(cameras, measurements, rank_tol, false);
154 
155  // The LOST estimate should have a smaller error.
156  EXPECT((landmark - *estimateLOST).norm() <= (landmark - *estimateDLT).norm());
157 }
158 
159 //******************************************************************************
160 // Simple test with a well-behaved two camera situation with Cal3DS2 calibration.
161 TEST(triangulation, twoPosesCal3DS2) {
162  static const std::shared_ptr<Cal3DS2> sharedDistortedCal = //
163  std::make_shared<Cal3DS2>(1500, 1200, 0, 640, 480, -.3, 0.1, 0.0001,
164  -0.0003);
165 
166  PinholeCamera<Cal3DS2> camera1Distorted(kPose1, *sharedDistortedCal);
167 
168  PinholeCamera<Cal3DS2> camera2Distorted(kPose2, *sharedDistortedCal);
169 
170  // 0. Project two landmarks into two cameras and triangulate
171  Point2 z1Distorted = camera1Distorted.project(kLandmark);
172  Point2 z2Distorted = camera2Distorted.project(kLandmark);
173 
174  Point2Vector measurements{z1Distorted, z2Distorted};
175 
176  double rank_tol = 1e-9;
177 
178  // 1. Test simple DLT, perfect in no noise situation
179  bool optimize = false;
180  std::optional<Point3> actual1 = //
181  triangulatePoint3<Cal3DS2>(kPoses, sharedDistortedCal, measurements,
182  rank_tol, optimize);
183  EXPECT(assert_equal(kLandmark, *actual1, 1e-7));
184 
185  // 2. test with optimization on, same answer
186  optimize = true;
187  std::optional<Point3> actual2 = //
188  triangulatePoint3<Cal3DS2>(kPoses, sharedDistortedCal, measurements,
189  rank_tol, optimize);
190  EXPECT(assert_equal(kLandmark, *actual2, 1e-7));
191 
192  // 3. Add some noise and try again: result should be ~ (4.995,
193  // 0.499167, 1.19814)
194  measurements.at(0) += Point2(0.1, 0.5);
195  measurements.at(1) += Point2(-0.2, 0.3);
196  optimize = false;
197  std::optional<Point3> actual3 = //
198  triangulatePoint3<Cal3DS2>(kPoses, sharedDistortedCal, measurements,
199  rank_tol, optimize);
200  EXPECT(assert_equal(Point3(4.995, 0.499167, 1.19814), *actual3, 1e-3));
201 
202  // 4. Now with optimization on
203  optimize = true;
204  std::optional<Point3> actual4 = //
205  triangulatePoint3<Cal3DS2>(kPoses, sharedDistortedCal, measurements,
206  rank_tol, optimize);
207  EXPECT(assert_equal(Point3(4.995, 0.499167, 1.19814), *actual4, 1e-3));
208 }
209 
210 //******************************************************************************
211 // Simple test with a well-behaved two camera situation with Fisheye
212 // calibration.
213 TEST(triangulation, twoPosesFisheye) {
214  using Calibration = Cal3Fisheye;
215  static const std::shared_ptr<Calibration> sharedDistortedCal = //
216  std::make_shared<Calibration>(1500, 1200, .1, 640, 480, -.3, 0.1,
217  0.0001, -0.0003);
218 
219  PinholeCamera<Calibration> camera1Distorted(kPose1, *sharedDistortedCal);
220 
221  PinholeCamera<Calibration> camera2Distorted(kPose2, *sharedDistortedCal);
222 
223  // 0. Project two landmarks into two cameras and triangulate
224  Point2 z1Distorted = camera1Distorted.project(kLandmark);
225  Point2 z2Distorted = camera2Distorted.project(kLandmark);
226 
227  Point2Vector measurements{z1Distorted, z2Distorted};
228 
229  double rank_tol = 1e-9;
230 
231  // 1. Test simple DLT, perfect in no noise situation
232  bool optimize = false;
233  std::optional<Point3> actual1 = //
234  triangulatePoint3<Calibration>(kPoses, sharedDistortedCal, measurements,
235  rank_tol, optimize);
236  EXPECT(assert_equal(kLandmark, *actual1, 1e-7));
237 
238  // 2. test with optimization on, same answer
239  optimize = true;
240  std::optional<Point3> actual2 = //
241  triangulatePoint3<Calibration>(kPoses, sharedDistortedCal, measurements,
242  rank_tol, optimize);
243  EXPECT(assert_equal(kLandmark, *actual2, 1e-7));
244 
245  // 3. Add some noise and try again: result should be ~ (4.995,
246  // 0.499167, 1.19814)
247  measurements.at(0) += Point2(0.1, 0.5);
248  measurements.at(1) += Point2(-0.2, 0.3);
249  optimize = false;
250  std::optional<Point3> actual3 = //
251  triangulatePoint3<Calibration>(kPoses, sharedDistortedCal, measurements,
252  rank_tol, optimize);
253  EXPECT(assert_equal(Point3(4.995, 0.499167, 1.19814), *actual3, 1e-3));
254 
255  // 4. Now with optimization on
256  optimize = true;
257  std::optional<Point3> actual4 = //
258  triangulatePoint3<Calibration>(kPoses, sharedDistortedCal, measurements,
259  rank_tol, optimize);
260  EXPECT(assert_equal(Point3(4.995, 0.499167, 1.19814), *actual4, 1e-3));
261 }
262 
263 //******************************************************************************
264 // Similar, but now with Bundler calibration
265 TEST(triangulation, twoPosesBundler) {
266  std::shared_ptr<Cal3Bundler> bundlerCal = //
267  std::make_shared<Cal3Bundler>(1500, 0.1, 0.2, 640, 480);
270 
271  // 1. Project two landmarks into two cameras and triangulate
272  Point2 z1 = camera1.project(kLandmark);
274 
275  Point2Vector measurements{z1, z2};
276 
277  bool optimize = true;
278  double rank_tol = 1e-9;
279 
280  std::optional<Point3> actual = //
281  triangulatePoint3<Cal3Bundler>(kPoses, bundlerCal, measurements, rank_tol,
282  optimize);
283  EXPECT(assert_equal(kLandmark, *actual, 1e-7));
284 
285  // Add some noise and try again
286  measurements.at(0) += Point2(0.1, 0.5);
287  measurements.at(1) += Point2(-0.2, 0.3);
288 
289  std::optional<Point3> actual2 = //
290  triangulatePoint3<Cal3Bundler>(kPoses, bundlerCal, measurements, rank_tol,
291  optimize);
292  EXPECT(assert_equal(Point3(4.995, 0.499167, 1.19847), *actual2, 1e-3));
293 }
294 
295 //******************************************************************************
296 TEST(triangulation, fourPoses) {
297  Pose3Vector poses = kPoses;
298  Point2Vector measurements = kMeasurements;
299  std::optional<Point3> actual =
300  triangulatePoint3<Cal3_S2>(poses, kSharedCal, measurements);
301  EXPECT(assert_equal(kLandmark, *actual, 1e-2));
302 
303  // 2. Add some noise and try again: result should be ~ (4.995,
304  // 0.499167, 1.19814)
305  measurements.at(0) += Point2(0.1, 0.5);
306  measurements.at(1) += Point2(-0.2, 0.3);
307 
308  std::optional<Point3> actual2 = //
309  triangulatePoint3<Cal3_S2>(poses, kSharedCal, measurements);
310  EXPECT(assert_equal(kLandmark, *actual2, 1e-2));
311 
312  // 3. Add a slightly rotated third camera above, again with measurement noise
313  Pose3 pose3 = kPose1 * Pose3(Rot3::Ypr(0.1, 0.2, 0.1), Point3(0.1, -2, -.1));
316 
317  poses.push_back(pose3);
318  measurements.push_back(z3 + Point2(0.1, -0.1));
319 
320  std::optional<Point3> triangulated_3cameras = //
321  triangulatePoint3<Cal3_S2>(poses, kSharedCal, measurements);
322  EXPECT(assert_equal(kLandmark, *triangulated_3cameras, 1e-2));
323 
324  // Again with nonlinear optimization
325  std::optional<Point3> triangulated_3cameras_opt =
326  triangulatePoint3<Cal3_S2>(poses, kSharedCal, measurements, 1e-9, true);
327  EXPECT(assert_equal(kLandmark, *triangulated_3cameras_opt, 1e-2));
328 
329  // 4. Test failure: Add a 4th camera facing the wrong way
330  Pose3 pose4 = Pose3(Rot3::Ypr(M_PI / 2, 0., -M_PI / 2), Point3(0, 0, 1));
331  PinholeCamera<Cal3_S2> camera4(pose4, *kSharedCal);
332 
333 #ifdef GTSAM_THROW_CHEIRALITY_EXCEPTION
335 
336  poses.push_back(pose4);
337  measurements.emplace_back(400, 400);
338 
339  CHECK_EXCEPTION(triangulatePoint3<Cal3_S2>(poses, kSharedCal, measurements),
341 #endif
342 }
343 
344 //******************************************************************************
345 TEST(triangulation, threePoses_robustNoiseModel) {
346 
347  Pose3 pose3 = kPose1 * Pose3(Rot3::Ypr(0.1, 0.2, 0.1), Point3(0.1, -2, -.1));
350 
351  const vector<Pose3> poses{kPose1, kPose2, pose3};
352  Point2Vector measurements{kZ1, kZ2, z3};
353 
354  // noise free, so should give exactly the landmark
355  std::optional<Point3> actual =
356  triangulatePoint3<Cal3_S2>(poses, kSharedCal, measurements);
357  EXPECT(assert_equal(kLandmark, *actual, 1e-2));
358 
359  // Add outlier
360  measurements.at(0) += Point2(100, 120); // very large pixel noise!
361 
362  // now estimate does not match landmark
363  std::optional<Point3> actual2 = //
364  triangulatePoint3<Cal3_S2>(poses, kSharedCal, measurements);
365  // DLT is surprisingly robust, but still off (actual error is around 0.26m):
366  EXPECT( (kLandmark - *actual2).norm() >= 0.2);
367  EXPECT( (kLandmark - *actual2).norm() <= 0.5);
368 
369  // Again with nonlinear optimization
370  std::optional<Point3> actual3 =
371  triangulatePoint3<Cal3_S2>(poses, kSharedCal, measurements, 1e-9, true);
372  // result from nonlinear (but non-robust optimization) is close to DLT and still off
373  EXPECT(assert_equal(*actual2, *actual3, 0.1));
374 
375  // Again with nonlinear optimization, this time with robust loss
376  auto model = noiseModel::Robust::Create(
377  noiseModel::mEstimator::Huber::Create(1.345), noiseModel::Unit::Create(2));
378  std::optional<Point3> actual4 = triangulatePoint3<Cal3_S2>(
379  poses, kSharedCal, measurements, 1e-9, true, model);
380  // using the Huber loss we now have a quite small error!! nice!
381  EXPECT(assert_equal(kLandmark, *actual4, 0.05));
382 }
383 
384 //******************************************************************************
385 TEST(triangulation, fourPoses_robustNoiseModel) {
386 
387  Pose3 pose3 = kPose1 * Pose3(Rot3::Ypr(0.1, 0.2, 0.1), Point3(0.1, -2, -.1));
390 
391  const vector<Pose3> poses{kPose1, kPose1, kPose2, pose3};
392  // 2 measurements from pose 1:
393  Point2Vector measurements{kZ1, kZ1, kZ2, z3};
394 
395  // noise free, so should give exactly the landmark
396  std::optional<Point3> actual =
397  triangulatePoint3<Cal3_S2>(poses, kSharedCal, measurements);
398  EXPECT(assert_equal(kLandmark, *actual, 1e-2));
399 
400  // Add outlier
401  measurements.at(0) += Point2(100, 120); // very large pixel noise!
402  // add noise on other measurements:
403  measurements.at(1) += Point2(0.1, 0.2); // small noise
404  measurements.at(2) += Point2(0.2, 0.2);
405  measurements.at(3) += Point2(0.3, 0.1);
406 
407  // now estimate does not match landmark
408  std::optional<Point3> actual2 = //
409  triangulatePoint3<Cal3_S2>(poses, kSharedCal, measurements);
410  // DLT is surprisingly robust, but still off (actual error is around 0.17m):
411  EXPECT( (kLandmark - *actual2).norm() >= 0.1);
412  EXPECT( (kLandmark - *actual2).norm() <= 0.5);
413 
414  // Again with nonlinear optimization
415  std::optional<Point3> actual3 =
416  triangulatePoint3<Cal3_S2>(poses, kSharedCal, measurements, 1e-9, true);
417  // result from nonlinear (but non-robust optimization) is close to DLT and still off
418  EXPECT(assert_equal(*actual2, *actual3, 0.1));
419 
420  // Again with nonlinear optimization, this time with robust loss
421  auto model = noiseModel::Robust::Create(
422  noiseModel::mEstimator::Huber::Create(1.345), noiseModel::Unit::Create(2));
423  std::optional<Point3> actual4 = triangulatePoint3<Cal3_S2>(
424  poses, kSharedCal, measurements, 1e-9, true, model);
425  // using the Huber loss we now have a quite small error!! nice!
426  EXPECT(assert_equal(kLandmark, *actual4, 0.05));
427 }
428 
429 //******************************************************************************
430 TEST(triangulation, fourPoses_distinct_Ks) {
431  Cal3_S2 K1(1500, 1200, 0, 640, 480);
432  // create first camera. Looking along X-axis, 1 meter above ground plane (x-y)
434 
435  // create second camera 1 meter to the right of first camera
436  Cal3_S2 K2(1600, 1300, 0, 650, 440);
438 
439  // 1. Project two landmarks into two cameras and triangulate
440  Point2 z1 = camera1.project(kLandmark);
442 
444  Point2Vector measurements{z1, z2};
445 
446  std::optional<Point3> actual = //
447  triangulatePoint3<Cal3_S2>(cameras, measurements);
448  EXPECT(assert_equal(kLandmark, *actual, 1e-2));
449 
450  // 2. Add some noise and try again: result should be ~ (4.995,
451  // 0.499167, 1.19814)
452  measurements.at(0) += Point2(0.1, 0.5);
453  measurements.at(1) += Point2(-0.2, 0.3);
454 
455  std::optional<Point3> actual2 = //
456  triangulatePoint3<Cal3_S2>(cameras, measurements);
457  EXPECT(assert_equal(kLandmark, *actual2, 1e-2));
458 
459  // 3. Add a slightly rotated third camera above, again with measurement noise
460  Pose3 pose3 = kPose1 * Pose3(Rot3::Ypr(0.1, 0.2, 0.1), Point3(0.1, -2, -.1));
461  Cal3_S2 K3(700, 500, 0, 640, 480);
464 
465  cameras.push_back(camera3);
466  measurements.push_back(z3 + Point2(0.1, -0.1));
467 
468  std::optional<Point3> triangulated_3cameras = //
469  triangulatePoint3<Cal3_S2>(cameras, measurements);
470  EXPECT(assert_equal(kLandmark, *triangulated_3cameras, 1e-2));
471 
472  // Again with nonlinear optimization
473  std::optional<Point3> triangulated_3cameras_opt =
474  triangulatePoint3<Cal3_S2>(cameras, measurements, 1e-9, true);
475  EXPECT(assert_equal(kLandmark, *triangulated_3cameras_opt, 1e-2));
476 
477  // 4. Test failure: Add a 4th camera facing the wrong way
478  Pose3 pose4 = Pose3(Rot3::Ypr(M_PI / 2, 0., -M_PI / 2), Point3(0, 0, 1));
479  Cal3_S2 K4(700, 500, 0, 640, 480);
480  PinholeCamera<Cal3_S2> camera4(pose4, K4);
481 
482 #ifdef GTSAM_THROW_CHEIRALITY_EXCEPTION
484 
485  cameras.push_back(camera4);
486  measurements.emplace_back(400, 400);
487  CHECK_EXCEPTION(triangulatePoint3<Cal3_S2>(cameras, measurements),
489 #endif
490 }
491 
492 //******************************************************************************
493 TEST(triangulation, fourPoses_distinct_Ks_distortion) {
494  Cal3DS2 K1(1500, 1200, 0, 640, 480, -.3, 0.1, 0.0001, -0.0003);
495  // create first camera. Looking along X-axis, 1 meter above ground plane (x-y)
497 
498  // create second camera 1 meter to the right of first camera
499  Cal3DS2 K2(1600, 1300, 0, 650, 440, -.2, 0.05, 0.0002, -0.0001);
501 
502  // 1. Project two landmarks into two cameras and triangulate
503  Point2 z1 = camera1.project(kLandmark);
505 
507  const Point2Vector measurements{z1, z2};
508 
509  std::optional<Point3> actual = //
510  triangulatePoint3<Cal3DS2>(cameras, measurements);
511  EXPECT(assert_equal(kLandmark, *actual, 1e-2));
512 }
513 
514 //******************************************************************************
515 TEST(triangulation, outliersAndFarLandmarks) {
516  Cal3_S2 K1(1500, 1200, 0, 640, 480);
517  // create first camera. Looking along X-axis, 1 meter above ground plane (x-y)
519 
520  // create second camera 1 meter to the right of first camera
521  Cal3_S2 K2(1600, 1300, 0, 650, 440);
523 
524  // 1. Project two landmarks into two cameras and triangulate
525  Point2 z1 = camera1.project(kLandmark);
527 
529  Point2Vector measurements{z1, z2};
530 
531  double landmarkDistanceThreshold = 10; // landmark is closer than that
533  1.0, false, landmarkDistanceThreshold); // all default except
534  // landmarkDistanceThreshold
535  TriangulationResult actual = triangulateSafe(cameras, measurements, params);
536  EXPECT(assert_equal(kLandmark, *actual, 1e-2));
537  EXPECT(actual.valid());
538 
539  landmarkDistanceThreshold = 4; // landmark is farther than that
540  TriangulationParameters params2(
541  1.0, false, landmarkDistanceThreshold); // all default except
542  // landmarkDistanceThreshold
543  actual = triangulateSafe(cameras, measurements, params2);
544  EXPECT(actual.farPoint());
545 
546  // 3. Add a slightly rotated third camera above with a wrong measurement
547  // (OUTLIER)
548  Pose3 pose3 = kPose1 * Pose3(Rot3::Ypr(0.1, 0.2, 0.1), Point3(0.1, -2, -.1));
549  Cal3_S2 K3(700, 500, 0, 640, 480);
552 
553  cameras.push_back(camera3);
554  measurements.push_back(z3 + Point2(10, -10));
555 
556  landmarkDistanceThreshold = 10; // landmark is closer than that
557  double outlierThreshold = 100; // loose, the outlier is going to pass
558  TriangulationParameters params3(1.0, false, landmarkDistanceThreshold,
559  outlierThreshold);
560  actual = triangulateSafe(cameras, measurements, params3);
561  EXPECT(actual.valid());
562 
563  // now set stricter threshold for outlier rejection
564  outlierThreshold = 5; // tighter, the outlier is not going to pass
565  TriangulationParameters params4(1.0, false, landmarkDistanceThreshold,
566  outlierThreshold);
567  actual = triangulateSafe(cameras, measurements, params4);
568  EXPECT(actual.outlier());
569 }
570 
571 //******************************************************************************
572 TEST(triangulation, twoIdenticalPoses) {
573  // create first camera. Looking along X-axis, 1 meter above ground plane (x-y)
575 
576  // 1. Project two landmarks into two cameras and triangulate
577  Point2 z1 = camera1.project(kLandmark);
578 
579  const vector<Pose3> poses{kPose1, kPose1};
580  const Point2Vector measurements{z1, z1};
581 
582  CHECK_EXCEPTION(triangulatePoint3<Cal3_S2>(poses, kSharedCal, measurements),
584 }
585 
586 //******************************************************************************
587 TEST(triangulation, onePose) {
588  // we expect this test to fail with a TriangulationUnderconstrainedException
589  // because there's only one camera observation
590 
591  const vector<Pose3> poses{Pose3()};
592  const Point2Vector measurements {{0,0}};
593 
594  CHECK_EXCEPTION(triangulatePoint3<Cal3_S2>(poses, kSharedCal, measurements),
596 }
597 
598 //******************************************************************************
599 TEST(triangulation, StereoTriangulateNonlinear) {
600  auto stereoK = std::make_shared<Cal3_S2Stereo>(1733.75, 1733.75, 0, 689.645,
601  508.835, 0.0699612);
602 
603  // two camera kPoses m1, m2
604  Matrix4 m1, m2;
605  m1 << 0.796888717, 0.603404026, -0.0295271487, 46.6673779, 0.592783835,
606  -0.77156583, 0.230856632, 66.2186159, 0.116517574, -0.201470143,
607  -0.9725393, -4.28382528, 0, 0, 0, 1;
608 
609  m2 << -0.955959025, -0.29288915, -0.0189328569, 45.7169799, -0.29277519,
610  0.947083213, 0.131587097, 65.843136, -0.0206094928, 0.131334858,
611  -0.991123524, -4.3525033, 0, 0, 0, 1;
612 
613  typedef CameraSet<StereoCamera> StereoCameras;
614  const StereoCameras cameras{{Pose3(m1), stereoK}, {Pose3(m2), stereoK}};
615 
616  StereoPoint2Vector measurements;
617  measurements.push_back(StereoPoint2(226.936, 175.212, 424.469));
618  measurements.push_back(StereoPoint2(339.571, 285.547, 669.973));
619 
620  Point3 initial =
621  Point3(46.0536958, 66.4621179, -6.56285929); // error: 96.5715555191
622 
623  Point3 actual = triangulateNonlinear<StereoCamera>(cameras, measurements, initial);
624 
625  Point3 expected(46.0484569, 66.4710686,
626  -6.55046613); // error: 0.763510644187
627 
628  EXPECT(assert_equal(expected, actual, 1e-4));
629 
630  // regular stereo factor comparison - expect very similar result as above
631  {
633 
634  Values values;
635  values.insert(Symbol('x', 1), Pose3(m1));
636  values.insert(Symbol('x', 2), Pose3(m2));
637  values.insert(Symbol('l', 1), initial);
638 
640  static SharedNoiseModel unit(noiseModel::Unit::Create(3));
641  graph.emplace_shared<StereoFactor>(measurements[0], unit, Symbol('x', 1),
642  Symbol('l', 1), stereoK);
643  graph.emplace_shared<StereoFactor>(measurements[1], unit, Symbol('x', 2),
644  Symbol('l', 1), stereoK);
645 
646  const SharedDiagonal posePrior = noiseModel::Isotropic::Sigma(6, 1e-9);
647  graph.addPrior(Symbol('x', 1), Pose3(m1), posePrior);
648  graph.addPrior(Symbol('x', 2), Pose3(m2), posePrior);
649 
651  Values result = optimizer.optimize();
652 
653  EXPECT(assert_equal(expected, result.at<Point3>(Symbol('l', 1)), 1e-4));
654  }
655 
656  // use Triangulation Factor directly - expect same result as above
657  {
658  Values values;
659  values.insert(Symbol('l', 1), initial);
660 
662  static SharedNoiseModel unit(noiseModel::Unit::Create(3));
663 
665  cameras[0], measurements[0], unit, Symbol('l', 1));
667  cameras[1], measurements[1], unit, Symbol('l', 1));
668 
670  Values result = optimizer.optimize();
671 
672  EXPECT(assert_equal(expected, result.at<Point3>(Symbol('l', 1)), 1e-4));
673  }
674 
675  // use ExpressionFactor - expect same result as above
676  {
677  Values values;
678  values.insert(Symbol('l', 1), initial);
679 
681  static SharedNoiseModel unit(noiseModel::Unit::Create(3));
682 
683  Expression<Point3> point_(Symbol('l', 1));
684  Expression<StereoCamera> camera0_(cameras[0]);
685  Expression<StereoCamera> camera1_(cameras[1]);
687  point_);
689  point_);
690 
691  graph.addExpressionFactor(unit, measurements[0], project0_);
692  graph.addExpressionFactor(unit, measurements[1], project1_);
693 
695  Values result = optimizer.optimize();
696 
697  EXPECT(assert_equal(expected, result.at<Point3>(Symbol('l', 1)), 1e-4));
698  }
699 }
700 
701 //******************************************************************************
702 // Simple test with a well-behaved two camera situation
703 TEST(triangulation, twoPoses_sphericalCamera) {
704 
705  // Project landmark into two cameras and triangulate
708  Unit3 u1 = cam1.project(kLandmark);
710 
711  std::vector<Unit3> measurements{u1, u2};
712 
714  cameras.push_back(cam1);
715  cameras.push_back(cam2);
716 
717  double rank_tol = 1e-9;
718 
719  // 1. Test linear triangulation via DLT
720  auto projection_matrices = projectionMatricesFromCameras(cameras);
721  Point3 point = triangulateDLT(projection_matrices, measurements, rank_tol);
723 
724  // 2. Test nonlinear triangulation
725  point = triangulateNonlinear<SphericalCamera>(cameras, measurements, point);
727 
728  // 3. Test simple DLT, now within triangulatePoint3
729  bool optimize = false;
730  std::optional<Point3> actual1 = //
731  triangulatePoint3<SphericalCamera>(cameras, measurements, rank_tol,
732  optimize);
733  EXPECT(assert_equal(kLandmark, *actual1, 1e-7));
734 
735  // 4. test with optimization on, same answer
736  optimize = true;
737  std::optional<Point3> actual2 = //
738  triangulatePoint3<SphericalCamera>(cameras, measurements, rank_tol,
739  optimize);
740  EXPECT(assert_equal(kLandmark, *actual2, 1e-7));
741 
742  // 5. Add some noise and try again: result should be ~ (4.995,
743  // 0.499167, 1.19814)
744  measurements.at(0) =
745  u1.retract(Vector2(0.01, 0.05)); // note: perturbation smaller for Unit3
746  measurements.at(1) = u2.retract(Vector2(-0.02, 0.03));
747  optimize = false;
748  std::optional<Point3> actual3 = //
749  triangulatePoint3<SphericalCamera>(cameras, measurements, rank_tol,
750  optimize);
751  EXPECT(assert_equal(Point3(5.9432, 0.654319, 1.48192), *actual3, 1e-3));
752 
753  // 6. Now with optimization on
754  optimize = true;
755  std::optional<Point3> actual4 = //
756  triangulatePoint3<SphericalCamera>(cameras, measurements, rank_tol,
757  optimize);
758  EXPECT(assert_equal(Point3(5.9432, 0.654334, 1.48192), *actual4, 1e-3));
759 }
760 
761 //******************************************************************************
762 TEST(triangulation, twoPoses_sphericalCamera_extremeFOV) {
763  // Project landmark into two cameras and triangulate
764  Pose3 poseA = Pose3(
765  Rot3::Ypr(-M_PI / 2, 0., -M_PI / 2),
766  Point3(0.0, 0.0, 0.0)); // with z pointing along x axis of global frame
767  Pose3 poseB = Pose3(Rot3::Ypr(-M_PI / 2, 0., -M_PI / 2),
768  Point3(2.0, 0.0, 0.0)); // 2m in front of poseA
769  Point3 landmarkL(
770  1.0, -1.0,
771  0.0); // 1m to the right of both cameras, in front of poseA, behind poseB
772  SphericalCamera cam1(poseA);
773  SphericalCamera cam2(poseB);
774  Unit3 u1 = cam1.project(landmarkL);
775  Unit3 u2 = cam2.project(landmarkL);
776 
777  EXPECT(assert_equal(Unit3(Point3(1.0, 0.0, 1.0)), u1,
778  1e-7)); // in front and to the right of PoseA
779  EXPECT(assert_equal(Unit3(Point3(1.0, 0.0, -1.0)), u2,
780  1e-7)); // behind and to the right of PoseB
781 
782  const std::vector<Unit3> measurements{u1, u2};
783 
785  cameras.push_back(cam1);
786  cameras.push_back(cam2);
787 
788  double rank_tol = 1e-9;
789 
790  {
791  // 1. Test simple DLT, when 1 point is behind spherical camera
792  bool optimize = false;
793 #ifdef GTSAM_THROW_CHEIRALITY_EXCEPTION
794  CHECK_EXCEPTION(triangulatePoint3<SphericalCamera>(cameras, measurements,
795  rank_tol, optimize),
797 #else // otherwise project should not throw the exception
798  std::optional<Point3> actual1 = //
799  triangulatePoint3<SphericalCamera>(cameras, measurements, rank_tol,
800  optimize);
801  EXPECT(assert_equal(landmarkL, *actual1, 1e-7));
802 #endif
803  }
804  {
805  // 2. test with optimization on, same answer
806  bool optimize = true;
807 #ifdef GTSAM_THROW_CHEIRALITY_EXCEPTION
808  CHECK_EXCEPTION(triangulatePoint3<SphericalCamera>(cameras, measurements,
809  rank_tol, optimize),
811 #else // otherwise project should not throw the exception
812  std::optional<Point3> actual1 = //
813  triangulatePoint3<SphericalCamera>(cameras, measurements, rank_tol,
814  optimize);
815  EXPECT(assert_equal(landmarkL, *actual1, 1e-7));
816 #endif
817  }
818 }
819 
820 //******************************************************************************
821 TEST(triangulation, reprojectionError_cameraComparison) {
822  Pose3 poseA = Pose3(
823  Rot3::Ypr(-M_PI / 2, 0., -M_PI / 2),
824  Point3(0.0, 0.0, 0.0)); // with z pointing along x axis of global frame
825  Point3 landmarkL(5.0, 0.0, 0.0); // 1m in front of poseA
827  // TODO(dellaert): check Unit3 u = sphericalCamera.project(landmarkL);
828 
829  static Cal3_S2::shared_ptr sharedK(new Cal3_S2(60, 640, 480));
830  PinholePose<Cal3_S2> pinholeCamera(poseA, sharedK);
831  Vector2 px = pinholeCamera.project(landmarkL);
832 
833  // add perturbation and compare error in both cameras
834  Vector2 px_noise(1.0, 2.0); // px perturbation vertically and horizontally
835  Vector2 measured_px = px + px_noise;
836  Vector2 measured_px_calibrated = sharedK->calibrate(measured_px);
837  Unit3 measured_u =
838  Unit3(measured_px_calibrated[0], measured_px_calibrated[1], 1.0);
839  Unit3 expected_measured_u =
840  Unit3(px_noise[0] / sharedK->fx(), px_noise[1] / sharedK->fy(), 1.0);
841  EXPECT(assert_equal(expected_measured_u, measured_u, 1e-7));
842 
843  Vector2 actualErrorPinhole =
844  pinholeCamera.reprojectionError(landmarkL, measured_px);
845  Vector2 expectedErrorPinhole = Vector2(-px_noise[0], -px_noise[1]);
846  EXPECT(assert_equal(expectedErrorPinhole, actualErrorPinhole,
847  1e-7)); //- sign due to definition of error
848 
849  Vector2 actualErrorSpherical =
850  sphericalCamera.reprojectionError(landmarkL, measured_u);
851  // expectedError: not easy to calculate, since it involves the unit3 basis
852  Vector2 expectedErrorSpherical(-0.00360842, 0.00180419);
853  EXPECT(assert_equal(expectedErrorSpherical, actualErrorSpherical, 1e-7));
854 }
855 
856 //******************************************************************************
857 int main() {
858  TestResult tr;
859  return TestRegistry::runAllTests(tr);
860 }
861 //******************************************************************************
TestRegistry::runAllTests
static int runAllTests(TestResult &result)
Definition: TestRegistry.cpp:27
cam1
static PinholeCamera< Cal3_S2 > cam1(pose3, cal1)
CHECK_EXCEPTION
#define CHECK_EXCEPTION(condition, exception_name)
Definition: Test.h:118
gtsam::projectionMatricesFromCameras
std::vector< Matrix34, Eigen::aligned_allocator< Matrix34 > > projectionMatricesFromCameras(const CameraSet< CAMERA > &cameras)
Definition: triangulation.h:225
camera1
PinholeCamera< Cal3_S2 > camera1(pose1, *sharedCal)
gtsam::NonlinearOptimizer::optimize
virtual const Values & optimize()
Definition: NonlinearOptimizer.h:98
gtsam::Pose3Vector
std::vector< Pose3 > Pose3Vector
Definition: Pose3.h:428
upright
static const Rot3 upright
Definition: testTriangulation.cpp:42
CameraSet.h
Base class to create smart factors on poses or cameras.
gtsam::TriangulationFactor
Definition: TriangulationFactor.h:31
e
Array< double, 1, 3 > e(1./3., 0.5, 2.)
SphericalCamera.h
Calibrated camera with spherical projection.
project2
static Point2 project2(const CalibratedCamera &camera, const Point3 &point)
Definition: testCalibratedCamera.cpp:127
EXPECT
#define EXPECT(condition)
Definition: Test.h:150
gtsam::Vector2
Eigen::Vector2d Vector2
Definition: Vector.h:42
optimize
int optimize(const SfmData &db, const NonlinearFactorGraph &graph, const Values &initial, bool separateCalibration=false)
Definition: timeSFMBAL.h:64
kCamera2
static const PinholeCamera< Cal3_S2 > kCamera2(kPose2, *kSharedCal)
gtsam::Cal3DS2
Calibration of a camera with radial distortion that also supports Lie-group behaviors for optimizatio...
Definition: Cal3DS2.h:35
TestHarness.h
gtsam::TriangulationResult::outlier
bool outlier() const
Definition: triangulation.h:668
kZ2
static const Point2 kZ2
Definition: testTriangulation.cpp:58
camera3
static const CalibratedCamera camera3(pose1)
z1
Point2 z1
Definition: testTriangulationFactor.cpp:45
LevenbergMarquardtOptimizer.h
A nonlinear optimizer that uses the Levenberg-Marquardt trust-region scheme.
main
int main()
Definition: testTriangulation.cpp:857
kZ1
static const Point2 kZ1
Definition: testTriangulation.cpp:57
pose3
static Pose3 pose3(rt3, pt3)
gtsam::StereoPoint2Vector
std::vector< StereoPoint2 > StereoPoint2Vector
Definition: StereoPoint2.h:168
gtsam::SphericalCamera
Definition: SphericalCamera.h:74
m1
Matrix3d m1
Definition: IOFormat.cpp:2
gtsam::StereoFactor
Definition: testSmartFactorBase.cpp:80
kPoses
static const std::vector< Pose3 > kPoses
Definition: testTriangulation.cpp:50
vanillaPose::sharedK
static const Cal3_S2::shared_ptr sharedK(new Cal3_S2(fov, w, h))
kPose2
static const Pose3 kPose2
Definition: testTriangulation.cpp:47
simple::pose2
static Pose3 pose2
Definition: testInitializePose3.cpp:58
TEST
TEST(triangulation, twoPoses)
Definition: testTriangulation.cpp:63
kMeasurements
static const Point2Vector kMeasurements
Definition: testTriangulation.cpp:59
gtsam::GenericStereoFactor
Definition: StereoFactor.h:32
vanilla::params
static const SmartProjectionParams params
Definition: smartFactorScenarios.h:69
K2
static const Cal3Bundler K2(625, 1e-3, 1e-3)
result
Values result
Definition: OdometryOptimize.cpp:8
triangulation.h
Functions for triangulation.
gtsam::CameraSet
A set of cameras, all with their own calibration.
Definition: CameraSet.h:36
gtsam::PinholeBaseK< Calibration >::project
Point2 project(const Point3 &pw, OptionalJacobian< 2, 6 > Dpose={}, OptionalJacobian< 2, 3 > Dpoint={}, OptionalJacobian< 2, DimK > Dcal={}) const
project a 3D point from world coordinates into the image
Definition: PinholePose.h:112
gtsam::CheiralityException
Definition: CalibratedCamera.h:34
gtsam::Expression
Definition: Expression.h:47
kSharedCal
static const std::shared_ptr< Cal3_S2 > kSharedCal
Definition: testTriangulation.cpp:38
gtsam_unstable.tests.test_ProjectionFactorRollingShutter.point
point
Definition: test_ProjectionFactorRollingShutter.py:25
m2
MatrixType m2(n_dims)
gtsam::TriangulationParameters
Definition: triangulation.h:558
gtsam::Values::at
const ValueType at(Key j) const
Definition: Values-inl.h:261
z3
static const Unit3 z3
Definition: testRotateFactor.cpp:44
kPose1
static const Pose3 kPose1
Definition: testTriangulation.cpp:43
gtsam::Rot3
Rot3 is a 3D rotation represented as a rotation matrix if the preprocessor symbol GTSAM_USE_QUATERNIO...
Definition: Rot3.h:58
gtsam::Pose3
Definition: Pose3.h:37
StereoFactor.h
A non-linear factor for stereo measurements.
gtsam::PinholeCamera
Definition: PinholeCamera.h:33
gtsam::NonlinearFactorGraph
Definition: NonlinearFactorGraph.h:55
gtsam::Cal3Fisheye
Calibration of a fisheye camera.
Definition: Cal3Fisheye.h:51
gtsam::SharedDiagonal
noiseModel::Diagonal::shared_ptr SharedDiagonal
Definition: NoiseModel.h:743
cholesky::expected
Matrix expected
Definition: testMatrix.cpp:971
K4
DirectSum< Z2, Z2 > K4
Definition: testCyclic.cpp:90
gtsam::NonlinearFactorGraph::addPrior
void addPrior(Key key, const T &prior, const SharedNoiseModel &model=nullptr)
Definition: NonlinearFactorGraph.h:199
landmark
static Point3 landmark(0, 0, 5)
gtsam::TriangulationResult::valid
bool valid() const
Definition: triangulation.h:666
PinholeCamera.h
Base class for all pinhole cameras.
gtsam::LevenbergMarquardtOptimizer
Definition: LevenbergMarquardtOptimizer.h:35
u2
Vector u2
Definition: testSimpleHelicopter.cpp:32
gtsam::triangulateSafe
TriangulationResult triangulateSafe(const CameraSet< CAMERA > &cameras, const typename CAMERA::MeasurementVector &measured, const TriangulationParameters &params)
triangulateSafe: extensive checking of the outcome
Definition: triangulation.h:698
Vector2
Definition: test_operator_overloading.cpp:18
gtsam::Point2
Vector2 Point2
Definition: Point2.h:32
kLandmark
static const Point3 kLandmark(5, 0.5, 1.2)
gtsam::SharedNoiseModel
noiseModel::Base::shared_ptr SharedNoiseModel
Definition: NoiseModel.h:741
gtsam::Cal3_S2::shared_ptr
std::shared_ptr< Cal3_S2 > shared_ptr
Definition: Cal3_S2.h:39
K1
static Cal3_S2::shared_ptr K1(new Cal3_S2(fov, w, h))
gtsam::Unit3::retract
Unit3 retract(const Vector2 &v, OptionalJacobian< 2, 2 > H={}) const
The retract function.
Definition: Unit3.cpp:255
model
noiseModel::Diagonal::shared_ptr model
Definition: doc/Code/Pose2SLAMExample.cpp:7
TestResult
Definition: TestResult.h:26
gtsam::triangulateDLT
Point3 triangulateDLT(const std::vector< Matrix34, Eigen::aligned_allocator< Matrix34 >> &projection_matrices, const Point2Vector &measurements, double rank_tol)
Definition: triangulation.cpp:149
kCamera1
static const PinholeCamera< Cal3_S2 > kCamera1(kPose1, *kSharedCal)
cam2
static StereoCamera cam2(pose3, cal4ptr)
sphericalCamera
Definition: smartFactorScenarios.h:139
gtsam
traits
Definition: chartTesting.h:28
pose1
static const Pose3 pose1(Rot3(), Point3(0, 1, 0.5))
gtsam::TriangulationUnderconstrainedException
Exception thrown by triangulateDLT when SVD returns rank < 3.
Definition: triangulation.h:42
leaf::values
leaf::MyValues values
gtsam::Values
Definition: Values.h:65
Cal3DS2.h
Calibration of a camera with radial distortion, calculations in base class Cal3DS2_Base.
gtsam::StereoPoint2
Definition: StereoPoint2.h:34
std
Definition: BFloat16.h:88
gtsam::Values::insert
void insert(Key j, const Value &val)
Definition: Values.cpp:155
gtsam::assert_equal
bool assert_equal(const Matrix &expected, const Matrix &actual, double tol)
Definition: Matrix.cpp:40
gtsam::Point2Vector
std::vector< Point2, Eigen::aligned_allocator< Point2 > > Point2Vector
Definition: Point2.h:49
gtsam::PinholeBaseK::reprojectionError
Point2 reprojectionError(const Point3 &pw, const Point2 &measured, OptionalJacobian< 2, 6 > Dpose={}, OptionalJacobian< 2, 3 > Dpoint={}, OptionalJacobian< 2, DimK > Dcal={}) const
project a 3D point from world coordinates into the image
Definition: PinholePose.h:119
gtsam::Cal3_S2
The most common 5DOF 3D->2D calibration.
Definition: Cal3_S2.h:34
ExpressionFactor.h
gtsam::TriangulationResult
Definition: triangulation.h:638
gtsam::Point3
Vector3 Point3
Definition: Point3.h:38
initial
Definition: testScenarioRunner.cpp:148
gtsam::StereoCamera::project
StereoPoint2 project(const Point3 &point) const
Project 3D point to StereoPoint2 (uL,uR,v)
Definition: StereoCamera.cpp:32
gtsam::Unit3
Represents a 3D point on a unit sphere.
Definition: Unit3.h:42
gtsam::TriangulationCheiralityException
Exception thrown by triangulateDLT when landmark is behind one or more of the cameras.
Definition: triangulation.h:50
M_PI
#define M_PI
Definition: mconf.h:117
gtsam::PinholePose
Definition: PinholePose.h:239
graph
NonlinearFactorGraph graph
Definition: doc/Code/OdometryExample.cpp:2
camera2
static const Camera2 camera2(pose1, K2)
Cal3Bundler.h
Calibration used by Bundler.
gtsam::TriangulationResult::farPoint
bool farPoint() const
Definition: triangulation.h:669
gtsam::CalibratedCamera::project
Point2 project(const Point3 &point, OptionalJacobian< 2, 6 > Dcamera={}, OptionalJacobian< 2, 3 > Dpoint={}) const
Definition: CalibratedCamera.cpp:188
gtsam::FactorGraph::emplace_shared
IsDerived< DERIVEDFACTOR > emplace_shared(Args &&... args)
Emplace a shared pointer to factor of given type.
Definition: FactorGraph.h:153
gtsam::NonlinearFactorGraph::addExpressionFactor
void addExpressionFactor(const SharedNoiseModel &R, const T &z, const Expression< T > &h)
Definition: NonlinearFactorGraph.h:187
StereoCamera.h
A Stereo Camera based on two Simple Cameras.
z2
static const Unit3 z2
Definition: testRotateFactor.cpp:43
gtsam::Symbol
Definition: inference/Symbol.h:37
gtsam::DirectSum
Definition: Group.h:165
px
RealScalar RealScalar * px
Definition: level1_cplx_impl.h:28


gtsam
Author(s):
autogenerated on Tue Jun 25 2024 03:07:07