Zhyever
refactor
1f418ff
raw
history blame
7.82 kB
import cv2
import torch
import numpy as np
import torch.nn as nn
import matplotlib.pyplot as plt
from scipy import ndimage
from skimage.feature import canny
import kornia
def compute_errors(gt, pred):
"""Compute metrics for 'pred' compared to 'gt'
Args:
gt (numpy.ndarray): Ground truth values
pred (numpy.ndarray): Predicted values
gt.shape should be equal to pred.shape
Returns:
dict: Dictionary containing the following metrics:
'a1': Delta1 accuracy: Fraction of pixels that are within a scale factor of 1.25
'a2': Delta2 accuracy: Fraction of pixels that are within a scale factor of 1.25^2
'a3': Delta3 accuracy: Fraction of pixels that are within a scale factor of 1.25^3
'abs_rel': Absolute relative error
'rmse': Root mean squared error
'log_10': Absolute log10 error
'sq_rel': Squared relative error
'rmse_log': Root mean squared error on the log scale
'silog': Scale invariant log error
"""
thresh = np.maximum((gt / pred), (pred / gt))
a1 = (thresh < 1.25).mean()
a2 = (thresh < 1.25 ** 2).mean()
a3 = (thresh < 1.25 ** 3).mean()
abs_rel = np.mean(np.abs(gt - pred) / gt)
sq_rel = np.mean(((gt - pred) ** 2) / gt)
rmse = (gt - pred) ** 2
rmse = np.sqrt(rmse.mean())
rmse_log = (np.log(gt) - np.log(pred)) ** 2
rmse_log = np.sqrt(rmse_log.mean())
err = np.log(pred) - np.log(gt)
silog = np.sqrt(np.mean(err ** 2) - np.mean(err) ** 2) * 100
log_10 = (np.abs(np.log10(gt) - np.log10(pred))).mean()
return dict(a1=a1, a2=a2, a3=a3, abs_rel=abs_rel, rmse=rmse, log_10=log_10, rmse_log=rmse_log,
silog=silog, sq_rel=sq_rel)
def shift_2d_replace(data, dx, dy, constant=False):
shifted_data = np.roll(data, dx, axis=1)
if dx < 0:
shifted_data[:, dx:] = constant
elif dx > 0:
shifted_data[:, 0:dx] = constant
shifted_data = np.roll(shifted_data, dy, axis=0)
if dy < 0:
shifted_data[dy:, :] = constant
elif dy > 0:
shifted_data[0:dy, :] = constant
return shifted_data
def soft_edge_error(pred, gt, radius=1):
abs_diff=[]
for i in range(-radius, radius + 1):
for j in range(-radius, radius + 1):
abs_diff.append(np.abs(shift_2d_replace(gt, i, j, 0) - pred))
return np.minimum.reduce(abs_diff)
def get_boundaries(disp, th=1., dilation=10):
edges_y = np.logical_or(np.pad(np.abs(disp[1:, :] - disp[:-1, :]) > th, ((1, 0), (0, 0))),
np.pad(np.abs(disp[:-1, :] - disp[1:, :]) > th, ((0, 1), (0, 0))))
edges_x = np.logical_or(np.pad(np.abs(disp[:, 1:] - disp[:, :-1]) > th, ((0, 0), (1, 0))),
np.pad(np.abs(disp[:, :-1] - disp[:,1:]) > th, ((0, 0), (0, 1))))
edges = np.logical_or(edges_y, edges_x).astype(np.float32)
if dilation > 0:
kernel = np.ones((dilation, dilation), np.uint8)
edges = cv2.dilate(edges, kernel, iterations=1)
return edges
def compute_metrics(gt, pred, interpolate=True, garg_crop=False, eigen_crop=True, dataset='nyu', min_depth_eval=0.1, max_depth_eval=10, disp_gt_edges=None, additional_mask=None):
"""Compute metrics of predicted depth maps. Applies cropping and masking as necessary or specified via arguments. Refer to compute_errors for more details on metrics.
"""
if gt.shape[-2:] != pred.shape[-2:] and interpolate:
pred = nn.functional.interpolate(
# pred, gt.shape[-2:], mode='bilinear', align_corners=True).squeeze()
pred, gt.shape[-2:], mode='bilinear', align_corners=False).squeeze()
pred = pred.squeeze().cpu().numpy()
pred[pred < min_depth_eval] = min_depth_eval
pred[pred > max_depth_eval] = max_depth_eval
pred[np.isinf(pred)] = max_depth_eval
pred[np.isnan(pred)] = min_depth_eval
gt_depth = gt.squeeze().cpu().numpy()
valid_mask = np.logical_and(
gt_depth > min_depth_eval, gt_depth < max_depth_eval)
eval_mask = np.ones(valid_mask.shape)
if garg_crop or eigen_crop:
gt_height, gt_width = gt_depth.shape
eval_mask = np.zeros(valid_mask.shape)
if garg_crop:
eval_mask[int(0.40810811 * gt_height):int(0.99189189 * gt_height),
int(0.03594771 * gt_width):int(0.96405229 * gt_width)] = 1
elif eigen_crop:
# print("-"*10, " EIGEN CROP ", "-"*10)
if dataset == 'kitti':
eval_mask[int(0.3324324 * gt_height):int(0.91351351 * gt_height),
int(0.0359477 * gt_width):int(0.96405229 * gt_width)] = 1
else:
# assert gt_depth.shape == (480, 640), "Error: Eigen crop is currently only valid for (480, 640) images"
eval_mask[45:471, 41:601] = 1
else:
eval_mask = np.ones(valid_mask.shape)
valid_mask = np.logical_and(valid_mask, eval_mask)
# for prompt depth
if additional_mask is not None:
additional_mask = additional_mask.squeeze().detach().cpu().numpy()
valid_mask = np.logical_and(valid_mask, additional_mask)
metrics = compute_errors(gt_depth[valid_mask], pred[valid_mask])
if disp_gt_edges is not None:
edges = disp_gt_edges.squeeze().numpy()
mask = valid_mask.squeeze() # squeeze
mask = np.logical_and(mask, edges)
see_depth = torch.tensor([0])
if mask.sum() > 0:
see_depth_map = soft_edge_error(pred, gt_depth)
see_depth_map_valid = see_depth_map[mask]
see_depth = see_depth_map_valid.mean()
metrics['see'] = see_depth
return metrics
def eps(x):
"""Return the `eps` value for the given `input` dtype. (default=float32 ~= 1.19e-7)"""
dtype = torch.float32 if x is None else x.dtype
return torch.finfo(dtype).eps
def to_log(depth):
"""Convert linear depth into log depth."""
depth = torch.tensor(depth)
depth = (depth > 0) * depth.clamp(min=eps(depth)).log()
return depth
def to_inv(depth):
"""Convert linear depth into disparity."""
depth = torch.tensor(depth)
disp = (depth > 0) / depth.clamp(min=eps(depth))
return disp
def extract_edges(depth,
preprocess=None,
sigma=1,
mask=None,
use_canny=True):
"""Detect edges in a dense LiDAR depth map.
:param depth: (ndarray) (h, w, 1) Dense depth map to extract edges.
:param preprocess: (str) Additional depth map post-processing. (log, inv, none)
:param sigma: (int) Gaussian blurring sigma.
:param mask: (Optional[ndarray]) Optional boolean mask of valid pixels to keep.
:param use_canny: (bool) If `True`, use `Canny` edge detection, otherwise `Sobel`.
:return: (ndarray) (h, w) Detected depth edges in the image.
"""
if preprocess not in {'log', 'inv', 'none', None}:
raise ValueError(f'Invalid depth preprocessing. ({preprocess})')
depth = depth.squeeze()
if preprocess == 'log':
depth = to_log(depth)
elif preprocess == 'inv':
depth = to_inv(depth)
depth -= depth.min()
depth /= depth.max()
else:
depth = torch.tensor(depth)
input_value = (depth > 0) * depth.clamp(min=eps(depth))
# depth = torch.log(input_value) / torch.log(torch.tensor(1.9))
# depth = torch.log(input_value) / torch.log(torch.tensor(1.9))
depth = torch.log(input_value) / torch.log(torch.tensor(1.5))
depth = depth.numpy()
if use_canny:
edges = canny(depth, sigma=sigma, mask=mask)
else:
raise NotImplementedError("Sobel edge detection is not implemented yet.")
return edges