Shortcuts

Source code for caer.transforms.color

#    _____           ______  _____ 
#  / ____/    /\    |  ____ |  __ \
# | |        /  \   | |__   | |__) | Caer - Modern Computer Vision
# | |       / /\ \  |  __|  |  _  /  Languages: Python, C, C++, Cuda
# | |___   / ____ \ | |____ | | \ \  http://github.com/jasmcaus/caer
#  \_____\/_/    \_ \______ |_|  \_\

# Licensed under the MIT License <http://opensource.org/licenses/MIT>
# SPDX-License-Identifier: MIT
# Copyright (c) 2020-2021 The Caer Authors <http://github.com/jasmcaus>

#pylint:disable=unused-argument,unused-variable,eval-used

import numpy as np 
import random
import cv2 as cv 
import math

from ..coreten import Tensor, to_tensor
from ..annotations import Union,List,Tuple

from .functional import (
    is_list,
    _hls,
    _exposure_process
)


__all__ = [
    "adjust_brightness",
    "adjust_contrast",
    "adjust_hue",
    "adjust_saturation",
    "adjust_gamma",
    "affine",
    "darken",
    "brighten",
    "random_brightness",
    "correct_exposure",
    "augment_random"
]


[docs]def adjust_brightness(tens: Tensor, coeff : float, rgb : bool = True) -> Tensor: r""" Adjust the brightness of an image. Args: tens (Tensor) : Any regular ``caer.Tensor``. coeff (int): Coefficient value. - ``coeff < 1``, the image is darkened. - ``coeff = 1``, the image is unchanged. - ``coeff > 1``, the image is lightened. rgb (bool): Operate on RGB images. Default: True. Returns: Tensor of shape ``(height, width, channels)``. Examples:: >> tens = caer.data.sunrise(rgb=True) >> filtered = caer.transforms.adjust_brightness(tens, coeff=1.4, rgb=True) >> filtered (427, 640, 3) """ tens = to_tensor(tens, enforce_tensor=True) tens = _hls(tens) cspace = tens.cspace tens = np.array(tens, dtype=np.float64) tens[:,:,1] = tens[:,:,1] * coeff ## scale pixel values up or down for channel 1 (for lightness) if coeff > 1: tens[:,:,1][tens[:,:,1]>255] = 255 # Set all values > 255 to 255 else: tens[:,:,1][tens[:,:,1]<0] = 0 return to_tensor(tens, cspace=cspace, dtype=np.uint8)
[docs]def brighten(tens: Tensor, coeff : float = -1, rgb : bool = True) -> Tensor: r""" Brighten an image. Args: tens (Tensor) : Any regular ``caer.Tensor``. coeff (float): Coefficient value. rgb (bool): Operate on RGB images. Default: True. Returns: Tensor of shape ``(height, width, channels)``. Examples:: >> tens = caer.data.sunrise(rgb=True) >> filtered = caer.transforms.brighten(tens, coeff=-1, rgb=True) >> filtered (427, 640, 3) """ tens = to_tensor(tens, enforce_tensor=True) cspace = tens.cspace if coeff !=-1: if coeff < 0.0 or coeff > 1.0: raise ValueError("Brightness coefficient can only be between 0.0 and 1.0") if coeff == -1: coeff_t = 1 + random.uniform(0, 1) # coeff between 1.0 and 1.5 else: coeff_t = 1 + coeff # coeff between 1.0 and 2.0 tens = adjust_brightness(tens, coeff_t) return to_tensor(tens, cspace=cspace)
[docs]def darken(tens: Tensor, darkness_coeff : int = -1) -> Tensor: r""" Darken an image. Args: tens (Tensor) : Any regular ``caer.Tensor``. darkness_coeff (int): Coefficient value. rgb (bool): Operate on RGB images. Default: True. Returns: Tensor of shape ``(height, width, channels)``. Examples:: >> tens = caer.data.sunrise(rgb=True) >> filtered = caer.transforms.darken(tens, coeff=-1, rgb=True) >> filtered (427, 640, 3) """ tens = to_tensor(tens, enforce_tensor=True) cspace = tens.cspace if darkness_coeff != -1: if darkness_coeff < 0.0 or darkness_coeff > 1.0: raise ValueError("Darkness coeff must only be between 0.0 and 1.0") if darkness_coeff == -1: darkness_coeff_t = 1 - random.uniform(0, 1) else: darkness_coeff_t = 1 - darkness_coeff tens = adjust_brightness(tens, coeff=darkness_coeff_t) return to_tensor(tens, cspace=cspace)
[docs]def random_brightness(tens: Tensor) -> Tensor: r""" Add random brightness to an image. Args: tens (Tensor) : Any regular ``caer.Tensor``. Returns: Tensor of shape ``(height, width, channels)``. Examples:: >> tens = caer.data.sunrise() >> filtered = caer.transforms.random_brightness(tens, rgb=True) >> filtered (427, 640, 3) """ rand_br_coeff = 2 * np.random.uniform(0, 1) # Generates a value between 0.0 and 2.0 return adjust_brightness(tens, rand_br_coeff)
[docs]def adjust_contrast(tens: Tensor, contrast_factor:float) -> Tensor: """ Adjust contrast of an image. Args: tens (Tensor): Any valid ``caer.Tensor``. contrast_factor (float): How much to adjust the contrast. Can be any non negative number. 0 gives a solid gray image, 1 gives the original image while 2 increases the contrast by a factor of 2. Returns: ``caer.Tensor``: Contrast adjusted image. """ # It's much faster to use the LUT construction because you have to change dtypes multiple times tens = to_tensor(tens, enforce_tensor=True) cspace = tens.cspace table = np.array([(i - 74) * contrast_factor + 74 for i in range(0, 256)]).clip(0, 255).astype("uint8") try: from PIL import ImageEnhance except ImportError: raise ImportError("Pillow must be installed to use ``caer.color.adjust_saturation()``.") enhancer = ImageEnhance.Contrast(tens) tens = enhancer.enhance(contrast_factor) tens = cv.LUT(tens, table) return to_tensor(tens, cspace=cspace)
[docs]def adjust_saturation(tens: Tensor, saturation_factor:float) -> Tensor: """Adjust color saturation of an image. Args: tens (Tensor): Any valid ``caer.Tensor``. saturation_factor (float): How much to adjust the saturation. 0 will give a black and white image, 1 will give the original image while 2 will enhance the saturation by a factor of 2. Returns: Saturation-adjusted image. Examples:: >> tens = caer.data.sunrise(rgb=True) >> filtered = caer.transforms.adjust_saturation(tens, saturation_factor=1.5, rgb=True) >> filtered (427, 640, 3) """ tens = to_tensor(tens, enforce_tensor=True) cspace = tens.cspace try: from PIL import Image, ImageEnhance except ImportError: raise ImportError("Pillow must be installed to use ``caer.color.adjust_saturation()``.") tens = Image.fromarray(tens) enhancer = ImageEnhance.Color(tens) tens = enhancer.enhance(saturation_factor) return to_tensor(tens, cspace=cspace)
[docs]def adjust_hue(tens: Tensor, hue_factor:float) -> Tensor: r""" Adjust hue of an image. The image hue is adjusted by converting the image to HSV and cyclically shifting the intensities in the hue channel (H). The image is then converted back to original image mode. See `Hue`_ for more details. .. _Hue: https://en.wikipedia.org/wiki/Hue Args: tens (Tensor): Any valid ``caer.Tensor``. hue_factor (float): How much to shift the hue channel. Should be in the range [-0.5, 0.5]. 0.5 and -0.5 give complete reversal of hue channel in HSV space in positive and negative direction respectively. 0 means no shift. Therefore, both -0.5 and 0.5 will give an image with complementary colors while 0 gives the original image. Returns: Hue adjusted image. Examples:: >> tens = caer.data.sunrise(rgb=True) >> filtered = caer.transforms.adjust_hue(tens, hue_factor=1, rgb=True) >> filtered (427, 640, 3) """ tens = to_tensor(tens, enforce_tensor=True) cspace = tens.cspace if not (-0.5 <= hue_factor <= 0.5): raise ValueError("`hue_factor` is not in [-0.5, 0.5].") try: from PIL import Image except ImportError: raise ImportError("Pillow must be installed to use this ``caer.color.adjust_hue()``.") tens = Image.fromarray(tens) input_mode = tens.mode if input_mode in {"L", "1", "I", "F"}: return np.array(tens) h, s, v = tens.convert("hsv").split() np_h = np.array(h, dtype=np.uint8) # uint8 addition take cares of rotation across boundaries with np.errstate(over="ignore"): np_h += np.uint8(hue_factor * 255) h = Image.fromarray(np_h, "L") tens = Image.merge("hsv", (h, s, v)).convert(input_mode) return to_tensor(tens, cspace=cspace)
[docs]def adjust_gamma(tens: Tensor, gamma:float, gain:float=1) -> Tensor: r""" Perform gamma correction on an image. Also known as Power Law Transform. Intensities in RGB mode are adjusted based on the following equation: .. math:: I_{\text{out}} = 255 \times \text{gain} \times \left(\frac{I_{\text{in}}}{255}\right)^{\gamma} See `Gamma Correction`_ for more details. .. _Gamma Correction: https://en.wikipedia.org/wiki/Gamma_correction Args: tens (Tensor): Any valid ``caer.Tensor``. gamma (float): Non negative real number, same as :math:`\gamma` in the equation. gamma larger than 1 make the shadows darker, while gamma smaller than 1 make dark regions lighter. gain (float): The constant multiplier. Examples:: >> tens = caer.data.sunrise(rgb=True) >> filtered = caer.transforms.adjust_gamma(tens, gamma=1.5, rgb=True) >> filtered (427, 640, 3) """ tens = to_tensor(tens, enforce_tensor=True) cspace = tens.cspace if gamma < 0: raise ValueError("Gamma should be a non-negative real number") # from here # https://stackoverflow.com/questions/33322488/how-to-change-image-illumination-in-opencv-python/41061351 table = np.array([((i / 255.0)**gamma) * 255 * gain for i in np.arange(0, 256)]).astype("uint8") tens = cv.LUT(tens, table) return to_tensor(tens, cspace=cspace)
def _get_affine_matrix(center, angle, translate, scale, shear) -> Tensor: # Helper method to compute matrix for affine transformation # We need compute affine transformation matrix: M = T * C * RSS * C^-1 # where T is translation matrix: [1, 0, tx | 0, 1, ty | 0, 0, 1] # C is translation matrix to keep center: [1, 0, cx | 0, 1, cy | 0, 0, 1] # RSS is rotation with scale and shear matrix # RSS(a, scale, shear) = [ cos(a)*scale -sin(a + shear)*scale 0] # [ sin(a)*scale cos(a + shear)*scale 0] # [ 0 0 1] angle = math.radians(angle) shear = math.radians(shear) # scale = 1.0 / scale T = np.array([[1, 0, translate[0]], [0, 1, translate[1]], [0, 0, 1]]) C = np.array([[1, 0, center[0]], [0, 1, center[1]], [0, 0, 1]]) RSS = np.array( [[math.cos(angle) * scale, -math.sin(angle + shear) * scale, 0], [math.sin(angle) * scale, math.cos(angle + shear) * scale, 0], [0, 0, 1]]) matrix = T @ C @ RSS @ np.linalg.inv(C) return matrix[:2, :]
[docs]def affine( tens: Tensor, angle: Union[float,int], translate: Union[List[int],Tuple[int]], scale: float, shear: float, interpolation: str = "bilinear", mode: Union[str,int] = 0, fillcolor: int = 0 ) -> Tensor: """ Apply affine transformation on the image keeping image center invariant. Args: tens (Tensor): Any valid ``caer.Tensor``. angle (float or int): Rotation angle in degrees between -180 and 180, clockwise direction. translate (list or tuple of integers): Horizontal and vertical translations (post-rotation translation) scale (float): Overall scale shear (float): Shear angle value in degrees between -180 to 180, clockwise direction. interpolation (int, str): Interpolation to use for resizing. Defaults to `"bilinear"`. Supports `"bilinear"`, `"bicubic"`, `"area"`, `"nearest"`. mode (int, str): Method for filling in border regions. Defaults to ``constant`` meaning areas outside the image are filled with a value (val, default 0). Supports ``"replicate"``, ``"reflect"``, ``"reflect-101"``. fillcolor (int): Optional fill color for the area outside the transform in the output image. Default: 0 """ tens = to_tensor(tens, enforce_tensor=True) cspace = tens.cspace assert isinstance(translate, (tuple, list)) and len(translate) == 2, \ "Argument translate should be a list or tuple of length 2" assert scale > 0.0, "Argument scale should be positive" interpolation_methods = { "nearest": 0, "0": 0, 0: 0, # 0 "bilinear": 1, "1": 1, 1: 1, # 1 "bicubic": 2, "2": 2, 2: 2, # 2 "area": 3, "3": 3, 3: 3 # 3 } border_methods = { "constant": 0, "0": 0, 0: 0, # 0 "replicate": 1, "1": 1, 1: 1, # 1 "reflect": 2, "2": 2, 2: 2, # 2 "reflect-101": 4, "4": 4, 4: 4 # 4 } if interpolation not in interpolation_methods: raise ValueError("Specify a valid interpolation type - area/nearest/bicubic/bilinear") if mode not in border_methods: raise ValueError("Specify a valid border type - constant/replicate/reflect/reflect-101") output_size = tens.shape[0:2] center = (tens.shape[1] * 0.5 + 0.5, tens.shape[0] * 0.5 + 0.5) matrix = _get_affine_matrix(center, angle, translate, scale, shear) tens = cv.warpAffine(tens, matrix, output_size[::-1], interpolation, borderMode=mode, borderValue=fillcolor) return to_tensor(tens, cspace=cspace)
[docs]def correct_exposure(tens: Tensor, rgb : bool = True) -> Tensor: r""" Correct the exposure of an image. Args: tens (Tensor) : Any regular ``caer.Tensor``. rgb (bool): Operate on RGB images. Default: True. Returns: Tensor of shape ``(height, width, channels)``. Examples:: >> tens = caer.data.sunrise(rgb=True) >> filtered = caer.transforms.correct_exposure(tens, rgb=True) >> filtered (427, 640, 3) """ return _exposure_process(tens)
def augment_random(tens: Tensor, aug_types : Union[str, List[str]] = "", volume : str = "expand") -> List: aug_types_all = [ "random_brightness", "add_shadow", "add_snow", "add_rain", "add_fog", "add_gravel", "add_sun_flare","add_motion_blur","add_autumn","random_flip" ] if aug_types == "": aug_types = aug_types_all output : list = [] if not is_list(aug_types): raise ValueError("`aug_types` should be a list of function names (str)") if volume == "expand": for aug_type in aug_types: if not(aug_type in aug_types_all): raise ValueError("Incorrect transformation function defined") command = aug_type + "(tens)" result = eval(command) output.append(result) elif volume == "same": for aug_type in aug_types: if not (aug_type in aug_types_all): raise ValueError("Incorrect transformation function defined") selected_aug = aug_types[random.randint(0, len(aug_types)-1)] command = selected_aug+"(tens)" output = eval(command) else: raise ValueError("volume type can only be `same` or `expand`") return output