Shortcuts

Source code for mmrotate.datasets.pipelines.transforms

# Copyright (c) OpenMMLab. All rights reserved.
import cv2
import numpy as np
from mmdet.datasets.pipelines.transforms import RandomFlip, Resize

from mmrotate.core import norm_angle, obb2poly_np, poly2obb_np
from ..builder import ROTATED_PIPELINES


[docs]@ROTATED_PIPELINES.register_module() class RResize(Resize): """Resize images & rotated bbox Inherit Resize pipeline class to handle rotated bboxes. Args: img_scale (tuple or list[tuple]): Images scales for resizing. multiscale_mode (str): Either "range" or "value". ratio_range (tuple[float]): (min_ratio, max_ratio). """ def __init__(self, img_scale=None, multiscale_mode='range', ratio_range=None): super(RResize, self).__init__( img_scale=img_scale, multiscale_mode=multiscale_mode, ratio_range=ratio_range, keep_ratio=True) def _resize_bboxes(self, results): """Resize bounding boxes with ``results['scale_factor']``.""" for key in results.get('bbox_fields', []): bboxes = results[key] orig_shape = bboxes.shape bboxes = bboxes.reshape((-1, 5)) w_scale, h_scale, _, _ = results['scale_factor'] bboxes[:, 0] *= w_scale bboxes[:, 1] *= h_scale bboxes[:, 2:4] *= np.sqrt(w_scale * h_scale) results[key] = bboxes.reshape(orig_shape)
[docs]@ROTATED_PIPELINES.register_module() class RRandomFlip(RandomFlip): """ Args: flip_ratio (float | list[float], optional): The flipping probability. Default: None. direction(str | list[str], optional): The flipping direction. Options are 'horizontal', 'vertical', 'diagonal'. version (str, optional): Angle representations. Defaults to 'oc'. """ def __init__(self, flip_ratio=None, direction='horizontal', version='oc'): self.version = version super(RRandomFlip, self).__init__(flip_ratio, direction)
[docs] def bbox_flip(self, bboxes, img_shape, direction): """Flip bboxes horizontally or vertically. Args: bboxes(ndarray): shape (..., 5*k) img_shape(tuple): (height, width) Returns: numpy.ndarray: Flipped bounding boxes. """ assert bboxes.shape[-1] % 5 == 0 orig_shape = bboxes.shape bboxes = bboxes.reshape((-1, 5)) flipped = bboxes.copy() if direction == 'horizontal': flipped[:, 0] = img_shape[1] - bboxes[:, 0] - 1 elif direction == 'vertical': flipped[:, 1] = img_shape[0] - bboxes[:, 1] - 1 elif direction == 'diagonal': flipped[:, 0] = img_shape[1] - bboxes[:, 0] - 1 flipped[:, 1] = img_shape[0] - bboxes[:, 1] - 1 return flipped.reshape(orig_shape) else: raise ValueError(f'Invalid flipping direction "{direction}"') if self.version == 'oc': rotated_flag = (bboxes[:, 4] != np.pi / 2) flipped[rotated_flag, 4] = np.pi / 2 - bboxes[rotated_flag, 4] flipped[rotated_flag, 2] = bboxes[rotated_flag, 3] flipped[rotated_flag, 3] = bboxes[rotated_flag, 2] else: flipped[:, 4] = norm_angle(np.pi - bboxes[:, 4], self.version) return flipped.reshape(orig_shape)
[docs]@ROTATED_PIPELINES.register_module() class PolyRandomRotate(object): """Rotate img & bbox. Reference: https://github.com/hukaixuan19970627/OrientedRepPoints_DOTA Args: rate (bool): (float, optional): The rotating probability. Default: 0.5. angles_range(int, optional): The rotate angle defined by random (-angles_range, +angles_range). auto_bound(bool, optional): whether to find the new width and height bounds. rect_classes (None|list, optional): Specifies classes that needs to be rotated by a multiple of 90 degrees. version (str, optional): Angle representations. Defaults to 'oc'. """ def __init__(self, rotate_ratio=0.5, angles_range=180, auto_bound=False, rect_classes=None, version='le90'): self.rotate_ratio = rotate_ratio self.auto_bound = auto_bound self.angles_range = angles_range self.discrete_range = [90, 180, -90, -180] self.rect_classes = rect_classes self.version = version @property def is_rotate(self): """Randomly decide whether to rotate.""" return np.random.rand() < self.rotate_ratio
[docs] def apply_image(self, img, bound_h, bound_w, interp=cv2.INTER_LINEAR): """ img should be a numpy array, formatted as Height * Width * Nchannels """ if len(img) == 0: return img return cv2.warpAffine( img, self.rm_image, (bound_w, bound_h), flags=interp)
[docs] def apply_coords(self, coords): """ coords should be a N * 2 array-like, containing N couples of (x, y) points """ if len(coords) == 0: return coords coords = np.asarray(coords, dtype=float) return cv2.transform(coords[:, np.newaxis, :], self.rm_coords)[:, 0, :]
[docs] def create_rotation_matrix(self, center, angle, bound_h, bound_w, offset=0): """Create rotation matrix.""" center = (center[0] + offset, center[1] + offset) rm = cv2.getRotationMatrix2D(tuple(center), angle, 1) if self.auto_bound: rot_im_center = cv2.transform(center[None, None, :] + offset, rm)[0, 0, :] new_center = np.array([bound_w / 2, bound_h / 2 ]) + offset - rot_im_center rm[:, 2] += new_center return rm
[docs] def filter_border(self, bboxes, h, w): """Filter the box whose center point is outside or whose side length is less than 5.""" x_ctr, y_ctr = bboxes[:, 0], bboxes[:, 1] w_bbox, h_bbox = bboxes[:, 2], bboxes[:, 3] keep_inds = (x_ctr > 0) & (x_ctr < w) & (y_ctr > 0) & (y_ctr < h) & \ (w_bbox > 5) & (h_bbox > 5) return keep_inds
def __call__(self, results): """Call function of PolyRandomRotate.""" if not self.is_rotate: results['rotate'] = False angle = 0 else: angle = 2 * self.angles_range * np.random.rand() - \ self.angles_range results['rotate'] = True class_labels = results['gt_labels'] for classid in class_labels: if self.rect_classes: if classid in self.rect_classes: np.random.shuffle(self.discrete_range) angle = self.discrete_range[0] break h, w, c = results['img_shape'] img = results['img'] results['rotate_angle'] = angle image_center = np.array((w / 2, h / 2)) abs_cos, abs_sin = abs(np.cos(angle)), abs(np.sin(angle)) if self.auto_bound: bound_w, bound_h = np.rint( [h * abs_sin + w * abs_cos, h * abs_cos + w * abs_sin]).astype(int) else: bound_w, bound_h = w, h self.rm_coords = self.create_rotation_matrix(image_center, angle, bound_h, bound_w) self.rm_image = self.create_rotation_matrix( image_center, angle, bound_h, bound_w, offset=-0.5) img = self.apply_image(img, bound_h, bound_w) results['img'] = img results['img_shape'] = (bound_h, bound_w, c) gt_bboxes = results.get('gt_bboxes', []) labels = results.get('gt_labels', []) gt_bboxes = np.concatenate( [gt_bboxes, np.zeros((gt_bboxes.shape[0], 1))], axis=-1) polys = obb2poly_np(gt_bboxes, self.version)[:, :-1].reshape(-1, 2) polys = self.apply_coords(polys).reshape(-1, 8) gt_bboxes = [] for pt in polys: pt = np.array(pt, dtype=np.float32) obb = poly2obb_np(pt, self.version) \ if poly2obb_np(pt, self.version) is not None\ else [0, 0, 0, 0, 0] gt_bboxes.append(obb) gt_bboxes = np.array(gt_bboxes, dtype=np.float32) keep_inds = self.filter_border(gt_bboxes, bound_h, bound_w) gt_bboxes = gt_bboxes[keep_inds, :] labels = labels[keep_inds] if len(gt_bboxes) == 0: return None results['gt_bboxes'] = gt_bboxes results['gt_labels'] = labels return results def __repr__(self): repr_str = self.__class__.__name__ repr_str += f'(rotate_ratio={self.rotate_ratio}, ' \ f'angles_range={self.angles_range}, ' \ f'auto_bound={self.auto_bound})' return repr_str
Read the Docs v: v0.2.0
Versions
latest
stable
v0.2.0
v0.1.1
v0.1.0
main
dev
Downloads
On Read the Docs
Project Home
Builds

Free document hosting provided by Read the Docs.