| |
| import cv2 |
| import numpy as np |
|
|
| from ..utils import is_tuple_of |
| from .colorspace import bgr2gray, gray2bgr |
|
|
|
|
| def imnormalize(img, mean, std, to_rgb=True): |
| """Normalize an image with mean and std. |
| |
| Args: |
| img (ndarray): Image to be normalized. |
| mean (ndarray): The mean to be used for normalize. |
| std (ndarray): The std to be used for normalize. |
| to_rgb (bool): Whether to convert to rgb. |
| |
| Returns: |
| ndarray: The normalized image. |
| """ |
| img = img.copy().astype(np.float32) |
| return imnormalize_(img, mean, std, to_rgb) |
|
|
|
|
| def imnormalize_(img, mean, std, to_rgb=True): |
| """Inplace normalize an image with mean and std. |
| |
| Args: |
| img (ndarray): Image to be normalized. |
| mean (ndarray): The mean to be used for normalize. |
| std (ndarray): The std to be used for normalize. |
| to_rgb (bool): Whether to convert to rgb. |
| |
| Returns: |
| ndarray: The normalized image. |
| """ |
| |
| assert img.dtype != np.uint8 |
| mean = np.float64(mean.reshape(1, -1)) |
| stdinv = 1 / np.float64(std.reshape(1, -1)) |
| if to_rgb: |
| cv2.cvtColor(img, cv2.COLOR_BGR2RGB, img) |
| cv2.subtract(img, mean, img) |
| cv2.multiply(img, stdinv, img) |
| return img |
|
|
|
|
| def imdenormalize(img, mean, std, to_bgr=True): |
| assert img.dtype != np.uint8 |
| mean = mean.reshape(1, -1).astype(np.float64) |
| std = std.reshape(1, -1).astype(np.float64) |
| img = cv2.multiply(img, std) |
| cv2.add(img, mean, img) |
| if to_bgr: |
| cv2.cvtColor(img, cv2.COLOR_RGB2BGR, img) |
| return img |
|
|
|
|
| def iminvert(img): |
| """Invert (negate) an image. |
| |
| Args: |
| img (ndarray): Image to be inverted. |
| |
| Returns: |
| ndarray: The inverted image. |
| """ |
| return np.full_like(img, 255) - img |
|
|
|
|
| def solarize(img, thr=128): |
| """Solarize an image (invert all pixel values above a threshold) |
| |
| Args: |
| img (ndarray): Image to be solarized. |
| thr (int): Threshold for solarizing (0 - 255). |
| |
| Returns: |
| ndarray: The solarized image. |
| """ |
| img = np.where(img < thr, img, 255 - img) |
| return img |
|
|
|
|
| def posterize(img, bits): |
| """Posterize an image (reduce the number of bits for each color channel) |
| |
| Args: |
| img (ndarray): Image to be posterized. |
| bits (int): Number of bits (1 to 8) to use for posterizing. |
| |
| Returns: |
| ndarray: The posterized image. |
| """ |
| shift = 8 - bits |
| img = np.left_shift(np.right_shift(img, shift), shift) |
| return img |
|
|
|
|
| def adjust_color(img, alpha=1, beta=None, gamma=0): |
| r"""It blends the source image and its gray image: |
| |
| .. math:: |
| output = img * alpha + gray\_img * beta + gamma |
| |
| Args: |
| img (ndarray): The input source image. |
| alpha (int | float): Weight for the source image. Default 1. |
| beta (int | float): Weight for the converted gray image. |
| If None, it's assigned the value (1 - `alpha`). |
| gamma (int | float): Scalar added to each sum. |
| Same as :func:`cv2.addWeighted`. Default 0. |
| |
| Returns: |
| ndarray: Colored image which has the same size and dtype as input. |
| """ |
| gray_img = bgr2gray(img) |
| gray_img = np.tile(gray_img[..., None], [1, 1, 3]) |
| if beta is None: |
| beta = 1 - alpha |
| colored_img = cv2.addWeighted(img, alpha, gray_img, beta, gamma) |
| if not colored_img.dtype == np.uint8: |
| |
| |
| |
| |
| colored_img = np.clip(colored_img, 0, 255) |
| return colored_img |
|
|
|
|
| def imequalize(img): |
| """Equalize the image histogram. |
| |
| This function applies a non-linear mapping to the input image, |
| in order to create a uniform distribution of grayscale values |
| in the output image. |
| |
| Args: |
| img (ndarray): Image to be equalized. |
| |
| Returns: |
| ndarray: The equalized image. |
| """ |
|
|
| def _scale_channel(im, c): |
| """Scale the data in the corresponding channel.""" |
| im = im[:, :, c] |
| |
| histo = np.histogram(im, 256, (0, 255))[0] |
| |
| nonzero_histo = histo[histo > 0] |
| step = (np.sum(nonzero_histo) - nonzero_histo[-1]) // 255 |
| if not step: |
| lut = np.array(range(256)) |
| else: |
| |
| |
| lut = (np.cumsum(histo) + (step // 2)) // step |
| |
| lut = np.concatenate([[0], lut[:-1]], 0) |
| |
| lut[lut > 255] = 255 |
| |
| |
| return np.where(np.equal(step, 0), im, lut[im]) |
|
|
| |
| |
| s1 = _scale_channel(img, 0) |
| s2 = _scale_channel(img, 1) |
| s3 = _scale_channel(img, 2) |
| equalized_img = np.stack([s1, s2, s3], axis=-1) |
| return equalized_img.astype(img.dtype) |
|
|
|
|
| def adjust_brightness(img, factor=1.): |
| """Adjust image brightness. |
| |
| This function controls the brightness of an image. An |
| enhancement factor of 0.0 gives a black image. |
| A factor of 1.0 gives the original image. This function |
| blends the source image and the degenerated black image: |
| |
| .. math:: |
| output = img * factor + degenerated * (1 - factor) |
| |
| Args: |
| img (ndarray): Image to be brightened. |
| factor (float): A value controls the enhancement. |
| Factor 1.0 returns the original image, lower |
| factors mean less color (brightness, contrast, |
| etc), and higher values more. Default 1. |
| |
| Returns: |
| ndarray: The brightened image. |
| """ |
| degenerated = np.zeros_like(img) |
| |
| |
| |
| brightened_img = cv2.addWeighted( |
| img.astype(np.float32), factor, degenerated.astype(np.float32), |
| 1 - factor, 0) |
| brightened_img = np.clip(brightened_img, 0, 255) |
| return brightened_img.astype(img.dtype) |
|
|
|
|
| def adjust_contrast(img, factor=1.): |
| """Adjust image contrast. |
| |
| This function controls the contrast of an image. An |
| enhancement factor of 0.0 gives a solid grey |
| image. A factor of 1.0 gives the original image. It |
| blends the source image and the degenerated mean image: |
| |
| .. math:: |
| output = img * factor + degenerated * (1 - factor) |
| |
| Args: |
| img (ndarray): Image to be contrasted. BGR order. |
| factor (float): Same as :func:`mmcv.adjust_brightness`. |
| |
| Returns: |
| ndarray: The contrasted image. |
| """ |
| gray_img = bgr2gray(img) |
| hist = np.histogram(gray_img, 256, (0, 255))[0] |
| mean = round(np.sum(gray_img) / np.sum(hist)) |
| degenerated = (np.ones_like(img[..., 0]) * mean).astype(img.dtype) |
| degenerated = gray2bgr(degenerated) |
| contrasted_img = cv2.addWeighted( |
| img.astype(np.float32), factor, degenerated.astype(np.float32), |
| 1 - factor, 0) |
| contrasted_img = np.clip(contrasted_img, 0, 255) |
| return contrasted_img.astype(img.dtype) |
|
|
|
|
| def auto_contrast(img, cutoff=0): |
| """Auto adjust image contrast. |
| |
| This function maximize (normalize) image contrast by first removing cutoff |
| percent of the lightest and darkest pixels from the histogram and remapping |
| the image so that the darkest pixel becomes black (0), and the lightest |
| becomes white (255). |
| |
| Args: |
| img (ndarray): Image to be contrasted. BGR order. |
| cutoff (int | float | tuple): The cutoff percent of the lightest and |
| darkest pixels to be removed. If given as tuple, it shall be |
| (low, high). Otherwise, the single value will be used for both. |
| Defaults to 0. |
| |
| Returns: |
| ndarray: The contrasted image. |
| """ |
|
|
| def _auto_contrast_channel(im, c, cutoff): |
| im = im[:, :, c] |
| |
| histo = np.histogram(im, 256, (0, 255))[0] |
| |
| histo_sum = np.cumsum(histo) |
| cut_low = histo_sum[-1] * cutoff[0] // 100 |
| cut_high = histo_sum[-1] - histo_sum[-1] * cutoff[1] // 100 |
| histo_sum = np.clip(histo_sum, cut_low, cut_high) - cut_low |
| histo = np.concatenate([[histo_sum[0]], np.diff(histo_sum)], 0) |
|
|
| |
| low, high = np.nonzero(histo)[0][0], np.nonzero(histo)[0][-1] |
| |
| if low >= high: |
| return im |
| scale = 255.0 / (high - low) |
| offset = -low * scale |
| lut = np.array(range(256)) |
| lut = lut * scale + offset |
| lut = np.clip(lut, 0, 255) |
| return lut[im] |
|
|
| if isinstance(cutoff, (int, float)): |
| cutoff = (cutoff, cutoff) |
| else: |
| assert isinstance(cutoff, tuple), 'cutoff must be of type int, ' \ |
| f'float or tuple, but got {type(cutoff)} instead.' |
| |
| |
| s1 = _auto_contrast_channel(img, 0, cutoff) |
| s2 = _auto_contrast_channel(img, 1, cutoff) |
| s3 = _auto_contrast_channel(img, 2, cutoff) |
| contrasted_img = np.stack([s1, s2, s3], axis=-1) |
| return contrasted_img.astype(img.dtype) |
|
|
|
|
| def adjust_sharpness(img, factor=1., kernel=None): |
| """Adjust image sharpness. |
| |
| This function controls the sharpness of an image. An |
| enhancement factor of 0.0 gives a blurred image. A |
| factor of 1.0 gives the original image. And a factor |
| of 2.0 gives a sharpened image. It blends the source |
| image and the degenerated mean image: |
| |
| .. math:: |
| output = img * factor + degenerated * (1 - factor) |
| |
| Args: |
| img (ndarray): Image to be sharpened. BGR order. |
| factor (float): Same as :func:`mmcv.adjust_brightness`. |
| kernel (np.ndarray, optional): Filter kernel to be applied on the img |
| to obtain the degenerated img. Defaults to None. |
| |
| Note: |
| No value sanity check is enforced on the kernel set by users. So with |
| an inappropriate kernel, the ``adjust_sharpness`` may fail to perform |
| the function its name indicates but end up performing whatever |
| transform determined by the kernel. |
| |
| Returns: |
| ndarray: The sharpened image. |
| """ |
|
|
| if kernel is None: |
| |
| kernel = np.array([[1., 1., 1.], [1., 5., 1.], [1., 1., 1.]]) / 13 |
| assert isinstance(kernel, np.ndarray), \ |
| f'kernel must be of type np.ndarray, but got {type(kernel)} instead.' |
| assert kernel.ndim == 2, \ |
| f'kernel must have a dimension of 2, but got {kernel.ndim} instead.' |
|
|
| degenerated = cv2.filter2D(img, -1, kernel) |
| sharpened_img = cv2.addWeighted( |
| img.astype(np.float32), factor, degenerated.astype(np.float32), |
| 1 - factor, 0) |
| sharpened_img = np.clip(sharpened_img, 0, 255) |
| return sharpened_img.astype(img.dtype) |
|
|
|
|
| def adjust_lighting(img, eigval, eigvec, alphastd=0.1, to_rgb=True): |
| """AlexNet-style PCA jitter. |
| |
| This data augmentation is proposed in `ImageNet Classification with Deep |
| Convolutional Neural Networks |
| <https://dl.acm.org/doi/pdf/10.1145/3065386>`_. |
| |
| Args: |
| img (ndarray): Image to be adjusted lighting. BGR order. |
| eigval (ndarray): the eigenvalue of the convariance matrix of pixel |
| values, respectively. |
| eigvec (ndarray): the eigenvector of the convariance matrix of pixel |
| values, respectively. |
| alphastd (float): The standard deviation for distribution of alpha. |
| Defaults to 0.1 |
| to_rgb (bool): Whether to convert img to rgb. |
| |
| Returns: |
| ndarray: The adjusted image. |
| """ |
| assert isinstance(eigval, np.ndarray) and isinstance(eigvec, np.ndarray), \ |
| f'eigval and eigvec should both be of type np.ndarray, got ' \ |
| f'{type(eigval)} and {type(eigvec)} instead.' |
|
|
| assert eigval.ndim == 1 and eigvec.ndim == 2 |
| assert eigvec.shape == (3, eigval.shape[0]) |
| n_eigval = eigval.shape[0] |
| assert isinstance(alphastd, float), 'alphastd should be of type float, ' \ |
| f'got {type(alphastd)} instead.' |
|
|
| img = img.copy().astype(np.float32) |
| if to_rgb: |
| cv2.cvtColor(img, cv2.COLOR_BGR2RGB, img) |
|
|
| alpha = np.random.normal(0, alphastd, n_eigval) |
| alter = eigvec \ |
| * np.broadcast_to(alpha.reshape(1, n_eigval), (3, n_eigval)) \ |
| * np.broadcast_to(eigval.reshape(1, n_eigval), (3, n_eigval)) |
| alter = np.broadcast_to(alter.sum(axis=1).reshape(1, 1, 3), img.shape) |
| img_adjusted = img + alter |
| return img_adjusted |
|
|
|
|
| def lut_transform(img, lut_table): |
| """Transform array by look-up table. |
| |
| The function lut_transform fills the output array with values from the |
| look-up table. Indices of the entries are taken from the input array. |
| |
| Args: |
| img (ndarray): Image to be transformed. |
| lut_table (ndarray): look-up table of 256 elements; in case of |
| multi-channel input array, the table should either have a single |
| channel (in this case the same table is used for all channels) or |
| the same number of channels as in the input array. |
| |
| Returns: |
| ndarray: The transformed image. |
| """ |
| assert isinstance(img, np.ndarray) |
| assert 0 <= np.min(img) and np.max(img) <= 255 |
| assert isinstance(lut_table, np.ndarray) |
| assert lut_table.shape == (256, ) |
|
|
| return cv2.LUT(np.array(img, dtype=np.uint8), lut_table) |
|
|
|
|
| def clahe(img, clip_limit=40.0, tile_grid_size=(8, 8)): |
| """Use CLAHE method to process the image. |
| |
| See `ZUIDERVELD,K. Contrast Limited Adaptive Histogram Equalization[J]. |
| Graphics Gems, 1994:474-485.` for more information. |
| |
| Args: |
| img (ndarray): Image to be processed. |
| clip_limit (float): Threshold for contrast limiting. Default: 40.0. |
| tile_grid_size (tuple[int]): Size of grid for histogram equalization. |
| Input image will be divided into equally sized rectangular tiles. |
| It defines the number of tiles in row and column. Default: (8, 8). |
| |
| Returns: |
| ndarray: The processed image. |
| """ |
| assert isinstance(img, np.ndarray) |
| assert img.ndim == 2 |
| assert isinstance(clip_limit, (float, int)) |
| assert is_tuple_of(tile_grid_size, int) |
| assert len(tile_grid_size) == 2 |
|
|
| clahe = cv2.createCLAHE(clip_limit, tile_grid_size) |
| return clahe.apply(np.array(img, dtype=np.uint8)) |
|
|