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