| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| import os, argparse, h5py, warnings |
| import numpy as np |
| from tqdm import tqdm |
| from PIL import Image, ExifTags |
|
|
| from database import COLMAPDatabase, image_ids_to_pair_id |
|
|
| def get_focal(image_path, err_on_default=False): |
| image = Image.open(image_path) |
| max_size = max(image.size) |
|
|
| exif = image.getexif() |
| focal = None |
| if exif is not None: |
| focal_35mm = None |
| |
| for tag, value in exif.items(): |
| focal_35mm = None |
| if ExifTags.TAGS.get(tag, None) == 'FocalLengthIn35mmFilm': |
| focal_35mm = float(value) |
| break |
|
|
| if focal_35mm is not None: |
| focal = focal_35mm / 35. * max_size |
| |
| if focal is None: |
| if err_on_default: |
| raise RuntimeError("Failed to find focal length") |
|
|
| |
| FOCAL_PRIOR = 1.2 |
| focal = FOCAL_PRIOR * max_size |
|
|
| return focal |
|
|
| def create_camera(db, image_path, camera_model): |
| image = Image.open(image_path) |
| width, height = image.size |
|
|
| focal = get_focal(image_path) |
|
|
| if camera_model == 'simple-pinhole': |
| model = 0 |
| param_arr = np.array([focal, width / 2, height / 2]) |
| if camera_model == 'pinhole': |
| model = 1 |
| param_arr = np.array([focal, focal, width / 2, height / 2]) |
| elif camera_model == 'simple-radial': |
| model = 2 |
| param_arr = np.array([focal, width / 2, height / 2, 0.1]) |
| elif camera_model == 'opencv': |
| model = 4 |
| param_arr = np.array([focal, focal, width / 2, height / 2, 0., 0., 0., 0.]) |
| |
| return db.add_camera(model, width, height, param_arr) |
|
|
|
|
|
|
| def add_keypoints(db, h5_path, image_path, img_ext, camera_model, single_camera=True): |
| import h5py |
| import numpy as np |
| import os |
| import glob |
| from tqdm import tqdm |
| from PIL import Image |
| |
| keypoint_f = h5py.File(os.path.join(h5_path, 'keypoints.h5'), 'r') |
| camera_id = None |
| fname_to_id = {} |
| |
| |
| all_images = {} |
| for ext in ['.jpg', '.jpeg', '.png', '.JPG', '.JPEG', '.PNG']: |
| for img_file in glob.glob(os.path.join(image_path, f'*{ext}')): |
| base_name = os.path.splitext(os.path.basename(img_file))[0] |
| all_images[base_name] = img_file |
| |
| |
| camera_model_ids = { |
| 'SIMPLE_PINHOLE': 0, |
| 'PINHOLE': 1, |
| 'SIMPLE_RADIAL': 2, |
| 'RADIAL': 3, |
| 'OPENCV': 4, |
| } |
| |
| if camera_model not in camera_model_ids: |
| raise ValueError(f"Unknown camera model: {camera_model}") |
| |
| model_id = camera_model_ids[camera_model] |
| |
| for filename in tqdm(list(keypoint_f.keys())): |
| keypoints = keypoint_f[filename][()] |
| |
| if filename not in all_images: |
| raise IOError(f'Image not found for key: {filename}') |
| |
| path = all_images[filename] |
| fname_with_ext = os.path.basename(path) |
| |
| |
| if camera_id is None: |
| img = Image.open(path) |
| width, height = img.size |
| |
| |
| focal_length = float(max(width, height)) |
| cx = float(width / 2.0) |
| cy = float(height / 2.0) |
| |
| |
| if camera_model == 'PINHOLE': |
| |
| params = np.array([focal_length, focal_length, cx, cy], dtype=np.float64) |
| print(f"Camera created: PINHOLE, f={focal_length}, cx={cx}, cy={cy}") |
| elif camera_model == 'SIMPLE_PINHOLE': |
| |
| params = np.array([focal_length, cx, cy], dtype=np.float64) |
| print(f"Camera created: SIMPLE_PINHOLE, f={focal_length}, cx={cx}, cy={cy}") |
| elif camera_model == 'SIMPLE_RADIAL': |
| |
| k = 0.0 |
| params = np.array([focal_length, cx, cy, k], dtype=np.float64) |
| print(f"Camera created: SIMPLE_RADIAL, f={focal_length}, cx={cx}, cy={cy}, k={k}") |
| else: |
| raise ValueError(f"Unsupported camera model: {camera_model}") |
| |
| camera_id = db.add_camera( |
| model=model_id, |
| width=width, |
| height=height, |
| params=params |
| ) |
| |
| image_id = db.add_image(fname_with_ext, camera_id) |
| fname_to_id[filename] = image_id |
| db.add_keypoints(image_id, keypoints) |
| |
| return fname_to_id |
|
|
|
|
|
|
| def add_matches(db, h5_path, fname_to_id): |
| """ |
| Directly add matches from matches.h5 (compatible with COLMAP format). |
| """ |
| import h5py |
| import numpy as np |
| import os |
| from tqdm import tqdm |
|
|
| match_file = h5py.File(os.path.join(h5_path, 'matches.h5'), 'r') |
| |
| added_pairs = set() |
| n_keys = len(match_file.keys()) |
| |
| with tqdm(total=n_keys, desc="Importing matches to database") as pbar: |
| for pair_key in match_file.keys(): |
| |
| parts = pair_key.split('_') |
| mid = len(parts) // 2 |
| key_1 = '_'.join(parts[:mid]) |
| key_2 = '_'.join(parts[mid:]) |
| |
| if key_1 not in fname_to_id or key_2 not in fname_to_id: |
| print(f"Warning: Filename lookup failed for {key_1} or {key_2}") |
| pbar.update(1) |
| continue |
| |
| id_1 = fname_to_id[key_1] |
| id_2 = fname_to_id[key_2] |
| pair_id = image_ids_to_pair_id(id_1, id_2) |
| |
| |
| if pair_id in added_pairs: |
| pbar.update(1) |
| continue |
| |
| matches = match_file[pair_key][()] |
| |
| |
| if len(matches) == 0: |
| pbar.update(1) |
| continue |
| |
| |
| matches = matches.astype(np.uint32) |
| |
| |
| db.add_matches(id_1, id_2, matches) |
| |
| |
| |
| db.add_two_view_geometry(id_1, id_2, matches) |
| |
| added_pairs.add(pair_id) |
| pbar.update(1) |
| |
| match_file.close() |
| |
|
|
|
|
| def import_into_colmap(img_dir, |
| feature_dir ='.featureout', |
| database_path = 'colmap.db', |
| img_ext='.jpg'): |
| db = COLMAPDatabase.connect(database_path) |
| db.create_tables() |
| single_camera = False |
| fname_to_id = add_keypoints(db, feature_dir, img_dir, img_ext, 'simple-radial', single_camera) |
| add_matches( |
| db, |
| feature_dir, |
| fname_to_id, |
| ) |
|
|
| db.commit() |
| return |
|
|
| if __name__ == '__main__': |
| parser = argparse.ArgumentParser() |
| parser.add_argument('h5_path', help=('Path to the directory with ' |
| 'keypoints.h5 and matches.h5')) |
| parser.add_argument('image_path', help='Path to source images') |
| parser.add_argument( |
| '--image-extension', default='.jpg', type=str, |
| help='Extension of files in image_path' |
| ) |
| parser.add_argument('--database-path', default='database.db', |
| help='Location where the COLMAP .db file will be created' |
| ) |
| parser.add_argument( |
| '--single-camera', action='store_true', |
| help=('Consider all photos to be made with a single camera (COLMAP ' |
| 'will reduce the number of degrees of freedom'), |
| ) |
| parser.add_argument( |
| '--camera-model', |
| choices=['simple-pinhole', 'pinhole', 'simple-radial', 'opencv'], |
| default='simple-radial', |
| help=('Camera model to use in COLMAP. ' |
| 'See https://github.com/colmap/colmap/blob/master/src/base/camera_models.h' |
| ' for explanations') |
| ) |
|
|
| args = parser.parse_args() |
|
|
| if args.camera_model == 'opencv' and not args.single_camera: |
| raise RuntimeError("Cannot use --camera-model=opencv camera without " |
| "--single-camera (the COLMAP optimisation will " |
| "likely fail to converge)") |
|
|
| if os.path.exists(args.database_path): |
| raise RuntimeError("database path already exists - will not modify it.") |
|
|
| db = COLMAPDatabase.connect(args.database_path) |
| db.create_tables() |
|
|
| fname_to_id = add_keypoints(db, args.h5_path, args.image_path, args.image_extension, |
| args.camera_model, args.single_camera) |
| add_matches( |
| db, |
| args.h5_path, |
| fname_to_id, |
| ) |
|
|
| db.commit() |
|
|