Spaces:
Running
Running
import cv2 | |
import logging | |
import numpy as np | |
from hloc.utils.read_write_model import ( | |
read_cameras_binary, | |
read_images_binary, | |
read_model, | |
write_model, | |
qvec2rotmat, | |
read_images_text, | |
read_cameras_text, | |
) | |
logger = logging.getLogger(__name__) | |
def scale_sfm_images(full_model, scaled_model, image_dir): | |
"""Duplicate the provided model and scale the camera intrinsics so that | |
they match the original image resolution - makes everything easier. | |
""" | |
logger.info("Scaling the COLMAP model to the original image size.") | |
scaled_model.mkdir(exist_ok=True) | |
cameras, images, points3D = read_model(full_model) | |
scaled_cameras = {} | |
for id_, image in images.items(): | |
name = image.name | |
img = cv2.imread(str(image_dir / name)) | |
assert img is not None, image_dir / name | |
h, w = img.shape[:2] | |
cam_id = image.camera_id | |
if cam_id in scaled_cameras: | |
assert scaled_cameras[cam_id].width == w | |
assert scaled_cameras[cam_id].height == h | |
continue | |
camera = cameras[cam_id] | |
assert camera.model == "SIMPLE_RADIAL" | |
sx = w / camera.width | |
sy = h / camera.height | |
assert sx == sy, (sx, sy) | |
scaled_cameras[cam_id] = camera._replace( | |
width=w, height=h, params=camera.params * np.array([sx, sx, sy, 1.0]) | |
) | |
write_model(scaled_cameras, images, points3D, scaled_model) | |
def create_query_list_with_intrinsics( | |
model, out, list_file=None, ext=".bin", image_dir=None | |
): | |
"""Create a list of query images with intrinsics from the colmap model.""" | |
if ext == ".bin": | |
images = read_images_binary(model / "images.bin") | |
cameras = read_cameras_binary(model / "cameras.bin") | |
else: | |
images = read_images_text(model / "images.txt") | |
cameras = read_cameras_text(model / "cameras.txt") | |
name2id = {image.name: i for i, image in images.items()} | |
if list_file is None: | |
names = list(name2id) | |
else: | |
with open(list_file, "r") as f: | |
names = f.read().rstrip().split("\n") | |
data = [] | |
for name in names: | |
image = images[name2id[name]] | |
camera = cameras[image.camera_id] | |
w, h, params = camera.width, camera.height, camera.params | |
if image_dir is not None: | |
# Check the original image size and rescale the camera intrinsics | |
img = cv2.imread(str(image_dir / name)) | |
assert img is not None, image_dir / name | |
h_orig, w_orig = img.shape[:2] | |
assert camera.model == "SIMPLE_RADIAL" | |
sx = w_orig / w | |
sy = h_orig / h | |
assert sx == sy, (sx, sy) | |
w, h = w_orig, h_orig | |
params = params * np.array([sx, sx, sy, 1.0]) | |
p = [name, camera.model, w, h] + params.tolist() | |
data.append(" ".join(map(str, p))) | |
with open(out, "w") as f: | |
f.write("\n".join(data)) | |
def evaluate(model, results, list_file=None, ext=".bin", only_localized=False): | |
predictions = {} | |
with open(results, "r") as f: | |
for data in f.read().rstrip().split("\n"): | |
data = data.split() | |
name = data[0] | |
q, t = np.split(np.array(data[1:], float), [4]) | |
predictions[name] = (qvec2rotmat(q), t) | |
if ext == ".bin": | |
images = read_images_binary(model / "images.bin") | |
else: | |
images = read_images_text(model / "images.txt") | |
name2id = {image.name: i for i, image in images.items()} | |
if list_file is None: | |
test_names = list(name2id) | |
else: | |
with open(list_file, "r") as f: | |
test_names = f.read().rstrip().split("\n") | |
errors_t = [] | |
errors_R = [] | |
for name in test_names: | |
if name not in predictions: | |
if only_localized: | |
continue | |
e_t = np.inf | |
e_R = 180.0 | |
else: | |
image = images[name2id[name]] | |
R_gt, t_gt = image.qvec2rotmat(), image.tvec | |
R, t = predictions[name] | |
e_t = np.linalg.norm(-R_gt.T @ t_gt + R.T @ t, axis=0) | |
cos = np.clip((np.trace(np.dot(R_gt.T, R)) - 1) / 2, -1.0, 1.0) | |
e_R = np.rad2deg(np.abs(np.arccos(cos))) | |
errors_t.append(e_t) | |
errors_R.append(e_R) | |
errors_t = np.array(errors_t) | |
errors_R = np.array(errors_R) | |
med_t = np.median(errors_t) | |
med_R = np.median(errors_R) | |
out = f"Results for file {results.name}:" | |
out += f"\nMedian errors: {med_t:.3f}m, {med_R:.3f}deg" | |
out += "\nPercentage of test images localized within:" | |
threshs_t = [0.01, 0.02, 0.03, 0.05, 0.25, 0.5, 5.0] | |
threshs_R = [1.0, 2.0, 3.0, 5.0, 2.0, 5.0, 10.0] | |
for th_t, th_R in zip(threshs_t, threshs_R): | |
ratio = np.mean((errors_t < th_t) & (errors_R < th_R)) | |
out += f"\n\t{th_t*100:.0f}cm, {th_R:.0f}deg : {ratio*100:.2f}%" | |
logger.info(out) | |