|
|
|
from pathlib import Path
|
|
|
|
from typing import List, Tuple, Union
|
|
|
|
|
|
|
|
import cv2
|
|
|
|
import numpy as np
|
|
|
|
from numpy import ndarray
|
|
|
|
|
|
|
|
# image suffixs
|
|
|
|
SUFFIXS = ('.bmp', '.dng', '.jpeg', '.jpg', '.mpo', '.png', '.tif', '.tiff',
|
|
|
|
'.webp', '.pfm')
|
|
|
|
|
|
|
|
# angle scale
|
|
|
|
ANGLE_SCALE = 1 / np.pi * 180.0
|
|
|
|
|
|
|
|
|
|
|
|
def letterbox(im: ndarray,
|
|
|
|
new_shape: Union[Tuple, List] = (640, 640),
|
|
|
|
color: Union[Tuple, List] = (114, 114, 114)) \
|
|
|
|
-> Tuple[ndarray, float, Tuple[float, float]]:
|
|
|
|
# Resize and pad image while meeting stride-multiple constraints
|
|
|
|
shape = im.shape[:2] # current shape [height, width]
|
|
|
|
if isinstance(new_shape, int):
|
|
|
|
new_shape = (new_shape, new_shape)
|
|
|
|
# new_shape: [width, height]
|
|
|
|
|
|
|
|
# Scale ratio (new / old)
|
|
|
|
r = min(new_shape[0] / shape[1], new_shape[1] / shape[0])
|
|
|
|
# Compute padding [width, height]
|
|
|
|
new_unpad = int(round(shape[1] * r)), int(round(shape[0] * r))
|
|
|
|
dw, dh = new_shape[0] - new_unpad[0], new_shape[1] - new_unpad[
|
|
|
|
1] # wh padding
|
|
|
|
|
|
|
|
dw /= 2 # divide padding into 2 sides
|
|
|
|
dh /= 2
|
|
|
|
|
|
|
|
if shape[::-1] != new_unpad: # resize
|
|
|
|
im = cv2.resize(im, new_unpad, interpolation=cv2.INTER_LINEAR)
|
|
|
|
top, bottom = int(round(dh - 0.1)), int(round(dh + 0.1))
|
|
|
|
left, right = int(round(dw - 0.1)), int(round(dw + 0.1))
|
|
|
|
im = cv2.copyMakeBorder(im,
|
|
|
|
top,
|
|
|
|
bottom,
|
|
|
|
left,
|
|
|
|
right,
|
|
|
|
cv2.BORDER_CONSTANT,
|
|
|
|
value=color) # add border
|
|
|
|
return im, r, (dw, dh)
|
|
|
|
|
|
|
|
|
|
|
|
def blob(im: ndarray, return_seg: bool = False) -> Union[ndarray, Tuple]:
|
|
|
|
seg = None
|
|
|
|
if return_seg:
|
|
|
|
seg = im.astype(np.float32) / 255
|
|
|
|
im = im.transpose([2, 0, 1])
|
|
|
|
im = im[np.newaxis, ...]
|
|
|
|
im = np.ascontiguousarray(im).astype(np.float32) / 255
|
|
|
|
if return_seg:
|
|
|
|
return im, seg
|
|
|
|
else:
|
|
|
|
return im
|
|
|
|
|
|
|
|
|
|
|
|
def sigmoid(x: ndarray) -> ndarray:
|
|
|
|
return 1. / (1. + np.exp(-x))
|
|
|
|
|
|
|
|
|
|
|
|
def path_to_list(images_path: Union[str, Path]) -> List:
|
|
|
|
if isinstance(images_path, str):
|
|
|
|
images_path = Path(images_path)
|
|
|
|
assert images_path.exists()
|
|
|
|
if images_path.is_dir():
|
|
|
|
images = [
|
|
|
|
i.absolute() for i in images_path.iterdir() if i.suffix in SUFFIXS
|
|
|
|
]
|
|
|
|
else:
|
|
|
|
assert images_path.suffix in SUFFIXS
|
|
|
|
images = [images_path.absolute()]
|
|
|
|
return images
|
|
|
|
|
|
|
|
|
|
|
|
def crop_mask(masks: ndarray, bboxes: ndarray) -> ndarray:
|
|
|
|
n, h, w = masks.shape
|
|
|
|
x1, y1, x2, y2 = np.split(bboxes[:, :, None], [1, 2, 3],
|
|
|
|
1) # x1 shape(1,1,n)
|
|
|
|
r = np.arange(w, dtype=x1.dtype)[None, None, :] # rows shape(1,w,1)
|
|
|
|
c = np.arange(h, dtype=x1.dtype)[None, :, None] # cols shape(h,1,1)
|
|
|
|
|
|
|
|
return masks * ((r >= x1) * (r < x2) * (c >= y1) * (c < y2))
|
|
|
|
|
|
|
|
|
|
|
|
def box_iou(box1: ndarray, box2: ndarray) -> float:
|
|
|
|
x11, y11, x21, y21 = box1
|
|
|
|
x12, y12, x22, y22 = box2
|
|
|
|
x1 = max(x11, x12)
|
|
|
|
y1 = max(y11, y12)
|
|
|
|
x2 = min(x21, x22)
|
|
|
|
y2 = min(y21, y22)
|
|
|
|
inter_area = max(0, x2 - x1) * max(0, y2 - y1)
|
|
|
|
union_area = (x21 - x11) * (y21 - y11) + (x22 - x12) * (y22 -
|
|
|
|
y12) - inter_area
|
|
|
|
return max(0, inter_area / union_area)
|
|
|
|
|
|
|
|
|
|
|
|
def NMSBoxes(boxes: ndarray,
|
|
|
|
scores: ndarray,
|
|
|
|
labels: ndarray,
|
|
|
|
iou_thres: float,
|
|
|
|
agnostic: bool = False):
|
|
|
|
num_boxes = boxes.shape[0]
|
|
|
|
order = np.argsort(scores)[::-1]
|
|
|
|
boxes = boxes[order]
|
|
|
|
labels = labels[order]
|
|
|
|
|
|
|
|
indices = []
|
|
|
|
|
|
|
|
for i in range(num_boxes):
|
|
|
|
box_a = boxes[i]
|
|
|
|
label_a = labels[i]
|
|
|
|
keep = True
|
|
|
|
for j in indices:
|
|
|
|
box_b = boxes[j]
|
|
|
|
label_b = labels[j]
|
|
|
|
if not agnostic and label_a != label_b:
|
|
|
|
continue
|
|
|
|
if box_iou(box_a, box_b) > iou_thres:
|
|
|
|
keep = False
|
|
|
|
if keep:
|
|
|
|
indices.append(i)
|
|
|
|
|
|
|
|
indices = np.array(indices, dtype=np.int32)
|
|
|
|
return order[indices]
|
|
|
|
|
|
|
|
|
|
|
|
def det_postprocess(data: Tuple[ndarray, ndarray, ndarray, ndarray]):
|
|
|
|
assert len(data) == 4
|
|
|
|
num_dets, bboxes, scores, labels = (i[0] for i in data)
|
|
|
|
nums = num_dets.item()
|
|
|
|
if nums == 0:
|
|
|
|
return np.empty((0, 4), dtype=np.float32), np.empty(
|
|
|
|
(0, ), dtype=np.float32), np.empty((0, ), dtype=np.int32)
|
|
|
|
# check score negative
|
|
|
|
scores[scores < 0] = 1 + scores[scores < 0]
|
|
|
|
bboxes = bboxes[:nums]
|
|
|
|
scores = scores[:nums]
|
|
|
|
labels = labels[:nums]
|
|
|
|
return bboxes, scores, labels
|
|
|
|
|
|
|
|
|
|
|
|
def seg_postprocess(
|
|
|
|
data: Tuple[ndarray],
|
|
|
|
shape: Union[Tuple, List],
|
|
|
|
conf_thres: float = 0.25,
|
|
|
|
iou_thres: float = 0.65) \
|
|
|
|
-> Tuple[ndarray, ndarray, ndarray, ndarray]:
|
|
|
|
assert len(data) == 2
|
|
|
|
h, w = shape[0] // 4, shape[1] // 4 # 4x downsampling
|
|
|
|
outputs, proto = (i[0] for i in data)
|
|
|
|
bboxes, scores, labels, maskconf = np.split(outputs, [4, 5, 6], 1)
|
|
|
|
scores, labels = scores.squeeze(), labels.squeeze()
|
|
|
|
idx = scores > conf_thres
|
|
|
|
if not idx.any(): # no bounding boxes or seg were created
|
|
|
|
return np.empty((0, 4), dtype=np.float32), \
|
|
|
|
np.empty((0,), dtype=np.float32), \
|
|
|
|
np.empty((0,), dtype=np.int32), \
|
|
|
|
np.empty((0, 0, 0, 0), dtype=np.int32)
|
|
|
|
|
|
|
|
bboxes, scores, labels, maskconf = \
|
|
|
|
bboxes[idx], scores[idx], labels[idx], maskconf[idx]
|
|
|
|
cvbboxes = np.concatenate([bboxes[:, :2], bboxes[:, 2:] - bboxes[:, :2]],
|
|
|
|
1)
|
|
|
|
labels = labels.astype(np.int32)
|
|
|
|
v0, v1 = map(int, (cv2.__version__).split('.')[:2])
|
|
|
|
assert v0 == 4, 'OpenCV version is wrong'
|
|
|
|
if v1 > 6:
|
|
|
|
idx = cv2.dnn.NMSBoxesBatched(cvbboxes, scores, labels, conf_thres,
|
|
|
|
iou_thres)
|
|
|
|
else:
|
|
|
|
idx = cv2.dnn.NMSBoxes(cvbboxes, scores, conf_thres, iou_thres)
|
|
|
|
bboxes, scores, labels, maskconf = \
|
|
|
|
bboxes[idx], scores[idx], labels[idx], maskconf[idx]
|
|
|
|
masks = sigmoid(maskconf @ proto).reshape(-1, h, w)
|
|
|
|
masks = crop_mask(masks, bboxes / 4.)
|
|
|
|
masks = masks.transpose([1, 2, 0])
|
|
|
|
masks = cv2.resize(masks, (shape[1], shape[0]),
|
|
|
|
interpolation=cv2.INTER_LINEAR)
|
|
|
|
masks = masks.transpose(2, 0, 1)
|
|
|
|
masks = np.ascontiguousarray((masks > 0.5)[..., None], dtype=np.float32)
|
|
|
|
return bboxes, scores, labels, masks
|
|
|
|
|
|
|
|
|
|
|
|
def pose_postprocess(
|
|
|
|
data: Union[Tuple, ndarray],
|
|
|
|
conf_thres: float = 0.25,
|
|
|
|
iou_thres: float = 0.65) \
|
|
|
|
-> Tuple[ndarray, ndarray, ndarray]:
|
|
|
|
if isinstance(data, tuple):
|
|
|
|
assert len(data) == 1
|
|
|
|
data = data[0]
|
|
|
|
outputs = np.transpose(data[0], (1, 0))
|
|
|
|
bboxes, scores, kpts = np.split(outputs, [4, 5], 1)
|
|
|
|
scores, kpts = scores.squeeze(), kpts.squeeze()
|
|
|
|
idx = scores > conf_thres
|
|
|
|
if not idx.any(): # no bounding boxes or seg were created
|
|
|
|
return np.empty((0, 4), dtype=np.float32), np.empty(
|
|
|
|
(0, ), dtype=np.float32), np.empty((0, 0, 0), dtype=np.float32)
|
|
|
|
bboxes, scores, kpts = bboxes[idx], scores[idx], kpts[idx]
|
|
|
|
xycenter, wh = np.split(bboxes, [
|
|
|
|
2,
|
|
|
|
], -1)
|
|
|
|
cvbboxes = np.concatenate([xycenter - 0.5 * wh, wh], -1)
|
|
|
|
idx = cv2.dnn.NMSBoxes(cvbboxes, scores, conf_thres, iou_thres)
|
|
|
|
cvbboxes, scores, kpts = cvbboxes[idx], scores[idx], kpts[idx]
|
|
|
|
cvbboxes[:, 2:] += cvbboxes[:, :2]
|
|
|
|
return cvbboxes, scores, kpts.reshape(idx.shape[0], -1, 3)
|
|
|
|
|
|
|
|
|
|
|
|
def obb_postprocess(
|
|
|
|
data: Union[Tuple, ndarray],
|
|
|
|
conf_thres: float = 0.25,
|
|
|
|
iou_thres: float = 0.65) \
|
|
|
|
-> Tuple[ndarray, ndarray, ndarray]:
|
|
|
|
if isinstance(data, tuple):
|
|
|
|
assert len(data) == 1
|
|
|
|
data = data[0]
|
|
|
|
outputs = np.transpose(data[0], (1, 0))
|
|
|
|
num_cls = outputs.shape[-1] - 5
|
|
|
|
bboxes, scores, angles = np.split(outputs, [4, num_cls + 4], 1)
|
|
|
|
scores, labels = scores.max(-1), scores.argmax(-1)
|
|
|
|
scores, labels, angles = scores.squeeze(), labels.squeeze(
|
|
|
|
), angles.squeeze()
|
|
|
|
idx = scores > conf_thres
|
|
|
|
if not idx.any(): # no obbs were created
|
|
|
|
return np.empty((0, 4, 2), dtype=np.float32), np.empty(
|
|
|
|
(0, ), dtype=np.float32), np.empty((0, ), dtype=np.int32)
|
|
|
|
bboxes, scores, labels, angles = bboxes[idx], scores[idx], labels[
|
|
|
|
idx], angles[idx] * ANGLE_SCALE
|
|
|
|
cvrbboxes = [[(xc, yc), (w, h), a]
|
|
|
|
for (xc, yc, w, h), a in zip(bboxes, angles)]
|
|
|
|
idx = cv2.dnn.NMSBoxesRotated(cvrbboxes, scores, conf_thres, iou_thres)
|
|
|
|
points = np.array([cv2.boxPoints(cvrbboxes[i]) for i in idx],
|
|
|
|
dtype=np.float32)
|
|
|
|
return points, scores, labels
|