image-matching-webui / hloc /localize_inloc.py
Vincentqyw
update: sync with hloc
4c88343
raw
history blame
5.58 kB
import argparse
import pickle
from pathlib import Path
import cv2
import h5py
import numpy as np
import pycolmap
import torch
from scipy.io import loadmat
from tqdm import tqdm
from . import logger
from .utils.parsers import names_to_pair, parse_retrieval
def interpolate_scan(scan, kp):
h, w, c = scan.shape
kp = kp / np.array([[w - 1, h - 1]]) * 2 - 1
assert np.all(kp > -1) and np.all(kp < 1)
scan = torch.from_numpy(scan).permute(2, 0, 1)[None]
kp = torch.from_numpy(kp)[None, None]
grid_sample = torch.nn.functional.grid_sample
# To maximize the number of points that have depth:
# do bilinear interpolation first and then nearest for the remaining points
interp_lin = grid_sample(scan, kp, align_corners=True, mode="bilinear")[0, :, 0]
interp_nn = torch.nn.functional.grid_sample(
scan, kp, align_corners=True, mode="nearest"
)[0, :, 0]
interp = torch.where(torch.isnan(interp_lin), interp_nn, interp_lin)
valid = ~torch.any(torch.isnan(interp), 0)
kp3d = interp.T.numpy()
valid = valid.numpy()
return kp3d, valid
def get_scan_pose(dataset_dir, rpath):
split_image_rpath = rpath.split("/")
floor_name = split_image_rpath[-3]
scan_id = split_image_rpath[-2]
image_name = split_image_rpath[-1]
building_name = image_name[:3]
path = Path(
dataset_dir,
"database/alignments",
floor_name,
f"transformations/{building_name}_trans_{scan_id}.txt",
)
with open(path) as f:
raw_lines = f.readlines()
P_after_GICP = np.array(
[
np.fromstring(raw_lines[7], sep=" "),
np.fromstring(raw_lines[8], sep=" "),
np.fromstring(raw_lines[9], sep=" "),
np.fromstring(raw_lines[10], sep=" "),
]
)
return P_after_GICP
def pose_from_cluster(dataset_dir, q, retrieved, feature_file, match_file, skip=None):
height, width = cv2.imread(str(dataset_dir / q)).shape[:2]
cx = 0.5 * width
cy = 0.5 * height
focal_length = 4032.0 * 28.0 / 36.0
all_mkpq = []
all_mkpr = []
all_mkp3d = []
all_indices = []
kpq = feature_file[q]["keypoints"].__array__()
num_matches = 0
for i, r in enumerate(retrieved):
kpr = feature_file[r]["keypoints"].__array__()
pair = names_to_pair(q, r)
m = match_file[pair]["matches0"].__array__()
v = m > -1
if skip and (np.count_nonzero(v) < skip):
continue
mkpq, mkpr = kpq[v], kpr[m[v]]
num_matches += len(mkpq)
scan_r = loadmat(Path(dataset_dir, r + ".mat"))["XYZcut"]
mkp3d, valid = interpolate_scan(scan_r, mkpr)
Tr = get_scan_pose(dataset_dir, r)
mkp3d = (Tr[:3, :3] @ mkp3d.T + Tr[:3, -1:]).T
all_mkpq.append(mkpq[valid])
all_mkpr.append(mkpr[valid])
all_mkp3d.append(mkp3d[valid])
all_indices.append(np.full(np.count_nonzero(valid), i))
all_mkpq = np.concatenate(all_mkpq, 0)
all_mkpr = np.concatenate(all_mkpr, 0)
all_mkp3d = np.concatenate(all_mkp3d, 0)
all_indices = np.concatenate(all_indices, 0)
cfg = {
"model": "SIMPLE_PINHOLE",
"width": width,
"height": height,
"params": [focal_length, cx, cy],
}
ret = pycolmap.absolute_pose_estimation(all_mkpq, all_mkp3d, cfg, 48.00)
ret["cfg"] = cfg
return ret, all_mkpq, all_mkpr, all_mkp3d, all_indices, num_matches
def main(dataset_dir, retrieval, features, matches, results, skip_matches=None):
assert retrieval.exists(), retrieval
assert features.exists(), features
assert matches.exists(), matches
retrieval_dict = parse_retrieval(retrieval)
queries = list(retrieval_dict.keys())
feature_file = h5py.File(features, "r", libver="latest")
match_file = h5py.File(matches, "r", libver="latest")
poses = {}
logs = {
"features": features,
"matches": matches,
"retrieval": retrieval,
"loc": {},
}
logger.info("Starting localization...")
for q in tqdm(queries):
db = retrieval_dict[q]
ret, mkpq, mkpr, mkp3d, indices, num_matches = pose_from_cluster(
dataset_dir, q, db, feature_file, match_file, skip_matches
)
poses[q] = (ret["qvec"], ret["tvec"])
logs["loc"][q] = {
"db": db,
"PnP_ret": ret,
"keypoints_query": mkpq,
"keypoints_db": mkpr,
"3d_points": mkp3d,
"indices_db": indices,
"num_matches": num_matches,
}
logger.info(f"Writing poses to {results}...")
with open(results, "w") as f:
for q in queries:
qvec, tvec = poses[q]
qvec = " ".join(map(str, qvec))
tvec = " ".join(map(str, tvec))
name = q.split("/")[-1]
f.write(f"{name} {qvec} {tvec}\n")
logs_path = f"{results}_logs.pkl"
logger.info(f"Writing logs to {logs_path}...")
with open(logs_path, "wb") as f:
pickle.dump(logs, f)
logger.info("Done!")
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument("--dataset_dir", type=Path, required=True)
parser.add_argument("--retrieval", type=Path, required=True)
parser.add_argument("--features", type=Path, required=True)
parser.add_argument("--matches", type=Path, required=True)
parser.add_argument("--results", type=Path, required=True)
parser.add_argument("--skip_matches", type=int)
args = parser.parse_args()
main(**args.__dict__)