|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
from itertools import product |
|
|
from typing import Tuple |
|
|
|
|
|
import cv2 |
|
|
import numpy as np |
|
|
import torch |
|
|
import torch.nn.functional as F |
|
|
from torch import Tensor |
|
|
|
|
|
|
|
|
def get_simcc_normalized(batch_pred_simcc, sigma=None): |
|
|
"""Normalize the predicted SimCC. |
|
|
|
|
|
Args: |
|
|
batch_pred_simcc (torch.Tensor): The predicted SimCC. |
|
|
sigma (float): The sigma of the Gaussian distribution. |
|
|
|
|
|
Returns: |
|
|
torch.Tensor: The normalized SimCC. |
|
|
""" |
|
|
B, K, _ = batch_pred_simcc.shape |
|
|
|
|
|
|
|
|
if sigma is not None: |
|
|
batch_pred_simcc = batch_pred_simcc / (sigma * np.sqrt(np.pi * 2)) |
|
|
batch_pred_simcc = batch_pred_simcc.clamp(min=0) |
|
|
|
|
|
|
|
|
mask = (batch_pred_simcc.amax(dim=-1) > 1).reshape(B, K, 1) |
|
|
|
|
|
|
|
|
norm = (batch_pred_simcc / batch_pred_simcc.amax(dim=-1).reshape(B, K, 1)) |
|
|
|
|
|
|
|
|
batch_pred_simcc = torch.where(mask, norm, batch_pred_simcc) |
|
|
|
|
|
return batch_pred_simcc |
|
|
|
|
|
|
|
|
def get_simcc_maximum(simcc_x: np.ndarray, |
|
|
simcc_y: np.ndarray) -> Tuple[np.ndarray, np.ndarray]: |
|
|
"""Get maximum response location and value from simcc representations. |
|
|
|
|
|
Note: |
|
|
instance number: N |
|
|
num_keypoints: K |
|
|
heatmap height: H |
|
|
heatmap width: W |
|
|
|
|
|
Args: |
|
|
simcc_x (np.ndarray): x-axis SimCC in shape (K, Wx) or (N, K, Wx) |
|
|
simcc_y (np.ndarray): y-axis SimCC in shape (K, Wy) or (N, K, Wy) |
|
|
|
|
|
Returns: |
|
|
tuple: |
|
|
- locs (np.ndarray): locations of maximum heatmap responses in shape |
|
|
(K, 2) or (N, K, 2) |
|
|
- vals (np.ndarray): values of maximum heatmap responses in shape |
|
|
(K,) or (N, K) |
|
|
""" |
|
|
|
|
|
assert isinstance(simcc_x, np.ndarray), ('simcc_x should be numpy.ndarray') |
|
|
assert isinstance(simcc_y, np.ndarray), ('simcc_y should be numpy.ndarray') |
|
|
assert simcc_x.ndim == 2 or simcc_x.ndim == 3, ( |
|
|
f'Invalid shape {simcc_x.shape}') |
|
|
assert simcc_y.ndim == 2 or simcc_y.ndim == 3, ( |
|
|
f'Invalid shape {simcc_y.shape}') |
|
|
assert simcc_x.ndim == simcc_y.ndim, ( |
|
|
f'{simcc_x.shape} != {simcc_y.shape}') |
|
|
|
|
|
if simcc_x.ndim == 3: |
|
|
N, K, Wx = simcc_x.shape |
|
|
simcc_x = simcc_x.reshape(N * K, -1) |
|
|
simcc_y = simcc_y.reshape(N * K, -1) |
|
|
else: |
|
|
N = None |
|
|
|
|
|
x_locs = np.argmax(simcc_x, axis=1) |
|
|
y_locs = np.argmax(simcc_y, axis=1) |
|
|
locs = np.stack((x_locs, y_locs), axis=-1).astype(np.float32) |
|
|
max_val_x = np.amax(simcc_x, axis=1) |
|
|
max_val_y = np.amax(simcc_y, axis=1) |
|
|
|
|
|
mask = max_val_x > max_val_y |
|
|
max_val_x[mask] = max_val_y[mask] |
|
|
vals = max_val_x |
|
|
locs[vals <= 0.] = -1 |
|
|
|
|
|
if N: |
|
|
locs = locs.reshape(N, K, 2) |
|
|
vals = vals.reshape(N, K) |
|
|
|
|
|
return locs, vals |
|
|
|
|
|
|
|
|
def get_heatmap_maximum(heatmaps: np.ndarray) -> Tuple[np.ndarray, np.ndarray]: |
|
|
"""Get maximum response location and value from heatmaps. |
|
|
|
|
|
Note: |
|
|
batch_size: B |
|
|
num_keypoints: K |
|
|
heatmap height: H |
|
|
heatmap width: W |
|
|
|
|
|
Args: |
|
|
heatmaps (np.ndarray): Heatmaps in shape (K, H, W) or (B, K, H, W) |
|
|
|
|
|
Returns: |
|
|
tuple: |
|
|
- locs (np.ndarray): locations of maximum heatmap responses in shape |
|
|
(K, 2) or (B, K, 2) |
|
|
- vals (np.ndarray): values of maximum heatmap responses in shape |
|
|
(K,) or (B, K) |
|
|
""" |
|
|
assert isinstance(heatmaps, |
|
|
np.ndarray), ('heatmaps should be numpy.ndarray') |
|
|
assert heatmaps.ndim == 3 or heatmaps.ndim == 4, ( |
|
|
f'Invalid shape {heatmaps.shape}') |
|
|
|
|
|
if heatmaps.ndim == 3: |
|
|
K, H, W = heatmaps.shape |
|
|
B = None |
|
|
heatmaps_flatten = heatmaps.reshape(K, -1) |
|
|
else: |
|
|
B, K, H, W = heatmaps.shape |
|
|
heatmaps_flatten = heatmaps.reshape(B * K, -1) |
|
|
|
|
|
y_locs, x_locs = np.unravel_index( |
|
|
np.argmax(heatmaps_flatten, axis=1), shape=(H, W)) |
|
|
locs = np.stack((x_locs, y_locs), axis=-1).astype(np.float32) |
|
|
vals = np.amax(heatmaps_flatten, axis=1) |
|
|
locs[vals <= 0.] = -1 |
|
|
|
|
|
if B: |
|
|
locs = locs.reshape(B, K, 2) |
|
|
vals = vals.reshape(B, K) |
|
|
|
|
|
return locs, vals |
|
|
|
|
|
|
|
|
def gaussian_blur(heatmaps: np.ndarray, kernel: int = 11) -> np.ndarray: |
|
|
"""Modulate heatmap distribution with Gaussian. |
|
|
|
|
|
Note: |
|
|
- num_keypoints: K |
|
|
- heatmap height: H |
|
|
- heatmap width: W |
|
|
|
|
|
Args: |
|
|
heatmaps (np.ndarray[K, H, W]): model predicted heatmaps. |
|
|
kernel (int): Gaussian kernel size (K) for modulation, which should |
|
|
match the heatmap gaussian sigma when training. |
|
|
K=17 for sigma=3 and k=11 for sigma=2. |
|
|
|
|
|
Returns: |
|
|
np.ndarray ([K, H, W]): Modulated heatmap distribution. |
|
|
""" |
|
|
assert kernel % 2 == 1 |
|
|
|
|
|
border = (kernel - 1) // 2 |
|
|
K, H, W = heatmaps.shape |
|
|
|
|
|
for k in range(K): |
|
|
origin_max = np.max(heatmaps[k]) |
|
|
dr = np.zeros((H + 2 * border, W + 2 * border), dtype=np.float32) |
|
|
dr[border:-border, border:-border] = heatmaps[k].copy() |
|
|
dr = cv2.GaussianBlur(dr, (kernel, kernel), 0) |
|
|
heatmaps[k] = dr[border:-border, border:-border].copy() |
|
|
heatmaps[k] *= origin_max / np.max(heatmaps[k]) |
|
|
return heatmaps |
|
|
|
|
|
|
|
|
def gaussian_blur1d(simcc: np.ndarray, kernel: int = 11) -> np.ndarray: |
|
|
"""Modulate simcc distribution with Gaussian. |
|
|
|
|
|
Note: |
|
|
- num_keypoints: K |
|
|
- simcc length: Wx |
|
|
|
|
|
Args: |
|
|
simcc (np.ndarray[K, Wx]): model predicted simcc. |
|
|
kernel (int): Gaussian kernel size (K) for modulation, which should |
|
|
match the simcc gaussian sigma when training. |
|
|
K=17 for sigma=3 and k=11 for sigma=2. |
|
|
|
|
|
Returns: |
|
|
np.ndarray ([K, Wx]): Modulated simcc distribution. |
|
|
""" |
|
|
assert kernel % 2 == 1 |
|
|
|
|
|
border = (kernel - 1) // 2 |
|
|
N, K, Wx = simcc.shape |
|
|
|
|
|
for n, k in product(range(N), range(K)): |
|
|
origin_max = np.max(simcc[n, k]) |
|
|
dr = np.zeros((1, Wx + 2 * border), dtype=np.float32) |
|
|
dr[0, border:-border] = simcc[n, k].copy() |
|
|
dr = cv2.GaussianBlur(dr, (kernel, 1), 0) |
|
|
simcc[n, k] = dr[0, border:-border].copy() |
|
|
simcc[n, k] *= origin_max / np.max(simcc[n, k]) |
|
|
return simcc |
|
|
|
|
|
|
|
|
def batch_heatmap_nms(batch_heatmaps: Tensor, kernel_size: int = 5): |
|
|
"""Apply NMS on a batch of heatmaps. |
|
|
|
|
|
Args: |
|
|
batch_heatmaps (Tensor): batch heatmaps in shape (B, K, H, W) |
|
|
kernel_size (int): The kernel size of the NMS which should be |
|
|
a odd integer. Defaults to 5 |
|
|
|
|
|
Returns: |
|
|
Tensor: The batch heatmaps after NMS. |
|
|
""" |
|
|
|
|
|
assert isinstance(kernel_size, int) and kernel_size % 2 == 1, \ |
|
|
f'The kernel_size should be an odd integer, got {kernel_size}' |
|
|
|
|
|
padding = (kernel_size - 1) // 2 |
|
|
|
|
|
maximum = F.max_pool2d( |
|
|
batch_heatmaps, kernel_size, stride=1, padding=padding) |
|
|
maximum_indicator = torch.eq(batch_heatmaps, maximum) |
|
|
batch_heatmaps = batch_heatmaps * maximum_indicator.float() |
|
|
|
|
|
return batch_heatmaps |
|
|
|