| diff --git a/configs/cbnet/cascade_rcnn_cbv2d1_r2_101_mdconv_fpn_20e_fp16_ms400-1400_giou_4conv1f_coco.py b/configs/cbnet/cascade_rcnn_cbv2d1_r2_101_mdconv_fpn_20e_fp16_ms400-1400_giou_4conv1f_coco.py |
| index 167d4379..7c0bd239 100644 |
| --- a/configs/cbnet/cascade_rcnn_cbv2d1_r2_101_mdconv_fpn_20e_fp16_ms400-1400_giou_4conv1f_coco.py |
| +++ b/configs/cbnet/cascade_rcnn_cbv2d1_r2_101_mdconv_fpn_20e_fp16_ms400-1400_giou_4conv1f_coco.py |
| @@ -2,9 +2,9 @@ _base_ = '../res2net/cascade_rcnn_r2_101_fpn_20e_coco.py' |
| |
| model = dict( |
| backbone=dict( |
| - type='CBRes2Net', |
| + type='CBRes2Net', |
| cb_del_stages=1, |
| - cb_inplanes=[64, 256, 512, 1024, 2048], |
| + cb_inplanes=[64, 256, 512, 1024, 2048], |
| dcn=dict(type='DCNv2', deform_groups=1, fallback_on_stride=False), |
| stage_with_dcn=(False, True, True, True) |
| ), |
| @@ -28,7 +28,7 @@ model = dict( |
| target_stds=[0.1, 0.1, 0.2, 0.2]), |
| reg_class_agnostic=False, |
| reg_decoded_bbox=True, |
| - norm_cfg=dict(type='SyncBN', requires_grad=True), |
| + norm_cfg=dict(type='BN', requires_grad=True), |
| loss_cls=dict( |
| type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0), |
| loss_bbox=dict(type='GIoULoss', loss_weight=10.0)), |
| @@ -47,7 +47,7 @@ model = dict( |
| target_stds=[0.05, 0.05, 0.1, 0.1]), |
| reg_class_agnostic=False, |
| reg_decoded_bbox=True, |
| - norm_cfg=dict(type='SyncBN', requires_grad=True), |
| + norm_cfg=dict(type='BN', requires_grad=True), |
| loss_cls=dict( |
| type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0), |
| loss_bbox=dict(type='GIoULoss', loss_weight=10.0)), |
| @@ -66,7 +66,7 @@ model = dict( |
| target_stds=[0.033, 0.033, 0.067, 0.067]), |
| reg_class_agnostic=False, |
| reg_decoded_bbox=True, |
| - norm_cfg=dict(type='SyncBN', requires_grad=True), |
| + norm_cfg=dict(type='BN', requires_grad=True), |
| loss_cls=dict( |
| type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0), |
| loss_bbox=dict(type='GIoULoss', loss_weight=10.0)) |
| diff --git a/configs/cbnet/htc_cbv2_swin_base_patch4_window7_mstrain_400-1400_giou_4conv1f_adamw_20e_coco.py b/configs/cbnet/htc_cbv2_swin_base_patch4_window7_mstrain_400-1400_giou_4conv1f_adamw_20e_coco.py |
| index 51edfd62..a7434c5d 100644 |
| --- a/configs/cbnet/htc_cbv2_swin_base_patch4_window7_mstrain_400-1400_giou_4conv1f_adamw_20e_coco.py |
| +++ b/configs/cbnet/htc_cbv2_swin_base_patch4_window7_mstrain_400-1400_giou_4conv1f_adamw_20e_coco.py |
| @@ -18,7 +18,7 @@ model = dict( |
| target_stds=[0.1, 0.1, 0.2, 0.2]), |
| reg_class_agnostic=True, |
| reg_decoded_bbox=True, |
| - norm_cfg=dict(type='SyncBN', requires_grad=True), |
| + norm_cfg=dict(type='BN', requires_grad=True), |
| loss_cls=dict( |
| type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0), |
| loss_bbox=dict(type='GIoULoss', loss_weight=10.0)), |
| @@ -37,7 +37,7 @@ model = dict( |
| target_stds=[0.05, 0.05, 0.1, 0.1]), |
| reg_class_agnostic=True, |
| reg_decoded_bbox=True, |
| - norm_cfg=dict(type='SyncBN', requires_grad=True), |
| + norm_cfg=dict(type='BN', requires_grad=True), |
| loss_cls=dict( |
| type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0), |
| loss_bbox=dict(type='GIoULoss', loss_weight=10.0)), |
| @@ -56,7 +56,7 @@ model = dict( |
| target_stds=[0.033, 0.033, 0.067, 0.067]), |
| reg_class_agnostic=True, |
| reg_decoded_bbox=True, |
| - norm_cfg=dict(type='SyncBN', requires_grad=True), |
| + norm_cfg=dict(type='BN', requires_grad=True), |
| loss_cls=dict( |
| type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0), |
| loss_bbox=dict(type='GIoULoss', loss_weight=10.0)) |
| diff --git a/mmdet/__init__.py b/mmdet/__init__.py |
| index 646ee84e..9e846286 100644 |
| --- a/mmdet/__init__.py |
| +++ b/mmdet/__init__.py |
| @@ -20,9 +20,9 @@ mmcv_maximum_version = '1.4.0' |
| mmcv_version = digit_version(mmcv.__version__) |
| |
| |
| -assert (mmcv_version >= digit_version(mmcv_minimum_version) |
| - and mmcv_version <= digit_version(mmcv_maximum_version)), \ |
| - f'MMCV=={mmcv.__version__} is used but incompatible. ' \ |
| - f'Please install mmcv>={mmcv_minimum_version}, <={mmcv_maximum_version}.' |
| +#assert (mmcv_version >= digit_version(mmcv_minimum_version) |
| +# and mmcv_version <= digit_version(mmcv_maximum_version)), \ |
| +# f'MMCV=={mmcv.__version__} is used but incompatible. ' \ |
| +# f'Please install mmcv>={mmcv_minimum_version}, <={mmcv_maximum_version}.' |
| |
| __all__ = ['__version__', 'short_version'] |
| diff --git a/mmdet/core/mask/structures.py b/mmdet/core/mask/structures.py |
| index 6f5a62ae..a9d0ebb4 100644 |
| --- a/mmdet/core/mask/structures.py |
| +++ b/mmdet/core/mask/structures.py |
| @@ -1,3 +1,4 @@ |
| +# Copyright (c) OpenMMLab. All rights reserved. |
| from abc import ABCMeta, abstractmethod |
| |
| import cv2 |
| @@ -528,6 +529,21 @@ class BitmapMasks(BaseInstanceMasks): |
| self = cls(masks, height=height, width=width) |
| return self |
| |
| + def get_bboxes(self): |
| + num_masks = len(self) |
| + boxes = np.zeros((num_masks, 4), dtype=np.float32) |
| + x_any = self.masks.any(axis=1) |
| + y_any = self.masks.any(axis=2) |
| + for idx in range(num_masks): |
| + x = np.where(x_any[idx, :])[0] |
| + y = np.where(y_any[idx, :])[0] |
| + if len(x) > 0 and len(y) > 0: |
| + # use +1 for x_max and y_max so that the right and bottom |
| + # boundary of instance masks are fully included by the box |
| + boxes[idx, :] = np.array([x[0], y[0], x[-1] + 1, y[-1] + 1], |
| + dtype=np.float32) |
| + return boxes |
| + |
| |
| class PolygonMasks(BaseInstanceMasks): |
| """This class represents masks in the form of polygons. |
| @@ -637,8 +653,8 @@ class PolygonMasks(BaseInstanceMasks): |
| resized_poly = [] |
| for p in poly_per_obj: |
| p = p.copy() |
| - p[0::2] *= w_scale |
| - p[1::2] *= h_scale |
| + p[0::2] = p[0::2] * w_scale |
| + p[1::2] = p[1::2] * h_scale |
| resized_poly.append(p) |
| resized_masks.append(resized_poly) |
| resized_masks = PolygonMasks(resized_masks, *out_shape) |
| @@ -690,8 +706,8 @@ class PolygonMasks(BaseInstanceMasks): |
| for p in poly_per_obj: |
| # pycocotools will clip the boundary |
| p = p.copy() |
| - p[0::2] -= bbox[0] |
| - p[1::2] -= bbox[1] |
| + p[0::2] = p[0::2] - bbox[0] |
| + p[1::2] = p[1::2] - bbox[1] |
| cropped_poly_per_obj.append(p) |
| cropped_masks.append(cropped_poly_per_obj) |
| cropped_masks = PolygonMasks(cropped_masks, h, w) |
| @@ -736,12 +752,12 @@ class PolygonMasks(BaseInstanceMasks): |
| p = p.copy() |
| # crop |
| # pycocotools will clip the boundary |
| - p[0::2] -= bbox[0] |
| - p[1::2] -= bbox[1] |
| + p[0::2] = p[0::2] - bbox[0] |
| + p[1::2] = p[1::2] - bbox[1] |
| |
| # resize |
| - p[0::2] *= w_scale |
| - p[1::2] *= h_scale |
| + p[0::2] = p[0::2] * w_scale |
| + p[1::2] = p[1::2] * h_scale |
| resized_mask.append(p) |
| resized_masks.append(resized_mask) |
| return PolygonMasks(resized_masks, *out_shape) |
| @@ -944,6 +960,7 @@ class PolygonMasks(BaseInstanceMasks): |
| a list of vertices, in CCW order. |
| """ |
| from scipy.stats import truncnorm |
| + |
| # Generate around the unit circle |
| cx, cy = (0.0, 0.0) |
| radius = 1 |
| @@ -1019,6 +1036,24 @@ class PolygonMasks(BaseInstanceMasks): |
| self = cls(masks, height, width) |
| return self |
| |
| + def get_bboxes(self): |
| + num_masks = len(self) |
| + boxes = np.zeros((num_masks, 4), dtype=np.float32) |
| + for idx, poly_per_obj in enumerate(self.masks): |
| + # simply use a number that is big enough for comparison with |
| + # coordinates |
| + xy_min = np.array([self.width * 2, self.height * 2], |
| + dtype=np.float32) |
| + xy_max = np.zeros(2, dtype=np.float32) |
| + for p in poly_per_obj: |
| + xy = np.array(p).reshape(-1, 2).astype(np.float32) |
| + xy_min = np.minimum(xy_min, np.min(xy, axis=0)) |
| + xy_max = np.maximum(xy_max, np.max(xy, axis=0)) |
| + boxes[idx, :2] = xy_min |
| + boxes[idx, 2:] = xy_max |
| + |
| + return boxes |
| + |
| |
| def polygon_to_bitmap(polygons, height, width): |
| """Convert masks from the form of polygons to bitmaps. |
| @@ -1035,3 +1070,33 @@ def polygon_to_bitmap(polygons, height, width): |
| rle = maskUtils.merge(rles) |
| bitmap_mask = maskUtils.decode(rle).astype(np.bool) |
| return bitmap_mask |
| + |
| + |
| +def bitmap_to_polygon(bitmap): |
| + """Convert masks from the form of bitmaps to polygons. |
| + |
| + Args: |
| + bitmap (ndarray): masks in bitmap representation. |
| + |
| + Return: |
| + list[ndarray]: the converted mask in polygon representation. |
| + bool: whether the mask has holes. |
| + """ |
| + bitmap = np.ascontiguousarray(bitmap).astype(np.uint8) |
| + # cv2.RETR_CCOMP: retrieves all of the contours and organizes them |
| + # into a two-level hierarchy. At the top level, there are external |
| + # boundaries of the components. At the second level, there are |
| + # boundaries of the holes. If there is another contour inside a hole |
| + # of a connected component, it is still put at the top level. |
| + # cv2.CHAIN_APPROX_NONE: stores absolutely all the contour points. |
| + outs = cv2.findContours(bitmap, cv2.RETR_CCOMP, cv2.CHAIN_APPROX_NONE) |
| + contours = outs[-2] |
| + hierarchy = outs[-1] |
| + if hierarchy is None: |
| + return [], False |
| + # hierarchy[i]: 4 elements, for the indexes of next, previous, |
| + # parent, or nested contours. If there is no corresponding contour, |
| + # it will be -1. |
| + with_hole = (hierarchy.reshape(-1, 4)[:, 3] >= 0).any() |
| + contours = [c.reshape(-1, 2) for c in contours] |
| + return contours, with_hole |
| diff --git a/mmdet/core/visualization/image.py b/mmdet/core/visualization/image.py |
| index 5a148384..66f82a38 100644 |
| --- a/mmdet/core/visualization/image.py |
| +++ b/mmdet/core/visualization/image.py |
| @@ -1,3 +1,5 @@ |
| +# Copyright (c) OpenMMLab. All rights reserved. |
| +import cv2 |
| import matplotlib.pyplot as plt |
| import mmcv |
| import numpy as np |
| @@ -5,17 +7,25 @@ import pycocotools.mask as mask_util |
| from matplotlib.collections import PatchCollection |
| from matplotlib.patches import Polygon |
| |
| +#from mmdet.core.evaluation.panoptic_utils import INSTANCE_OFFSET |
| +from ..mask.structures import bitmap_to_polygon |
| from ..utils import mask2ndarray |
| +from .palette import get_palette, palette_val |
| + |
| +__all__ = [ |
| + 'color_val_matplotlib', 'draw_masks', 'draw_bboxes', 'draw_labels', |
| + 'imshow_det_bboxes', 'imshow_gt_det_bboxes' |
| +] |
| |
| EPS = 1e-2 |
| |
| |
| def color_val_matplotlib(color): |
| """Convert various input in BGR order to normalized RGB matplotlib color |
| - tuples, |
| + tuples. |
| |
| Args: |
| - color (:obj:`Color`/str/tuple/int/ndarray): Color inputs |
| + color (:obj`Color` | str | tuple | int | ndarray): Color inputs. |
| |
| Returns: |
| tuple[float]: A tuple of 3 normalized floats indicating RGB channels. |
| @@ -25,9 +35,177 @@ def color_val_matplotlib(color): |
| return tuple(color) |
| |
| |
| +def _get_adaptive_scales(areas, min_area=800, max_area=30000): |
| + """Get adaptive scales according to areas. |
| + |
| + The scale range is [0.5, 1.0]. When the area is less than |
| + ``'min_area'``, the scale is 0.5 while the area is larger than |
| + ``'max_area'``, the scale is 1.0. |
| + |
| + Args: |
| + areas (ndarray): The areas of bboxes or masks with the |
| + shape of (n, ). |
| + min_area (int): Lower bound areas for adaptive scales. |
| + Default: 800. |
| + max_area (int): Upper bound areas for adaptive scales. |
| + Default: 30000. |
| + |
| + Returns: |
| + ndarray: The adaotive scales with the shape of (n, ). |
| + """ |
| + scales = 0.5 + (areas - min_area) / (max_area - min_area) |
| + scales = np.clip(scales, 0.5, 1.0) |
| + return scales |
| + |
| + |
| +def _get_bias_color(base, max_dist=30): |
| + """Get different colors for each masks. |
| + |
| + Get different colors for each masks by adding a bias |
| + color to the base category color. |
| + Args: |
| + base (ndarray): The base category color with the shape |
| + of (3, ). |
| + max_dist (int): The max distance of bias. Default: 30. |
| + |
| + Returns: |
| + ndarray: The new color for a mask with the shape of (3, ). |
| + """ |
| + new_color = base + np.random.randint( |
| + low=-max_dist, high=max_dist + 1, size=3) |
| + return np.clip(new_color, 0, 255, new_color) |
| + |
| + |
| +def draw_bboxes(ax, bboxes, color='g', alpha=0.8, thickness=2): |
| + """Draw bounding boxes on the axes. |
| + |
| + Args: |
| + ax (matplotlib.Axes): The input axes. |
| + bboxes (ndarray): The input bounding boxes with the shape |
| + of (n, 4). |
| + color (list[tuple] | matplotlib.color): the colors for each |
| + bounding boxes. |
| + alpha (float): Transparency of bounding boxes. Default: 0.8. |
| + thickness (int): Thickness of lines. Default: 2. |
| + |
| + Returns: |
| + matplotlib.Axes: The result axes. |
| + """ |
| + polygons = [] |
| + for i, bbox in enumerate(bboxes): |
| + bbox_int = bbox.astype(np.int32) |
| + poly = [[bbox_int[0], bbox_int[1]], [bbox_int[0], bbox_int[3]], |
| + [bbox_int[2], bbox_int[3]], [bbox_int[2], bbox_int[1]]] |
| + np_poly = np.array(poly).reshape((4, 2)) |
| + polygons.append(Polygon(np_poly)) |
| + p = PatchCollection( |
| + polygons, |
| + facecolor='none', |
| + edgecolors=color, |
| + linewidths=thickness, |
| + alpha=alpha) |
| + ax.add_collection(p) |
| + |
| + return ax |
| + |
| + |
| +def draw_labels(ax, |
| + labels, |
| + positions, |
| + scores=None, |
| + class_names=None, |
| + color='w', |
| + font_size=8, |
| + scales=None, |
| + horizontal_alignment='left'): |
| + """Draw labels on the axes. |
| + |
| + Args: |
| + ax (matplotlib.Axes): The input axes. |
| + labels (ndarray): The labels with the shape of (n, ). |
| + positions (ndarray): The positions to draw each labels. |
| + scores (ndarray): The scores for each labels. |
| + class_names (list[str]): The class names. |
| + color (list[tuple] | matplotlib.color): The colors for labels. |
| + font_size (int): Font size of texts. Default: 8. |
| + scales (list[float]): Scales of texts. Default: None. |
| + horizontal_alignment (str): The horizontal alignment method of |
| + texts. Default: 'left'. |
| + |
| + Returns: |
| + matplotlib.Axes: The result axes. |
| + """ |
| + for i, (pos, label) in enumerate(zip(positions, labels)): |
| + label_text = class_names[ |
| + label] if class_names is not None else f'class {label}' |
| + if scores is not None: |
| + label_text += f'|{scores[i]:.02f}' |
| + text_color = color[i] if isinstance(color, list) else color |
| + |
| + font_size_mask = font_size if scales is None else font_size * scales[i] |
| + ax.text( |
| + pos[0], |
| + pos[1], |
| + f'{label_text}', |
| + bbox={ |
| + 'facecolor': 'black', |
| + 'alpha': 0.8, |
| + 'pad': 0.7, |
| + 'edgecolor': 'none' |
| + }, |
| + color=text_color, |
| + fontsize=font_size_mask, |
| + verticalalignment='top', |
| + horizontalalignment=horizontal_alignment) |
| + |
| + return ax |
| + |
| + |
| +def draw_masks(ax, img, masks, color=None, with_edge=True, alpha=0.8): |
| + """Draw masks on the image and their edges on the axes. |
| + |
| + Args: |
| + ax (matplotlib.Axes): The input axes. |
| + img (ndarray): The image with the shape of (3, h, w). |
| + masks (ndarray): The masks with the shape of (n, h, w). |
| + color (ndarray): The colors for each masks with the shape |
| + of (n, 3). |
| + with_edge (bool): Whether to draw edges. Default: True. |
| + alpha (float): Transparency of bounding boxes. Default: 0.8. |
| + |
| + Returns: |
| + matplotlib.Axes: The result axes. |
| + ndarray: The result image. |
| + """ |
| + taken_colors = set([0, 0, 0]) |
| + if color is None: |
| + random_colors = np.random.randint(0, 255, (masks.size(0), 3)) |
| + color = [tuple(c) for c in random_colors] |
| + color = np.array(color, dtype=np.uint8) |
| + polygons = [] |
| + for i, mask in enumerate(masks): |
| + if with_edge: |
| + contours, _ = bitmap_to_polygon(mask) |
| + polygons += [Polygon(c) for c in contours] |
| + |
| + color_mask = color[i] |
| + while tuple(color_mask) in taken_colors: |
| + color_mask = _get_bias_color(color_mask) |
| + taken_colors.add(tuple(color_mask)) |
| + |
| + mask = mask.astype(bool) |
| + img[mask] = img[mask] * (1 - alpha) + color_mask * alpha |
| + |
| + p = PatchCollection( |
| + polygons, facecolor='none', edgecolors='w', linewidths=1, alpha=0.8) |
| + ax.add_collection(p) |
| + |
| + return ax, img |
| + |
| + |
| def imshow_det_bboxes(img, |
| - bboxes, |
| - labels, |
| + bboxes=None, |
| + labels=None, |
| segms=None, |
| class_names=None, |
| score_thr=0, |
| @@ -35,7 +213,7 @@ def imshow_det_bboxes(img, |
| text_color='green', |
| mask_color=None, |
| thickness=2, |
| - font_size=13, |
| + font_size=8, |
| win_name='', |
| show=True, |
| wait_time=0, |
| @@ -43,43 +221,51 @@ def imshow_det_bboxes(img, |
| """Draw bboxes and class labels (with scores) on an image. |
| |
| Args: |
| - img (str or ndarray): The image to be displayed. |
| + img (str | ndarray): The image to be displayed. |
| bboxes (ndarray): Bounding boxes (with scores), shaped (n, 4) or |
| (n, 5). |
| labels (ndarray): Labels of bboxes. |
| - segms (ndarray or None): Masks, shaped (n,h,w) or None |
| + segms (ndarray | None): Masks, shaped (n,h,w) or None. |
| class_names (list[str]): Names of each classes. |
| - score_thr (float): Minimum score of bboxes to be shown. Default: 0 |
| - bbox_color (str or tuple(int) or :obj:`Color`):Color of bbox lines. |
| - The tuple of color should be in BGR order. Default: 'green' |
| - text_color (str or tuple(int) or :obj:`Color`):Color of texts. |
| - The tuple of color should be in BGR order. Default: 'green' |
| - mask_color (str or tuple(int) or :obj:`Color`, optional): |
| - Color of masks. The tuple of color should be in BGR order. |
| - Default: None |
| - thickness (int): Thickness of lines. Default: 2 |
| - font_size (int): Font size of texts. Default: 13 |
| - show (bool): Whether to show the image. Default: True |
| - win_name (str): The window name. Default: '' |
| + score_thr (float): Minimum score of bboxes to be shown. Default: 0. |
| + bbox_color (list[tuple] | tuple | str | None): Colors of bbox lines. |
| + If a single color is given, it will be applied to all classes. |
| + The tuple of color should be in RGB order. Default: 'green'. |
| + text_color (list[tuple] | tuple | str | None): Colors of texts. |
| + If a single color is given, it will be applied to all classes. |
| + The tuple of color should be in RGB order. Default: 'green'. |
| + mask_color (list[tuple] | tuple | str | None, optional): Colors of |
| + masks. If a single color is given, it will be applied to all |
| + classes. The tuple of color should be in RGB order. |
| + Default: None. |
| + thickness (int): Thickness of lines. Default: 2. |
| + font_size (int): Font size of texts. Default: 13. |
| + show (bool): Whether to show the image. Default: True. |
| + win_name (str): The window name. Default: ''. |
| wait_time (float): Value of waitKey param. Default: 0. |
| out_file (str, optional): The filename to write the image. |
| - Default: None |
| + Default: None. |
| |
| Returns: |
| ndarray: The image with bboxes drawn on it. |
| """ |
| - assert bboxes.ndim == 2, \ |
| + assert bboxes is None or bboxes.ndim == 2, \ |
| f' bboxes ndim should be 2, but its ndim is {bboxes.ndim}.' |
| assert labels.ndim == 1, \ |
| f' labels ndim should be 1, but its ndim is {labels.ndim}.' |
| - assert bboxes.shape[0] == labels.shape[0], \ |
| - 'bboxes.shape[0] and labels.shape[0] should have the same length.' |
| - assert bboxes.shape[1] == 4 or bboxes.shape[1] == 5, \ |
| + assert bboxes is None or bboxes.shape[1] == 4 or bboxes.shape[1] == 5, \ |
| f' bboxes.shape[1] should be 4 or 5, but its {bboxes.shape[1]}.' |
| + assert bboxes is None or bboxes.shape[0] <= labels.shape[0], \ |
| + 'labels.shape[0] should not be less than bboxes.shape[0].' |
| + assert segms is None or segms.shape[0] == labels.shape[0], \ |
| + 'segms.shape[0] and labels.shape[0] should have the same length.' |
| + assert segms is not None or bboxes is not None, \ |
| + 'segms and bboxes should not be None at the same time.' |
| + |
| img = mmcv.imread(img).astype(np.uint8) |
| |
| if score_thr > 0: |
| - assert bboxes.shape[1] == 5 |
| + assert bboxes is not None and bboxes.shape[1] == 5 |
| scores = bboxes[:, -1] |
| inds = scores > score_thr |
| bboxes = bboxes[inds, :] |
| @@ -87,25 +273,6 @@ def imshow_det_bboxes(img, |
| if segms is not None: |
| segms = segms[inds, ...] |
| |
| - mask_colors = [] |
| - if labels.shape[0] > 0: |
| - if mask_color is None: |
| - # random color |
| - np.random.seed(42) |
| - mask_colors = [ |
| - np.random.randint(0, 256, (1, 3), dtype=np.uint8) |
| - for _ in range(max(labels) + 1) |
| - ] |
| - else: |
| - # specify color |
| - mask_colors = [ |
| - np.array(mmcv.color_val(mask_color)[::-1], dtype=np.uint8) |
| - ] * ( |
| - max(labels) + 1) |
| - |
| - bbox_color = color_val_matplotlib(bbox_color) |
| - text_color = color_val_matplotlib(text_color) |
| - |
| img = mmcv.bgr2rgb(img) |
| width, height = img.shape[1], img.shape[0] |
| img = np.ascontiguousarray(img) |
| @@ -123,44 +290,64 @@ def imshow_det_bboxes(img, |
| ax = plt.gca() |
| ax.axis('off') |
| |
| - polygons = [] |
| - color = [] |
| - for i, (bbox, label) in enumerate(zip(bboxes, labels)): |
| - bbox_int = bbox.astype(np.int32) |
| - poly = [[bbox_int[0], bbox_int[1]], [bbox_int[0], bbox_int[3]], |
| - [bbox_int[2], bbox_int[3]], [bbox_int[2], bbox_int[1]]] |
| - np_poly = np.array(poly).reshape((4, 2)) |
| - polygons.append(Polygon(np_poly)) |
| - color.append(bbox_color) |
| - label_text = class_names[ |
| - label] if class_names is not None else f'class {label}' |
| - if len(bbox) > 4: |
| - label_text += f'|{bbox[-1]:.02f}' |
| - ax.text( |
| - bbox_int[0], |
| - bbox_int[1], |
| - f'{label_text}', |
| - bbox={ |
| - 'facecolor': 'black', |
| - 'alpha': 0.8, |
| - 'pad': 0.7, |
| - 'edgecolor': 'none' |
| - }, |
| - color=text_color, |
| - fontsize=font_size, |
| - verticalalignment='top', |
| - horizontalalignment='left') |
| - if segms is not None: |
| - color_mask = mask_colors[labels[i]] |
| - mask = segms[i].astype(bool) |
| - img[mask] = img[mask] * 0.5 + color_mask * 0.5 |
| + max_label = int(max(labels) if len(labels) > 0 else 0) |
| + text_palette = palette_val(get_palette(text_color, max_label + 1)) |
| + text_colors = [text_palette[label] for label in labels] |
| + |
| + num_bboxes = 0 |
| + if bboxes is not None: |
| + num_bboxes = bboxes.shape[0] |
| + bbox_palette = palette_val(get_palette(bbox_color, max_label + 1)) |
| + colors = [bbox_palette[label] for label in labels[:num_bboxes]] |
| + draw_bboxes(ax, bboxes, colors, alpha=0.8, thickness=thickness) |
| + |
| + horizontal_alignment = 'left' |
| + positions = bboxes[:, :2].astype(np.int32) + thickness |
| + areas = (bboxes[:, 3] - bboxes[:, 1]) * (bboxes[:, 2] - bboxes[:, 0]) |
| + scales = _get_adaptive_scales(areas) |
| + scores = bboxes[:, 4] if bboxes.shape[1] == 5 else None |
| + draw_labels( |
| + ax, |
| + labels[:num_bboxes], |
| + positions, |
| + scores=scores, |
| + class_names=class_names, |
| + color=text_colors, |
| + font_size=font_size, |
| + scales=scales, |
| + horizontal_alignment=horizontal_alignment) |
| + |
| + if segms is not None: |
| + mask_palette = get_palette(mask_color, max_label + 1) |
| + colors = [mask_palette[label] for label in labels] |
| + colors = np.array(colors, dtype=np.uint8) |
| + draw_masks(ax, img, segms, colors, with_edge=True) |
| + |
| + if num_bboxes < segms.shape[0]: |
| + segms = segms[num_bboxes:] |
| + horizontal_alignment = 'center' |
| + areas = [] |
| + positions = [] |
| + for mask in segms: |
| + _, _, stats, centroids = cv2.connectedComponentsWithStats( |
| + mask.astype(np.uint8), connectivity=8) |
| + largest_id = np.argmax(stats[1:, -1]) + 1 |
| + positions.append(centroids[largest_id]) |
| + areas.append(stats[largest_id, -1]) |
| + areas = np.stack(areas, axis=0) |
| + scales = _get_adaptive_scales(areas) |
| + draw_labels( |
| + ax, |
| + labels[num_bboxes:], |
| + positions, |
| + class_names=class_names, |
| + color=text_colors, |
| + font_size=font_size, |
| + scales=scales, |
| + horizontal_alignment=horizontal_alignment) |
| |
| plt.imshow(img) |
| |
| - p = PatchCollection( |
| - polygons, facecolor='none', edgecolors=color, linewidths=thickness) |
| - ax.add_collection(p) |
| - |
| stream, _ = canvas.print_to_buffer() |
| buffer = np.frombuffer(stream, dtype='uint8') |
| img_rgba = buffer.reshape(height, width, 4) |
| @@ -191,12 +378,12 @@ def imshow_gt_det_bboxes(img, |
| result, |
| class_names=None, |
| score_thr=0, |
| - gt_bbox_color=(255, 102, 61), |
| - gt_text_color=(255, 102, 61), |
| - gt_mask_color=(255, 102, 61), |
| - det_bbox_color=(72, 101, 241), |
| - det_text_color=(72, 101, 241), |
| - det_mask_color=(72, 101, 241), |
| + gt_bbox_color=(61, 102, 255), |
| + gt_text_color=(200, 200, 200), |
| + gt_mask_color=(61, 102, 255), |
| + det_bbox_color=(241, 101, 72), |
| + det_text_color=(200, 200, 200), |
| + det_mask_color=(241, 101, 72), |
| thickness=2, |
| font_size=13, |
| win_name='', |
| @@ -206,54 +393,75 @@ def imshow_gt_det_bboxes(img, |
| """General visualization GT and result function. |
| |
| Args: |
| - img (str or ndarray): The image to be displayed.) |
| + img (str | ndarray): The image to be displayed. |
| annotation (dict): Ground truth annotations where contain keys of |
| - 'gt_bboxes' and 'gt_labels' or 'gt_masks' |
| - result (tuple[list] or list): The detection result, can be either |
| + 'gt_bboxes' and 'gt_labels' or 'gt_masks'. |
| + result (tuple[list] | list): The detection result, can be either |
| (bbox, segm) or just bbox. |
| class_names (list[str]): Names of each classes. |
| - score_thr (float): Minimum score of bboxes to be shown. Default: 0 |
| - gt_bbox_color (str or tuple(int) or :obj:`Color`):Color of bbox lines. |
| - The tuple of color should be in BGR order. Default: (255, 102, 61) |
| - gt_text_color (str or tuple(int) or :obj:`Color`):Color of texts. |
| - The tuple of color should be in BGR order. Default: (255, 102, 61) |
| - gt_mask_color (str or tuple(int) or :obj:`Color`, optional): |
| - Color of masks. The tuple of color should be in BGR order. |
| - Default: (255, 102, 61) |
| - det_bbox_color (str or tuple(int) or :obj:`Color`):Color of bbox lines. |
| - The tuple of color should be in BGR order. Default: (72, 101, 241) |
| - det_text_color (str or tuple(int) or :obj:`Color`):Color of texts. |
| - The tuple of color should be in BGR order. Default: (72, 101, 241) |
| - det_mask_color (str or tuple(int) or :obj:`Color`, optional): |
| - Color of masks. The tuple of color should be in BGR order. |
| - Default: (72, 101, 241) |
| - thickness (int): Thickness of lines. Default: 2 |
| - font_size (int): Font size of texts. Default: 13 |
| - win_name (str): The window name. Default: '' |
| - show (bool): Whether to show the image. Default: True |
| + score_thr (float): Minimum score of bboxes to be shown. Default: 0. |
| + gt_bbox_color (list[tuple] | tuple | str | None): Colors of bbox lines. |
| + If a single color is given, it will be applied to all classes. |
| + The tuple of color should be in RGB order. Default: (61, 102, 255). |
| + gt_text_color (list[tuple] | tuple | str | None): Colors of texts. |
| + If a single color is given, it will be applied to all classes. |
| + The tuple of color should be in RGB order. Default: (200, 200, 200). |
| + gt_mask_color (list[tuple] | tuple | str | None, optional): Colors of |
| + masks. If a single color is given, it will be applied to all classes. |
| + The tuple of color should be in RGB order. Default: (61, 102, 255). |
| + det_bbox_color (list[tuple] | tuple | str | None):Colors of bbox lines. |
| + If a single color is given, it will be applied to all classes. |
| + The tuple of color should be in RGB order. Default: (241, 101, 72). |
| + det_text_color (list[tuple] | tuple | str | None):Colors of texts. |
| + If a single color is given, it will be applied to all classes. |
| + The tuple of color should be in RGB order. Default: (200, 200, 200). |
| + det_mask_color (list[tuple] | tuple | str | None, optional): Color of |
| + masks. If a single color is given, it will be applied to all classes. |
| + The tuple of color should be in RGB order. Default: (241, 101, 72). |
| + thickness (int): Thickness of lines. Default: 2. |
| + font_size (int): Font size of texts. Default: 13. |
| + win_name (str): The window name. Default: ''. |
| + show (bool): Whether to show the image. Default: True. |
| wait_time (float): Value of waitKey param. Default: 0. |
| out_file (str, optional): The filename to write the image. |
| - Default: None |
| + Default: None. |
| |
| Returns: |
| ndarray: The image with bboxes or masks drawn on it. |
| """ |
| assert 'gt_bboxes' in annotation |
| assert 'gt_labels' in annotation |
| - assert isinstance( |
| - result, |
| - (tuple, list)), f'Expected tuple or list, but get {type(result)}' |
| + assert isinstance(result, (tuple, list, dict)), 'Expected ' \ |
| + f'tuple or list or dict, but get {type(result)}' |
| |
| + gt_bboxes = annotation['gt_bboxes'] |
| + gt_labels = annotation['gt_labels'] |
| gt_masks = annotation.get('gt_masks', None) |
| if gt_masks is not None: |
| gt_masks = mask2ndarray(gt_masks) |
| |
| + gt_seg = annotation.get('gt_semantic_seg', None) |
| + if gt_seg is not None: |
| + pad_value = 255 # the padding value of gt_seg |
| + sem_labels = np.unique(gt_seg) |
| + all_labels = np.concatenate((gt_labels, sem_labels), axis=0) |
| + all_labels, counts = np.unique(all_labels, return_counts=True) |
| + stuff_labels = all_labels[np.logical_and(counts < 2, |
| + all_labels != pad_value)] |
| + stuff_masks = gt_seg[None] == stuff_labels[:, None, None] |
| + gt_labels = np.concatenate((gt_labels, stuff_labels), axis=0) |
| + gt_masks = np.concatenate((gt_masks, stuff_masks.astype(np.uint8)), |
| + axis=0) |
| + # If you need to show the bounding boxes, |
| + # please comment the following line |
| + # gt_bboxes = None |
| + |
| img = mmcv.imread(img) |
| |
| img = imshow_det_bboxes( |
| img, |
| - annotation['gt_bboxes'], |
| - annotation['gt_labels'], |
| + gt_bboxes, |
| + gt_labels, |
| gt_masks, |
| class_names=class_names, |
| bbox_color=gt_bbox_color, |
| @@ -264,25 +472,38 @@ def imshow_gt_det_bboxes(img, |
| win_name=win_name, |
| show=False) |
| |
| - if isinstance(result, tuple): |
| - bbox_result, segm_result = result |
| - if isinstance(segm_result, tuple): |
| - segm_result = segm_result[0] # ms rcnn |
| + if not isinstance(result, dict): |
| + if isinstance(result, tuple): |
| + bbox_result, segm_result = result |
| + if isinstance(segm_result, tuple): |
| + segm_result = segm_result[0] # ms rcnn |
| + else: |
| + bbox_result, segm_result = result, None |
| + |
| + bboxes = np.vstack(bbox_result) |
| + labels = [ |
| + np.full(bbox.shape[0], i, dtype=np.int32) |
| + for i, bbox in enumerate(bbox_result) |
| + ] |
| + labels = np.concatenate(labels) |
| + |
| + segms = None |
| + if segm_result is not None and len(labels) > 0: # non empty |
| + segms = mmcv.concat_list(segm_result) |
| + segms = mask_util.decode(segms) |
| + segms = segms.transpose(2, 0, 1) |
| else: |
| - bbox_result, segm_result = result, None |
| - |
| - bboxes = np.vstack(bbox_result) |
| - labels = [ |
| - np.full(bbox.shape[0], i, dtype=np.int32) |
| - for i, bbox in enumerate(bbox_result) |
| - ] |
| - labels = np.concatenate(labels) |
| - |
| - segms = None |
| - if segm_result is not None and len(labels) > 0: # non empty |
| - segms = mmcv.concat_list(segm_result) |
| - segms = mask_util.decode(segms) |
| - segms = segms.transpose(2, 0, 1) |
| + assert class_names is not None, 'We need to know the number ' \ |
| + 'of classes.' |
| + VOID = len(class_names) |
| + bboxes = None |
| + pan_results = result['pan_results'] |
| + # keep objects ahead |
| + ids = np.unique(pan_results)[::-1] |
| + legal_indices = ids != VOID |
| + ids = ids[legal_indices] |
| + labels = np.array([id % INSTANCE_OFFSET for id in ids], dtype=np.int64) |
| + segms = (pan_results[None] == ids[:, None, None]) |
| |
| img = imshow_det_bboxes( |
| img, |
|
|