|
|
|
import numbers
|
|
|
|
import cv2
|
|
import numpy as np
|
|
|
|
from ..utils import to_2tuple
|
|
from .io import imread_backend
|
|
|
|
try:
|
|
from PIL import Image
|
|
except ImportError:
|
|
Image = None
|
|
|
|
|
|
def _scale_size(size, scale):
|
|
"""Rescale a size by a ratio.
|
|
|
|
Args:
|
|
size (tuple[int]): (w, h).
|
|
scale (float | tuple(float)): Scaling factor.
|
|
|
|
Returns:
|
|
tuple[int]: scaled size.
|
|
"""
|
|
if isinstance(scale, (float, int)):
|
|
scale = (scale, scale)
|
|
w, h = size
|
|
return int(w * float(scale[0]) + 0.5), int(h * float(scale[1]) + 0.5)
|
|
|
|
|
|
cv2_interp_codes = {
|
|
'nearest': cv2.INTER_NEAREST,
|
|
'bilinear': cv2.INTER_LINEAR,
|
|
'bicubic': cv2.INTER_CUBIC,
|
|
'area': cv2.INTER_AREA,
|
|
'lanczos': cv2.INTER_LANCZOS4
|
|
}
|
|
|
|
if Image is not None:
|
|
pillow_interp_codes = {
|
|
'nearest': Image.NEAREST,
|
|
'bilinear': Image.BILINEAR,
|
|
'bicubic': Image.BICUBIC,
|
|
'box': Image.BOX,
|
|
'lanczos': Image.LANCZOS,
|
|
'hamming': Image.HAMMING
|
|
}
|
|
|
|
|
|
def imresize(img,
|
|
size,
|
|
return_scale=False,
|
|
interpolation='bilinear',
|
|
out=None,
|
|
backend=None):
|
|
"""Resize image to a given size.
|
|
|
|
Args:
|
|
img (ndarray): The input image.
|
|
size (tuple[int]): Target size (w, h).
|
|
return_scale (bool): Whether to return `w_scale` and `h_scale`.
|
|
interpolation (str): Interpolation method, accepted values are
|
|
"nearest", "bilinear", "bicubic", "area", "lanczos" for 'cv2'
|
|
backend, "nearest", "bilinear" for 'pillow' backend.
|
|
out (ndarray): The output destination.
|
|
backend (str | None): The image resize backend type. Options are `cv2`,
|
|
`pillow`, `None`. If backend is None, the global imread_backend
|
|
specified by ``mmcv.use_backend()`` will be used. Default: None.
|
|
|
|
Returns:
|
|
tuple | ndarray: (`resized_img`, `w_scale`, `h_scale`) or
|
|
`resized_img`.
|
|
"""
|
|
h, w = img.shape[:2]
|
|
if backend is None:
|
|
backend = imread_backend
|
|
if backend not in ['cv2', 'pillow']:
|
|
raise ValueError(f'backend: {backend} is not supported for resize.'
|
|
f"Supported backends are 'cv2', 'pillow'")
|
|
|
|
if backend == 'pillow':
|
|
assert img.dtype == np.uint8, 'Pillow backend only support uint8 type'
|
|
pil_image = Image.fromarray(img)
|
|
pil_image = pil_image.resize(size, pillow_interp_codes[interpolation])
|
|
resized_img = np.array(pil_image)
|
|
else:
|
|
resized_img = cv2.resize(
|
|
img, size, dst=out, interpolation=cv2_interp_codes[interpolation])
|
|
if not return_scale:
|
|
return resized_img
|
|
else:
|
|
w_scale = size[0] / w
|
|
h_scale = size[1] / h
|
|
return resized_img, w_scale, h_scale
|
|
|
|
|
|
def imresize_to_multiple(img,
|
|
divisor,
|
|
size=None,
|
|
scale_factor=None,
|
|
keep_ratio=False,
|
|
return_scale=False,
|
|
interpolation='bilinear',
|
|
out=None,
|
|
backend=None):
|
|
"""Resize image according to a given size or scale factor and then rounds
|
|
up the the resized or rescaled image size to the nearest value that can be
|
|
divided by the divisor.
|
|
|
|
Args:
|
|
img (ndarray): The input image.
|
|
divisor (int | tuple): Resized image size will be a multiple of
|
|
divisor. If divisor is a tuple, divisor should be
|
|
(w_divisor, h_divisor).
|
|
size (None | int | tuple[int]): Target size (w, h). Default: None.
|
|
scale_factor (None | float | tuple[float]): Multiplier for spatial
|
|
size. Should match input size if it is a tuple and the 2D style is
|
|
(w_scale_factor, h_scale_factor). Default: None.
|
|
keep_ratio (bool): Whether to keep the aspect ratio when resizing the
|
|
image. Default: False.
|
|
return_scale (bool): Whether to return `w_scale` and `h_scale`.
|
|
interpolation (str): Interpolation method, accepted values are
|
|
"nearest", "bilinear", "bicubic", "area", "lanczos" for 'cv2'
|
|
backend, "nearest", "bilinear" for 'pillow' backend.
|
|
out (ndarray): The output destination.
|
|
backend (str | None): The image resize backend type. Options are `cv2`,
|
|
`pillow`, `None`. If backend is None, the global imread_backend
|
|
specified by ``mmcv.use_backend()`` will be used. Default: None.
|
|
|
|
Returns:
|
|
tuple | ndarray: (`resized_img`, `w_scale`, `h_scale`) or
|
|
`resized_img`.
|
|
"""
|
|
h, w = img.shape[:2]
|
|
if size is not None and scale_factor is not None:
|
|
raise ValueError('only one of size or scale_factor should be defined')
|
|
elif size is None and scale_factor is None:
|
|
raise ValueError('one of size or scale_factor should be defined')
|
|
elif size is not None:
|
|
size = to_2tuple(size)
|
|
if keep_ratio:
|
|
size = rescale_size((w, h), size, return_scale=False)
|
|
else:
|
|
size = _scale_size((w, h), scale_factor)
|
|
|
|
divisor = to_2tuple(divisor)
|
|
size = tuple([int(np.ceil(s / d)) * d for s, d in zip(size, divisor)])
|
|
resized_img, w_scale, h_scale = imresize(
|
|
img,
|
|
size,
|
|
return_scale=True,
|
|
interpolation=interpolation,
|
|
out=out,
|
|
backend=backend)
|
|
if return_scale:
|
|
return resized_img, w_scale, h_scale
|
|
else:
|
|
return resized_img
|
|
|
|
|
|
def imresize_like(img,
|
|
dst_img,
|
|
return_scale=False,
|
|
interpolation='bilinear',
|
|
backend=None):
|
|
"""Resize image to the same size of a given image.
|
|
|
|
Args:
|
|
img (ndarray): The input image.
|
|
dst_img (ndarray): The target image.
|
|
return_scale (bool): Whether to return `w_scale` and `h_scale`.
|
|
interpolation (str): Same as :func:`resize`.
|
|
backend (str | None): Same as :func:`resize`.
|
|
|
|
Returns:
|
|
tuple or ndarray: (`resized_img`, `w_scale`, `h_scale`) or
|
|
`resized_img`.
|
|
"""
|
|
h, w = dst_img.shape[:2]
|
|
return imresize(img, (w, h), return_scale, interpolation, backend=backend)
|
|
|
|
|
|
def rescale_size(old_size, scale, return_scale=False):
|
|
"""Calculate the new size to be rescaled to.
|
|
|
|
Args:
|
|
old_size (tuple[int]): The old size (w, h) of image.
|
|
scale (float | tuple[int]): The scaling factor or maximum size.
|
|
If it is a float number, then the image will be rescaled by this
|
|
factor, else if it is a tuple of 2 integers, then the image will
|
|
be rescaled as large as possible within the scale.
|
|
return_scale (bool): Whether to return the scaling factor besides the
|
|
rescaled image size.
|
|
|
|
Returns:
|
|
tuple[int]: The new rescaled image size.
|
|
"""
|
|
w, h = old_size
|
|
if isinstance(scale, (float, int)):
|
|
if scale <= 0:
|
|
raise ValueError(f'Invalid scale {scale}, must be positive.')
|
|
scale_factor = scale
|
|
elif isinstance(scale, tuple):
|
|
max_long_edge = max(scale)
|
|
max_short_edge = min(scale)
|
|
scale_factor = min(max_long_edge / max(h, w),
|
|
max_short_edge / min(h, w))
|
|
else:
|
|
raise TypeError(
|
|
f'Scale must be a number or tuple of int, but got {type(scale)}')
|
|
|
|
new_size = _scale_size((w, h), scale_factor)
|
|
|
|
if return_scale:
|
|
return new_size, scale_factor
|
|
else:
|
|
return new_size
|
|
|
|
|
|
def imrescale(img,
|
|
scale,
|
|
return_scale=False,
|
|
interpolation='bilinear',
|
|
backend=None):
|
|
"""Resize image while keeping the aspect ratio.
|
|
|
|
Args:
|
|
img (ndarray): The input image.
|
|
scale (float | tuple[int]): The scaling factor or maximum size.
|
|
If it is a float number, then the image will be rescaled by this
|
|
factor, else if it is a tuple of 2 integers, then the image will
|
|
be rescaled as large as possible within the scale.
|
|
return_scale (bool): Whether to return the scaling factor besides the
|
|
rescaled image.
|
|
interpolation (str): Same as :func:`resize`.
|
|
backend (str | None): Same as :func:`resize`.
|
|
|
|
Returns:
|
|
ndarray: The rescaled image.
|
|
"""
|
|
h, w = img.shape[:2]
|
|
new_size, scale_factor = rescale_size((w, h), scale, return_scale=True)
|
|
rescaled_img = imresize(
|
|
img, new_size, interpolation=interpolation, backend=backend)
|
|
if return_scale:
|
|
return rescaled_img, scale_factor
|
|
else:
|
|
return rescaled_img
|
|
|
|
|
|
def imflip(img, direction='horizontal'):
|
|
"""Flip an image horizontally or vertically.
|
|
|
|
Args:
|
|
img (ndarray): Image to be flipped.
|
|
direction (str): The flip direction, either "horizontal" or
|
|
"vertical" or "diagonal".
|
|
|
|
Returns:
|
|
ndarray: The flipped image.
|
|
"""
|
|
assert direction in ['horizontal', 'vertical', 'diagonal']
|
|
if direction == 'horizontal':
|
|
return np.flip(img, axis=1)
|
|
elif direction == 'vertical':
|
|
return np.flip(img, axis=0)
|
|
else:
|
|
return np.flip(img, axis=(0, 1))
|
|
|
|
|
|
def imflip_(img, direction='horizontal'):
|
|
"""Inplace flip an image horizontally or vertically.
|
|
|
|
Args:
|
|
img (ndarray): Image to be flipped.
|
|
direction (str): The flip direction, either "horizontal" or
|
|
"vertical" or "diagonal".
|
|
|
|
Returns:
|
|
ndarray: The flipped image (inplace).
|
|
"""
|
|
assert direction in ['horizontal', 'vertical', 'diagonal']
|
|
if direction == 'horizontal':
|
|
return cv2.flip(img, 1, img)
|
|
elif direction == 'vertical':
|
|
return cv2.flip(img, 0, img)
|
|
else:
|
|
return cv2.flip(img, -1, img)
|
|
|
|
|
|
def imrotate(img,
|
|
angle,
|
|
center=None,
|
|
scale=1.0,
|
|
border_value=0,
|
|
interpolation='bilinear',
|
|
auto_bound=False):
|
|
"""Rotate an image.
|
|
|
|
Args:
|
|
img (ndarray): Image to be rotated.
|
|
angle (float): Rotation angle in degrees, positive values mean
|
|
clockwise rotation.
|
|
center (tuple[float], optional): Center point (w, h) of the rotation in
|
|
the source image. If not specified, the center of the image will be
|
|
used.
|
|
scale (float): Isotropic scale factor.
|
|
border_value (int): Border value.
|
|
interpolation (str): Same as :func:`resize`.
|
|
auto_bound (bool): Whether to adjust the image size to cover the whole
|
|
rotated image.
|
|
|
|
Returns:
|
|
ndarray: The rotated image.
|
|
"""
|
|
if center is not None and auto_bound:
|
|
raise ValueError('`auto_bound` conflicts with `center`')
|
|
h, w = img.shape[:2]
|
|
if center is None:
|
|
center = ((w - 1) * 0.5, (h - 1) * 0.5)
|
|
assert isinstance(center, tuple)
|
|
|
|
matrix = cv2.getRotationMatrix2D(center, -angle, scale)
|
|
if auto_bound:
|
|
cos = np.abs(matrix[0, 0])
|
|
sin = np.abs(matrix[0, 1])
|
|
new_w = h * sin + w * cos
|
|
new_h = h * cos + w * sin
|
|
matrix[0, 2] += (new_w - w) * 0.5
|
|
matrix[1, 2] += (new_h - h) * 0.5
|
|
w = int(np.round(new_w))
|
|
h = int(np.round(new_h))
|
|
rotated = cv2.warpAffine(
|
|
img,
|
|
matrix, (w, h),
|
|
flags=cv2_interp_codes[interpolation],
|
|
borderValue=border_value)
|
|
return rotated
|
|
|
|
|
|
def bbox_clip(bboxes, img_shape):
|
|
"""Clip bboxes to fit the image shape.
|
|
|
|
Args:
|
|
bboxes (ndarray): Shape (..., 4*k)
|
|
img_shape (tuple[int]): (height, width) of the image.
|
|
|
|
Returns:
|
|
ndarray: Clipped bboxes.
|
|
"""
|
|
assert bboxes.shape[-1] % 4 == 0
|
|
cmin = np.empty(bboxes.shape[-1], dtype=bboxes.dtype)
|
|
cmin[0::2] = img_shape[1] - 1
|
|
cmin[1::2] = img_shape[0] - 1
|
|
clipped_bboxes = np.maximum(np.minimum(bboxes, cmin), 0)
|
|
return clipped_bboxes
|
|
|
|
|
|
def bbox_scaling(bboxes, scale, clip_shape=None):
|
|
"""Scaling bboxes w.r.t the box center.
|
|
|
|
Args:
|
|
bboxes (ndarray): Shape(..., 4).
|
|
scale (float): Scaling factor.
|
|
clip_shape (tuple[int], optional): If specified, bboxes that exceed the
|
|
boundary will be clipped according to the given shape (h, w).
|
|
|
|
Returns:
|
|
ndarray: Scaled bboxes.
|
|
"""
|
|
if float(scale) == 1.0:
|
|
scaled_bboxes = bboxes.copy()
|
|
else:
|
|
w = bboxes[..., 2] - bboxes[..., 0] + 1
|
|
h = bboxes[..., 3] - bboxes[..., 1] + 1
|
|
dw = (w * (scale - 1)) * 0.5
|
|
dh = (h * (scale - 1)) * 0.5
|
|
scaled_bboxes = bboxes + np.stack((-dw, -dh, dw, dh), axis=-1)
|
|
if clip_shape is not None:
|
|
return bbox_clip(scaled_bboxes, clip_shape)
|
|
else:
|
|
return scaled_bboxes
|
|
|
|
|
|
def imcrop(img, bboxes, scale=1.0, pad_fill=None):
|
|
"""Crop image patches.
|
|
|
|
3 steps: scale the bboxes -> clip bboxes -> crop and pad.
|
|
|
|
Args:
|
|
img (ndarray): Image to be cropped.
|
|
bboxes (ndarray): Shape (k, 4) or (4, ), location of cropped bboxes.
|
|
scale (float, optional): Scale ratio of bboxes, the default value
|
|
1.0 means no padding.
|
|
pad_fill (Number | list[Number]): Value to be filled for padding.
|
|
Default: None, which means no padding.
|
|
|
|
Returns:
|
|
list[ndarray] | ndarray: The cropped image patches.
|
|
"""
|
|
chn = 1 if img.ndim == 2 else img.shape[2]
|
|
if pad_fill is not None:
|
|
if isinstance(pad_fill, (int, float)):
|
|
pad_fill = [pad_fill for _ in range(chn)]
|
|
assert len(pad_fill) == chn
|
|
|
|
_bboxes = bboxes[None, ...] if bboxes.ndim == 1 else bboxes
|
|
scaled_bboxes = bbox_scaling(_bboxes, scale).astype(np.int32)
|
|
clipped_bbox = bbox_clip(scaled_bboxes, img.shape)
|
|
|
|
patches = []
|
|
for i in range(clipped_bbox.shape[0]):
|
|
x1, y1, x2, y2 = tuple(clipped_bbox[i, :])
|
|
if pad_fill is None:
|
|
patch = img[y1:y2 + 1, x1:x2 + 1, ...]
|
|
else:
|
|
_x1, _y1, _x2, _y2 = tuple(scaled_bboxes[i, :])
|
|
if chn == 1:
|
|
patch_shape = (_y2 - _y1 + 1, _x2 - _x1 + 1)
|
|
else:
|
|
patch_shape = (_y2 - _y1 + 1, _x2 - _x1 + 1, chn)
|
|
patch = np.array(
|
|
pad_fill, dtype=img.dtype) * np.ones(
|
|
patch_shape, dtype=img.dtype)
|
|
x_start = 0 if _x1 >= 0 else -_x1
|
|
y_start = 0 if _y1 >= 0 else -_y1
|
|
w = x2 - x1 + 1
|
|
h = y2 - y1 + 1
|
|
patch[y_start:y_start + h, x_start:x_start + w,
|
|
...] = img[y1:y1 + h, x1:x1 + w, ...]
|
|
patches.append(patch)
|
|
|
|
if bboxes.ndim == 1:
|
|
return patches[0]
|
|
else:
|
|
return patches
|
|
|
|
|
|
def impad(img,
|
|
*,
|
|
shape=None,
|
|
padding=None,
|
|
pad_val=0,
|
|
padding_mode='constant'):
|
|
"""Pad the given image to a certain shape or pad on all sides with
|
|
specified padding mode and padding value.
|
|
|
|
Args:
|
|
img (ndarray): Image to be padded.
|
|
shape (tuple[int]): Expected padding shape (h, w). Default: None.
|
|
padding (int or tuple[int]): Padding on each border. If a single int is
|
|
provided this is used to pad all borders. If tuple of length 2 is
|
|
provided this is the padding on left/right and top/bottom
|
|
respectively. If a tuple of length 4 is provided this is the
|
|
padding for the left, top, right and bottom borders respectively.
|
|
Default: None. Note that `shape` and `padding` can not be both
|
|
set.
|
|
pad_val (Number | Sequence[Number]): Values to be filled in padding
|
|
areas when padding_mode is 'constant'. Default: 0.
|
|
padding_mode (str): Type of padding. Should be: constant, edge,
|
|
reflect or symmetric. Default: constant.
|
|
|
|
- constant: pads with a constant value, this value is specified
|
|
with pad_val.
|
|
- edge: pads with the last value at the edge of the image.
|
|
- reflect: pads with reflection of image without repeating the
|
|
last value on the edge. For example, padding [1, 2, 3, 4]
|
|
with 2 elements on both sides in reflect mode will result
|
|
in [3, 2, 1, 2, 3, 4, 3, 2].
|
|
- symmetric: pads with reflection of image repeating the last
|
|
value on the edge. For example, padding [1, 2, 3, 4] with
|
|
2 elements on both sides in symmetric mode will result in
|
|
[2, 1, 1, 2, 3, 4, 4, 3]
|
|
|
|
Returns:
|
|
ndarray: The padded image.
|
|
"""
|
|
|
|
assert (shape is not None) ^ (padding is not None)
|
|
if shape is not None:
|
|
padding = (0, 0, shape[1] - img.shape[1], shape[0] - img.shape[0])
|
|
|
|
|
|
if isinstance(pad_val, tuple):
|
|
assert len(pad_val) == img.shape[-1]
|
|
elif not isinstance(pad_val, numbers.Number):
|
|
raise TypeError('pad_val must be a int or a tuple. '
|
|
f'But received {type(pad_val)}')
|
|
|
|
|
|
if isinstance(padding, tuple) and len(padding) in [2, 4]:
|
|
if len(padding) == 2:
|
|
padding = (padding[0], padding[1], padding[0], padding[1])
|
|
elif isinstance(padding, numbers.Number):
|
|
padding = (padding, padding, padding, padding)
|
|
else:
|
|
raise ValueError('Padding must be a int or a 2, or 4 element tuple.'
|
|
f'But received {padding}')
|
|
|
|
|
|
assert padding_mode in ['constant', 'edge', 'reflect', 'symmetric']
|
|
|
|
border_type = {
|
|
'constant': cv2.BORDER_CONSTANT,
|
|
'edge': cv2.BORDER_REPLICATE,
|
|
'reflect': cv2.BORDER_REFLECT_101,
|
|
'symmetric': cv2.BORDER_REFLECT
|
|
}
|
|
img = cv2.copyMakeBorder(
|
|
img,
|
|
padding[1],
|
|
padding[3],
|
|
padding[0],
|
|
padding[2],
|
|
border_type[padding_mode],
|
|
value=pad_val)
|
|
|
|
return img
|
|
|
|
|
|
def impad_to_multiple(img, divisor, pad_val=0):
|
|
"""Pad an image to ensure each edge to be multiple to some number.
|
|
|
|
Args:
|
|
img (ndarray): Image to be padded.
|
|
divisor (int): Padded image edges will be multiple to divisor.
|
|
pad_val (Number | Sequence[Number]): Same as :func:`impad`.
|
|
|
|
Returns:
|
|
ndarray: The padded image.
|
|
"""
|
|
pad_h = int(np.ceil(img.shape[0] / divisor)) * divisor
|
|
pad_w = int(np.ceil(img.shape[1] / divisor)) * divisor
|
|
return impad(img, shape=(pad_h, pad_w), pad_val=pad_val)
|
|
|
|
|
|
def cutout(img, shape, pad_val=0):
|
|
"""Randomly cut out a rectangle from the original img.
|
|
|
|
Args:
|
|
img (ndarray): Image to be cutout.
|
|
shape (int | tuple[int]): Expected cutout shape (h, w). If given as a
|
|
int, the value will be used for both h and w.
|
|
pad_val (int | float | tuple[int | float]): Values to be filled in the
|
|
cut area. Defaults to 0.
|
|
|
|
Returns:
|
|
ndarray: The cutout image.
|
|
"""
|
|
|
|
channels = 1 if img.ndim == 2 else img.shape[2]
|
|
if isinstance(shape, int):
|
|
cut_h, cut_w = shape, shape
|
|
else:
|
|
assert isinstance(shape, tuple) and len(shape) == 2, \
|
|
f'shape must be a int or a tuple with length 2, but got type ' \
|
|
f'{type(shape)} instead.'
|
|
cut_h, cut_w = shape
|
|
if isinstance(pad_val, (int, float)):
|
|
pad_val = tuple([pad_val] * channels)
|
|
elif isinstance(pad_val, tuple):
|
|
assert len(pad_val) == channels, \
|
|
'Expected the num of elements in tuple equals the channels' \
|
|
'of input image. Found {} vs {}'.format(
|
|
len(pad_val), channels)
|
|
else:
|
|
raise TypeError(f'Invalid type {type(pad_val)} for `pad_val`')
|
|
|
|
img_h, img_w = img.shape[:2]
|
|
y0 = np.random.uniform(img_h)
|
|
x0 = np.random.uniform(img_w)
|
|
|
|
y1 = int(max(0, y0 - cut_h / 2.))
|
|
x1 = int(max(0, x0 - cut_w / 2.))
|
|
y2 = min(img_h, y1 + cut_h)
|
|
x2 = min(img_w, x1 + cut_w)
|
|
|
|
if img.ndim == 2:
|
|
patch_shape = (y2 - y1, x2 - x1)
|
|
else:
|
|
patch_shape = (y2 - y1, x2 - x1, channels)
|
|
|
|
img_cutout = img.copy()
|
|
patch = np.array(
|
|
pad_val, dtype=img.dtype) * np.ones(
|
|
patch_shape, dtype=img.dtype)
|
|
img_cutout[y1:y2, x1:x2, ...] = patch
|
|
|
|
return img_cutout
|
|
|
|
|
|
def _get_shear_matrix(magnitude, direction='horizontal'):
|
|
"""Generate the shear matrix for transformation.
|
|
|
|
Args:
|
|
magnitude (int | float): The magnitude used for shear.
|
|
direction (str): The flip direction, either "horizontal"
|
|
or "vertical".
|
|
|
|
Returns:
|
|
ndarray: The shear matrix with dtype float32.
|
|
"""
|
|
if direction == 'horizontal':
|
|
shear_matrix = np.float32([[1, magnitude, 0], [0, 1, 0]])
|
|
elif direction == 'vertical':
|
|
shear_matrix = np.float32([[1, 0, 0], [magnitude, 1, 0]])
|
|
return shear_matrix
|
|
|
|
|
|
def imshear(img,
|
|
magnitude,
|
|
direction='horizontal',
|
|
border_value=0,
|
|
interpolation='bilinear'):
|
|
"""Shear an image.
|
|
|
|
Args:
|
|
img (ndarray): Image to be sheared with format (h, w)
|
|
or (h, w, c).
|
|
magnitude (int | float): The magnitude used for shear.
|
|
direction (str): The flip direction, either "horizontal"
|
|
or "vertical".
|
|
border_value (int | tuple[int]): Value used in case of a
|
|
constant border.
|
|
interpolation (str): Same as :func:`resize`.
|
|
|
|
Returns:
|
|
ndarray: The sheared image.
|
|
"""
|
|
assert direction in ['horizontal',
|
|
'vertical'], f'Invalid direction: {direction}'
|
|
height, width = img.shape[:2]
|
|
if img.ndim == 2:
|
|
channels = 1
|
|
elif img.ndim == 3:
|
|
channels = img.shape[-1]
|
|
if isinstance(border_value, int):
|
|
border_value = tuple([border_value] * channels)
|
|
elif isinstance(border_value, tuple):
|
|
assert len(border_value) == channels, \
|
|
'Expected the num of elements in tuple equals the channels' \
|
|
'of input image. Found {} vs {}'.format(
|
|
len(border_value), channels)
|
|
else:
|
|
raise ValueError(
|
|
f'Invalid type {type(border_value)} for `border_value`')
|
|
shear_matrix = _get_shear_matrix(magnitude, direction)
|
|
sheared = cv2.warpAffine(
|
|
img,
|
|
shear_matrix,
|
|
(width, height),
|
|
|
|
|
|
|
|
|
|
borderValue=border_value[:3],
|
|
flags=cv2_interp_codes[interpolation])
|
|
return sheared
|
|
|
|
|
|
def _get_translate_matrix(offset, direction='horizontal'):
|
|
"""Generate the translate matrix.
|
|
|
|
Args:
|
|
offset (int | float): The offset used for translate.
|
|
direction (str): The translate direction, either
|
|
"horizontal" or "vertical".
|
|
|
|
Returns:
|
|
ndarray: The translate matrix with dtype float32.
|
|
"""
|
|
if direction == 'horizontal':
|
|
translate_matrix = np.float32([[1, 0, offset], [0, 1, 0]])
|
|
elif direction == 'vertical':
|
|
translate_matrix = np.float32([[1, 0, 0], [0, 1, offset]])
|
|
return translate_matrix
|
|
|
|
|
|
def imtranslate(img,
|
|
offset,
|
|
direction='horizontal',
|
|
border_value=0,
|
|
interpolation='bilinear'):
|
|
"""Translate an image.
|
|
|
|
Args:
|
|
img (ndarray): Image to be translated with format
|
|
(h, w) or (h, w, c).
|
|
offset (int | float): The offset used for translate.
|
|
direction (str): The translate direction, either "horizontal"
|
|
or "vertical".
|
|
border_value (int | tuple[int]): Value used in case of a
|
|
constant border.
|
|
interpolation (str): Same as :func:`resize`.
|
|
|
|
Returns:
|
|
ndarray: The translated image.
|
|
"""
|
|
assert direction in ['horizontal',
|
|
'vertical'], f'Invalid direction: {direction}'
|
|
height, width = img.shape[:2]
|
|
if img.ndim == 2:
|
|
channels = 1
|
|
elif img.ndim == 3:
|
|
channels = img.shape[-1]
|
|
if isinstance(border_value, int):
|
|
border_value = tuple([border_value] * channels)
|
|
elif isinstance(border_value, tuple):
|
|
assert len(border_value) == channels, \
|
|
'Expected the num of elements in tuple equals the channels' \
|
|
'of input image. Found {} vs {}'.format(
|
|
len(border_value), channels)
|
|
else:
|
|
raise ValueError(
|
|
f'Invalid type {type(border_value)} for `border_value`.')
|
|
translate_matrix = _get_translate_matrix(offset, direction)
|
|
translated = cv2.warpAffine(
|
|
img,
|
|
translate_matrix,
|
|
(width, height),
|
|
|
|
|
|
|
|
|
|
borderValue=border_value[:3],
|
|
flags=cv2_interp_codes[interpolation])
|
|
return translated
|
|
|