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