Spaces:
Running
Running
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, | |