|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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() |
|
|
|