1 // This file is part of OpenMVG, an Open Multiple View Geometry C++ library.
2
3 // Copyright (c) 2012, 2013, 2014 Pierre MOULON.
4
5 // This Source Code Form is subject to the terms of the Mozilla Public
6 // License, v. 2.0. If a copy of the MPL was not distributed with this
7 // file, You can obtain one at http://mozilla.org/MPL/2.0/.
8
9 #include "openMVG/multiview/essential.hpp"
10 #include "openMVG/multiview/rotation_averaging.hpp"
11 #include "openMVG/multiview/test_data_sets.hpp"
12
13 #include "CppUnitLite/TestHarness.h"
14 #include "testing/testing.h"
15
16 #include <fstream>
17 #include <iostream>
18 #include <iterator>
19 #include <numeric>
20 #include <vector>
21
22 using namespace openMVG;
23 using namespace openMVG::rotation_averaging;
24 using namespace openMVG::rotation_averaging::l1;
25 using namespace openMVG::rotation_averaging::l2;
26
TEST(rotation_averaging,ClosestSVDRotationMatrix)27 TEST ( rotation_averaging, ClosestSVDRotationMatrix )
28 {
29 const Mat3 rotx = RotationAroundX(0.3);
30
31 const Mat3 Approximative_rotx = rotation_averaging::l2::ClosestSVDRotationMatrix(rotx);
32
33 // Check that SVD have rebuilt the matrix correctly
34 EXPECT_MATRIX_NEAR( rotx, Approximative_rotx, 1e-8);
35 // Check the Frobenius distance between the approximated rot matrix and the GT
36 EXPECT_NEAR( 0.0, FrobeniusDistance( rotx, Approximative_rotx), 1e-8);
37 // Check that the Matrix is a rotation matrix (determinant == 1)
38 EXPECT_NEAR( 1.0, Approximative_rotx.determinant(), 1e-8);
39 }
40
41
TEST(rotation_averaging,ClosestSVDRotationMatrixNoisy)42 TEST ( rotation_averaging, ClosestSVDRotationMatrixNoisy )
43 {
44 Mat3 rotx = RotationAroundX(0.3);
45 //-- Set a little of noise in the rotMatrix :
46 rotx(2,2) -= 0.02;
47
48 const Mat3 Approximative_rotx = rotation_averaging::l2::ClosestSVDRotationMatrix(rotx);
49
50 // Check the Frobenius distance between the approximated rot matrix and the GT
51 CHECK( FrobeniusDistance( rotx, Approximative_rotx) < 0.02);
52 // Check that the Matrix is a rotation matrix (determinant == 1)
53 EXPECT_NEAR( 1.0, Approximative_rotx.determinant(), 1e-8);
54 }
55
56 // Rotation averaging in a triplet:
57 // 0_______2
58 // \ /
59 // \ /
60 // \ /
61 // 1
TEST(rotation_averaging,RotationLeastSquare_3_Camera)62 TEST ( rotation_averaging, RotationLeastSquare_3_Camera)
63 {
64 using namespace std;
65
66 //--
67 // Setup 3 camera that have a relative orientation of 120 degree
68 // Set Z axis as UP Vector for the rotation
69 // They are in the same plane and looking in O={0,0,0}
70 //--
71 const Mat3 R01 = RotationAroundZ(2.*M_PI/3.0); //120 degree
72 const Mat3 R12 = RotationAroundZ(2.*M_PI/3.0); //120 degree
73 const Mat3 R20 = RotationAroundZ(2.*M_PI/3.0); //120 degree
74
75 std::vector<RelativeRotation > vec_relativeRotEstimate;
76 vec_relativeRotEstimate.push_back( RelativeRotation(0,1, R01));
77 vec_relativeRotEstimate.push_back( RelativeRotation(1,2, R12));
78 vec_relativeRotEstimate.push_back( RelativeRotation(2,0, R20));
79
80 //- Solve the global rotation estimation problem :
81 std::vector<Mat3> vec_globalR;
82 L2RotationAveraging(3, vec_relativeRotEstimate, vec_globalR);
83 EXPECT_EQ(3, vec_globalR.size());
84 // Check that the loop is closing
85 EXPECT_MATRIX_NEAR(Mat3::Identity(), (vec_globalR[0]*vec_globalR[1]*vec_globalR[2]), 1e-8);
86
87 //--
88 // Check that the found relative rotation matrix give the expected rotation.
89 // -> the started relative rotation (used in the action matrix).
90 //// /!\ Translations are not checked they are 0 by default.
91 //--
92 Mat3 R;
93 Vec3 t, t0 = Vec3::Zero(), t1 = Vec3::Zero();
94 RelativeCameraMotion(vec_globalR[0], t0, vec_globalR[1], t1, &R, &t);
95 EXPECT_NEAR( 0, FrobeniusDistance( R01, R), 1e-2);
96
97 RelativeCameraMotion(vec_globalR[1], t0, vec_globalR[2], t1, &R, &t);
98 EXPECT_NEAR( 0, FrobeniusDistance( R12, R), 1e-2);
99
100 RelativeCameraMotion(vec_globalR[2], t0, vec_globalR[0], t1, &R, &t);
101 EXPECT_NEAR( 0, FrobeniusDistance( R20, R), 1e-2);
102 }
103
104 // Test over a loop of cameras
TEST(rotation_averaging,RefineRotationsL2_CompleteGraph)105 TEST ( rotation_averaging, RefineRotationsL2_CompleteGraph)
106 {
107 //-- Setup a circular camera rig
108 const int iNviews = 5;
109 const NViewDataSet d = NRealisticCamerasRing(iNviews, 5,
110 nViewDatasetConfigurator(1,1,0,0,5,0)); // Suppose a camera with Unit matrix as K
111
112 //Link each camera to the two next ones
113 RelativeRotations vec_relativeRotEstimate;
114 for (size_t i = 0; i < iNviews; ++i)
115 {
116 const size_t index0 = i;
117 const size_t index1 = (i+1)%iNviews;
118 const size_t index2 = (i+2)%iNviews;
119
120 //-- compute true relative rotations of the triplet
121 // (index0->index1), (index1,index2), (index0->index2)
122 Mat3 Rrel;
123 Vec3 trel;
124 RelativeCameraMotion(d._R[index0], d._t[index0], d._R[index1], d._t[index1], &Rrel, &trel);
125 vec_relativeRotEstimate.push_back(RelativeRotation(index0, index1, Rrel, 1));
126
127 RelativeCameraMotion(d._R[index1], d._t[index1], d._R[index2], d._t[index2], &Rrel, &trel);
128 vec_relativeRotEstimate.push_back(RelativeRotation(index1, index2, Rrel, 1));
129
130 RelativeCameraMotion(d._R[index0], d._t[index0], d._R[index2], d._t[index2], &Rrel, &trel);
131 vec_relativeRotEstimate.push_back(RelativeRotation(index0, index2, Rrel, 1));
132 }
133
134 //- Solve the global rotation estimation problem :
135 std::vector<Mat3> vec_globalR(iNviews);
136 L2RotationAveraging(iNviews, vec_relativeRotEstimate, vec_globalR);
137
138 // Check that the loop is closing
139 Mat3 rotCumul = Mat3::Identity();
140 for (size_t i = 0; i < iNviews; ++i)
141 {
142 rotCumul*= vec_globalR[i];
143 }
144 EXPECT_MATRIX_NEAR(Mat3::Identity(), rotCumul, 1e-4);
145
146 // Fix the sign of the rotations (put the global rotation in the same rotation axis as GT)
147 if ( SIGN(vec_globalR[0](0,0)) != SIGN( d._R[0](0,0) ))
148 {
149 Mat3 Rrel;
150 Vec3 trel;
151 RelativeCameraMotion(vec_globalR[0], Vec3::Zero(), d._R[0], Vec3::Zero(), &Rrel, &trel);
152 for (size_t i = 0; i < iNviews; ++i)
153 vec_globalR[i] *= Rrel;
154 }
155
156 // Check that each global rotations is near the true ones
157 for (size_t i = 0; i < iNviews; ++i)
158 {
159 EXPECT_NEAR(0.0, FrobeniusDistance(d._R[i], vec_globalR[i]), 1e-8);
160 }
161 }
162
TEST(rotation_averaging,RefineRotationsAvgL1IRLS_SimpleTriplet)163 TEST ( rotation_averaging, RefineRotationsAvgL1IRLS_SimpleTriplet)
164 {
165 using namespace std;
166
167 //--
168 // Setup 3 camera that have a relative orientation of 120 degree
169 // Set Z axis as UP Vector for the rotation
170 // They are in the same plane and looking in O={0,0,0}
171 //--
172 const Mat3 R01 = RotationAroundZ(2.*M_PI/3.0); //120 degree
173 const Mat3 R12 = RotationAroundZ(2.*M_PI/3.0); //120 degree
174 const Mat3 R20 = RotationAroundZ(2.*M_PI/3.0); //120 degree
175
176 // Setup the relative motions (relative rotations)
177 RelativeRotations vec_relativeRotEstimate;
178 vec_relativeRotEstimate.push_back(RelativeRotation(0, 1, R01, 1));
179 vec_relativeRotEstimate.push_back(RelativeRotation(1, 2, R12, 1));
180 vec_relativeRotEstimate.push_back(RelativeRotation(2, 0, R20, 1));
181
182 //- Solve the global rotation estimation problem :
183 Matrix3x3Arr vec_globalR(3);
184 const size_t nMainViewID = 0;
185 EXPECT_TRUE(GlobalRotationsRobust(vec_relativeRotEstimate, vec_globalR, nMainViewID, 0.0f, nullptr));
186
187 // Check that the loop is closing
188 EXPECT_MATRIX_NEAR(Mat3::Identity(), (vec_globalR[0]*vec_globalR[1]*vec_globalR[2]), 1e-4);
189
190 //--
191 // Check that the found relative rotation matrix give the expected rotation.
192 // -> the started relative rotation (used in the action matrix).
193 //// /!\ Translation are not checked they are 0 by default.
194 //--
195 Mat3 R;
196 Vec3 t, t0 = Vec3::Zero(), t1 = Vec3::Zero();
197 RelativeCameraMotion(vec_globalR[0], t0, vec_globalR[1], t1, &R, &t);
198 EXPECT_NEAR( 0, FrobeniusDistance( R01, R), 1e-8);
199
200 RelativeCameraMotion(vec_globalR[1], t0, vec_globalR[2], t1, &R, &t);
201 EXPECT_NEAR( 0, FrobeniusDistance( R12, R), 1e-8);
202
203 RelativeCameraMotion(vec_globalR[2], t0, vec_globalR[0], t1, &R, &t);
204 EXPECT_NEAR( 0, FrobeniusDistance( R20, R), 1e-8);
205 }
206
207 // Test over a loop of cameras
TEST(rotation_averaging,RefineRotationsAvgL1IRLS_CompleteGraph)208 TEST ( rotation_averaging, RefineRotationsAvgL1IRLS_CompleteGraph)
209 {
210 //-- Setup a circular camera rig
211 const int iNviews = 5;
212 const NViewDataSet d = NRealisticCamerasRing(iNviews, 5,
213 nViewDatasetConfigurator(1,1,0,0,5,0)); // Suppose a camera with Unit matrix as K
214
215 //Link each camera to the two next ones
216 RelativeRotations vec_relativeRotEstimate;
217 for (size_t i = 0; i < iNviews; ++i)
218 {
219 const size_t index0 = i;
220 const size_t index1 = (i+1)%iNviews;
221 const size_t index2 = (i+2)%iNviews;
222
223 //-- compute true relative rotations of the triplet
224 // (index0->index1), (index1,index2), (index0->index2)
225 Mat3 Rrel;
226 Vec3 trel;
227 RelativeCameraMotion(d._R[index0], d._t[index0], d._R[index1], d._t[index1], &Rrel, &trel);
228 vec_relativeRotEstimate.push_back(RelativeRotation(index0, index1, Rrel, 1));
229
230 RelativeCameraMotion(d._R[index1], d._t[index1], d._R[index2], d._t[index2], &Rrel, &trel);
231 vec_relativeRotEstimate.push_back(RelativeRotation(index1, index2, Rrel, 1));
232
233 RelativeCameraMotion(d._R[index0], d._t[index0], d._R[index2], d._t[index2], &Rrel, &trel);
234 vec_relativeRotEstimate.push_back(RelativeRotation(index0, index2, Rrel, 1));
235 }
236
237 //- Solve the global rotation estimation problem :
238 Matrix3x3Arr vec_globalR(iNviews);
239 size_t nMainViewID = 0;
240 const bool bTest = GlobalRotationsRobust(vec_relativeRotEstimate, vec_globalR, nMainViewID, 0.0f, nullptr);
241 EXPECT_TRUE(bTest);
242
243 // Check that the loop is closing
244 Mat3 rotCumul = Mat3::Identity();
245 for (size_t i = 0; i < iNviews; ++i)
246 {
247 rotCumul*= vec_globalR[i];
248 }
249 EXPECT_MATRIX_NEAR(Mat3::Identity(), rotCumul, 1e-4);
250
251 // Fix the sign of the rotations (put the global rotation in the same rotation axis as GT)
252 if ( SIGN(vec_globalR[0](0,0)) != SIGN( d._R[0](0,0) ))
253 {
254 Mat3 Rrel;
255 Vec3 trel;
256 RelativeCameraMotion(vec_globalR[0], Vec3::Zero(), d._R[0], Vec3::Zero(), &Rrel, &trel);
257 for (size_t i = 0; i < iNviews; ++i)
258 vec_globalR[i] *= Rrel;
259 }
260
261 // Check that each global rotations is near the true ones
262 for (size_t i = 0; i < iNviews; ++i)
263 {
264 EXPECT_NEAR(0.0, FrobeniusDistance(d._R[i], vec_globalR[i]), 1e-8);
265 }
266 }
267
268
269 // Test over a loop of camera that have 2 relative outliers rotations
TEST(rotation_averaging,RefineRotationsAvgL1IRLS_CompleteGraph_outliers)270 TEST ( rotation_averaging, RefineRotationsAvgL1IRLS_CompleteGraph_outliers)
271 {
272 //-- Setup a circular camera rig
273 const int iNviews = 5;
274 NViewDataSet d = NRealisticCamerasRing(iNviews, 5,
275 nViewDatasetConfigurator(1,1,0,0,5,0)); // Suppose a camera with Unit matrix as K
276
277 //Link each camera to the two next ones
278 RelativeRotations vec_relativeRotEstimate;
279 std::vector<std::pair<size_t,size_t>> vec_unique;
280 for (size_t i = 0; i < iNviews; ++i)
281 {
282 size_t index0 = i;
283 size_t index1 = (i+1)%iNviews;
284 size_t index2 = (i+2)%iNviews;
285
286 //-- compute true relative rotations of the triplet
287 // (index0->index1), (index1,index2), (index0->index2)
288
289 Mat3 Rrel;
290 Vec3 trel;
291 // Add the relative motion if do not exist yet
292 if ( std::find(vec_unique.begin(), vec_unique.end(), std::make_pair(index0, index1)) == vec_unique.end()
293 && std::find(vec_unique.begin(), vec_unique.end(), std::make_pair(index1, index0)) == vec_unique.end())
294 {
295 RelativeCameraMotion(d._R[index0], d._t[index0], d._R[index1], d._t[index1], &Rrel, &trel);
296 vec_relativeRotEstimate.push_back(RelativeRotation(index0, index1, Rrel, 1));
297 vec_unique.emplace_back(index0, index1);
298 }
299
300 if ( std::find(vec_unique.begin(), vec_unique.end(), std::make_pair(index1, index2)) == vec_unique.end()
301 && std::find(vec_unique.begin(), vec_unique.end(), std::make_pair(index2, index1)) == vec_unique.end())
302 {
303 RelativeCameraMotion(d._R[index1], d._t[index1], d._R[index2], d._t[index2], &Rrel, &trel);
304 vec_relativeRotEstimate.push_back(RelativeRotation(index1, index2, Rrel, 1));
305 vec_unique.emplace_back(index1, index2);
306 }
307
308 if ( std::find(vec_unique.begin(), vec_unique.end(), std::make_pair(index0, index2)) == vec_unique.end()
309 && std::find(vec_unique.begin(), vec_unique.end(), std::make_pair(index2, index0)) == vec_unique.end())
310 {
311 RelativeCameraMotion(d._R[index0], d._t[index0], d._R[index2], d._t[index2], &Rrel, &trel);
312 vec_relativeRotEstimate.push_back(RelativeRotation(index0, index2, Rrel, 1));
313 vec_unique.emplace_back(index0, index2);
314 }
315 }
316
317 // Add 2 outliers rotations between (0->1), (2->3)
318 // (use a smaller weight since those rotations are less accurate)
319 for (size_t i = 0; i < vec_relativeRotEstimate.size(); ++i)
320 {
321 if (vec_relativeRotEstimate[i].i == 0 && vec_relativeRotEstimate[i].j == 1)
322 vec_relativeRotEstimate[i] = RelativeRotation(0, 1, RotationAroundX(D2R(0.1)), 0.5);
323 if (vec_relativeRotEstimate[i].i == 2 && vec_relativeRotEstimate[i].j == 3)
324 vec_relativeRotEstimate[i] = RelativeRotation(2, 3, RotationAroundX(D2R(0.6)), 0.5);
325 }
326
327 //- Solve the global rotation estimation problem :
328 Matrix3x3Arr vec_globalR(iNviews);
329 vec_globalR = d._R;
330 size_t nMainViewID = 0;
331 std::vector<bool> vec_inliers;
332 const bool bTest = GlobalRotationsRobust(vec_relativeRotEstimate, vec_globalR, nMainViewID, 0.0f, &vec_inliers);
333 EXPECT_TRUE(bTest);
334
335 std::cout << "Inliers: " << std::endl;
336 std::copy(vec_inliers.begin(), vec_inliers.end(), std::ostream_iterator<bool>(std::cout, " "));
337 std::cout << std::endl;
338
339 // Check inlier list
340 CHECK(std::accumulate(vec_inliers.begin(), vec_inliers.end(), 0) == 8);
341 // Check outlier(s) have been found
342 CHECK(vec_inliers[0] == 0);
343 CHECK(vec_inliers[3] == 0);
344
345 // Remove outliers and refine
346 RelativeRotations vec_relativeRotEstimateTemp;
347 for (size_t i = 0; i < vec_inliers.size(); ++i)
348 {
349 if (vec_inliers[i] == 1)
350 vec_relativeRotEstimateTemp.push_back(vec_relativeRotEstimate[i]);
351 }
352 vec_relativeRotEstimate.swap(vec_relativeRotEstimateTemp);
353 EXPECT_TRUE( GlobalRotationsRobust(vec_relativeRotEstimate, vec_globalR, nMainViewID, 0.0f, &vec_inliers));
354
355 // Check that the loop is closing
356 Mat3 rotCumul = vec_globalR[0];
357 for (size_t i = 1; i < iNviews; ++i)
358 {
359 rotCumul*= vec_globalR[i];
360 }
361
362 EXPECT_MATRIX_NEAR(Mat3::Identity(), rotCumul, 1e-4);
363 // Fix the sign of the rotations (put the global rotation in the same rotation axis as GT)
364 if ( SIGN(vec_globalR[0](0,0)) != SIGN( d._R[0](0,0) ))
365 {
366 Mat3 Rrel;
367 Vec3 trel;
368 RelativeCameraMotion(vec_globalR[0], Vec3::Zero(), d._R[0], Vec3::Zero(), &Rrel, &trel);
369 for (size_t i = 0; i < iNviews; ++i)
370 vec_globalR[i] *= Rrel;
371 }
372
373 // Check that each global rotations is near the true ones
374 for (size_t i = 0; i < iNviews; ++i)
375 {
376 EXPECT_NEAR(0.0, FrobeniusDistance(d._R[i], vec_globalR[i]), 1e-8);
377 }
378 }
379
380 /* ************************************************************************* */
main()381 int main() { TestResult tr; return TestRegistry::runAllTests(tr);}
382 /* ************************************************************************* */
383