Skip to content

Commit 0189436

Browse files
committed
unit test for rotation averager
1 parent 9f132ea commit 0189436

File tree

5 files changed

+255
-1
lines changed

5 files changed

+255
-1
lines changed

glomap/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,7 @@ install(TARGETS glomap_main DESTINATION bin)
116116
if(TESTS_ENABLED)
117117
add_executable(glomap_test
118118
controllers/global_mapper_test.cc
119+
controllers/rotation_averager_test.cc
119120
)
120121
target_link_libraries(
121122
glomap_test
Lines changed: 244 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,244 @@
1+
#include "glomap/controllers/global_mapper.h"
2+
#include "glomap/controllers/rotation_averager.h"
3+
#include "glomap/estimators/gravity_refinement.h"
4+
#include "glomap/io/colmap_io.h"
5+
#include "glomap/math/rigid3d.h"
6+
#include "glomap/types.h"
7+
8+
#include <colmap/estimators/alignment.h>
9+
#include <colmap/scene/synthetic.h>
10+
#include <colmap/util/testing.h>
11+
12+
#include <gtest/gtest.h>
13+
14+
namespace glomap {
15+
namespace {
16+
17+
void CreateRandomRotation(const double stddev, Eigen::Quaterniond& q) {
18+
std::random_device rd{};
19+
std::mt19937 gen{rd()};
20+
21+
// Construct a random axis
22+
double theta = double(rand()) / RAND_MAX * 2 * M_PI;
23+
double phi = double(rand()) / RAND_MAX * M_PI;
24+
Eigen::Vector3d axis(std::cos(theta) * std::sin(phi),
25+
std::sin(theta) * std::sin(phi),
26+
std::cos(phi));
27+
28+
// Construct a random angle
29+
std::normal_distribution<double> d{0, stddev};
30+
double angle = d(gen);
31+
q = Eigen::AngleAxisd(angle, axis);
32+
}
33+
34+
void PrepareGravity(const colmap::Reconstruction& gt,
35+
std::unordered_map<image_t, Image>& images,
36+
double stddev_gravity = 0.0,
37+
double outlier_ratio = 0.0) {
38+
for (auto& image_id : gt.RegImageIds()) {
39+
Eigen::Vector3d gravity =
40+
gt.Image(image_id).CamFromWorld().rotation * Eigen::Vector3d(0, 1, 0);
41+
42+
if (stddev_gravity > 0.0) {
43+
Eigen::Quaterniond q;
44+
CreateRandomRotation(DegToRad(stddev_gravity), q);
45+
gravity = q * gravity;
46+
}
47+
48+
if (outlier_ratio > 0.0 && double(rand()) / RAND_MAX < outlier_ratio) {
49+
Eigen::Quaterniond q;
50+
CreateRandomRotation(1., q);
51+
gravity = Rigid3dToAngleAxis(Rigid3d(q, Eigen::Vector3d::Zero())).normalized();
52+
}
53+
images[image_id].gravity_info.SetGravity(gravity);
54+
}
55+
}
56+
57+
58+
GlobalMapperOptions CreateMapperTestOptions() {
59+
GlobalMapperOptions options;
60+
options.skip_view_graph_calibration = false;
61+
options.skip_relative_pose_estimation = false;
62+
options.skip_rotation_averaging = true;
63+
options.skip_track_establishment = true;
64+
options.skip_global_positioning = true;
65+
options.skip_bundle_adjustment = true;
66+
options.skip_retriangulation = true;
67+
68+
return options;
69+
}
70+
71+
RotationAveragerOptions CreateRATestOptions(bool use_gravity = false) {
72+
RotationAveragerOptions options;
73+
options.skip_initialization = true;
74+
options.use_gravity = use_gravity;
75+
return options;
76+
}
77+
78+
void ExpectEqualRotations(
79+
const colmap::Reconstruction& gt,
80+
const colmap::Reconstruction& computed,
81+
const double max_rotation_error_deg) {
82+
83+
const std::set<image_t> reg_image_ids_set = gt.RegImageIds();
84+
std::vector<image_t> reg_image_ids(reg_image_ids_set.begin(),
85+
reg_image_ids_set.end());
86+
for (size_t i = 0; i < reg_image_ids.size(); i++) {
87+
const image_t image_id1 = reg_image_ids[i];
88+
for (size_t j = 0; j < reg_image_ids.size(); j++) {
89+
if (i == j) continue;
90+
const image_t image_id2 = reg_image_ids[j];
91+
92+
const Rigid3d cam2_from_cam1 =
93+
computed.Image(image_id2).CamFromWorld() *
94+
colmap::Inverse(computed.Image(image_id1).CamFromWorld());
95+
96+
const Rigid3d cam2_from_cam1_gt =
97+
gt.Image(image_id2).CamFromWorld() *
98+
colmap::Inverse(gt.Image(image_id1).CamFromWorld());
99+
100+
double rotation_error_deg = CalcAngle(cam2_from_cam1_gt, cam2_from_cam1);
101+
EXPECT_LT(rotation_error_deg, max_rotation_error_deg);
102+
}
103+
}
104+
}
105+
106+
void ExpectEqualGravity(
107+
const colmap::Reconstruction& gt,
108+
const std::unordered_map<image_t, Image>& images_computed,
109+
const double max_gravity_error_deg) {
110+
for (const auto& image_id : gt.RegImageIds()) {
111+
const Eigen::Vector3d gravity_gt =
112+
gt.Image(image_id).CamFromWorld().rotation * Eigen::Vector3d(0, 1, 0);
113+
const Eigen::Vector3d gravity_computed =
114+
images_computed.at(image_id).gravity_info.GetGravity();
115+
116+
double gravity_error_deg = CalcAngle(gravity_gt, gravity_computed);
117+
EXPECT_LT(gravity_error_deg, max_gravity_error_deg);
118+
119+
}
120+
}
121+
122+
TEST(RotationEstimator, WithoutNoise) {
123+
const std::string database_path = colmap::CreateTestDir() + "/database.db";
124+
125+
// FLAGS_v = 2;
126+
colmap::Database database(database_path);
127+
colmap::Reconstruction gt_reconstruction;
128+
colmap::SyntheticDatasetOptions synthetic_dataset_options;
129+
synthetic_dataset_options.num_cameras = 2;
130+
synthetic_dataset_options.num_images = 9;
131+
synthetic_dataset_options.num_points3D = 50;
132+
synthetic_dataset_options.point2D_stddev = 0;
133+
colmap::SynthesizeDataset(
134+
synthetic_dataset_options, &gt_reconstruction, &database);
135+
136+
ViewGraph view_graph;
137+
std::unordered_map<camera_t, Camera> cameras;
138+
std::unordered_map<image_t, Image> images;
139+
std::unordered_map<track_t, Track> tracks;
140+
141+
ConvertDatabaseToGlomap(database, view_graph, cameras, images);
142+
143+
// PrepareRelativeRotations(view_graph, images);
144+
PrepareGravity(gt_reconstruction, images);
145+
146+
GlobalMapper global_mapper(CreateMapperTestOptions());
147+
global_mapper.Solve(database, view_graph, cameras, images, tracks);
148+
149+
// Version with Gravity
150+
for (bool use_gravity : {true, false}) {
151+
SolveRotationAveraging(
152+
view_graph, images, CreateRATestOptions(use_gravity));
153+
154+
colmap::Reconstruction reconstruction;
155+
ConvertGlomapToColmap(cameras, images, tracks, reconstruction);
156+
ExpectEqualRotations(
157+
gt_reconstruction, reconstruction, /*max_rotation_error_deg=*/1e-2);
158+
}
159+
}
160+
161+
TEST(RotationEstimator, WithNoiseAndOutliers) {
162+
const std::string database_path = colmap::CreateTestDir() + "/database.db";
163+
164+
// FLAGS_v = 1;
165+
colmap::Database database(database_path);
166+
colmap::Reconstruction gt_reconstruction;
167+
colmap::SyntheticDatasetOptions synthetic_dataset_options;
168+
synthetic_dataset_options.num_cameras = 2;
169+
synthetic_dataset_options.num_images = 7;
170+
synthetic_dataset_options.num_points3D = 100;
171+
// synthetic_dataset_options.point2D_stddev = 0.5;
172+
synthetic_dataset_options.point2D_stddev = 1;
173+
synthetic_dataset_options.inlier_match_ratio = 0.6;
174+
colmap::SynthesizeDataset(
175+
synthetic_dataset_options, &gt_reconstruction, &database);
176+
177+
ViewGraph view_graph;
178+
std::unordered_map<camera_t, Camera> cameras;
179+
std::unordered_map<image_t, Image> images;
180+
std::unordered_map<track_t, Track> tracks;
181+
182+
ConvertDatabaseToGlomap(database, view_graph, cameras, images);
183+
184+
PrepareGravity(gt_reconstruction, images, /*stddev_gravity=*/3e-1);
185+
186+
GlobalMapper global_mapper(CreateMapperTestOptions());
187+
global_mapper.Solve(database, view_graph, cameras, images, tracks);
188+
189+
for (bool use_gravity : {true, false}) {
190+
SolveRotationAveraging(
191+
view_graph, images, CreateRATestOptions(use_gravity));
192+
193+
colmap::Reconstruction reconstruction;
194+
ConvertGlomapToColmap(cameras, images, tracks, reconstruction);
195+
if (use_gravity)
196+
ExpectEqualRotations(
197+
gt_reconstruction, reconstruction, /*max_rotation_error_deg=*/1.);
198+
else
199+
ExpectEqualRotations(
200+
gt_reconstruction, reconstruction, /*max_rotation_error_deg=*/2.);
201+
}
202+
203+
}
204+
205+
TEST(RotationEstimator, RefineGravity) {
206+
const std::string database_path = colmap::CreateTestDir() + "/database.db";
207+
208+
// FLAGS_v = 2;
209+
colmap::Database database(database_path);
210+
colmap::Reconstruction gt_reconstruction;
211+
colmap::SyntheticDatasetOptions synthetic_dataset_options;
212+
synthetic_dataset_options.num_cameras = 2;
213+
synthetic_dataset_options.num_images = 100;
214+
synthetic_dataset_options.num_points3D = 50;
215+
synthetic_dataset_options.point2D_stddev = 0;
216+
colmap::SynthesizeDataset(
217+
synthetic_dataset_options, &gt_reconstruction, &database);
218+
219+
ViewGraph view_graph;
220+
std::unordered_map<camera_t, Camera> cameras;
221+
std::unordered_map<image_t, Image> images;
222+
std::unordered_map<track_t, Track> tracks;
223+
224+
ConvertDatabaseToGlomap(database, view_graph, cameras, images);
225+
226+
// PrepareRelativeRotations(view_graph, images);
227+
PrepareGravity(gt_reconstruction, images, /*stddev_gravity=*/0., /*outlier_ratio=*/0.3);
228+
229+
GlobalMapper global_mapper(CreateMapperTestOptions());
230+
global_mapper.Solve(database, view_graph, cameras, images, tracks);
231+
232+
233+
GravityRefinerOptions opt_grav_refine;
234+
GravityRefiner grav_refiner(opt_grav_refine);
235+
grav_refiner.RefineGravity(view_graph, images);
236+
237+
// Check whether the gravity does not have error after refinement
238+
ExpectEqualGravity(gt_reconstruction, images, /*max_gravity_error_deg=*/1e-2);
239+
240+
241+
}
242+
243+
} // namespace
244+
} // namespace glomap

glomap/exe/rotation_averager.cc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
#include "glomap/io/pose_io.h"
88
#include "glomap/types.h"
99

10-
#include <colmap/util/misc.h>
10+
#include <colmap/util/file.h>
1111
#include <colmap/util/timer.h>
1212

1313
namespace glomap {

glomap/math/gravity.cc

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,4 +90,12 @@ Eigen::Vector3d AverageGravity(const std::vector<Eigen::Vector3d>& gravities) {
9090
return average;
9191
}
9292

93+
double CalcAngle(const Eigen::Vector3d& gravity1,
94+
const Eigen::Vector3d& gravity2) {
95+
double cos_r = gravity1.dot(gravity2) /
96+
(gravity1.norm() * gravity2.norm());
97+
cos_r = std::min(std::max(cos_r, -1.), 1.);
98+
99+
return std::acos(cos_r) * 180 / EIGEN_PI;
100+
}
93101
} // namespace glomap

glomap/math/gravity.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,5 @@ Eigen::Matrix3d AngleToRotUp(double angle);
1717
// Estimate the average gravity direction from a set of gravity directions
1818
Eigen::Vector3d AverageGravity(const std::vector<Eigen::Vector3d>& gravities);
1919

20+
double CalcAngle(const Eigen::Vector3d& gravity1, const Eigen::Vector3d& gravity2);
2021
} // namespace glomap

0 commit comments

Comments
 (0)