-
Notifications
You must be signed in to change notification settings - Fork 51
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add tanks and temples dataset loader #653
Changes from 139 commits
392f8b1
0977efb
4d65e30
5cfb8fc
a4b8775
77b9178
7038d18
117fdc6
324fc98
432daa9
581c6cb
d274689
346a7fe
e8efe4d
40418ec
1aadc03
888246d
7b80409
6efbeb0
af1d26e
8fe66bf
be50f6e
888f878
8d3b339
b6fdffb
b3a652c
1e5b58e
52b1140
e0186d0
1703165
cb31830
00c5d4f
6687f8a
5824974
c70e937
750e147
d4802dc
cbb68c5
9062f12
52ee748
968d16a
1ba2dc2
6faf44a
c7864be
98c8d0c
39b3cf8
5554638
6a331d4
25c0f68
2f3e9e2
574ac81
38fbe1d
f81667e
897923a
ef74582
f59d10c
bf4cbbf
eadd6ee
dd3d3c9
97304d3
68a8fa1
96de130
51ed829
cfb46fe
f366590
b3d0d9e
46dd6f9
9b26318
6fabe5d
8ce2a4c
78e4e1d
62c99d2
e658341
279623e
420cfb4
8aa9f9f
d6ebb47
ea235cd
afd83e2
8fd61c1
9b0be98
59bf16b
44f02f7
6b5dc0e
92fc731
614f682
aa9618c
793e51c
09bc83a
7254c29
39504fe
ee861ec
9d7596d
c3fab34
ed69f72
8d6cf88
640ad75
6021108
b7d9dc3
5778d1f
d0c9e67
5529914
a60732d
38971bd
f781d32
eb2dc0c
29cc2c3
cb801a3
ee6ba95
0cbee24
8f5a78b
aa75137
e005be2
634f220
75d92a6
511c361
68f75aa
78d7282
0a74180
7fb8630
6d3ca41
ea532a9
2e4eda5
34a0358
f3fd910
480a9f9
0043d37
a429482
706ef7e
783344b
4f9005c
c084bd1
845f35c
e032f49
0237101
303b695
e564993
c79c21c
306893b
5c9e26d
c2a3ee6
3ca4559
6e6b691
032606a
5448dee
a8b6532
bc16d0d
7195956
6e97ff6
4abf136
395ff3e
f3f8926
c3a9b57
4afbfa5
4850f1f
6ae33ae
c0a14a7
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,102 @@ | ||
# Synthetic front-end configuration specifically for the Tanks & Temples dataset. | ||
|
||
SceneOptimizer: | ||
_target_: gtsfm.scene_optimizer.SceneOptimizer | ||
save_gtsfm_data: True | ||
save_two_view_correspondences_viz: False | ||
save_3d_viz: True | ||
pose_angular_error_thresh: 5 # degrees | ||
|
||
image_pairs_generator: | ||
_target_: gtsfm.retriever.image_pairs_generator.ImagePairsGenerator | ||
global_descriptor: | ||
_target_: gtsfm.frontend.cacher.global_descriptor_cacher.GlobalDescriptorCacher | ||
global_descriptor_obj: | ||
_target_: gtsfm.frontend.global_descriptor.netvlad_global_descriptor.NetVLADGlobalDescriptor | ||
retriever: | ||
_target_: gtsfm.retriever.joint_netvlad_sequential_retriever.JointNetVLADSequentialRetriever | ||
num_matched: 2 | ||
min_score: 0.2 | ||
max_frame_lookahead: 3 | ||
|
||
# retriever: | ||
# _target_: gtsfm.retriever.sequential_retriever.SequentialRetriever | ||
# max_frame_lookahead: 4 | ||
|
||
# retriever: | ||
# _target_: gtsfm.retriever.netvlad_retriever.NetVLADRetriever | ||
# num_matched: 50 | ||
# min_score: 0.3 | ||
|
||
correspondence_generator: | ||
_target_: gtsfm.frontend.correspondence_generator.synthetic_correspondence_generator.SyntheticCorrespondenceGenerator | ||
#dataset_root: /Users/johnlambert/Downloads/Tanks_and_Temples_Barn_410 | ||
#dataset_root: /usr/local/gtsfm-data/Tanks_and_Temples_Barn_410 | ||
dataset_root: /home/runner/work/gtsfm/gtsfm/Tanks_and_Temples_Barn_410 # Path for CI. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd prefer this being loaded from the dataset root passed from the terminal, but its not an easy fix. So we should be fine with it. |
||
scene_name: Barn | ||
|
||
two_view_estimator: | ||
_target_: gtsfm.two_view_estimator.TwoViewEstimator | ||
bundle_adjust_2view: False | ||
eval_threshold_px: 4 # in px | ||
ba_reproj_error_thresholds: [0.5] | ||
bundle_adjust_2view_maxiters: 100 | ||
|
||
verifier: | ||
_target_: gtsfm.frontend.verifier.loransac.LoRansac | ||
use_intrinsics_in_verification: True | ||
estimation_threshold_px: 0.5 # for H/E/F estimators | ||
|
||
triangulation_options: | ||
_target_: gtsfm.data_association.point3d_initializer.TriangulationOptions | ||
mode: | ||
_target_: gtsfm.data_association.point3d_initializer.TriangulationSamplingMode | ||
value: NO_RANSAC | ||
|
||
inlier_support_processor: | ||
_target_: gtsfm.two_view_estimator.InlierSupportProcessor | ||
min_num_inliers_est_model: 15 | ||
min_inlier_ratio_est_model: 0.1 | ||
|
||
multiview_optimizer: | ||
_target_: gtsfm.multi_view_optimizer.MultiViewOptimizer | ||
|
||
# comment out to not run | ||
view_graph_estimator: | ||
_target_: gtsfm.view_graph_estimator.cycle_consistent_rotation_estimator.CycleConsistentRotationViewGraphEstimator | ||
edge_error_aggregation_criterion: MEDIAN_EDGE_ERROR | ||
|
||
rot_avg_module: | ||
_target_: gtsfm.averaging.rotation.shonan.ShonanRotationAveraging | ||
# Use a very low value. | ||
two_view_rotation_sigma: 0.1 | ||
|
||
trans_avg_module: | ||
_target_: gtsfm.averaging.translation.averaging_1dsfm.TranslationAveraging1DSFM | ||
robust_measurement_noise: True | ||
projection_sampling_method: SAMPLE_INPUT_MEASUREMENTS | ||
|
||
data_association_module: | ||
_target_: gtsfm.data_association.data_assoc.DataAssociation | ||
min_track_len: 2 | ||
triangulation_options: | ||
_target_: gtsfm.data_association.point3d_initializer.TriangulationOptions | ||
reproj_error_threshold: 10 | ||
mode: | ||
_target_: gtsfm.data_association.point3d_initializer.TriangulationSamplingMode | ||
value: RANSAC_SAMPLE_UNIFORM | ||
max_num_hypotheses: 100 | ||
save_track_patches_viz: False | ||
|
||
bundle_adjustment_module: | ||
_target_: gtsfm.bundle.bundle_adjustment.BundleAdjustmentOptimizer | ||
reproj_error_thresholds: [10, 5, 3] # for (multistage) post-optimization filtering | ||
robust_measurement_noise: True | ||
shared_calib: False | ||
cam_pose3_prior_noise_sigma: 0.01 | ||
calibration_prior_noise_sigma: 1e-5 | ||
measurement_noise_sigma: 1.0 | ||
|
||
# # comment out to not run | ||
# dense_multiview_optimizer: | ||
# _target_: gtsfm.densify.mvs_patchmatchnet.MVSPatchmatchNet |
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I am wondering if we can just generate the matches apriori and use something like a cache to just load the matches from the disk. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I was also thinking this would be the best approach, as this is more for debugging and will likely only be used by us. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I see -- if we wish to use the Tanks and Temples loader to get GT poses to measure pose error, what's the difference between computing matches offline vs. online? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Because the synthetic matching frontend is only used for debugging, and I'm not sure we should merge it into the main repo There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @ayushbaid @travisdriver wanted to revisit this -- I would prefer to keep as-is and make this code self-contained so that we only need to run one command, instead of having to make two new scripts (one to generate all the correspondences beforehand, and then making a new one to accept saved correspondences) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,119 @@ | ||
"""Correspondence generator that creates synthetic keypoint correspondences using a 3d mesh. | ||
|
||
Authors: John Lambert | ||
""" | ||
import tempfile | ||
from typing import Dict, List, Tuple | ||
|
||
from dask.distributed import Client, Future | ||
import numpy as np | ||
import open3d | ||
|
||
from gtsfm.common.keypoints import Keypoints | ||
from gtsfm.common.types import CAMERA_TYPE | ||
from gtsfm.frontend.correspondence_generator.correspondence_generator_base import CorrespondenceGeneratorBase | ||
from gtsfm.frontend.correspondence_generator.keypoint_aggregator.keypoint_aggregator_base import KeypointAggregatorBase | ||
from gtsfm.frontend.correspondence_generator.keypoint_aggregator.keypoint_aggregator_dedup import ( | ||
KeypointAggregatorDedup, | ||
) | ||
from gtsfm.frontend.correspondence_generator.keypoint_aggregator.keypoint_aggregator_unique import ( | ||
KeypointAggregatorUnique, | ||
) | ||
from gtsfm.loader.loader_base import LoaderBase | ||
from gtsfm.loader.tanks_and_temples_loader import TanksAndTemplesLoader | ||
|
||
|
||
class SyntheticCorrespondenceGenerator(CorrespondenceGeneratorBase): | ||
"""Pair-wise synthetic keypoint correspondence generator.""" | ||
|
||
def __init__(self, dataset_root: str, scene_name: str, deduplicate: bool = True) -> None: | ||
""" | ||
Args: | ||
dataset_root: Path to where Tanks & Temples dataset is stored. | ||
scene_name: Name of scene from Tanks & Temples dataset. | ||
deduplicate: Whether to de-duplicate with a single image the detections received from each image pair. | ||
""" | ||
self._dataset_root = dataset_root | ||
self._scene_name = scene_name | ||
self._aggregator: KeypointAggregatorBase = ( | ||
KeypointAggregatorDedup() if deduplicate else KeypointAggregatorUnique() | ||
) | ||
|
||
def generate_correspondences( | ||
self, | ||
client: Client, | ||
images: List[Future], | ||
image_pairs: List[Tuple[int, int]], | ||
num_sampled_3d_points: int = 500, | ||
) -> Tuple[List[Keypoints], Dict[Tuple[int, int], np.ndarray]]: | ||
"""Apply the correspondence generator to generate putative correspondences. | ||
|
||
Args: | ||
client: Dask client, used to execute the front-end as futures. | ||
images: List of all images, as futures. | ||
image_pairs: Indices of the pairs of images to estimate two-view pose and correspondences. | ||
|
||
Returns: | ||
List of keypoints, with one entry for each input image. | ||
Putative correspondences as indices of keypoints (N,2), for pairs of images (i1,i2). | ||
""" | ||
dataset_root = self._dataset_root | ||
scene_name = self._scene_name | ||
|
||
img_dir = f"{dataset_root}/{scene_name}" | ||
poses_fpath = f"{dataset_root}/{scene_name}_COLMAP_SfM.log" | ||
lidar_ply_fpath = f"{dataset_root}/{scene_name}.ply" | ||
colmap_ply_fpath = f"{dataset_root}/{scene_name}_COLMAP.ply" | ||
ply_alignment_fpath = f"{dataset_root}/{scene_name}_trans.txt" | ||
bounding_polyhedron_json_fpath = f"{dataset_root}/{scene_name}.json" | ||
loader = TanksAndTemplesLoader( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this will have to be pinned to the machine with the input worker, right? Hence I was suggesting we create the correspondences on disk beforehand. But fine with it given the time constraint. |
||
img_dir=img_dir, | ||
poses_fpath=poses_fpath, | ||
lidar_ply_fpath=lidar_ply_fpath, | ||
ply_alignment_fpath=ply_alignment_fpath, | ||
bounding_polyhedron_json_fpath=bounding_polyhedron_json_fpath, | ||
colmap_ply_fpath=colmap_ply_fpath, | ||
) | ||
|
||
mesh = loader.reconstruct_mesh() | ||
|
||
# Sample random 3d points. This sampling must occur only once, to avoid clusters from repeated sampling. | ||
pcd = mesh.sample_points_uniformly(number_of_points=num_sampled_3d_points) | ||
pcd = mesh.sample_points_poisson_disk(number_of_points=num_sampled_3d_points, pcl=pcd) | ||
sampled_points = np.asarray(pcd.points) | ||
|
||
# TODO(jolambert): File Open3d bug to add pickle support for TriangleMesh. | ||
open3d_mesh_path = tempfile.NamedTemporaryFile(suffix=".obj").name | ||
open3d.io.write_triangle_mesh(filename=open3d_mesh_path, mesh=mesh) | ||
|
||
loader_future = client.scatter(loader, broadcast=False) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We could just avoid futures for this file I guess? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hmm wouldn't we lose all parallelization benefits then? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, but since we are just loading from disk, we would not lose much in terms of time I think |
||
|
||
def apply_synthetic_corr_generator( | ||
loader_: LoaderBase, | ||
camera_i1: CAMERA_TYPE, | ||
camera_i2: CAMERA_TYPE, | ||
open3d_mesh_fpath: str, | ||
points: np.ndarray, | ||
) -> Tuple[Keypoints, Keypoints]: | ||
return loader_.generate_synthetic_correspondences_for_image_pair( | ||
camera_i1, camera_i2, open3d_mesh_fpath, points | ||
) | ||
|
||
pairwise_correspondence_futures = { | ||
(i1, i2): client.submit( | ||
apply_synthetic_corr_generator, | ||
loader_future, | ||
loader.get_camera(index=i1), | ||
loader.get_camera(index=i2), | ||
open3d_mesh_path, | ||
sampled_points, | ||
) | ||
for i1, i2 in image_pairs | ||
} | ||
|
||
pairwise_correspondences: Dict[Tuple[int, int], Tuple[Keypoints, Keypoints]] = client.gather( | ||
pairwise_correspondence_futures | ||
) | ||
|
||
keypoints_list, putative_corr_idxs_dict = self._aggregator.aggregate(keypoints_dict=pairwise_correspondences) | ||
return keypoints_list, putative_corr_idxs_dict |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Removed, thanks.