|
|
|
|
|
|
|
import random |
|
from glob import glob |
|
from os import path as osp |
|
|
|
import cv2 |
|
import numpy as np |
|
|
|
from lanet_utils import warp_keypoints |
|
|
|
|
|
def select_k_best(points, descriptors, k): |
|
"""Select the k most probable points (and strip their probability). |
|
points has shape (num_points, 3) where the last coordinate is the probability. |
|
|
|
Parameters |
|
---------- |
|
points: numpy.ndarray (N,3) |
|
Keypoint vector, consisting of (x,y,probability). |
|
descriptors: numpy.ndarray (N,256) |
|
Keypoint descriptors. |
|
k: int |
|
Number of keypoints to select, based on probability. |
|
Returns |
|
------- |
|
|
|
selected_points: numpy.ndarray (k,2) |
|
k most probable keypoints. |
|
selected_descriptors: numpy.ndarray (k,256) |
|
Descriptors corresponding to the k most probable keypoints. |
|
""" |
|
sorted_prob = points[points[:, 2].argsort(), :2] |
|
sorted_desc = descriptors[points[:, 2].argsort(), :] |
|
start = min(k, points.shape[0]) |
|
selected_points = sorted_prob[-start:, :] |
|
selected_descriptors = sorted_desc[-start:, :] |
|
return selected_points, selected_descriptors |
|
|
|
|
|
def keep_shared_points(keypoints, descriptors, H, shape, keep_k_points=1000): |
|
""" |
|
Compute a list of keypoints from the map, filter the list of points by keeping |
|
only the points that once mapped by H are still inside the shape of the map |
|
and keep at most 'keep_k_points' keypoints in the image. |
|
|
|
Parameters |
|
---------- |
|
keypoints: numpy.ndarray (N,3) |
|
Keypoint vector, consisting of (x,y,probability). |
|
descriptors: numpy.ndarray (N,256) |
|
Keypoint descriptors. |
|
H: numpy.ndarray (3,3) |
|
Homography. |
|
shape: tuple |
|
Image shape. |
|
keep_k_points: int |
|
Number of keypoints to select, based on probability. |
|
|
|
Returns |
|
------- |
|
selected_points: numpy.ndarray (k,2) |
|
k most probable keypoints. |
|
selected_descriptors: numpy.ndarray (k,256) |
|
Descriptors corresponding to the k most probable keypoints. |
|
""" |
|
|
|
def keep_true_keypoints(points, descriptors, H, shape): |
|
"""Keep only the points whose warped coordinates by H are still inside shape.""" |
|
warped_points = warp_keypoints(points[:, [1, 0]], H) |
|
warped_points[:, [0, 1]] = warped_points[:, [1, 0]] |
|
mask = ( |
|
(warped_points[:, 0] >= 0) |
|
& (warped_points[:, 0] < shape[0]) |
|
& (warped_points[:, 1] >= 0) |
|
& (warped_points[:, 1] < shape[1]) |
|
) |
|
return points[mask, :], descriptors[mask, :] |
|
|
|
selected_keypoints, selected_descriptors = keep_true_keypoints( |
|
keypoints, descriptors, H, shape |
|
) |
|
selected_keypoints, selected_descriptors = select_k_best( |
|
selected_keypoints, selected_descriptors, keep_k_points |
|
) |
|
return selected_keypoints, selected_descriptors |
|
|
|
|
|
def compute_matching_score(data, keep_k_points=1000): |
|
""" |
|
Compute the matching score between two sets of keypoints with associated descriptors. |
|
|
|
Parameters |
|
---------- |
|
data: dict |
|
Input dictionary containing: |
|
image_shape: tuple (H,W) |
|
Original image shape. |
|
homography: numpy.ndarray (3,3) |
|
Ground truth homography. |
|
prob: numpy.ndarray (N,3) |
|
Keypoint vector, consisting of (x,y,probability). |
|
warped_prob: numpy.ndarray (N,3) |
|
Warped keypoint vector, consisting of (x,y,probability). |
|
desc: numpy.ndarray (N,256) |
|
Keypoint descriptors. |
|
warped_desc: numpy.ndarray (N,256) |
|
Warped keypoint descriptors. |
|
keep_k_points: int |
|
Number of keypoints to select, based on probability. |
|
|
|
Returns |
|
------- |
|
ms: float |
|
Matching score. |
|
""" |
|
shape = data["image_shape"] |
|
real_H = data["homography"] |
|
|
|
|
|
keypoints = data["prob"][:, :2].T |
|
keypoints = keypoints[::-1] |
|
prob = data["prob"][:, 2] |
|
keypoints = np.stack([keypoints[0], keypoints[1], prob], axis=-1) |
|
|
|
warped_keypoints = data["warped_prob"][:, :2].T |
|
warped_keypoints = warped_keypoints[::-1] |
|
warped_prob = data["warped_prob"][:, 2] |
|
warped_keypoints = np.stack( |
|
[warped_keypoints[0], warped_keypoints[1], warped_prob], axis=-1 |
|
) |
|
|
|
desc = data["desc"] |
|
warped_desc = data["warped_desc"] |
|
|
|
|
|
keypoints, desc = select_k_best(keypoints, desc, keep_k_points) |
|
warped_keypoints, warped_desc = select_k_best( |
|
warped_keypoints, warped_desc, keep_k_points |
|
) |
|
|
|
|
|
|
|
|
|
bf = cv2.BFMatcher(cv2.NORM_L2, crossCheck=False) |
|
|
|
matches = bf.match(desc, warped_desc) |
|
matches_idx = np.array([m.queryIdx for m in matches]) |
|
m_keypoints = keypoints[matches_idx, :] |
|
matches_idx = np.array([m.trainIdx for m in matches]) |
|
m_warped_keypoints = warped_keypoints[matches_idx, :] |
|
|
|
true_warped_keypoints = warp_keypoints( |
|
m_warped_keypoints[:, [1, 0]], np.linalg.inv(real_H) |
|
)[:, ::-1] |
|
vis_warped = np.all( |
|
(true_warped_keypoints >= 0) & (true_warped_keypoints <= (np.array(shape) - 1)), |
|
axis=-1, |
|
) |
|
norm1 = np.linalg.norm(true_warped_keypoints - m_keypoints, axis=-1) |
|
|
|
correct1 = norm1 < 3 |
|
count1 = np.sum(correct1 * vis_warped) |
|
score1 = count1 / np.maximum(np.sum(vis_warped), 1.0) |
|
|
|
matches = bf.match(warped_desc, desc) |
|
matches_idx = np.array([m.queryIdx for m in matches]) |
|
m_warped_keypoints = warped_keypoints[matches_idx, :] |
|
matches_idx = np.array([m.trainIdx for m in matches]) |
|
m_keypoints = keypoints[matches_idx, :] |
|
|
|
true_keypoints = warp_keypoints(m_keypoints[:, [1, 0]], real_H)[:, ::-1] |
|
vis = np.all( |
|
(true_keypoints >= 0) & (true_keypoints <= (np.array(shape) - 1)), axis=-1 |
|
) |
|
norm2 = np.linalg.norm(true_keypoints - m_warped_keypoints, axis=-1) |
|
|
|
correct2 = norm2 < 3 |
|
count2 = np.sum(correct2 * vis) |
|
score2 = count2 / np.maximum(np.sum(vis), 1.0) |
|
|
|
ms = (score1 + score2) / 2 |
|
|
|
return ms |
|
|
|
|
|
def compute_homography(data, keep_k_points=1000): |
|
""" |
|
Compute the homography between 2 sets of Keypoints and descriptors inside data. |
|
Use the homography to compute the correctness metrics (1,3,5). |
|
|
|
Parameters |
|
---------- |
|
data: dict |
|
Input dictionary containing: |
|
image_shape: tuple (H,W) |
|
Original image shape. |
|
homography: numpy.ndarray (3,3) |
|
Ground truth homography. |
|
prob: numpy.ndarray (N,3) |
|
Keypoint vector, consisting of (x,y,probability). |
|
warped_prob: numpy.ndarray (N,3) |
|
Warped keypoint vector, consisting of (x,y,probability). |
|
desc: numpy.ndarray (N,256) |
|
Keypoint descriptors. |
|
warped_desc: numpy.ndarray (N,256) |
|
Warped keypoint descriptors. |
|
keep_k_points: int |
|
Number of keypoints to select, based on probability. |
|
|
|
Returns |
|
------- |
|
correctness1: float |
|
correctness1 metric. |
|
correctness3: float |
|
correctness3 metric. |
|
correctness5: float |
|
correctness5 metric. |
|
""" |
|
shape = data["image_shape"] |
|
real_H = data["homography"] |
|
|
|
|
|
keypoints = data["prob"][:, :2].T |
|
keypoints = keypoints[::-1] |
|
prob = data["prob"][:, 2] |
|
keypoints = np.stack([keypoints[0], keypoints[1], prob], axis=-1) |
|
|
|
warped_keypoints = data["warped_prob"][:, :2].T |
|
warped_keypoints = warped_keypoints[::-1] |
|
warped_prob = data["warped_prob"][:, 2] |
|
warped_keypoints = np.stack( |
|
[warped_keypoints[0], warped_keypoints[1], warped_prob], axis=-1 |
|
) |
|
|
|
desc = data["desc"] |
|
warped_desc = data["warped_desc"] |
|
|
|
|
|
keypoints, desc = keep_shared_points(keypoints, desc, real_H, shape, keep_k_points) |
|
warped_keypoints, warped_desc = keep_shared_points( |
|
warped_keypoints, warped_desc, np.linalg.inv(real_H), shape, keep_k_points |
|
) |
|
|
|
bf = cv2.BFMatcher(cv2.NORM_L2, crossCheck=True) |
|
matches = bf.match(desc, warped_desc) |
|
matches_idx = np.array([m.queryIdx for m in matches]) |
|
m_keypoints = keypoints[matches_idx, :] |
|
matches_idx = np.array([m.trainIdx for m in matches]) |
|
m_warped_keypoints = warped_keypoints[matches_idx, :] |
|
|
|
|
|
H, _ = cv2.findHomography( |
|
m_keypoints[:, [1, 0]], |
|
m_warped_keypoints[:, [1, 0]], |
|
cv2.RANSAC, |
|
3, |
|
maxIters=5000, |
|
) |
|
|
|
if H is None: |
|
return 0, 0, 0 |
|
|
|
shape = shape[::-1] |
|
|
|
|
|
corners = np.array( |
|
[ |
|
[0, 0, 1], |
|
[0, shape[1] - 1, 1], |
|
[shape[0] - 1, 0, 1], |
|
[shape[0] - 1, shape[1] - 1, 1], |
|
] |
|
) |
|
real_warped_corners = np.dot(corners, np.transpose(real_H)) |
|
real_warped_corners = real_warped_corners[:, :2] / real_warped_corners[:, 2:] |
|
warped_corners = np.dot(corners, np.transpose(H)) |
|
warped_corners = warped_corners[:, :2] / warped_corners[:, 2:] |
|
|
|
mean_dist = np.mean(np.linalg.norm(real_warped_corners - warped_corners, axis=1)) |
|
correctness1 = float(mean_dist <= 1) |
|
correctness3 = float(mean_dist <= 3) |
|
correctness5 = float(mean_dist <= 5) |
|
|
|
return correctness1, correctness3, correctness5 |
|
|