| 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, | |