| | import numpy as np |
| | import cv2 |
| | from itertools import product as product |
| | from math import ceil |
| |
|
| | import torch |
| | import torch.nn.functional as F |
| |
|
| |
|
| | class PriorBox(object): |
| | def __init__(self, cfg, image_size=None, phase="train"): |
| | super(PriorBox, self).__init__() |
| | self.min_sizes = cfg["min_sizes"] |
| | self.steps = cfg["steps"] |
| | self.clip = cfg["clip"] |
| | self.image_size = image_size |
| | self.feature_maps = [ |
| | [ceil(self.image_size[0] / step), ceil(self.image_size[1] / step)] |
| | for step in self.steps |
| | ] |
| |
|
| | def forward(self): |
| | anchors = [] |
| | for k, f in enumerate(self.feature_maps): |
| | min_sizes = self.min_sizes[k] |
| | for i, j in product(range(f[0]), range(f[1])): |
| | for min_size in min_sizes: |
| | s_kx = min_size / self.image_size[1] |
| | s_ky = min_size / self.image_size[0] |
| | dense_cx = [ |
| | x * self.steps[k] / self.image_size[1] for x in [j + 0.5] |
| | ] |
| | dense_cy = [ |
| | y * self.steps[k] / self.image_size[0] for y in [i + 0.5] |
| | ] |
| | for cy, cx in product(dense_cy, dense_cx): |
| | anchors += [cx, cy, s_kx, s_ky] |
| | |
| | output = torch.Tensor(anchors).view(-1, 4) |
| | if self.clip: |
| | output.clamp_(max=1, min=0) |
| | return output |
| |
|
| |
|
| | def py_cpu_nms(dets, thresh): |
| | """Pure Python NMS baseline. |
| | Args: |
| | dets: detections before nms |
| | thresh: nms threshold |
| | Return: |
| | keep: index after nms |
| | """ |
| | x1 = dets[:, 0] |
| | y1 = dets[:, 1] |
| | x2 = dets[:, 2] |
| | y2 = dets[:, 3] |
| | scores = dets[:, 4] |
| | areas = (x2 - x1 + 1) * (y2 - y1 + 1) |
| | order = scores.argsort()[::-1] |
| |
|
| | keep = [] |
| | while order.size > 0: |
| | i = order[0] |
| | keep.append(i) |
| | xx1 = np.maximum(x1[i], x1[order[1:]]) |
| | yy1 = np.maximum(y1[i], y1[order[1:]]) |
| | xx2 = np.minimum(x2[i], x2[order[1:]]) |
| | yy2 = np.minimum(y2[i], y2[order[1:]]) |
| |
|
| | w = np.maximum(0.0, xx2 - xx1 + 1) |
| | h = np.maximum(0.0, yy2 - yy1 + 1) |
| | inter = w * h |
| | ovr = inter / (areas[i] + areas[order[1:]] - inter) |
| |
|
| | inds = np.where(ovr <= thresh)[0] |
| | order = order[inds + 1] |
| | return keep |
| |
|
| |
|
| | def decode(loc, priors, variances): |
| | """Decode locations from predictions using priors to undo |
| | the encoding we did for offset regression at train time. |
| | Args: |
| | loc (tensor): location predictions for loc layers, |
| | Shape: [num_priors,4] |
| | priors (tensor): Prior boxes in center-offset form. |
| | Shape: [num_priors,4]. |
| | variances: (list[float]) Variances of priorboxes |
| | Return: |
| | decoded bounding box predictions |
| | """ |
| |
|
| | boxes = torch.cat( |
| | ( |
| | priors[:, :2] + loc[:, :2] * variances[0] * priors[:, 2:], |
| | priors[:, 2:] * torch.exp(loc[:, 2:] * variances[1]), |
| | ), |
| | 1, |
| | ) |
| | boxes[:, :2] -= boxes[:, 2:] / 2 |
| | boxes[:, 2:] += boxes[:, :2] |
| | return boxes |
| |
|
| |
|
| | def decode_landm(pre, priors, variances): |
| | """Decode landm from predictions using priors to undo |
| | the encoding we did for offset regression at train time. |
| | Args: |
| | pre (tensor): landm predictions for loc layers, |
| | Shape: [num_priors,10] |
| | priors (tensor): Prior boxes in center-offset form. |
| | Shape: [num_priors,4]. |
| | variances: (list[float]) Variances of priorboxes |
| | Return: |
| | decoded landm predictions |
| | """ |
| | landms = torch.cat( |
| | ( |
| | priors[:, :2] + pre[:, :2] * variances[0] * priors[:, 2:], |
| | priors[:, :2] + pre[:, 2:4] * variances[0] * priors[:, 2:], |
| | priors[:, :2] + pre[:, 4:6] * variances[0] * priors[:, 2:], |
| | priors[:, :2] + pre[:, 6:8] * variances[0] * priors[:, 2:], |
| | priors[:, :2] + pre[:, 8:10] * variances[0] * priors[:, 2:], |
| | ), |
| | dim=1, |
| | ) |
| | return landms |
| |
|
| |
|
| | def pad_image(image, h, w, size, padvalue): |
| | pad_image = image.copy() |
| | pad_h = max(size[0] - h, 0) |
| | pad_w = max(size[1] - w, 0) |
| | if pad_h > 0 or pad_w > 0: |
| | pad_image = cv2.copyMakeBorder(image, 0, pad_h, 0, |
| | pad_w, cv2.BORDER_CONSTANT, |
| | value=padvalue) |
| | return pad_image |
| |
|
| |
|
| | def resize_image(image, re_size, keep_ratio=True): |
| | """Resize image |
| | Args: |
| | image: origin image |
| | re_size: resize scale |
| | keep_ratio: keep aspect ratio. Default is set to true. |
| | Returns: |
| | re_image: resized image |
| | resize_ratio: resize ratio |
| | """ |
| | if not keep_ratio: |
| | re_image = cv2.resize(image, (re_size[0], re_size[1])).astype('float32') |
| | return re_image, 0, 0 |
| | ratio = re_size[0] * 1.0 / re_size[1] |
| | h, w = image.shape[0:2] |
| | if h * 1.0 / w <= ratio: |
| | resize_ratio = re_size[1] * 1.0 / w |
| | re_h, re_w = int(h * resize_ratio), re_size[1] |
| | else: |
| | resize_ratio = re_size[0] * 1.0 / h |
| | re_h, re_w = re_size[0], int(w * resize_ratio) |
| | |
| | re_image = cv2.resize(image, (re_w, re_h)).astype('float32') |
| | re_image = pad_image(re_image, re_h, re_w, re_size, (0.0, 0.0, 0.0)) |
| | return re_image, resize_ratio |
| |
|
| |
|
| | def preprocess(img_raw, input_size, device): |
| | """preprocess |
| | Args: |
| | img_raw: origin image |
| | Returns: |
| | img: resized image |
| | scale: resized image scale |
| | resize: resize ratio |
| | """ |
| | img = np.float32(img_raw) |
| | |
| | img, resize = resize_image(img, input_size) |
| | scale = torch.Tensor([img.shape[1], img.shape[0], img.shape[1], img.shape[0]]) |
| | img -= (104, 117, 123) |
| | img = img.transpose(2, 0, 1) |
| | img = torch.from_numpy(img).unsqueeze(0) |
| | img = img.numpy() |
| | scale = scale.to(device) |
| | return img, scale, resize |
| |
|
| |
|
| | def postprocess(cfg, img, outputs, scale, resize, confidence_threshold, nms_threshold, device): |
| | """post_process |
| | Args: |
| | img: resized image |
| | outputs: forward outputs |
| | scale: resized image scale |
| | resize: resize ratio |
| | confidence_threshold: confidence threshold |
| | nms_threshold: non-maximum suppression threshold |
| | Returns: |
| | detetcion results |
| | """ |
| | _, im_height, im_width, _= img.shape |
| | loc = torch.from_numpy(outputs[0]) |
| | conf = torch.from_numpy(outputs[1]) |
| | landms = torch.from_numpy(outputs[2]) |
| | |
| | conf = F.softmax(conf, dim=-1) |
| |
|
| | priorbox = PriorBox(cfg, image_size=(im_height, im_width)) |
| | priors = priorbox.forward() |
| | priors = priors.to(device) |
| | prior_data = priors.data |
| | boxes = decode(loc.squeeze(0), prior_data, cfg["variance"]) |
| | boxes = boxes * scale / resize |
| | boxes = boxes.cpu().numpy() |
| | scores = conf.squeeze(0).data.cpu().numpy()[:, 1] |
| | landms = decode_landm(landms.squeeze(0), prior_data, cfg["variance"]) |
| | scale1 = torch.Tensor( |
| | [img.shape[2], img.shape[1], img.shape[2], img.shape[1], img.shape[2], |
| | img.shape[1], img.shape[2], img.shape[1], img.shape[2], img.shape[1],] |
| | ) |
| | scale1 = scale1.to(device) |
| | landms = landms * scale1 / resize |
| | landms = landms.cpu().numpy() |
| |
|
| | |
| | inds = np.where(scores > confidence_threshold)[0] |
| | boxes = boxes[inds] |
| | landms = landms[inds] |
| | scores = scores[inds] |
| |
|
| | |
| | order = scores.argsort()[::-1] |
| | boxes = boxes[order] |
| | landms = landms[order] |
| | scores = scores[order] |
| |
|
| | |
| | dets = np.hstack((boxes, scores[:, np.newaxis])).astype(np.float32, copy=False) |
| | keep = py_cpu_nms(dets, nms_threshold) |
| | dets = dets[keep, :] |
| | landms = landms[keep] |
| | dets = np.concatenate((dets, landms), axis=1) |
| | return dets |