Skip to content

Commit e44ce35

Browse files
lpanafahojnnes
andauthored
Add the rotation averager to GLOMAP (#113)
* Fix conversion of colmap pose prior * d * restore the pose prior after rotation averaging * f * f * f * gravity refinement tested * add the stratified rotation averager and CLI * f * d * d * add timer * add options for gravity refinement * merge gravity_io to pose_io * add readme for the rotation averager * f * Update glomap/math/gravity.cc Co-authored-by: Johannes Schönberger <joschonb@microsoft.com> * f * Update glomap/controllers/rotation_averager.cc Co-authored-by: Johannes Schönberger <joschonb@microsoft.com> * Update glomap/controllers/rotation_averager.cc Co-authored-by: Johannes Schönberger <joschonb@microsoft.com> * change rotation averager from class to struct * d * d * add weighting and corresponding IO. Tested * change the writing of relative pose to be sorted * unit test for rotation averager * inject noise to avoid local minima for error-free case * f * add more points in the unit test --------- Co-authored-by: Johannes Schönberger <joschonb@microsoft.com>
1 parent ce51d8b commit e44ce35

24 files changed

+904
-83
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ If you use this project for your research, please cite
2020
year={2024},
2121
}
2222
```
23+
To use the seperate rotation averaging module, refer to [this README](docs/rotation_averager.md).
2324

2425
## Getting Started
2526

docs/rotation_averager.md

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
# Gravity-aligned Rotation Averaging with Circular Regression
2+
3+
[Project page](https://lpanaf.github.io/eccv24_ra1dof/) | [Paper](https://www.ecva.net/papers/eccv_2024/papers_ECCV/papers/05651.pdf) | [Supp.](https://lpanaf.github.io/assets/pdf/eccv24_ra1dof_sm.pdf)
4+
---
5+
6+
## About
7+
8+
This project aims at solving the rotation averaging problem with gravity prior.
9+
To achieve this, circular regression is leveraged.
10+
11+
If you use this project for your research, please cite
12+
```
13+
@inproceedings{pan2024ra1dof,
14+
author={Pan, Linfei and Pollefeys, Marc and Barath, Daniel},
15+
title={{Gravity-aligned Rotation Averaging with Circular Regression}},
16+
booktitle={European Conference on Computer Vision (ECCV)},
17+
year={2024},
18+
}
19+
```
20+
21+
## Getting Started
22+
Install GLOMAP as instrcucted in [README](../README.md).
23+
Then, call the rotation averager (3 degree-of-freedom) via
24+
```
25+
glomap rotation_averager --relpose_path RELPOSE_PATH --output_path OUTPUT_PATH
26+
```
27+
28+
If gravity directions are available, call the rotation averager (1 degree-of-freedom) via
29+
```
30+
glomap rotation_averager \
31+
--relpose_path RELPOSE_PATH \
32+
--output_path OUTPUT_PATH \
33+
--gravity_path GRAVTIY PATH
34+
```
35+
It is recommended to set `--use_stratified=1` if only a subset of images have gravity direction.
36+
If gravity measurements are subject to i.i.d. noise, they can be refined by setting `--refine_gravity=1`.
37+
38+
39+
## File Formats
40+
### Relative Pose
41+
The relative pose file is expected to be of the following format
42+
```
43+
IMAGE_NAME_1 IMAGE_NAME_2 QW QX QY QZ TX TY TZ
44+
```
45+
Only images contained in at least one relative pose will be included in the following procedure.
46+
The relative pose should be <sub>2</sub>R<sub>1</sub> x<sub>1</sub> + <sub>2</sub>t<sub>1</sub> = x<sub>2</sub>.
47+
48+
### Gravity Direction
49+
The gravity direction file is expected to be of the following format
50+
```
51+
IMAGE_NAME GX GY GZ
52+
```
53+
The gravity direction $g$ should $[0, 1, 0]$ if the image is parallel to the ground plane, and the estimated rotation would have the property that $R_i \cdot [0, 1, 0]^\top = g$.
54+
If is acceptable if only a subset of all images have gravity direciton.
55+
If the specified image name does not match any known image name from relative pose, it is ignored.
56+
57+
### Output
58+
The estimated global rotation will be in the following format
59+
```
60+
IMAGE_NAME QW QX QY QZ
61+
```
62+
Any images that are not within the largest connected component of the view-graph formed by the relative pose will be ignored.

glomap/CMakeLists.txt

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
set(SOURCES
22
controllers/global_mapper.cc
33
controllers/option_manager.cc
4+
controllers/rotation_averager.cc
45
controllers/track_establishment.cc
56
controllers/track_retriangulation.cc
67
estimators/bundle_adjustment.cc
@@ -11,7 +12,7 @@ set(SOURCES
1112
estimators/view_graph_calibration.cc
1213
io/colmap_converter.cc
1314
io/colmap_io.cc
14-
io/gravity_io.cc
15+
io/pose_io.cc
1516
math/gravity.cc
1617
math/rigid3d.cc
1718
math/tree.cc
@@ -29,6 +30,7 @@ set(SOURCES
2930
set(HEADERS
3031
controllers/global_mapper.h
3132
controllers/option_manager.h
33+
controllers/rotation_averager.h
3234
controllers/track_establishment.h
3335
controllers/track_retriangulation.h
3436
estimators/bundle_adjustment.h
@@ -41,7 +43,7 @@ set(HEADERS
4143
estimators/view_graph_calibration.h
4244
io/colmap_converter.h
4345
io/colmap_io.h
44-
io/gravity_io.h
46+
io/pose_io.h
4547
math/gravity.h
4648
math/l1_solver.h
4749
math/rigid3d.h
@@ -101,7 +103,10 @@ endif()
101103
add_executable(glomap_main
102104
glomap.cc
103105
exe/global_mapper.h
104-
exe/global_mapper.cc)
106+
exe/global_mapper.cc
107+
exe/rotation_averager.h
108+
exe/rotation_averager.cc
109+
)
105110
target_link_libraries(glomap_main glomap)
106111

107112
set_target_properties(glomap_main PROPERTIES OUTPUT_NAME glomap)
@@ -111,6 +116,7 @@ install(TARGETS glomap_main DESTINATION bin)
111116
if(TESTS_ENABLED)
112117
add_executable(glomap_test
113118
controllers/global_mapper_test.cc
119+
controllers/rotation_averager_test.cc
114120
)
115121
target_link_libraries(
116122
glomap_test

glomap/controllers/option_manager.cc

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#include "option_manager.h"
22

33
#include "glomap/controllers/global_mapper.h"
4+
#include "glomap/estimators/gravity_refinement.h"
45

56
#include <boost/filesystem/operations.hpp>
67
#include <boost/property_tree/ini_parser.hpp>
@@ -14,6 +15,7 @@ OptionManager::OptionManager(bool add_project_options) {
1415
image_path = std::make_shared<std::string>();
1516

1617
mapper = std::make_shared<GlobalMapperOptions>();
18+
gravity_refiner = std::make_shared<GravityRefinerOptions>();
1719
Reset();
1820

1921
desc_->add_options()("help,h", "");
@@ -266,6 +268,18 @@ void OptionManager::AddInlierThresholdOptions() {
266268
&mapper->inlier_thresholds.max_rotation_error);
267269
}
268270

271+
void OptionManager::AddGravityRefinerOptions() {
272+
if (added_gravity_refiner_options_) {
273+
return;
274+
}
275+
added_gravity_refiner_options_ = true;
276+
AddAndRegisterDefaultOption("GravityRefiner.max_outlier_ratio",
277+
&gravity_refiner->max_outlier_ratio);
278+
AddAndRegisterDefaultOption("GravityRefiner.max_gravity_error",
279+
&gravity_refiner->max_gravity_error);
280+
AddAndRegisterDefaultOption("GravityRefiner.min_num_neighbors",
281+
&gravity_refiner->min_num_neighbors);
282+
}
269283
void OptionManager::Reset() {
270284
const bool kResetPaths = true;
271285
ResetOptions(kResetPaths);
@@ -294,6 +308,7 @@ void OptionManager::ResetOptions(const bool reset_paths) {
294308
*image_path = "";
295309
}
296310
*mapper = GlobalMapperOptions();
311+
*gravity_refiner = GravityRefinerOptions();
297312
}
298313

299314
void OptionManager::Parse(const int argc, char** argv) {

glomap/controllers/option_manager.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ struct GlobalPositionerOptions;
1818
struct BundleAdjusterOptions;
1919
struct TriangulatorOptions;
2020
struct InlierThresholdOptions;
21+
struct GravityRefinerOptions;
2122

2223
class OptionManager {
2324
public:
@@ -37,6 +38,7 @@ class OptionManager {
3738
void AddBundleAdjusterOptions();
3839
void AddTriangulatorOptions();
3940
void AddInlierThresholdOptions();
41+
void AddGravityRefinerOptions();
4042

4143
template <typename T>
4244
void AddRequiredOption(const std::string& name,
@@ -56,6 +58,7 @@ class OptionManager {
5658
std::shared_ptr<std::string> image_path;
5759

5860
std::shared_ptr<GlobalMapperOptions> mapper;
61+
std::shared_ptr<GravityRefinerOptions> gravity_refiner;
5962

6063
private:
6164
template <typename T>
@@ -88,6 +91,7 @@ class OptionManager {
8891
bool added_bundle_adjustment_options_ = false;
8992
bool added_triangulation_options_ = false;
9093
bool added_inliers_options_ = false;
94+
bool added_gravity_refiner_options_ = false;
9195
};
9296

9397
template <typename T>
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
#include "glomap/controllers/rotation_averager.h"
2+
3+
namespace glomap {
4+
5+
bool SolveRotationAveraging(ViewGraph& view_graph,
6+
std::unordered_map<image_t, Image>& images,
7+
const RotationAveragerOptions& options) {
8+
view_graph.KeepLargestConnectedComponents(images);
9+
10+
bool solve_1dof_system = options.use_gravity && options.use_stratified;
11+
12+
ViewGraph view_graph_grav;
13+
image_pair_t total_pairs = 0;
14+
image_pair_t grav_pairs = 0;
15+
if (solve_1dof_system) {
16+
// Prepare two sets: ones all with gravity, and one does not have gravity.
17+
// Solve them separately first, then solve them in a single system
18+
for (const auto& [pair_id, image_pair] : view_graph.image_pairs) {
19+
if (!image_pair.is_valid) continue;
20+
21+
image_t image_id1 = image_pair.image_id1;
22+
image_t image_id2 = image_pair.image_id2;
23+
24+
Image& image1 = images[image_id1];
25+
Image& image2 = images[image_id2];
26+
27+
if (!image1.is_registered || !image2.is_registered) continue;
28+
29+
total_pairs++;
30+
31+
if (image1.gravity_info.has_gravity && image2.gravity_info.has_gravity) {
32+
view_graph_grav.image_pairs.emplace(
33+
pair_id,
34+
ImagePair(image_id1, image_id2, image_pair.cam2_from_cam1));
35+
grav_pairs++;
36+
}
37+
}
38+
}
39+
40+
// If there is no image pairs with gravity or most image pairs are with
41+
// gravity, then just run the 3dof version
42+
bool status = (grav_pairs == 0) || (grav_pairs > total_pairs * 0.95);
43+
solve_1dof_system = solve_1dof_system && (!status);
44+
45+
if (solve_1dof_system) {
46+
// Run the 1dof optimization
47+
LOG(INFO) << "Solving subset 1DoF rotation averaging problem in the mixed "
48+
"prior system";
49+
int num_img_grv = view_graph_grav.KeepLargestConnectedComponents(images);
50+
RotationEstimator rotation_estimator_grav(options);
51+
if (!rotation_estimator_grav.EstimateRotations(view_graph_grav, images)) {
52+
return false;
53+
}
54+
view_graph.KeepLargestConnectedComponents(images);
55+
}
56+
57+
RotationEstimator rotation_estimator(options);
58+
return rotation_estimator.EstimateRotations(view_graph, images);
59+
}
60+
61+
} // namespace glomap
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
#pragma once
2+
3+
#include "glomap/estimators/global_rotation_averaging.h"
4+
5+
namespace glomap {
6+
7+
struct RotationAveragerOptions : public RotationEstimatorOptions {
8+
bool use_stratified = true;
9+
};
10+
11+
bool SolveRotationAveraging(ViewGraph& view_graph,
12+
std::unordered_map<image_t, Image>& images,
13+
const RotationAveragerOptions& options);
14+
15+
} // namespace glomap

0 commit comments

Comments
 (0)