Skip to content

Commit

Permalink
updated triangulation module
Browse files Browse the repository at this point in the history
  • Loading branch information
franioli committed Mar 30, 2024
1 parent 1567ddc commit 7433e77
Show file tree
Hide file tree
Showing 2 changed files with 109 additions and 27 deletions.
76 changes: 75 additions & 1 deletion src/deep_image_matching/triangulation.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
logger = logging.getLogger("dim")


# Utils functions
def parse_retrieval(path):
retrieval = defaultdict(list)
with open(path, "r") as f:
Expand All @@ -30,6 +29,18 @@ def parse_retrieval(path):


def create_db_from_model(reconstruction: pycolmap.Reconstruction, database_path: Path) -> Dict[str, int]:
"""
Creates a COLMAP database from a PyCOLMAP reconstruction (it deletes an existing database if found at the specified path). The function
populates the database with camera parameters and image information, but does not add 2D and 3D points.
Args:
reconstruction (pycolmap.Reconstruction): The input PyCOLMAP reconstruction.
database_path (Path): Path to the COLMAP database file.
Returns:
Dict[str, int]: A dictionary mapping image names to their corresponding
image IDs in the database.
"""
if database_path.exists():
logger.warning("The database already exists, deleting it.")
database_path.unlink()
Expand All @@ -56,13 +67,40 @@ def create_db_from_model(reconstruction: pycolmap.Reconstruction, database_path:


def get_keypoints(features_h5: Path, name: str) -> np.ndarray:
"""
Loads keypoints from an HDF5 file.
Args:
features_h5 (Path): Path to the HDF5 file containing image features.
name (str): The name of the image whose keypoints are to be retrieved.
Returns:
np.ndarray: An array of keypoints.
Raises:
KeyError: If the specified image name is not found within the HDF5 file.
"""
with h5py.File(str(features_h5), "r", libver="latest") as f:
if name not in f:
raise KeyError(f"Key '{name}' not found in '{features_h5}'")
return f[name]["keypoints"][:]


def get_matches(matches_h5: Path, name0: str, name1) -> np.ndarray:
"""
Retrieves feature matches between two images from an HDF5 file.
Args:
matches_h5 (Path): Path to the HDF5 file storing feature matches.
name0 (str): Name of the first image.
name1 (str): Name of the second image.
Returns:
np.ndarray: An array representing the feature matches.
Raises:
KeyError: If either image name is not found in the HDF5 file.
"""
with h5py.File(str(matches_h5), "r", libver="latest") as f:
if name0 not in f:
if name1 in f:
Expand All @@ -73,6 +111,14 @@ def get_matches(matches_h5: Path, name0: str, name1) -> np.ndarray:


def import_keypoints(features_h5: Path, image_ids: Dict[str, int], database_path: Path) -> None:
"""
Imports keypoints from an HDF5 file into a COLMAP database.
Args:
features_h5 (Path): Path to the HDF5 file containing image features.
image_ids (Dict[str, int]): A dictionary mapping image names to image IDs.
database_path (Path): Path to the COLMAP database file.
"""
with COLMAPDatabase.connect(database_path) as db:
for name, image_id in tqdm(image_ids.items(), desc="Importing keypoints"):
keypoints = get_keypoints(features_h5, name)
Expand All @@ -88,6 +134,19 @@ def import_matches(
pair_file: Path,
add_two_view_geometry: bool = False,
):
"""
Imports feature matches into a COLMAP database.
Reads image pairs from a file and retrieves corresponding matches from
an HDF5 file, adding them to the specified database.
Args:
matches_h5 (Path): Path to the HDF5 file containing feature matches.
image_ids (Dict[str, int]): A dictionary mapping image names to their corresponding image IDs.
database_path (Path): Path to the COLMAP database file.
pair_file (Path): Path to a file containing image pairs (one pair per line).
add_two_view_geometry (bool): If True, calculates and adds two-view geometry to the database. Defaults to False.
"""
pairs = get_pairs_from_file(pair_file)
with COLMAPDatabase.connect(database_path) as db:
for name0, name1 in tqdm(pairs, desc="Importing matches"):
Expand All @@ -108,6 +167,21 @@ def import_verifed_matches(
pairs_path: Path,
max_error: float = 4.0,
):
"""
Imports geometrically verified matches into a COLMAP database.
Performs geometric verification of matches using epipolar constraints.
Only matches that pass the verification are added to the database.
Args:
image_ids (Dict[str, int]): A dictionary mapping image names to their corresponding image IDs.
reference (pycolmap.Reconstruction): The reference PyCOLMAP reconstruction.
database_path (Path): Path to the COLMAP database file.
features_path (Path): Path to the HDF5 file containing image features.
matches_path (Path): Path to the HDF5 file containing feature matches.
pairs_path (Path): Path to a file specifying image pairs for retrieval.
max_error (float): Maximum allowable epipolar error (in pixels) for a match to be considered valid. Defaults to 4.0.
"""
logger.info("Performing geometric verification of the matches...")

pairs = parse_retrieval(pairs_path)
Expand Down
60 changes: 34 additions & 26 deletions src/deep_image_matching/utils/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,44 +62,52 @@ def get_pairs_from_file(pair_file: Path) -> list:


def to_homogeneous(p):
"""
Converts 2D points to homogeneous coordinates.
Args:
p (np.ndarray): A numpy array of shape (N, 2) representing 2D points.
Returns:
np.ndarray: Array of shape (N, 3) where the last column is appended
with ones for homogeneous representation.
"""
return np.pad(p, ((0, 0),) * (p.ndim - 1) + ((0, 1),), constant_values=1)


def vector_to_cross_product_matrix(v):
"""
Converts a 3D vector into its corresponding skew-symmetric cross-product matrix.
This matrix is useful for representing cross-product operations in vector-matrix calculations.
Args:
v (np.ndarray): A 3-dimensional NumPy array representing the input vector.
Returns:
np.ndarray: A 3x3 NumPy array representing the skew-symmetric cross-product matrix.
"""
return np.array([[0, -v[2], v[1]], [v[2], 0, -v[0]], [-v[1], v[0], 0]])


def compute_epipolar_errors(j_from_i: pycolmap.Rigid3d, p2d_i, p2d_j):
"""
Computes epipolar errors for corresponding 2D points between two images.
Args:
j_from_i (pycolmap.Rigid3d): The transformation from image i to image j.
p2d_i (np.ndarray): 2D points in image i (shape: (N, 2)).
p2d_j (np.ndarray): Corresponding 2D points in image j (shape: (N, 2)).
Returns:
tuple: A tuple containing two numpy arrays:
* errors_i: Epipolar errors for points in image i.
* errors_j: Epipolar errors for points in image j.
"""
j_E_i = j_from_i.essential_matrix()
l2d_j = to_homogeneous(p2d_i) @ j_E_i.T
l2d_i = to_homogeneous(p2d_j) @ j_E_i
dist = np.abs(np.sum(to_homogeneous(p2d_i) * l2d_i, axis=1))
errors_i = dist / np.linalg.norm(l2d_i[:, :2], axis=1)
errors_j = dist / np.linalg.norm(l2d_j[:, :2], axis=1)
return errors_i, errors_j


def create_db_from_model(reconstruction: pycolmap.Reconstruction, database_path: Path) -> Dict[str, int]:
if database_path.exists():
logger.warning("The database already exists, deleting it.")
database_path.unlink()

with COLMAPDatabase.connect(database_path) as db:
db.create_tables()

for i, camera in reconstruction.cameras.items():
db.add_camera(
camera.model.value,
camera.width,
camera.height,
camera.params,
camera_id=i,
prior_focal_length=True,
)

for i, image in reconstruction.images.items():
db.add_image(image.name, image.camera_id, image_id=i)

db.commit()

return {image.name: i for i, image in reconstruction.images.items()}

0 comments on commit 7433e77

Please sign in to comment.