Spaces:
Running
Running
Upload folder using huggingface_hub
Browse files- FoL/__init__.py +0 -0
- FoL/split_local_feats.py +77 -0
- localisation/VPR_eval-all-v2.py +246 -0
- localisation/VPR_eval-fol-mem.py +214 -0
- localisation/VPR_eval-fol.py +316 -0
- submit-job.sh +18 -0
FoL/__init__.py
ADDED
|
File without changes
|
FoL/split_local_feats.py
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import sys
|
| 3 |
+
sys.path.append(os.path.abspath(".")) # one level up
|
| 4 |
+
import numpy as np
|
| 5 |
+
from natsort import natsorted, index_natsorted
|
| 6 |
+
import torch
|
| 7 |
+
from tqdm import tqdm
|
| 8 |
+
from glob import glob
|
| 9 |
+
|
| 10 |
+
################## set device based on cuda availability #################
|
| 11 |
+
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
|
| 12 |
+
|
| 13 |
+
print('CUDA availability: ' + str(torch.cuda.is_available()))
|
| 14 |
+
|
| 15 |
+
####################### Functions for matching using numpy on CPU or Pytorch on GPU ###################
|
| 16 |
+
|
| 17 |
+
slice_size = 1000
|
| 18 |
+
# qry_set = '20210909_124816_v2'
|
| 19 |
+
qry_set = '20230509_115540_v2'
|
| 20 |
+
vpr_desc = 'FoL'
|
| 21 |
+
img_calib_file = f"./camera_calib.txt"
|
| 22 |
+
# User parameters
|
| 23 |
+
location = 'dalby-to-brigalow'
|
| 24 |
+
|
| 25 |
+
################ Query filenames and directories #################################
|
| 26 |
+
qry_condition = ''
|
| 27 |
+
qry_camera_pos = 'front'
|
| 28 |
+
|
| 29 |
+
qry_root_directory = f"../../Datasets/dalby/{location}"
|
| 30 |
+
qry_vpr_root = f"../../Datasets/dalby/{location}/vpr_ftrs/"
|
| 31 |
+
qry_image_dir = f"{qry_root_directory}/{qry_set}/{qry_camera_pos}-imgs/"
|
| 32 |
+
save_dir = f"../../Datasets/dalby/{location}/vpr_ftrs/{qry_set}/{vpr_desc}/sliced/"
|
| 33 |
+
|
| 34 |
+
os.makedirs(save_dir, exist_ok=True)
|
| 35 |
+
|
| 36 |
+
|
| 37 |
+
qry_timestamps = [filename.split('.png')[0] for filename in natsorted(os.listdir(qry_image_dir)) if os.path.isfile(qry_image_dir+filename)]
|
| 38 |
+
|
| 39 |
+
# Get the two orderings
|
| 40 |
+
glob_sorted_paths = sorted(glob(f"{qry_image_dir}/*.png"))
|
| 41 |
+
glob_sorted_filenames = [os.path.basename(p) for p in glob_sorted_paths]
|
| 42 |
+
|
| 43 |
+
# Get the indices that would sort glob_sorted_filenames into natsorted order
|
| 44 |
+
qry_name_sort_idx = index_natsorted(glob_sorted_filenames)
|
| 45 |
+
|
| 46 |
+
print(f"Loading query features")
|
| 47 |
+
|
| 48 |
+
qry_ftrs = np.load(f"{qry_vpr_root}/{qry_set}/{vpr_desc}/queries_descriptors.npy")
|
| 49 |
+
|
| 50 |
+
print(f"Loading query local features")
|
| 51 |
+
qry_local_ftrs = np.load(f"{qry_vpr_root}/{qry_set}/{vpr_desc}/qry_local_feats.npy")
|
| 52 |
+
qry_ftrs = qry_ftrs[qry_name_sort_idx]
|
| 53 |
+
qry_local_ftrs = qry_local_ftrs[qry_name_sort_idx]
|
| 54 |
+
|
| 55 |
+
assert qry_ftrs.shape[0] == qry_local_ftrs.shape[0], f"There should be equal number of global ({qry_ftrs.shape[0]}) and local ({qry_local_ftrs.shape[0]}) features"
|
| 56 |
+
|
| 57 |
+
check_len_ftrs = 0
|
| 58 |
+
check_len_local_ftrs = 0
|
| 59 |
+
slice_num = 0
|
| 60 |
+
# f"{42:05d}"
|
| 61 |
+
print(f"Starting slice n dice")
|
| 62 |
+
for idx in tqdm(range(0, qry_ftrs.shape[0], slice_size)):
|
| 63 |
+
end_idx = idx+min(slice_size, qry_ftrs.shape[0]-idx)
|
| 64 |
+
qry_ftrs_slice = qry_ftrs[idx:end_idx]
|
| 65 |
+
qry_local_ftrs_slice = qry_local_ftrs[idx:end_idx]
|
| 66 |
+
|
| 67 |
+
np.save(f"{save_dir}/queries_descriptors_slice_{slice_num:05d}.npy", qry_ftrs_slice)
|
| 68 |
+
np.save(f"{save_dir}/qry_local_feats_slice_{slice_num:05d}.npy", qry_local_ftrs_slice)
|
| 69 |
+
|
| 70 |
+
|
| 71 |
+
check_len_ftrs += qry_ftrs_slice.shape[0]
|
| 72 |
+
check_len_local_ftrs += qry_local_ftrs_slice.shape[0]
|
| 73 |
+
slice_num += 1
|
| 74 |
+
|
| 75 |
+
print(f"Query descriptors: {qry_ftrs.shape[0]}, Slice query descriptors: {check_len_ftrs}")
|
| 76 |
+
print(f"Query local descriptors: {qry_local_ftrs.shape[0]}, Slice query local descriptors: {check_len_local_ftrs}")
|
| 77 |
+
print(f"Number of slices: {slice_num}")
|
localisation/VPR_eval-all-v2.py
ADDED
|
@@ -0,0 +1,246 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import matplotlib.pyplot as plt
|
| 3 |
+
import matplotlib.image as mpimg
|
| 4 |
+
import sys
|
| 5 |
+
sys.path.append(os.path.abspath(".")) # one level up
|
| 6 |
+
import numpy as np
|
| 7 |
+
import cv2
|
| 8 |
+
import open3d as o3d
|
| 9 |
+
from scipy.spatial.transform import Rotation
|
| 10 |
+
from utils.lidar import PointCloud
|
| 11 |
+
from utils.camera import ImageData
|
| 12 |
+
import utils.utils as utils
|
| 13 |
+
from natsort import natsorted, index_natsorted
|
| 14 |
+
import torch
|
| 15 |
+
from tqdm import tqdm
|
| 16 |
+
from glob import glob
|
| 17 |
+
|
| 18 |
+
################## set device based on cuda availability #################
|
| 19 |
+
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
|
| 20 |
+
|
| 21 |
+
print('CUDA availability: ' + str(torch.cuda.is_available()))
|
| 22 |
+
|
| 23 |
+
####################### Functions for matching using numpy on CPU or Pytorch on GPU ###################
|
| 24 |
+
def getMatchIndsCPU(ft_ref,ft_qry,topK=20,metric='cosine'):
|
| 25 |
+
"""
|
| 26 |
+
metric: 'euclidean' or 'cosine'
|
| 27 |
+
"""
|
| 28 |
+
# dMat = cdist(ft_ref,ft_qry,metric)
|
| 29 |
+
|
| 30 |
+
ft_qry_norm = ft_qry / np.linalg.norm(ft_qry, axis=1, keepdims=True) # Shape (M, N)
|
| 31 |
+
ft_ref_norm = ft_ref / np.linalg.norm(ft_ref, axis=1, keepdims=True) # Shape (C, N)
|
| 32 |
+
|
| 33 |
+
# Step 2: Compute cosine similarity
|
| 34 |
+
dMat = 1 - (ft_ref_norm @ ft_qry_norm.T)
|
| 35 |
+
mInds = np.argsort(dMat,axis=0)[:topK].squeeze() # shape: K x ft_qry.shape[0]
|
| 36 |
+
return mInds, dMat
|
| 37 |
+
|
| 38 |
+
|
| 39 |
+
def getMatchIndsGPU(ft_ref, ft_qry,topK=20, metric='cosine'):
|
| 40 |
+
# metric: 'euclidean' or 'cosine'
|
| 41 |
+
ft_qry_tensor = torch.Tensor(ft_qry).to(device)
|
| 42 |
+
ft_ref_tensor = torch.Tensor(ft_ref).to(device)
|
| 43 |
+
|
| 44 |
+
if metric == 'euclidean':
|
| 45 |
+
# Use torch's cdist for Euclidean distance
|
| 46 |
+
dMat = torch.cdist(ft_ref, ft_qry)
|
| 47 |
+
|
| 48 |
+
elif metric == 'cosine':
|
| 49 |
+
# # Normalize both the query and reference tensors
|
| 50 |
+
ft_qry_norm = ft_qry_tensor / ft_qry_tensor.norm(dim=1, keepdim=True)
|
| 51 |
+
ft_ref_norm = ft_ref_tensor / ft_ref_tensor.norm(dim=1, keepdim=True)
|
| 52 |
+
# Compute cosine similarity (1 - cosine similarity for distance)
|
| 53 |
+
dMat = 1 - ft_ref_norm @ ft_qry_norm.t()
|
| 54 |
+
|
| 55 |
+
# Get the indices of the top 5 closest matches
|
| 56 |
+
mInds = torch.argsort(dMat.cpu(), dim=0)[:topK].squeeze()
|
| 57 |
+
|
| 58 |
+
return mInds, dMat
|
| 59 |
+
|
| 60 |
+
qry_sets = [
|
| 61 |
+
'20210909_124816_v2',
|
| 62 |
+
]
|
| 63 |
+
|
| 64 |
+
ref_sets = [
|
| 65 |
+
'20230509_115540_v2',
|
| 66 |
+
]
|
| 67 |
+
|
| 68 |
+
# vpr_descs = [
|
| 69 |
+
# 'cosplace',
|
| 70 |
+
# 'boq',
|
| 71 |
+
# 'clique-mining',
|
| 72 |
+
# 'cricavpr',
|
| 73 |
+
# 'eigenplaces',
|
| 74 |
+
# 'mixvpr',
|
| 75 |
+
# 'megaloc',
|
| 76 |
+
# 'salad',
|
| 77 |
+
# 'supervlad',
|
| 78 |
+
# ]
|
| 79 |
+
vpr_descs = [
|
| 80 |
+
'FoL',
|
| 81 |
+
]
|
| 82 |
+
|
| 83 |
+
|
| 84 |
+
img_calib_file = f"./camera_calib.txt"
|
| 85 |
+
|
| 86 |
+
dist_tolerance = 10 # metres
|
| 87 |
+
# qry_idx = 4
|
| 88 |
+
|
| 89 |
+
# User parameters
|
| 90 |
+
location = 'dalby-to-brigalow'
|
| 91 |
+
|
| 92 |
+
################ Reference filenames and directories #################################
|
| 93 |
+
ref_condition = ''
|
| 94 |
+
ref_camera_pos = 'front'
|
| 95 |
+
|
| 96 |
+
ref_timestamps = []
|
| 97 |
+
ref_utms = []
|
| 98 |
+
ref_img_filenames = []
|
| 99 |
+
ref_utm_filenames = []
|
| 100 |
+
|
| 101 |
+
for ref_set in ref_sets:
|
| 102 |
+
print(f"Loading {ref_set}")
|
| 103 |
+
|
| 104 |
+
ref_root_directory = f"../../Datasets/dalby/{location}"
|
| 105 |
+
ref_vpr_root = f"../../Datasets/dalby/{location}/vpr_ftrs/"
|
| 106 |
+
ref_image_dir = f"{ref_root_directory}/{ref_set}/{ref_camera_pos}-imgs/"
|
| 107 |
+
ref_utm_dir = f"{ref_root_directory}/{ref_set}/utm/"
|
| 108 |
+
|
| 109 |
+
|
| 110 |
+
this_ref_timestamp = [filename.split('.png')[0] for filename in natsorted(os.listdir(ref_image_dir)) if os.path.isfile(ref_image_dir+filename)]
|
| 111 |
+
ref_utms = ref_utms+[np.loadtxt(ref_utm_dir+filename) for filename in natsorted(os.listdir(ref_utm_dir)) if os.path.isfile(ref_utm_dir+filename)][55::]
|
| 112 |
+
ref_img_filenames = [filename for filename in natsorted(os.listdir(ref_image_dir)) if os.path.isfile(ref_image_dir+filename)]
|
| 113 |
+
ref_utm_filenames = np.array([filename for filename in natsorted(os.listdir(ref_utm_dir)) if os.path.isfile(ref_utm_dir+filename)])[:len(os.listdir(ref_utm_dir))-55]
|
| 114 |
+
ref_timestamps = ref_timestamps+this_ref_timestamp
|
| 115 |
+
|
| 116 |
+
ref_utms = np.array(ref_utms)
|
| 117 |
+
|
| 118 |
+
for vpr_desc in vpr_descs:
|
| 119 |
+
|
| 120 |
+
all_results = []
|
| 121 |
+
|
| 122 |
+
first = True
|
| 123 |
+
|
| 124 |
+
print(f"Loading references")
|
| 125 |
+
|
| 126 |
+
for ref_set in ref_sets:
|
| 127 |
+
print(f"Loading {ref_set} {vpr_desc} descriptors")
|
| 128 |
+
ref_root_directory = f"../../Datasets/dalby/{location}"
|
| 129 |
+
ref_vpr_root = f"../../Datasets/dalby/{location}/vpr_ftrs/"
|
| 130 |
+
|
| 131 |
+
ref_image_dir = f"{ref_root_directory}/{ref_set}/{ref_camera_pos}-imgs/"
|
| 132 |
+
|
| 133 |
+
# ref_name_sort_idx = index_natsorted(os.listdir(ref_image_dir))
|
| 134 |
+
# Get the two orderings
|
| 135 |
+
glob_sorted_paths = sorted(glob(f"{ref_image_dir}/*.png"))
|
| 136 |
+
glob_sorted_filenames = [os.path.basename(p) for p in glob_sorted_paths]
|
| 137 |
+
|
| 138 |
+
# Get the indices that would sort glob_sorted_filenames into natsorted order
|
| 139 |
+
ref_name_sort_idx = index_natsorted(glob_sorted_filenames)
|
| 140 |
+
|
| 141 |
+
ref_ftr = np.load(f"{ref_vpr_root}/{ref_set}/{vpr_desc}/queries_descriptors.npy")
|
| 142 |
+
if first:
|
| 143 |
+
ref_ftrs = ref_ftr[ref_name_sort_idx]
|
| 144 |
+
first = False
|
| 145 |
+
else:
|
| 146 |
+
ref_ftrs = np.vstack((ref_ftrs, ref_ftr[ref_name_sort_idx])) # [ref_name_sort_idx]
|
| 147 |
+
|
| 148 |
+
|
| 149 |
+
for qry_set in qry_sets:
|
| 150 |
+
|
| 151 |
+
################ Query filenames and directories #################################
|
| 152 |
+
qry_condition = ''
|
| 153 |
+
qry_camera_pos = 'front'
|
| 154 |
+
|
| 155 |
+
qry_root_directory = f"../../Datasets/dalby/{location}"
|
| 156 |
+
qry_vpr_root = f"../../Datasets/dalby/{location}/vpr_ftrs/"
|
| 157 |
+
qry_image_dir = f"{qry_root_directory}/{qry_set}/{qry_camera_pos}-imgs/"
|
| 158 |
+
qry_utm_dir = f"{qry_root_directory}/{qry_set}/utm/"
|
| 159 |
+
|
| 160 |
+
|
| 161 |
+
qry_timestamps = [filename.split('.png')[0] for filename in natsorted(os.listdir(qry_image_dir)) if os.path.isfile(qry_image_dir+filename)]
|
| 162 |
+
qry_utms = np.array([np.loadtxt(qry_utm_dir+filename) for filename in natsorted(os.listdir(qry_utm_dir)) if os.path.isfile(qry_utm_dir+filename)])
|
| 163 |
+
# qry_name_sort_idx = index_natsorted(os.listdir(qry_image_dir))
|
| 164 |
+
qry_ftrs = np.load(f"{qry_vpr_root}/{qry_set}/{vpr_desc}/queries_descriptors.npy")
|
| 165 |
+
|
| 166 |
+
# Get the two orderings
|
| 167 |
+
glob_sorted_paths = sorted(glob(f"{qry_image_dir}/*.png"))
|
| 168 |
+
glob_sorted_filenames = [os.path.basename(p) for p in glob_sorted_paths]
|
| 169 |
+
|
| 170 |
+
# Get the indices that would sort glob_sorted_filenames into natsorted order
|
| 171 |
+
qry_name_sort_idx = index_natsorted(glob_sorted_filenames)
|
| 172 |
+
|
| 173 |
+
qry_ftrs = qry_ftrs[qry_name_sort_idx]
|
| 174 |
+
|
| 175 |
+
# paths_sorted = sorted(glob(f"{qry_image_dir}/**/*", recursive=True))
|
| 176 |
+
# paths_natsorted = natsorted(os.listdir(qry_image_dir))
|
| 177 |
+
# paths_natsorted_idx = index_natsorted(os.listdir(qry_image_dir))
|
| 178 |
+
|
| 179 |
+
# # Compare just the filenames
|
| 180 |
+
# print(paths_sorted[:3])
|
| 181 |
+
# print((np.array(paths_sorted)[paths_natsorted_idx])[:3])
|
| 182 |
+
# print(paths_natsorted[:3])
|
| 183 |
+
|
| 184 |
+
|
| 185 |
+
# print(torch.cuda.memory_allocated()/1e9)
|
| 186 |
+
# print(torch.cuda.memory_reserved()/1e9)
|
| 187 |
+
mInds, dMat = getMatchIndsGPU(ref_ftrs,qry_ftrs,topK=1)
|
| 188 |
+
mInds = mInds.numpy() # .cpu()
|
| 189 |
+
in_tol = []
|
| 190 |
+
dists = []
|
| 191 |
+
valid_qry = 0
|
| 192 |
+
|
| 193 |
+
qry_utm_timestamps, qry_utm_idxs = utils.get_all_corr_files(qry_timestamps, [qry_utm_dir,])
|
| 194 |
+
ref_utm_timestamp, ref_utm_idxs = utils.get_all_corr_files(ref_timestamps, [ref_utm_dir,])
|
| 195 |
+
|
| 196 |
+
for qry_idx in tqdm(range(len(qry_timestamps))):
|
| 197 |
+
|
| 198 |
+
qry_image_timestamp = qry_timestamps[qry_idx]
|
| 199 |
+
qry_image_filename = f"{qry_image_dir}/{qry_image_timestamp}.png"
|
| 200 |
+
qry_utm = qry_utms[qry_utm_idxs[qry_idx]]
|
| 201 |
+
|
| 202 |
+
|
| 203 |
+
diffs = ref_utms - qry_utm # shape (N, 2)
|
| 204 |
+
qry_dists = np.linalg.norm(diffs, axis=1) # shape (N,)
|
| 205 |
+
if qry_dists.min() > dist_tolerance:
|
| 206 |
+
continue
|
| 207 |
+
else:
|
| 208 |
+
valid_qry += 1
|
| 209 |
+
|
| 210 |
+
ref_utm = ref_utms[ref_utm_idxs[int(mInds[qry_idx])]]
|
| 211 |
+
|
| 212 |
+
diff = ref_utm - qry_utm # shape (N, 2)
|
| 213 |
+
dist = np.linalg.norm(diff) # shape (N,)
|
| 214 |
+
dists.append(dist)
|
| 215 |
+
if dist < dist_tolerance:
|
| 216 |
+
in_tol.append(1)
|
| 217 |
+
else:
|
| 218 |
+
in_tol.append(0)
|
| 219 |
+
|
| 220 |
+
# qry_image = ImageData(qry_image_filename, img_calib_file)
|
| 221 |
+
|
| 222 |
+
# fig, ax = plt.subplots(1, 2, figsize=(19.4, 6))
|
| 223 |
+
# ax[0].clear()
|
| 224 |
+
# ax[1].clear()
|
| 225 |
+
|
| 226 |
+
# ax[0].imshow(qry_image.image[:, :, ::-1])
|
| 227 |
+
# ax[0].set_title(f"{qry_image_timestamp}.png")
|
| 228 |
+
# ax[0].axis("off")
|
| 229 |
+
|
| 230 |
+
# # Show matching reference image
|
| 231 |
+
# # ref_img_timestamp = utils.get_corr_files(ref_timestamps[int(mInds[qry_idx])], [ref_image_dir,])
|
| 232 |
+
# ref_image = ImageData(f"{ref_image_dir}/{ref_timestamps[int(mInds[qry_idx])]}.png", img_calib_file)
|
| 233 |
+
# ax[1].imshow(ref_image.image[:, :, ::-1])
|
| 234 |
+
# ax[1].set_title(f"{ref_timestamps[int(mInds[qry_idx])]}\nDist={dist:.2f}m")
|
| 235 |
+
|
| 236 |
+
# ax[1].axis("off")
|
| 237 |
+
# fig.canvas.draw()
|
| 238 |
+
|
| 239 |
+
print(f"Recall for {qry_set} using {vpr_desc}: {np.sum(np.array(in_tol))/valid_qry:.02%}")
|
| 240 |
+
all_results.append(np.sum(np.array(in_tol))/valid_qry)
|
| 241 |
+
# plt.figure()
|
| 242 |
+
# plt.plot(np.clip(dists, 0, 30))
|
| 243 |
+
# plt.ylim((0,35))
|
| 244 |
+
|
| 245 |
+
print(f"All {vpr_desc} results:")
|
| 246 |
+
print(all_results)
|
localisation/VPR_eval-fol-mem.py
ADDED
|
@@ -0,0 +1,214 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
# import matplotlib.pyplot as plt
|
| 3 |
+
# import matplotlib.image as mpimg
|
| 4 |
+
import sys
|
| 5 |
+
sys.path.append(os.path.abspath(".")) # one level up
|
| 6 |
+
import numpy as np
|
| 7 |
+
import cv2
|
| 8 |
+
import open3d as o3d
|
| 9 |
+
from scipy.spatial.transform import Rotation
|
| 10 |
+
from utils.lidar import PointCloud
|
| 11 |
+
from utils.camera import ImageData
|
| 12 |
+
import utils.utils as utils
|
| 13 |
+
from FoL.reranking import run_rerank
|
| 14 |
+
from natsort import natsorted, index_natsorted
|
| 15 |
+
import torch
|
| 16 |
+
from tqdm import tqdm
|
| 17 |
+
|
| 18 |
+
################## set device based on cuda availability #################
|
| 19 |
+
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
|
| 20 |
+
|
| 21 |
+
print('CUDA availability: ' + str(torch.cuda.is_available()))
|
| 22 |
+
|
| 23 |
+
####################### Functions for matching using numpy on CPU or Pytorch on GPU ###################
|
| 24 |
+
def getMatchIndsCPU(ft_ref,ft_qry,topK=20,metric='cosine'):
|
| 25 |
+
"""
|
| 26 |
+
metric: 'euclidean' or 'cosine'
|
| 27 |
+
"""
|
| 28 |
+
# dMat = cdist(ft_ref,ft_qry,metric)
|
| 29 |
+
|
| 30 |
+
ft_qry_norm = ft_qry / np.linalg.norm(ft_qry, axis=1, keepdims=True) # Shape (M, N)
|
| 31 |
+
ft_ref_norm = ft_ref / np.linalg.norm(ft_ref, axis=1, keepdims=True) # Shape (C, N)
|
| 32 |
+
|
| 33 |
+
# Step 2: Compute cosine similarity
|
| 34 |
+
dMat = 1 - (ft_ref_norm @ ft_qry_norm.T)
|
| 35 |
+
mInds = np.argsort(dMat,axis=0)[:topK].squeeze() # shape: K x ft_qry.shape[0]
|
| 36 |
+
return mInds, dMat
|
| 37 |
+
|
| 38 |
+
|
| 39 |
+
def getMatchIndsGPU(ft_ref, ft_qry,topK=20, metric='cosine'):
|
| 40 |
+
# metric: 'euclidean' or 'cosine'
|
| 41 |
+
ft_qry_tensor = torch.Tensor(ft_qry).to(device)
|
| 42 |
+
ft_ref_tensor = torch.Tensor(ft_ref).to(device)
|
| 43 |
+
|
| 44 |
+
if metric == 'euclidean':
|
| 45 |
+
# Use torch's cdist for Euclidean distance
|
| 46 |
+
dMat = torch.cdist(ft_ref, ft_qry)
|
| 47 |
+
|
| 48 |
+
elif metric == 'cosine':
|
| 49 |
+
# # Normalize both the query and reference tensors
|
| 50 |
+
ft_qry_norm = ft_qry_tensor / ft_qry_tensor.norm(dim=1, keepdim=True)
|
| 51 |
+
ft_ref_norm = ft_ref_tensor / ft_ref_tensor.norm(dim=1, keepdim=True)
|
| 52 |
+
# Compute cosine similarity (1 - cosine similarity for distance)
|
| 53 |
+
dMat = 1 - ft_ref_norm @ ft_qry_norm.t()
|
| 54 |
+
|
| 55 |
+
# Get the indices of the top 5 closest matches
|
| 56 |
+
mInds = torch.argsort(dMat, dim=0)[:topK].squeeze()
|
| 57 |
+
|
| 58 |
+
return mInds, dMat
|
| 59 |
+
|
| 60 |
+
qry_sets = [
|
| 61 |
+
'20210909_124816_v2',
|
| 62 |
+
]
|
| 63 |
+
|
| 64 |
+
ref_sets = [
|
| 65 |
+
'20230509_115540_v2',
|
| 66 |
+
]
|
| 67 |
+
|
| 68 |
+
vpr_descs = [
|
| 69 |
+
'FoL',
|
| 70 |
+
]
|
| 71 |
+
|
| 72 |
+
|
| 73 |
+
img_calib_file = f"./camera_calib.txt"
|
| 74 |
+
|
| 75 |
+
dist_tolerance = 10 # metres
|
| 76 |
+
# qry_idx = 4
|
| 77 |
+
|
| 78 |
+
# User parameters
|
| 79 |
+
location = 'dalby-to-brigalow'
|
| 80 |
+
|
| 81 |
+
################ Reference filenames and directories #################################
|
| 82 |
+
ref_condition = ''
|
| 83 |
+
ref_camera_pos = 'front'
|
| 84 |
+
|
| 85 |
+
ref_timestamps = []
|
| 86 |
+
ref_utms = []
|
| 87 |
+
ref_img_filenames = []
|
| 88 |
+
ref_utm_filenames = []
|
| 89 |
+
|
| 90 |
+
for ref_set in ref_sets:
|
| 91 |
+
print(f"Loading {ref_set}")
|
| 92 |
+
|
| 93 |
+
ref_root_directory = f"../../Datasets/dalby/{location}"
|
| 94 |
+
ref_vpr_root = f"../../Datasets/dalby/{location}/vpr_ftrs/"
|
| 95 |
+
ref_image_dir = f"{ref_root_directory}/{ref_set}/{ref_camera_pos}-imgs/"
|
| 96 |
+
ref_utm_dir = f"{ref_root_directory}/{ref_set}/utm/"
|
| 97 |
+
|
| 98 |
+
|
| 99 |
+
this_ref_timestamp = [filename.split('.png')[0] for filename in natsorted(os.listdir(ref_image_dir)) if os.path.isfile(ref_image_dir+filename)]
|
| 100 |
+
ref_utms = ref_utms+[np.loadtxt(ref_utm_dir+filename) for filename in natsorted(os.listdir(ref_utm_dir)) if os.path.isfile(ref_utm_dir+filename)][55::]
|
| 101 |
+
ref_img_filenames = [filename for filename in natsorted(os.listdir(ref_image_dir)) if os.path.isfile(ref_image_dir+filename)]
|
| 102 |
+
ref_utm_filenames = np.array([filename for filename in natsorted(os.listdir(ref_utm_dir)) if os.path.isfile(ref_utm_dir+filename)])[:len(os.listdir(ref_utm_dir))-55]
|
| 103 |
+
ref_timestamps = ref_timestamps+this_ref_timestamp
|
| 104 |
+
|
| 105 |
+
ref_utms = np.array(ref_utms)
|
| 106 |
+
|
| 107 |
+
for vpr_desc in vpr_descs:
|
| 108 |
+
|
| 109 |
+
all_results = []
|
| 110 |
+
|
| 111 |
+
first = True
|
| 112 |
+
|
| 113 |
+
print(f"Loading references")
|
| 114 |
+
|
| 115 |
+
for ref_set in ref_sets:
|
| 116 |
+
print(f"Loading {ref_set} {vpr_desc} descriptors")
|
| 117 |
+
ref_root_directory = f"../../Datasets/dalby/{location}"
|
| 118 |
+
ref_vpr_root = f"../../Datasets/dalby/{location}/vpr_ftrs/"
|
| 119 |
+
|
| 120 |
+
ref_image_dir = f"{ref_root_directory}/{ref_set}/{ref_camera_pos}-imgs/"
|
| 121 |
+
|
| 122 |
+
ref_name_sort_idx = index_natsorted(os.listdir(ref_image_dir))
|
| 123 |
+
ref_ftr = np.load(f"{ref_vpr_root}/{ref_set}/{vpr_desc}/qry_feats.npy")
|
| 124 |
+
ref_local_ftr = np.load(f"{ref_vpr_root}/{ref_set}/{vpr_desc}/qry_local_feats.npy")
|
| 125 |
+
if first:
|
| 126 |
+
ref_ftrs = ref_ftr[ref_name_sort_idx]
|
| 127 |
+
ref_local_ftrs = ref_local_ftr[ref_name_sort_idx]
|
| 128 |
+
first = False
|
| 129 |
+
else:
|
| 130 |
+
ref_ftrs = np.vstack((ref_ftrs, ref_ftr[ref_name_sort_idx]))
|
| 131 |
+
ref_local_ftrs = np.vstack((ref_local_ftrs, ref_local_ftr[ref_name_sort_idx]))
|
| 132 |
+
|
| 133 |
+
|
| 134 |
+
for qry_set in qry_sets:
|
| 135 |
+
|
| 136 |
+
################ Query filenames and directories #################################
|
| 137 |
+
qry_condition = ''
|
| 138 |
+
qry_camera_pos = 'front'
|
| 139 |
+
|
| 140 |
+
qry_root_directory = f"../../Datasets/dalby/{location}"
|
| 141 |
+
qry_vpr_root = f"../../Datasets/dalby/{location}/vpr_ftrs/"
|
| 142 |
+
qry_image_dir = f"{qry_root_directory}/{qry_set}/{qry_camera_pos}-imgs/"
|
| 143 |
+
qry_utm_dir = f"{qry_root_directory}/{qry_set}/utm/"
|
| 144 |
+
|
| 145 |
+
|
| 146 |
+
qry_timestamps = [filename.split('.png')[0] for filename in natsorted(os.listdir(qry_image_dir)) if os.path.isfile(qry_image_dir+filename)]
|
| 147 |
+
qry_utms = np.array([np.loadtxt(qry_utm_dir+filename) for filename in natsorted(os.listdir(qry_utm_dir)) if os.path.isfile(qry_utm_dir+filename)])
|
| 148 |
+
qry_name_sort_idx = index_natsorted(os.listdir(qry_image_dir))
|
| 149 |
+
qry_ftrs = np.load(f"{qry_vpr_root}/{qry_set}/{vpr_desc}/qry_feats.npy")
|
| 150 |
+
qry_local_ftrs = np.load(f"{qry_vpr_root}/{qry_set}/{vpr_desc}/qry_local_feats.npy")
|
| 151 |
+
qry_ftrs = qry_ftrs[qry_name_sort_idx]
|
| 152 |
+
qry_local_ftrs = qry_local_ftrs[qry_name_sort_idx]
|
| 153 |
+
|
| 154 |
+
# mInds, dMat = getMatchIndsGPU(ref_ftrs,qry_ftrs,topK=1)
|
| 155 |
+
# mInds = mInds.cpu().numpy()
|
| 156 |
+
mInds = run_rerank(qry_ftrs, ref_ftrs, qry_local_ftrs, ref_local_ftrs, recall_values=[1, 5, 10, 20])[:,0]
|
| 157 |
+
in_tol = []
|
| 158 |
+
dists = []
|
| 159 |
+
valid_qry = 0
|
| 160 |
+
|
| 161 |
+
qry_utm_timestamps, qry_utm_idxs = utils.get_all_corr_files(qry_timestamps, [qry_utm_dir,])
|
| 162 |
+
ref_utm_timestamp, ref_utm_idxs = utils.get_all_corr_files(ref_timestamps, [ref_utm_dir,])
|
| 163 |
+
|
| 164 |
+
for qry_idx in tqdm(range(len(qry_timestamps))):
|
| 165 |
+
|
| 166 |
+
qry_image_timestamp = qry_timestamps[qry_idx]
|
| 167 |
+
qry_image_filename = f"{qry_image_dir}/{qry_image_timestamp}.png"
|
| 168 |
+
qry_utm = qry_utms[qry_utm_idxs[qry_idx]]
|
| 169 |
+
|
| 170 |
+
|
| 171 |
+
diffs = ref_utms - qry_utm # shape (N, 2)
|
| 172 |
+
qry_dists = np.linalg.norm(diffs, axis=1) # shape (N,)
|
| 173 |
+
if qry_dists.min() > dist_tolerance:
|
| 174 |
+
continue
|
| 175 |
+
else:
|
| 176 |
+
valid_qry += 1
|
| 177 |
+
|
| 178 |
+
ref_utm = ref_utms[ref_utm_idxs[int(mInds[qry_idx])]]
|
| 179 |
+
|
| 180 |
+
diff = ref_utm - qry_utm # shape (N, 2)
|
| 181 |
+
dist = np.linalg.norm(diff) # shape (N,)
|
| 182 |
+
dists.append(dist)
|
| 183 |
+
if dist < dist_tolerance:
|
| 184 |
+
in_tol.append(1)
|
| 185 |
+
else:
|
| 186 |
+
in_tol.append(0)
|
| 187 |
+
|
| 188 |
+
# qry_image = ImageData(qry_image_filename, img_calib_file)
|
| 189 |
+
|
| 190 |
+
# fig, ax = plt.subplots(1, 2, figsize=(19.4, 6))
|
| 191 |
+
# ax[0].clear()
|
| 192 |
+
# ax[1].clear()
|
| 193 |
+
|
| 194 |
+
# ax[0].imshow(qry_image.image[:, :, ::-1])
|
| 195 |
+
# ax[0].set_title(f"{qry_image_timestamp}.png")
|
| 196 |
+
# ax[0].axis("off")
|
| 197 |
+
|
| 198 |
+
# # Show matching reference image
|
| 199 |
+
# # ref_img_timestamp = utils.get_corr_files(ref_timestamps[int(mInds[qry_idx])], [ref_image_dir,])
|
| 200 |
+
# ref_image = ImageData(f"{ref_image_dir}/{ref_timestamps[int(mInds[qry_idx])]}.png", img_calib_file)
|
| 201 |
+
# ax[1].imshow(ref_image.image[:, :, ::-1])
|
| 202 |
+
# ax[1].set_title(f"{ref_timestamps[int(mInds[qry_idx])]}\nDist={dist:.2f}m")
|
| 203 |
+
|
| 204 |
+
# ax[1].axis("off")
|
| 205 |
+
# fig.canvas.draw()
|
| 206 |
+
|
| 207 |
+
print(f"Recall for {qry_set} using {vpr_desc}: {np.sum(np.array(in_tol))/valid_qry:.02%}")
|
| 208 |
+
all_results.append(np.sum(np.array(in_tol))/valid_qry)
|
| 209 |
+
# plt.figure()
|
| 210 |
+
# plt.plot(np.clip(dists, 0, 30))
|
| 211 |
+
# plt.ylim((0,35))
|
| 212 |
+
|
| 213 |
+
print(f"All {vpr_desc} results:")
|
| 214 |
+
print(all_results)
|
localisation/VPR_eval-fol.py
ADDED
|
@@ -0,0 +1,316 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
# import matplotlib.pyplot as plt
|
| 3 |
+
# import matplotlib.image as mpimg
|
| 4 |
+
import sys
|
| 5 |
+
sys.path.append(os.path.abspath(".")) # one level up
|
| 6 |
+
import numpy as np
|
| 7 |
+
# import cv2
|
| 8 |
+
# import open3d as o3d
|
| 9 |
+
# from scipy.spatial.transform import Rotation
|
| 10 |
+
# from utils.lidar import PointCloud
|
| 11 |
+
# from utils.camera import ImageData
|
| 12 |
+
# import utils.utils as utils
|
| 13 |
+
from utils.utils import get_all_corr_files
|
| 14 |
+
from FoL.reranking import run_rerank
|
| 15 |
+
from natsort import natsorted, index_natsorted
|
| 16 |
+
import torch
|
| 17 |
+
from tqdm import tqdm
|
| 18 |
+
from glob import glob
|
| 19 |
+
from math import floor
|
| 20 |
+
|
| 21 |
+
################## set device based on cuda availability #################
|
| 22 |
+
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
|
| 23 |
+
|
| 24 |
+
print('CUDA availability: ' + str(torch.cuda.is_available()))
|
| 25 |
+
|
| 26 |
+
####################### Functions for matching using numpy on CPU or Pytorch on GPU ###################
|
| 27 |
+
def getMatchIndsCPU(ft_ref,ft_qry,topK=20,metric='cosine'):
|
| 28 |
+
"""
|
| 29 |
+
metric: 'euclidean' or 'cosine'
|
| 30 |
+
"""
|
| 31 |
+
# dMat = cdist(ft_ref,ft_qry,metric)
|
| 32 |
+
|
| 33 |
+
ft_qry_norm = ft_qry / np.linalg.norm(ft_qry, axis=1, keepdims=True) # Shape (M, N)
|
| 34 |
+
ft_ref_norm = ft_ref / np.linalg.norm(ft_ref, axis=1, keepdims=True) # Shape (C, N)
|
| 35 |
+
|
| 36 |
+
# Step 2: Compute cosine similarity
|
| 37 |
+
dMat = 1 - (ft_ref_norm @ ft_qry_norm.T)
|
| 38 |
+
mInds = np.argsort(dMat,axis=0)[:topK].squeeze() # shape: K x ft_qry.shape[0]
|
| 39 |
+
return mInds, dMat
|
| 40 |
+
|
| 41 |
+
|
| 42 |
+
def getMatchIndsGPU(ft_ref, ft_qry,topK=20, metric='cosine'):
|
| 43 |
+
# metric: 'euclidean' or 'cosine'
|
| 44 |
+
ft_qry_tensor = torch.Tensor(ft_qry).to(device)
|
| 45 |
+
ft_ref_tensor = torch.Tensor(ft_ref).to(device)
|
| 46 |
+
|
| 47 |
+
if metric == 'euclidean':
|
| 48 |
+
# Use torch's cdist for Euclidean distance
|
| 49 |
+
dMat = torch.cdist(ft_ref, ft_qry)
|
| 50 |
+
|
| 51 |
+
elif metric == 'cosine':
|
| 52 |
+
# # Normalize both the query and reference tensors
|
| 53 |
+
ft_qry_norm = ft_qry_tensor / ft_qry_tensor.norm(dim=1, keepdim=True)
|
| 54 |
+
ft_ref_norm = ft_ref_tensor / ft_ref_tensor.norm(dim=1, keepdim=True)
|
| 55 |
+
# Compute cosine similarity (1 - cosine similarity for distance)
|
| 56 |
+
dMat = 1 - ft_ref_norm @ ft_qry_norm.t()
|
| 57 |
+
|
| 58 |
+
# Get the indices of the top 5 closest matches
|
| 59 |
+
mInds = torch.argsort(dMat.cpu(), dim=0)[:topK].squeeze()
|
| 60 |
+
|
| 61 |
+
return mInds, dMat
|
| 62 |
+
|
| 63 |
+
qry_sets = [
|
| 64 |
+
'20210909_124816_v2',
|
| 65 |
+
]
|
| 66 |
+
|
| 67 |
+
ref_sets = [
|
| 68 |
+
'20230509_115540_v2',
|
| 69 |
+
]
|
| 70 |
+
|
| 71 |
+
vpr_descs = [
|
| 72 |
+
'FoL',
|
| 73 |
+
]
|
| 74 |
+
|
| 75 |
+
|
| 76 |
+
img_calib_file = f"./camera_calib.txt"
|
| 77 |
+
|
| 78 |
+
dist_tolerance = 10 # metres
|
| 79 |
+
# qry_idx = 4
|
| 80 |
+
slice_len = 1000
|
| 81 |
+
|
| 82 |
+
# User parameters
|
| 83 |
+
location = 'dalby-to-brigalow'
|
| 84 |
+
|
| 85 |
+
################ Reference filenames and directories #################################
|
| 86 |
+
ref_condition = ''
|
| 87 |
+
ref_camera_pos = 'front'
|
| 88 |
+
|
| 89 |
+
ref_timestamps = []
|
| 90 |
+
ref_utms = []
|
| 91 |
+
ref_img_filenames = []
|
| 92 |
+
ref_utm_filenames = []
|
| 93 |
+
|
| 94 |
+
for ref_set in ref_sets:
|
| 95 |
+
print(f"Loading {ref_set}")
|
| 96 |
+
|
| 97 |
+
ref_root_directory = f"../../Datasets/dalby/{location}"
|
| 98 |
+
ref_vpr_root = f"../../Datasets/dalby/{location}/vpr_ftrs/"
|
| 99 |
+
ref_image_dir = f"{ref_root_directory}/{ref_set}/{ref_camera_pos}-imgs/"
|
| 100 |
+
ref_utm_dir = f"{ref_root_directory}/{ref_set}/utm/"
|
| 101 |
+
|
| 102 |
+
|
| 103 |
+
this_ref_timestamp = [filename.split('.png')[0] for filename in natsorted(os.listdir(ref_image_dir)) if os.path.isfile(ref_image_dir+filename)]
|
| 104 |
+
ref_utms = ref_utms+[np.loadtxt(ref_utm_dir+filename) for filename in natsorted(os.listdir(ref_utm_dir)) if os.path.isfile(ref_utm_dir+filename)][55::]
|
| 105 |
+
ref_img_filenames = [filename for filename in natsorted(os.listdir(ref_image_dir)) if os.path.isfile(ref_image_dir+filename)]
|
| 106 |
+
ref_utm_filenames = np.array([filename for filename in natsorted(os.listdir(ref_utm_dir)) if os.path.isfile(ref_utm_dir+filename)])[:len(os.listdir(ref_utm_dir))-55]
|
| 107 |
+
ref_timestamps = ref_timestamps+this_ref_timestamp
|
| 108 |
+
|
| 109 |
+
ref_utms = np.array(ref_utms)
|
| 110 |
+
|
| 111 |
+
for vpr_desc in vpr_descs:
|
| 112 |
+
|
| 113 |
+
all_results = []
|
| 114 |
+
|
| 115 |
+
first = True
|
| 116 |
+
|
| 117 |
+
print(f"Loading references")
|
| 118 |
+
|
| 119 |
+
for ref_set in ref_sets:
|
| 120 |
+
print(f"Loading {ref_set} {vpr_desc} descriptors")
|
| 121 |
+
ref_root_directory = f"../../Datasets/dalby/{location}"
|
| 122 |
+
ref_vpr_root = f"../../Datasets/dalby/{location}/vpr_ftrs/"
|
| 123 |
+
|
| 124 |
+
ref_image_dir = f"{ref_root_directory}/{ref_set}/{ref_camera_pos}-imgs/"
|
| 125 |
+
|
| 126 |
+
# ref_name_sort_idx = index_natsorted(os.listdir(ref_image_dir))
|
| 127 |
+
|
| 128 |
+
if slice_len is None:
|
| 129 |
+
|
| 130 |
+
# Get the two orderings
|
| 131 |
+
glob_sorted_paths = sorted(glob(f"{ref_image_dir}/*.png"))
|
| 132 |
+
glob_sorted_filenames = [os.path.basename(p) for p in glob_sorted_paths]
|
| 133 |
+
|
| 134 |
+
# Get the indices that would sort glob_sorted_filenames into natsorted order
|
| 135 |
+
ref_name_sort_idx = index_natsorted(glob_sorted_filenames)
|
| 136 |
+
|
| 137 |
+
ref_ftr = np.load(f"{ref_vpr_root}/{ref_set}/{vpr_desc}/queries_descriptors.npy")
|
| 138 |
+
ref_local_ftr = np.load(f"{ref_vpr_root}/{ref_set}/{vpr_desc}/qry_local_feats.npy")
|
| 139 |
+
if first:
|
| 140 |
+
ref_ftrs = ref_ftr[ref_name_sort_idx]
|
| 141 |
+
ref_local_ftrs = ref_local_ftr[ref_name_sort_idx]
|
| 142 |
+
first = False
|
| 143 |
+
else:
|
| 144 |
+
ref_ftrs = np.vstack((ref_ftrs, ref_ftr[ref_name_sort_idx]))
|
| 145 |
+
ref_local_ftrs = np.vstack((ref_local_ftrs, ref_local_ftr[ref_name_sort_idx]))
|
| 146 |
+
|
| 147 |
+
else:
|
| 148 |
+
num_slices = floor(len(ref_img_filenames)/slice_len)
|
| 149 |
+
if len(ref_img_filenames) % slice_len > 0:
|
| 150 |
+
num_slices += 1
|
| 151 |
+
|
| 152 |
+
for idx in tqdm(range(num_slices)):
|
| 153 |
+
if idx == 0:
|
| 154 |
+
ref_ftrs = np.load(f"{ref_vpr_root}/{ref_set}/{vpr_desc}/sliced/queries_descriptors_slice_{idx:05d}.npy")
|
| 155 |
+
ref_local_ftrs = np.load(f"{ref_vpr_root}/{ref_set}/{vpr_desc}/sliced/qry_local_feats_slice_{idx:05d}.npy")
|
| 156 |
+
else:
|
| 157 |
+
ref_ftrs = np.vstack((ref_ftrs, np.load(f"{ref_vpr_root}/{ref_set}/{vpr_desc}/sliced/queries_descriptors_slice_{idx:05d}.npy")))
|
| 158 |
+
ref_local_ftrs = np.vstack((ref_local_ftrs, np.load(f"{ref_vpr_root}/{ref_set}/{vpr_desc}/sliced/qry_local_feats_slice_{idx:05d}.npy")))
|
| 159 |
+
|
| 160 |
+
|
| 161 |
+
print(f"Loaded ref ftr slices: {len(ref_ftrs)}")
|
| 162 |
+
print(f"Loaded ref local ftr slices: {len(ref_local_ftrs)}")
|
| 163 |
+
|
| 164 |
+
|
| 165 |
+
for qry_set in qry_sets:
|
| 166 |
+
|
| 167 |
+
################ Query filenames and directories #################################
|
| 168 |
+
qry_condition = ''
|
| 169 |
+
qry_camera_pos = 'front'
|
| 170 |
+
|
| 171 |
+
qry_root_directory = f"../../Datasets/dalby/{location}"
|
| 172 |
+
qry_vpr_root = f"../../Datasets/dalby/{location}/vpr_ftrs/"
|
| 173 |
+
qry_image_dir = f"{qry_root_directory}/{qry_set}/{qry_camera_pos}-imgs/"
|
| 174 |
+
qry_utm_dir = f"{qry_root_directory}/{qry_set}/utm/"
|
| 175 |
+
|
| 176 |
+
|
| 177 |
+
qry_timestamps = [filename.split('.png')[0] for filename in natsorted(os.listdir(qry_image_dir)) if os.path.isfile(qry_image_dir+filename)]
|
| 178 |
+
qry_utms = np.array([np.loadtxt(qry_utm_dir+filename) for filename in natsorted(os.listdir(qry_utm_dir)) if os.path.isfile(qry_utm_dir+filename)])
|
| 179 |
+
# qry_name_sort_idx = index_natsorted(os.listdir(qry_image_dir))
|
| 180 |
+
|
| 181 |
+
# if slice_len is None:
|
| 182 |
+
# # Get the two orderings
|
| 183 |
+
# glob_sorted_paths = sorted(glob(f"{qry_image_dir}/*.png"))
|
| 184 |
+
# glob_sorted_filenames = [os.path.basename(p) for p in glob_sorted_paths]
|
| 185 |
+
|
| 186 |
+
# # Get the indices that would sort glob_sorted_filenames into natsorted order
|
| 187 |
+
# qry_name_sort_idx = index_natsorted(glob_sorted_filenames)
|
| 188 |
+
|
| 189 |
+
# qry_ftrs = np.load(f"{qry_vpr_root}/{qry_set}/{vpr_desc}/queries_descriptors.npy")
|
| 190 |
+
# qry_local_ftrs = np.load(f"{qry_vpr_root}/{qry_set}/{vpr_desc}/qry_local_feats.npy")
|
| 191 |
+
# qry_ftrs = qry_ftrs[qry_name_sort_idx]
|
| 192 |
+
# qry_local_ftrs = qry_local_ftrs[qry_name_sort_idx]
|
| 193 |
+
|
| 194 |
+
# mInds, dMat = getMatchIndsGPU(ref_ftrs,qry_ftrs,topK=1)
|
| 195 |
+
# mInds = mInds.cpu().numpy()
|
| 196 |
+
if slice_len is None:
|
| 197 |
+
mInds = run_rerank(qry_ftrs, ref_ftrs, qry_local_ftrs, ref_local_ftrs, recall_values=[1, 5, 10, 20])[:,0] # 5, 10, 20
|
| 198 |
+
else:
|
| 199 |
+
print(f"Performing VPR on slices")
|
| 200 |
+
num_slices = floor(len(qry_timestamps)/slice_len)
|
| 201 |
+
if len(qry_timestamps) % slice_len > 0:
|
| 202 |
+
num_slices += 1
|
| 203 |
+
|
| 204 |
+
for idx in tqdm(range(num_slices)):
|
| 205 |
+
qry_ftrs = np.load(f"{qry_vpr_root}/{qry_set}/{vpr_desc}/sliced/queries_descriptors_slice_{idx:05d}.npy")
|
| 206 |
+
qry_local_ftrs = np.load(f"{qry_vpr_root}/{qry_set}/{vpr_desc}/sliced/qry_local_feats_slice_{idx:05d}.npy")
|
| 207 |
+
if idx == 0:
|
| 208 |
+
mInds = run_rerank(qry_ftrs, ref_ftrs, qry_local_ftrs, ref_local_ftrs, recall_values=[1, 5, 10, 20])[:,0]
|
| 209 |
+
else:
|
| 210 |
+
mInds_slice = run_rerank(qry_ftrs, ref_ftrs, qry_local_ftrs, ref_local_ftrs, recall_values=[1, 5, 10, 20])[:,0]
|
| 211 |
+
mInds = np.vstack((np.expand_dims(mInds, axis=1), np.expand_dims(mInds_slice, axis=1))).squeeze()
|
| 212 |
+
|
| 213 |
+
del qry_ftrs
|
| 214 |
+
del qry_local_ftrs
|
| 215 |
+
|
| 216 |
+
print(f"VPR on query slices: {len(mInds)}")
|
| 217 |
+
|
| 218 |
+
np.save(f"{qry_vpr_root}/{qry_set}/{vpr_desc}/mInds.npy", mInds)
|
| 219 |
+
|
| 220 |
+
|
| 221 |
+
in_tol = []
|
| 222 |
+
dists = []
|
| 223 |
+
valid_qry = 0
|
| 224 |
+
|
| 225 |
+
qry_utm_timestamps, qry_utm_idxs = get_all_corr_files(qry_timestamps, [qry_utm_dir,])
|
| 226 |
+
ref_utm_timestamp, ref_utm_idxs = get_all_corr_files(ref_timestamps, [ref_utm_dir,])
|
| 227 |
+
|
| 228 |
+
for qry_idx in tqdm(range(len(qry_timestamps))):
|
| 229 |
+
|
| 230 |
+
qry_image_timestamp = qry_timestamps[qry_idx]
|
| 231 |
+
qry_image_filename = f"{qry_image_dir}/{qry_image_timestamp}.png"
|
| 232 |
+
qry_utm = qry_utms[qry_utm_idxs[qry_idx]]
|
| 233 |
+
|
| 234 |
+
|
| 235 |
+
diffs = ref_utms - qry_utm # shape (N, 2)
|
| 236 |
+
qry_dists = np.linalg.norm(diffs, axis=1) # shape (N,)
|
| 237 |
+
if qry_dists.min() > dist_tolerance:
|
| 238 |
+
continue
|
| 239 |
+
else:
|
| 240 |
+
valid_qry += 1
|
| 241 |
+
|
| 242 |
+
ref_utm = ref_utms[ref_utm_idxs[int(mInds[qry_idx])]]
|
| 243 |
+
|
| 244 |
+
diff = ref_utm - qry_utm # shape (N, 2)
|
| 245 |
+
dist = np.linalg.norm(diff) # shape (N,)
|
| 246 |
+
dists.append(dist)
|
| 247 |
+
if dist < dist_tolerance:
|
| 248 |
+
in_tol.append(1)
|
| 249 |
+
else:
|
| 250 |
+
in_tol.append(0)
|
| 251 |
+
|
| 252 |
+
# qry_image = ImageData(qry_image_filename, img_calib_file)
|
| 253 |
+
|
| 254 |
+
# fig, ax = plt.subplots(1, 2, figsize=(19.4, 6))
|
| 255 |
+
# ax[0].clear()
|
| 256 |
+
# ax[1].clear()
|
| 257 |
+
|
| 258 |
+
# ax[0].imshow(qry_image.image[:, :, ::-1])
|
| 259 |
+
# ax[0].set_title(f"{qry_image_timestamp}.png")
|
| 260 |
+
# ax[0].axis("off")
|
| 261 |
+
|
| 262 |
+
# # Show matching reference image
|
| 263 |
+
# # ref_img_timestamp = utils.get_corr_files(ref_timestamps[int(mInds[qry_idx])], [ref_image_dir,])
|
| 264 |
+
# ref_image = ImageData(f"{ref_image_dir}/{ref_timestamps[int(mInds[qry_idx])]}.png", img_calib_file)
|
| 265 |
+
# ax[1].imshow(ref_image.image[:, :, ::-1])
|
| 266 |
+
# ax[1].set_title(f"{ref_timestamps[int(mInds[qry_idx])]}\nDist={dist:.2f}m")
|
| 267 |
+
|
| 268 |
+
# ax[1].axis("off")
|
| 269 |
+
# fig.canvas.draw()
|
| 270 |
+
|
| 271 |
+
print(f"Recall for {qry_set} using {vpr_desc}: {np.sum(np.array(in_tol))/valid_qry:.02%}")
|
| 272 |
+
all_results.append(np.sum(np.array(in_tol))/valid_qry)
|
| 273 |
+
# plt.figure()
|
| 274 |
+
# plt.plot(np.clip(dists, 0, 30))
|
| 275 |
+
# plt.ylim((0,35))
|
| 276 |
+
|
| 277 |
+
print(f"All {vpr_desc} results:")
|
| 278 |
+
print(all_results)
|
| 279 |
+
|
| 280 |
+
# else:
|
| 281 |
+
# num_slices = floor(len(ref_img_filenames)/slice_len)
|
| 282 |
+
# if len(ref_img_filenames) % slice_len > 0:
|
| 283 |
+
# num_slices += 1
|
| 284 |
+
|
| 285 |
+
# for idx in range(num_slices):
|
| 286 |
+
# if idx == 0:
|
| 287 |
+
# ref_ftrs = np.load(f"{ref_vpr_root}/{ref_set}/{vpr_desc}/sliced/queries_descriptors_slice_{idx:05d}.npy")
|
| 288 |
+
# else:
|
| 289 |
+
# ref_ftrs = np.vstack((ref_ftrs, np.load(f"{ref_vpr_root}/{ref_set}/{vpr_desc}/sliced/queries_descriptors_slice_{idx:05d}.npy")))
|
| 290 |
+
|
| 291 |
+
|
| 292 |
+
# print(f"Loaded ref ftr slices: {len(ref_ftrs)}")
|
| 293 |
+
|
| 294 |
+
|
| 295 |
+
|
| 296 |
+
|
| 297 |
+
# if slice_len is None:
|
| 298 |
+
# mInds, dMat = getMatchIndsGPU(ref_ftrs,qry_ftrs,topK=1)
|
| 299 |
+
# mInds = mInds.cpu().numpy()
|
| 300 |
+
# else:
|
| 301 |
+
# print(f"Performing VPR on slices")
|
| 302 |
+
# num_slices = floor(len(qry_timestamps)/slice_len)
|
| 303 |
+
# if len(qry_timestamps) % slice_len > 0:
|
| 304 |
+
# num_slices += 1
|
| 305 |
+
|
| 306 |
+
# for idx in tqdm(range(num_slices)):
|
| 307 |
+
# qry_ftrs = np.load(f"{qry_vpr_root}/{qry_set}/{vpr_desc}/sliced/queries_descriptors_slice_{idx:05d}.npy")
|
| 308 |
+
# if idx == 0:
|
| 309 |
+
# mInds, dMat = getMatchIndsGPU(ref_ftrs,qry_ftrs,topK=1)
|
| 310 |
+
# mInds = mInds.cpu().numpy()
|
| 311 |
+
# else:
|
| 312 |
+
# mInds_slice, dMat = getMatchIndsGPU(ref_ftrs,qry_ftrs,topK=1)
|
| 313 |
+
# mInds_slice = mInds_slice.cpu().numpy()
|
| 314 |
+
# mInds = np.vstack((np.expand_dims(mInds, axis=1), np.expand_dims(mInds_slice, axis=1))).squeeze()
|
| 315 |
+
|
| 316 |
+
# print(f"VPR on query slices: {len(mInds)}")
|
submit-job.sh
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/bin/bash
|
| 2 |
+
|
| 3 |
+
#PBS -N vpr-extract
|
| 4 |
+
#PBS -l select=1:ncpus=10:mem=100gb:ngpus=1:gpu_id=A100
|
| 5 |
+
#PBS -l walltime=06:00:00
|
| 6 |
+
#PBS -m abe
|
| 7 |
+
#PBS -M cj.malone@qut.edu.au
|
| 8 |
+
#PBS -j oe
|
| 9 |
+
|
| 10 |
+
micromamba activate fred
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
# Move to repo
|
| 14 |
+
cd "$HOME/cloned_repos/python-FRED/" || exit 1
|
| 15 |
+
|
| 16 |
+
# Run Python script
|
| 17 |
+
python localisation/VPR_eval-fol.py
|
| 18 |
+
# python FoL/split_local_feats.py
|