CoolFace
Apppublic

ICML2022/resefa

sourceHugging Faceupdated 4y agoView on Hugging Face
4likes
image_utils.py333 linesDownload Raw Back to utils
1# python3.72"""Contains utility functions for image processing.3 4The module is primarily built on `cv2`. But, differently, we assume all colorful5images are with `RGB` channel order by default. Also, we assume all gray-scale6images to be with shape [height, width, 1].7"""8 9import os10import cv211import numpy as np12 13from .misc import IMAGE_EXTENSIONS14from .misc import check_file_ext15 16__all__ = [17    'get_blank_image', 'load_image', 'save_image', 'resize_image',18    'add_text_to_image', 'preprocess_image', 'postprocess_image',19    'parse_image_size', 'get_grid_shape', 'list_images_from_dir'20]21 22 23def _check_2d_image(image):24    """Checks whether a given image is valid.25 26    A valid image is expected to be with dtype `uint8`. Also, it should have27    shape like:28 29    (1) (height, width, 1)  # gray-scale image.30    (2) (height, width, 3)  # colorful image.31    (3) (height, width, 4)  # colorful image with transparency (RGBA)32    """33    assert isinstance(image, np.ndarray)34    assert image.dtype == np.uint835    assert image.ndim == 3 and image.shape[2] in [1, 3, 4]36 37 38def get_blank_image(height, width, channels=3, use_black=True):39    """Gets a blank image, either white of black.40 41    NOTE: This function will always return an image with `RGB` channel order for42    color image and pixel range [0, 255].43 44    Args:45        height: Height of the returned image.46        width: Width of the returned image.47        channels: Number of channels. (default: 3)48        use_black: Whether to return a black image. (default: True)49    """50    shape = (height, width, channels)51    if use_black:52        return np.zeros(shape, dtype=np.uint8)53    return np.ones(shape, dtype=np.uint8) * 25554 55 56def load_image(path):57    """Loads an image from disk.58 59    NOTE: This function will always return an image with `RGB` channel order for60    color image and pixel range [0, 255].61 62    Args:63        path: Path to load the image from.64 65    Returns:66        An image with dtype `np.ndarray`, or `None` if `path` does not exist.67    """68    image = cv2.imread(path, cv2.IMREAD_UNCHANGED)69    if image is None:70        return None71 72    if image.ndim == 2:73        image = image[:, :, np.newaxis]74    _check_2d_image(image)75    if image.shape[2] == 3:76        return cv2.cvtColor(image, cv2.COLOR_BGR2RGB)77    if image.shape[2] == 4:78        return cv2.cvtColor(image, cv2.COLOR_BGRA2RGBA)79    return image80 81 82def save_image(path, image):83    """Saves an image to disk.84 85    NOTE: The input image (if colorful) is assumed to be with `RGB` channel86    order and pixel range [0, 255].87 88    Args:89        path: Path to save the image to.90        image: Image to save.91    """92    if image is None:93        return94 95    _check_2d_image(image)96    if image.shape[2] == 1:97        cv2.imwrite(path, image)98    elif image.shape[2] == 3:99        cv2.imwrite(path, cv2.cvtColor(image, cv2.COLOR_RGB2BGR))100    elif image.shape[2] == 4:101        cv2.imwrite(path, cv2.cvtColor(image, cv2.COLOR_RGBA2BGRA))102 103 104def resize_image(image, *args, **kwargs):105    """Resizes image.106 107    This is a wrap of `cv2.resize()`.108 109    NOTE: The channel order of the input image will not be changed.110 111    Args:112        image: Image to resize.113        *args: Additional positional arguments.114        **kwargs: Additional keyword arguments.115 116    Returns:117        An image with dtype `np.ndarray`, or `None` if `image` is empty.118    """119    if image is None:120        return None121 122    _check_2d_image(image)123    if image.shape[2] == 1:  # Re-expand the squeezed dim of gray-scale image.124        return cv2.resize(image, *args, **kwargs)[:, :, np.newaxis]125    return cv2.resize(image, *args, **kwargs)126 127 128def add_text_to_image(image,129                      text='',130                      position=None,131                      font=cv2.FONT_HERSHEY_TRIPLEX,132                      font_size=1.0,133                      line_type=cv2.LINE_8,134                      line_width=1,135                      color=(255, 255, 255)):136    """Overlays text on given image.137 138    NOTE: The input image is assumed to be with `RGB` channel order.139 140    Args:141        image: The image to overlay text on.142        text: Text content to overlay on the image. (default: empty)143        position: Target position (bottom-left corner) to add text. If not set,144            center of the image will be used by default. (default: None)145        font: Font of the text added. (default: cv2.FONT_HERSHEY_TRIPLEX)146        font_size: Font size of the text added. (default: 1.0)147        line_type: Line type used to depict the text. (default: cv2.LINE_8)148        line_width: Line width used to depict the text. (default: 1)149        color: Color of the text added in `RGB` channel order. (default:150            (255, 255, 255))151 152    Returns:153        An image with target text overlaid on.154    """155    if image is None or not text:156        return image157 158    _check_2d_image(image)159    cv2.putText(img=image,160                text=text,161                org=position,162                fontFace=font,163                fontScale=font_size,164                color=color,165                thickness=line_width,166                lineType=line_type,167                bottomLeftOrigin=False)168    return image169 170 171def preprocess_image(image, min_val=-1.0, max_val=1.0):172    """Pre-processes image by adjusting the pixel range and to dtype `float32`.173 174    This function is particularly used to convert an image or a batch of images175    to `NCHW` format, which matches the data type commonly used in deep models.176 177    NOTE: The input image is assumed to be with pixel range [0, 255] and with178    format `HWC` or `NHWC`. The returned image will be always be with format179    `NCHW`.180 181    Args:182        image: The input image for pre-processing.183        min_val: Minimum value of the output image.184        max_val: Maximum value of the output image.185 186    Returns:187        The pre-processed image.188    """189    assert isinstance(image, np.ndarray)190 191    image = image.astype(np.float64)192    image = image / 255.0 * (max_val - min_val) + min_val193 194    if image.ndim == 3:195        image = image[np.newaxis]196    assert image.ndim == 4 and image.shape[3] in [1, 3, 4]197    return image.transpose(0, 3, 1, 2)198 199 200def postprocess_image(image, min_val=-1.0, max_val=1.0):201    """Post-processes image to pixel range [0, 255] with dtype `uint8`.202 203    This function is particularly used to handle the results produced by deep204    models.205 206    NOTE: The input image is assumed to be with format `NCHW`, and the returned207    image will always be with format `NHWC`.208 209    Args:210        image: The input image for post-processing.211        min_val: Expected minimum value of the input image.212        max_val: Expected maximum value of the input image.213 214    Returns:215        The post-processed image.216    """217    assert isinstance(image, np.ndarray)218 219    image = image.astype(np.float64)220    image = (image - min_val) / (max_val - min_val) * 255221    image = np.clip(image + 0.5, 0, 255).astype(np.uint8)222 223    assert image.ndim == 4 and image.shape[1] in [1, 3, 4]224    return image.transpose(0, 2, 3, 1)225 226 227def parse_image_size(obj):228    """Parses an object to a pair of image size, i.e., (height, width).229 230    Args:231        obj: The input object to parse image size from.232 233    Returns:234        A two-element tuple, indicating image height and width respectively.235 236    Raises:237        If the input is invalid, i.e., neither a list or tuple, nor a string.238    """239    if obj is None or obj == '':240        height = 0241        width = 0242    elif isinstance(obj, int):243        height = obj244        width = obj245    elif isinstance(obj, (list, tuple, str, np.ndarray)):246        if isinstance(obj, str):247            splits = obj.replace(' ', '').split(',')248            numbers = tuple(map(int, splits))249        else:250            numbers = tuple(obj)251        if len(numbers) == 0:252            height = 0253            width = 0254        elif len(numbers) == 1:255            height = int(numbers[0])256            width = int(numbers[0])257        elif len(numbers) == 2:258            height = int(numbers[0])259            width = int(numbers[1])260        else:261            raise ValueError('At most two elements for image size.')262    else:263        raise ValueError(f'Invalid type of input: `{type(obj)}`!')264 265    return (max(0, height), max(0, width))266 267 268def get_grid_shape(size, height=0, width=0, is_portrait=False):269    """Gets the shape of a grid based on the size.270 271    This function makes greatest effort on making the output grid square if272    neither `height` nor `width` is set. If `is_portrait` is set as `False`, the273    height will always be equal to or smaller than the width. For example, if274    input `size = 16`, output shape will be `(4, 4)`; if input `size = 15`,275    output shape will be (3, 5). Otherwise, the height will always be equal to276    or larger than the width.277 278    Args:279        size: Size (height * width) of the target grid.280        height: Expected height. If `size % height != 0`, this field will be281            ignored. (default: 0)282        width: Expected width. If `size % width != 0`, this field will be283            ignored. (default: 0)284        is_portrait: Whether to return a portrait size of a landscape size.285            (default: False)286 287    Returns:288        A two-element tuple, representing height and width respectively.289    """290    assert isinstance(size, int)291    assert isinstance(height, int)292    assert isinstance(width, int)293    if size <= 0:294        return (0, 0)295 296    if height > 0 and width > 0 and height * width != size:297        height = 0298        width = 0299 300    if height > 0 and width > 0 and height * width == size:301        return (height, width)302    if height > 0 and size % height == 0:303        return (height, size // height)304    if width > 0 and size % width == 0:305        return (size // width, width)306 307    height = int(np.sqrt(size))308    while height > 0:309        if size % height == 0:310            width = size // height311            break312        height = height - 1313 314    return (width, height) if is_portrait else (height, width)315 316 317def list_images_from_dir(directory):318    """Lists all images from the given directory.319 320    NOTE: Do NOT support finding images recursively.321 322    Args:323        directory: The directory to find images from.324 325    Returns:326        A list of sorted filenames, with the directory as prefix.327    """328    image_list = []329    for filename in os.listdir(directory):330        if check_file_ext(filename, *IMAGE_EXTENSIONS):331            image_list.append(os.path.join(directory, filename))332    return sorted(image_list)333