| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| import numpy as np |
| import paddle |
| import paddle.nn as nn |
| import paddle.nn.functional as F |
| from ppdet.core.workspace import register |
| from ppdet.modeling.bbox_utils import nonempty_bbox |
| from .transformers import bbox_cxcywh_to_xyxy |
| try: |
| from collections.abc import Sequence |
| except Exception: |
| from collections import Sequence |
|
|
| __all__ = [ |
| 'BBoxPostProcess', 'MaskPostProcess', 'JDEBBoxPostProcess', |
| 'CenterNetPostProcess', 'DETRBBoxPostProcess', 'SparsePostProcess' |
| ] |
|
|
|
|
| @register |
| class BBoxPostProcess(object): |
| __shared__ = ['num_classes', 'export_onnx', 'export_eb'] |
| __inject__ = ['decode', 'nms'] |
|
|
| def __init__(self, |
| num_classes=80, |
| decode=None, |
| nms=None, |
| export_onnx=False, |
| export_eb=False): |
| super(BBoxPostProcess, self).__init__() |
| self.num_classes = num_classes |
| self.decode = decode |
| self.nms = nms |
| self.export_onnx = export_onnx |
| self.export_eb = export_eb |
|
|
| def __call__(self, head_out, rois, im_shape, scale_factor): |
| """ |
| Decode the bbox and do NMS if needed. |
| |
| Args: |
| head_out (tuple): bbox_pred and cls_prob of bbox_head output. |
| rois (tuple): roi and rois_num of rpn_head output. |
| im_shape (Tensor): The shape of the input image. |
| scale_factor (Tensor): The scale factor of the input image. |
| export_onnx (bool): whether export model to onnx |
| Returns: |
| bbox_pred (Tensor): The output prediction with shape [N, 6], including |
| labels, scores and bboxes. The size of bboxes are corresponding |
| to the input image, the bboxes may be used in other branch. |
| bbox_num (Tensor): The number of prediction boxes of each batch with |
| shape [1], and is N. |
| """ |
| if self.nms is not None: |
| bboxes, score = self.decode(head_out, rois, im_shape, scale_factor) |
| bbox_pred, bbox_num, before_nms_indexes = self.nms(bboxes, score, self.num_classes) |
|
|
| else: |
| bbox_pred, bbox_num = self.decode(head_out, rois, im_shape, |
| scale_factor) |
|
|
| if self.export_onnx: |
| |
| fake_bboxes = paddle.to_tensor( |
| np.array( |
| [[0., 0.0, 0.0, 0.0, 1.0, 1.0]], dtype='float32')) |
|
|
| bbox_pred = paddle.concat([bbox_pred, fake_bboxes]) |
| bbox_num = bbox_num + 1 |
|
|
| if self.nms is not None: |
| return bbox_pred, bbox_num, before_nms_indexes |
| else: |
| return bbox_pred, bbox_num |
|
|
| def get_pred(self, bboxes, bbox_num, im_shape, scale_factor): |
| """ |
| Rescale, clip and filter the bbox from the output of NMS to |
| get final prediction. |
| |
| Notes: |
| Currently only support bs = 1. |
| |
| Args: |
| bboxes (Tensor): The output bboxes with shape [N, 6] after decode |
| and NMS, including labels, scores and bboxes. |
| bbox_num (Tensor): The number of prediction boxes of each batch with |
| shape [1], and is N. |
| im_shape (Tensor): The shape of the input image. |
| scale_factor (Tensor): The scale factor of the input image. |
| Returns: |
| pred_result (Tensor): The final prediction results with shape [N, 6] |
| including labels, scores and bboxes. |
| """ |
| if self.export_eb: |
| |
| return bboxes, bboxes, bbox_num |
|
|
| if not self.export_onnx: |
| bboxes_list = [] |
| bbox_num_list = [] |
| id_start = 0 |
| fake_bboxes = paddle.to_tensor( |
| np.array( |
| [[0., 0.0, 0.0, 0.0, 1.0, 1.0]], dtype='float32')) |
| fake_bbox_num = paddle.to_tensor(np.array([1], dtype='int32')) |
|
|
| |
| for i in range(bbox_num.shape[0]): |
| if bbox_num[i] == 0: |
| bboxes_i = fake_bboxes |
| bbox_num_i = fake_bbox_num |
| else: |
| bboxes_i = bboxes[id_start:id_start + bbox_num[i], :] |
| bbox_num_i = bbox_num[i] |
| id_start += bbox_num[i] |
| bboxes_list.append(bboxes_i) |
| bbox_num_list.append(bbox_num_i) |
| bboxes = paddle.concat(bboxes_list) |
| bbox_num = paddle.concat(bbox_num_list) |
|
|
| origin_shape = paddle.floor(im_shape / scale_factor + 0.5) |
|
|
| if not self.export_onnx: |
| origin_shape_list = [] |
| scale_factor_list = [] |
| |
| for i in range(bbox_num.shape[0]): |
| expand_shape = paddle.expand(origin_shape[i:i + 1, :], |
| [bbox_num[i], 2]) |
| scale_y, scale_x = scale_factor[i][0], scale_factor[i][1] |
| scale = paddle.concat([scale_x, scale_y, scale_x, scale_y]) |
| expand_scale = paddle.expand(scale, [bbox_num[i], 4]) |
| origin_shape_list.append(expand_shape) |
| scale_factor_list.append(expand_scale) |
|
|
| self.origin_shape_list = paddle.concat(origin_shape_list) |
| scale_factor_list = paddle.concat(scale_factor_list) |
|
|
| else: |
| |
| scale_y, scale_x = scale_factor[0][0], scale_factor[0][1] |
| scale = paddle.concat( |
| [scale_x, scale_y, scale_x, scale_y]).unsqueeze(0) |
| self.origin_shape_list = paddle.expand(origin_shape, |
| [bbox_num[0], 2]) |
| scale_factor_list = paddle.expand(scale, [bbox_num[0], 4]) |
|
|
| |
| pred_label = bboxes[:, 0:1] |
| pred_score = bboxes[:, 1:2] |
| pred_bbox = bboxes[:, 2:] |
| |
| scaled_bbox = pred_bbox / scale_factor_list |
| origin_h = self.origin_shape_list[:, 0] |
| origin_w = self.origin_shape_list[:, 1] |
| zeros = paddle.zeros_like(origin_h) |
| |
| x1 = paddle.maximum(paddle.minimum(scaled_bbox[:, 0], origin_w), zeros) |
| y1 = paddle.maximum(paddle.minimum(scaled_bbox[:, 1], origin_h), zeros) |
| x2 = paddle.maximum(paddle.minimum(scaled_bbox[:, 2], origin_w), zeros) |
| y2 = paddle.maximum(paddle.minimum(scaled_bbox[:, 3], origin_h), zeros) |
| pred_bbox = paddle.stack([x1, y1, x2, y2], axis=-1) |
| |
| keep_mask = nonempty_bbox(pred_bbox, return_mask=True) |
| keep_mask = paddle.unsqueeze(keep_mask, [1]) |
| pred_label = paddle.where(keep_mask, pred_label, |
| paddle.ones_like(pred_label) * -1) |
| pred_result = paddle.concat([pred_label, pred_score, pred_bbox], axis=1) |
| return bboxes, pred_result, bbox_num |
|
|
| def get_origin_shape(self, ): |
| return self.origin_shape_list |
|
|
|
|
| @register |
| class MaskPostProcess(object): |
| __shared__ = ['export_onnx', 'assign_on_cpu'] |
| """ |
| refer to: |
| https://github.com/facebookresearch/detectron2/layers/mask_ops.py |
| |
| Get Mask output according to the output from model |
| """ |
|
|
| def __init__(self, |
| binary_thresh=0.5, |
| export_onnx=False, |
| assign_on_cpu=False): |
| super(MaskPostProcess, self).__init__() |
| self.binary_thresh = binary_thresh |
| self.export_onnx = export_onnx |
| self.assign_on_cpu = assign_on_cpu |
|
|
| def __call__(self, mask_out, bboxes, bbox_num, origin_shape): |
| """ |
| Decode the mask_out and paste the mask to the origin image. |
| |
| Args: |
| mask_out (Tensor): mask_head output with shape [N, 28, 28]. |
| bbox_pred (Tensor): The output bboxes with shape [N, 6] after decode |
| and NMS, including labels, scores and bboxes. |
| bbox_num (Tensor): The number of prediction boxes of each batch with |
| shape [1], and is N. |
| origin_shape (Tensor): The origin shape of the input image, the tensor |
| shape is [N, 2], and each row is [h, w]. |
| Returns: |
| pred_result (Tensor): The final prediction mask results with shape |
| [N, h, w] in binary mask style. |
| """ |
| num_mask = mask_out.shape[0] |
| origin_shape = paddle.cast(origin_shape, 'int32') |
| device = paddle.device.get_device() |
|
|
| if self.export_onnx: |
| h, w = origin_shape[0][0], origin_shape[0][1] |
| mask_onnx = paste_mask(mask_out[:, None, :, :], bboxes[:, 2:], h, w, |
| self.assign_on_cpu) |
| mask_onnx = mask_onnx >= self.binary_thresh |
| pred_result = paddle.cast(mask_onnx, 'int32') |
|
|
| else: |
| max_h = paddle.max(origin_shape[:, 0]) |
| max_w = paddle.max(origin_shape[:, 1]) |
| pred_result = paddle.zeros( |
| [num_mask, max_h, max_w], dtype='int32') - 1 |
|
|
| id_start = 0 |
| for i in range(paddle.shape(bbox_num)[0]): |
| bboxes_i = bboxes[id_start:id_start + bbox_num[i], :] |
| mask_out_i = mask_out[id_start:id_start + bbox_num[i], :, :] |
| im_h = origin_shape[i, 0] |
| im_w = origin_shape[i, 1] |
| pred_mask = paste_mask(mask_out_i[:, None, :, :], |
| bboxes_i[:, 2:], im_h, im_w, |
| self.assign_on_cpu) |
| pred_mask = paddle.cast(pred_mask >= self.binary_thresh, |
| 'int32') |
| pred_result[id_start:id_start + bbox_num[i], :im_h, : |
| im_w] = pred_mask |
| id_start += bbox_num[i] |
| if self.assign_on_cpu: |
| paddle.set_device(device) |
|
|
| return pred_result |
|
|
|
|
| @register |
| class JDEBBoxPostProcess(nn.Layer): |
| __shared__ = ['num_classes'] |
| __inject__ = ['decode', 'nms'] |
|
|
| def __init__(self, num_classes=1, decode=None, nms=None, return_idx=True): |
| super(JDEBBoxPostProcess, self).__init__() |
| self.num_classes = num_classes |
| self.decode = decode |
| self.nms = nms |
| self.return_idx = return_idx |
|
|
| self.fake_bbox_pred = paddle.to_tensor( |
| np.array( |
| [[-1, 0.0, 0.0, 0.0, 0.0, 0.0]], dtype='float32')) |
| self.fake_bbox_num = paddle.to_tensor(np.array([1], dtype='int32')) |
| self.fake_nms_keep_idx = paddle.to_tensor( |
| np.array( |
| [[0]], dtype='int32')) |
|
|
| self.fake_yolo_boxes_out = paddle.to_tensor( |
| np.array( |
| [[[0.0, 0.0, 0.0, 0.0]]], dtype='float32')) |
| self.fake_yolo_scores_out = paddle.to_tensor( |
| np.array( |
| [[[0.0]]], dtype='float32')) |
| self.fake_boxes_idx = paddle.to_tensor(np.array([[0]], dtype='int64')) |
|
|
| def forward(self, head_out, anchors): |
| """ |
| Decode the bbox and do NMS for JDE model. |
| |
| Args: |
| head_out (list): Bbox_pred and cls_prob of bbox_head output. |
| anchors (list): Anchors of JDE model. |
| |
| Returns: |
| boxes_idx (Tensor): The index of kept bboxes after decode 'JDEBox'. |
| bbox_pred (Tensor): The output is the prediction with shape [N, 6] |
| including labels, scores and bboxes. |
| bbox_num (Tensor): The number of prediction of each batch with shape [N]. |
| nms_keep_idx (Tensor): The index of kept bboxes after NMS. |
| """ |
| boxes_idx, yolo_boxes_scores = self.decode(head_out, anchors) |
|
|
| if len(boxes_idx) == 0: |
| boxes_idx = self.fake_boxes_idx |
| yolo_boxes_out = self.fake_yolo_boxes_out |
| yolo_scores_out = self.fake_yolo_scores_out |
| else: |
| yolo_boxes = paddle.gather_nd(yolo_boxes_scores, boxes_idx) |
| |
| yolo_boxes_out = paddle.reshape( |
| yolo_boxes[:, :4], shape=[1, len(boxes_idx), 4]) |
| yolo_scores_out = paddle.reshape( |
| yolo_boxes[:, 4:5], shape=[1, 1, len(boxes_idx)]) |
| boxes_idx = boxes_idx[:, 1:] |
|
|
| if self.return_idx: |
| bbox_pred, bbox_num, nms_keep_idx = self.nms( |
| yolo_boxes_out, yolo_scores_out, self.num_classes) |
| if bbox_pred.shape[0] == 0: |
| bbox_pred = self.fake_bbox_pred |
| bbox_num = self.fake_bbox_num |
| nms_keep_idx = self.fake_nms_keep_idx |
| return boxes_idx, bbox_pred, bbox_num, nms_keep_idx |
| else: |
| bbox_pred, bbox_num, _ = self.nms(yolo_boxes_out, yolo_scores_out, |
| self.num_classes) |
| if bbox_pred.shape[0] == 0: |
| bbox_pred = self.fake_bbox_pred |
| bbox_num = self.fake_bbox_num |
| return _, bbox_pred, bbox_num, _ |
|
|
|
|
| @register |
| class CenterNetPostProcess(object): |
| """ |
| Postprocess the model outputs to get final prediction: |
| 1. Do NMS for heatmap to get top `max_per_img` bboxes. |
| 2. Decode bboxes using center offset and box size. |
| 3. Rescale decoded bboxes reference to the origin image shape. |
| Args: |
| max_per_img(int): the maximum number of predicted objects in a image, |
| 500 by default. |
| down_ratio(int): the down ratio from images to heatmap, 4 by default. |
| regress_ltrb (bool): whether to regress left/top/right/bottom or |
| width/height for a box, true by default. |
| """ |
| __shared__ = ['down_ratio'] |
|
|
| def __init__(self, max_per_img=500, down_ratio=4, regress_ltrb=True): |
| super(CenterNetPostProcess, self).__init__() |
| self.max_per_img = max_per_img |
| self.down_ratio = down_ratio |
| self.regress_ltrb = regress_ltrb |
| |
|
|
| def _simple_nms(self, heat, kernel=3): |
| """ Use maxpool to filter the max score, get local peaks. """ |
| pad = (kernel - 1) // 2 |
| hmax = F.max_pool2d(heat, kernel, stride=1, padding=pad) |
| keep = paddle.cast(hmax == heat, 'float32') |
| return heat * keep |
|
|
| def _topk(self, scores): |
| """ Select top k scores and decode to get xy coordinates. """ |
| k = self.max_per_img |
| shape_fm = paddle.shape(scores) |
| shape_fm.stop_gradient = True |
| cat, height, width = shape_fm[1], shape_fm[2], shape_fm[3] |
| |
| scores_r = paddle.reshape(scores, [cat, -1]) |
| topk_scores, topk_inds = paddle.topk(scores_r, k) |
| topk_ys = topk_inds // width |
| topk_xs = topk_inds % width |
|
|
| topk_score_r = paddle.reshape(topk_scores, [-1]) |
| topk_score, topk_ind = paddle.topk(topk_score_r, k) |
| k_t = paddle.full(paddle.shape(topk_ind), k, dtype='int64') |
| topk_clses = paddle.cast(paddle.floor_divide(topk_ind, k_t), 'float32') |
|
|
| topk_inds = paddle.reshape(topk_inds, [-1]) |
| topk_ys = paddle.reshape(topk_ys, [-1, 1]) |
| topk_xs = paddle.reshape(topk_xs, [-1, 1]) |
| topk_inds = paddle.gather(topk_inds, topk_ind) |
| topk_ys = paddle.gather(topk_ys, topk_ind) |
| topk_xs = paddle.gather(topk_xs, topk_ind) |
| return topk_score, topk_inds, topk_clses, topk_ys, topk_xs |
|
|
| def __call__(self, hm, wh, reg, im_shape, scale_factor): |
| |
| heat = self._simple_nms(hm) |
| scores, inds, topk_clses, ys, xs = self._topk(heat) |
| clses = topk_clses.unsqueeze(1) |
| scores = scores.unsqueeze(1) |
|
|
| |
| reg_t = paddle.transpose(reg, [0, 2, 3, 1]) |
| reg = paddle.reshape(reg_t, [-1, reg_t.shape[-1]]) |
| reg = paddle.gather(reg, inds) |
| xs = paddle.cast(xs, 'float32') |
| ys = paddle.cast(ys, 'float32') |
| xs = xs + reg[:, 0:1] |
| ys = ys + reg[:, 1:2] |
| wh_t = paddle.transpose(wh, [0, 2, 3, 1]) |
| wh = paddle.reshape(wh_t, [-1, wh_t.shape[-1]]) |
| wh = paddle.gather(wh, inds) |
| if self.regress_ltrb: |
| x1 = xs - wh[:, 0:1] |
| y1 = ys - wh[:, 1:2] |
| x2 = xs + wh[:, 2:3] |
| y2 = ys + wh[:, 3:4] |
| else: |
| x1 = xs - wh[:, 0:1] / 2 |
| y1 = ys - wh[:, 1:2] / 2 |
| x2 = xs + wh[:, 0:1] / 2 |
| y2 = ys + wh[:, 1:2] / 2 |
| n, c, feat_h, feat_w = paddle.shape(hm) |
| padw = (feat_w * self.down_ratio - im_shape[0, 1]) / 2 |
| padh = (feat_h * self.down_ratio - im_shape[0, 0]) / 2 |
| x1 = x1 * self.down_ratio |
| y1 = y1 * self.down_ratio |
| x2 = x2 * self.down_ratio |
| y2 = y2 * self.down_ratio |
| x1 = x1 - padw |
| y1 = y1 - padh |
| x2 = x2 - padw |
| y2 = y2 - padh |
| bboxes = paddle.concat([x1, y1, x2, y2], axis=1) |
| scale_y = scale_factor[:, 0:1] |
| scale_x = scale_factor[:, 1:2] |
| scale_expand = paddle.concat( |
| [scale_x, scale_y, scale_x, scale_y], axis=1) |
| boxes_shape = bboxes.shape[:] |
| scale_expand = paddle.expand(scale_expand, shape=boxes_shape) |
| bboxes = paddle.divide(bboxes, scale_expand) |
|
|
| results = paddle.concat([clses, scores, bboxes], axis=1) |
| return results, paddle.shape(results)[0:1], inds, topk_clses, ys, xs |
|
|
|
|
| @register |
| class DETRBBoxPostProcess(object): |
| __shared__ = ['num_classes', 'use_focal_loss'] |
| __inject__ = [] |
|
|
| def __init__(self, |
| num_classes=80, |
| num_top_queries=100, |
| use_focal_loss=False): |
| super(DETRBBoxPostProcess, self).__init__() |
| self.num_classes = num_classes |
| self.num_top_queries = num_top_queries |
| self.use_focal_loss = use_focal_loss |
|
|
| def __call__(self, head_out, im_shape, scale_factor): |
| """ |
| Decode the bbox. |
| |
| Args: |
| head_out (tuple): bbox_pred, cls_logit and masks of bbox_head output. |
| im_shape (Tensor): The shape of the input image. |
| scale_factor (Tensor): The scale factor of the input image. |
| Returns: |
| bbox_pred (Tensor): The output prediction with shape [N, 6], including |
| labels, scores and bboxes. The size of bboxes are corresponding |
| to the input image, the bboxes may be used in other branch. |
| bbox_num (Tensor): The number of prediction boxes of each batch with |
| shape [bs], and is N. |
| """ |
| bboxes, logits, masks = head_out |
|
|
| bbox_pred = bbox_cxcywh_to_xyxy(bboxes) |
| origin_shape = paddle.floor(im_shape / scale_factor + 0.5) |
| img_h, img_w = paddle.split(origin_shape, 2, axis=-1) |
| origin_shape = paddle.concat( |
| [img_w, img_h, img_w, img_h], axis=-1).reshape([-1, 1, 4]) |
| bbox_pred *= origin_shape |
|
|
| scores = F.sigmoid(logits) if self.use_focal_loss else F.softmax( |
| logits)[:, :, :-1] |
|
|
| if not self.use_focal_loss: |
| scores, labels = scores.max(-1), scores.argmax(-1) |
| if scores.shape[1] > self.num_top_queries: |
| scores, index = paddle.topk( |
| scores, self.num_top_queries, axis=-1) |
| batch_ind = paddle.arange( |
| end=scores.shape[0]).unsqueeze(-1).tile( |
| [1, self.num_top_queries]) |
| index = paddle.stack([batch_ind, index], axis=-1) |
| labels = paddle.gather_nd(labels, index) |
| bbox_pred = paddle.gather_nd(bbox_pred, index) |
| else: |
| scores, index = paddle.topk( |
| scores.flatten(1), self.num_top_queries, axis=-1) |
| labels = index % self.num_classes |
| index = index // self.num_classes |
| batch_ind = paddle.arange(end=scores.shape[0]).unsqueeze(-1).tile( |
| [1, self.num_top_queries]) |
| index = paddle.stack([batch_ind, index], axis=-1) |
| bbox_pred = paddle.gather_nd(bbox_pred, index) |
|
|
| bbox_pred = paddle.concat( |
| [ |
| labels.unsqueeze(-1).astype('float32'), scores.unsqueeze(-1), |
| bbox_pred |
| ], |
| axis=-1) |
| bbox_num = paddle.to_tensor( |
| bbox_pred.shape[1], dtype='int32').tile([bbox_pred.shape[0]]) |
| bbox_pred = bbox_pred.reshape([-1, 6]) |
| return bbox_pred, bbox_num |
|
|
|
|
| @register |
| class SparsePostProcess(object): |
| __shared__ = ['num_classes', 'assign_on_cpu'] |
|
|
| def __init__(self, |
| num_proposals, |
| num_classes=80, |
| binary_thresh=0.5, |
| assign_on_cpu=False): |
| super(SparsePostProcess, self).__init__() |
| self.num_classes = num_classes |
| self.num_proposals = num_proposals |
| self.binary_thresh = binary_thresh |
| self.assign_on_cpu = assign_on_cpu |
|
|
| def __call__(self, scores, bboxes, scale_factor, ori_shape, masks=None): |
| assert len(scores) == len(bboxes) == \ |
| len(ori_shape) == len(scale_factor) |
| device = paddle.device.get_device() |
| batch_size = len(ori_shape) |
|
|
| scores = F.sigmoid(scores) |
| has_mask = masks is not None |
| if has_mask: |
| masks = F.sigmoid(masks) |
| masks = masks.reshape([batch_size, -1, *masks.shape[1:]]) |
|
|
| bbox_pred = [] |
| mask_pred = [] if has_mask else None |
| bbox_num = paddle.zeros([batch_size], dtype='int32') |
| for i in range(batch_size): |
| score = scores[i] |
| bbox = bboxes[i] |
| score, indices = score.flatten(0, 1).topk( |
| self.num_proposals, sorted=False) |
| label = indices % self.num_classes |
| if has_mask: |
| mask = masks[i] |
| mask = mask.flatten(0, 1)[indices] |
|
|
| H, W = ori_shape[i][0], ori_shape[i][1] |
| bbox = bbox[paddle.cast(indices / self.num_classes, indices.dtype)] |
| bbox /= scale_factor[i] |
| bbox[:, 0::2] = paddle.clip(bbox[:, 0::2], 0, W) |
| bbox[:, 1::2] = paddle.clip(bbox[:, 1::2], 0, H) |
|
|
| keep = ((bbox[:, 2] - bbox[:, 0]).numpy() > 1.) & \ |
| ((bbox[:, 3] - bbox[:, 1]).numpy() > 1.) |
| if keep.sum() == 0: |
| bbox = paddle.zeros([1, 6], dtype='float32') |
| if has_mask: |
| mask = paddle.zeros([1, H, W], dtype='uint8') |
| else: |
| label = paddle.to_tensor(label.numpy()[keep]).astype( |
| 'float32').unsqueeze(-1) |
| score = paddle.to_tensor(score.numpy()[keep]).astype( |
| 'float32').unsqueeze(-1) |
| bbox = paddle.to_tensor(bbox.numpy()[keep]).astype('float32') |
| if has_mask: |
| mask = paddle.to_tensor(mask.numpy()[keep]).astype( |
| 'float32').unsqueeze(1) |
| mask = paste_mask(mask, bbox, H, W, self.assign_on_cpu) |
| mask = paddle.cast(mask >= self.binary_thresh, 'uint8') |
| bbox = paddle.concat([label, score, bbox], axis=-1) |
|
|
| bbox_num[i] = bbox.shape[0] |
| bbox_pred.append(bbox) |
| if has_mask: |
| mask_pred.append(mask) |
|
|
| bbox_pred = paddle.concat(bbox_pred) |
| mask_pred = paddle.concat(mask_pred) if has_mask else None |
|
|
| if self.assign_on_cpu: |
| paddle.set_device(device) |
|
|
| if has_mask: |
| return bbox_pred, bbox_num, mask_pred |
| else: |
| return bbox_pred, bbox_num |
|
|
|
|
| def paste_mask(masks, boxes, im_h, im_w, assign_on_cpu=False): |
| """ |
| Paste the mask prediction to the original image. |
| """ |
| x0_int, y0_int = 0, 0 |
| x1_int, y1_int = im_w, im_h |
| x0, y0, x1, y1 = paddle.split(boxes, 4, axis=1) |
| N = masks.shape[0] |
| img_y = paddle.arange(y0_int, y1_int) + 0.5 |
| img_x = paddle.arange(x0_int, x1_int) + 0.5 |
|
|
| img_y = (img_y - y0) / (y1 - y0) * 2 - 1 |
| img_x = (img_x - x0) / (x1 - x0) * 2 - 1 |
| |
|
|
| if assign_on_cpu: |
| paddle.set_device('cpu') |
| gx = img_x[:, None, :].expand( |
| [N, paddle.shape(img_y)[1], paddle.shape(img_x)[1]]) |
| gy = img_y[:, :, None].expand( |
| [N, paddle.shape(img_y)[1], paddle.shape(img_x)[1]]) |
| grid = paddle.stack([gx, gy], axis=3) |
| img_masks = F.grid_sample(masks, grid, align_corners=False) |
| return img_masks[:, 0] |
|
|
|
|
| def multiclass_nms(bboxs, num_classes, match_threshold=0.6, match_metric='iou'): |
| final_boxes = [] |
| for c in range(num_classes): |
| idxs = bboxs[:, 0] == c |
| if np.count_nonzero(idxs) == 0: continue |
| r = nms(bboxs[idxs, 1:], match_threshold, match_metric) |
| final_boxes.append(np.concatenate([np.full((r.shape[0], 1), c), r], 1)) |
| return final_boxes |
|
|
|
|
| def nms(dets, match_threshold=0.6, match_metric='iou'): |
| """ Apply NMS to avoid detecting too many overlapping bounding boxes. |
| Args: |
| dets: shape [N, 5], [score, x1, y1, x2, y2] |
| match_metric: 'iou' or 'ios' |
| match_threshold: overlap thresh for match metric. |
| """ |
| if dets.shape[0] == 0: |
| return dets[[], :] |
| scores = dets[:, 0] |
| x1 = dets[:, 1] |
| y1 = dets[:, 2] |
| x2 = dets[:, 3] |
| y2 = dets[:, 4] |
| areas = (x2 - x1 + 1) * (y2 - y1 + 1) |
| order = scores.argsort()[::-1] |
|
|
| ndets = dets.shape[0] |
| suppressed = np.zeros((ndets), dtype=np.int32) |
|
|
| for _i in range(ndets): |
| i = order[_i] |
| if suppressed[i] == 1: |
| continue |
| ix1 = x1[i] |
| iy1 = y1[i] |
| ix2 = x2[i] |
| iy2 = y2[i] |
| iarea = areas[i] |
| for _j in range(_i + 1, ndets): |
| j = order[_j] |
| if suppressed[j] == 1: |
| continue |
| xx1 = max(ix1, x1[j]) |
| yy1 = max(iy1, y1[j]) |
| xx2 = min(ix2, x2[j]) |
| yy2 = min(iy2, y2[j]) |
| w = max(0.0, xx2 - xx1 + 1) |
| h = max(0.0, yy2 - yy1 + 1) |
| inter = w * h |
| if match_metric == 'iou': |
| union = iarea + areas[j] - inter |
| match_value = inter / union |
| elif match_metric == 'ios': |
| smaller = min(iarea, areas[j]) |
| match_value = inter / smaller |
| else: |
| raise ValueError() |
| if match_value >= match_threshold: |
| suppressed[j] = 1 |
| keep = np.where(suppressed == 0)[0] |
| dets = dets[keep, :] |
| return dets |
|
|