|
|
|
import io |
|
import os.path as osp |
|
from pathlib import Path |
|
|
|
import cv2 |
|
import numpy as np |
|
from cv2 import (IMREAD_COLOR, IMREAD_GRAYSCALE, IMREAD_IGNORE_ORIENTATION, |
|
IMREAD_UNCHANGED) |
|
|
|
from annotator.uniformer.mmcv.utils import check_file_exist, is_str, mkdir_or_exist |
|
|
|
try: |
|
from turbojpeg import TJCS_RGB, TJPF_BGR, TJPF_GRAY, TurboJPEG |
|
except ImportError: |
|
TJCS_RGB = TJPF_GRAY = TJPF_BGR = TurboJPEG = None |
|
|
|
try: |
|
from PIL import Image, ImageOps |
|
except ImportError: |
|
Image = None |
|
|
|
try: |
|
import tifffile |
|
except ImportError: |
|
tifffile = None |
|
|
|
jpeg = None |
|
supported_backends = ['cv2', 'turbojpeg', 'pillow', 'tifffile'] |
|
|
|
imread_flags = { |
|
'color': IMREAD_COLOR, |
|
'grayscale': IMREAD_GRAYSCALE, |
|
'unchanged': IMREAD_UNCHANGED, |
|
'color_ignore_orientation': IMREAD_IGNORE_ORIENTATION | IMREAD_COLOR, |
|
'grayscale_ignore_orientation': |
|
IMREAD_IGNORE_ORIENTATION | IMREAD_GRAYSCALE |
|
} |
|
|
|
imread_backend = 'cv2' |
|
|
|
|
|
def use_backend(backend): |
|
"""Select a backend for image decoding. |
|
|
|
Args: |
|
backend (str): The image decoding backend type. Options are `cv2`, |
|
`pillow`, `turbojpeg` (see https://github.com/lilohuang/PyTurboJPEG) |
|
and `tifffile`. `turbojpeg` is faster but it only supports `.jpeg` |
|
file format. |
|
""" |
|
assert backend in supported_backends |
|
global imread_backend |
|
imread_backend = backend |
|
if imread_backend == 'turbojpeg': |
|
if TurboJPEG is None: |
|
raise ImportError('`PyTurboJPEG` is not installed') |
|
global jpeg |
|
if jpeg is None: |
|
jpeg = TurboJPEG() |
|
elif imread_backend == 'pillow': |
|
if Image is None: |
|
raise ImportError('`Pillow` is not installed') |
|
elif imread_backend == 'tifffile': |
|
if tifffile is None: |
|
raise ImportError('`tifffile` is not installed') |
|
|
|
|
|
def _jpegflag(flag='color', channel_order='bgr'): |
|
channel_order = channel_order.lower() |
|
if channel_order not in ['rgb', 'bgr']: |
|
raise ValueError('channel order must be either "rgb" or "bgr"') |
|
|
|
if flag == 'color': |
|
if channel_order == 'bgr': |
|
return TJPF_BGR |
|
elif channel_order == 'rgb': |
|
return TJCS_RGB |
|
elif flag == 'grayscale': |
|
return TJPF_GRAY |
|
else: |
|
raise ValueError('flag must be "color" or "grayscale"') |
|
|
|
|
|
def _pillow2array(img, flag='color', channel_order='bgr'): |
|
"""Convert a pillow image to numpy array. |
|
|
|
Args: |
|
img (:obj:`PIL.Image.Image`): The image loaded using PIL |
|
flag (str): Flags specifying the color type of a loaded image, |
|
candidates are 'color', 'grayscale' and 'unchanged'. |
|
Default to 'color'. |
|
channel_order (str): The channel order of the output image array, |
|
candidates are 'bgr' and 'rgb'. Default to 'bgr'. |
|
|
|
Returns: |
|
np.ndarray: The converted numpy array |
|
""" |
|
channel_order = channel_order.lower() |
|
if channel_order not in ['rgb', 'bgr']: |
|
raise ValueError('channel order must be either "rgb" or "bgr"') |
|
|
|
if flag == 'unchanged': |
|
array = np.array(img) |
|
if array.ndim >= 3 and array.shape[2] >= 3: |
|
array[:, :, :3] = array[:, :, (2, 1, 0)] |
|
else: |
|
|
|
if flag in ['color', 'grayscale']: |
|
img = ImageOps.exif_transpose(img) |
|
|
|
if img.mode != 'RGB': |
|
if img.mode != 'LA': |
|
|
|
img = img.convert('RGB') |
|
else: |
|
|
|
|
|
|
|
|
|
|
|
img_rgba = img.convert('RGBA') |
|
img = Image.new('RGB', img_rgba.size, (124, 117, 104)) |
|
img.paste(img_rgba, mask=img_rgba.split()[3]) |
|
if flag in ['color', 'color_ignore_orientation']: |
|
array = np.array(img) |
|
if channel_order != 'rgb': |
|
array = array[:, :, ::-1] |
|
elif flag in ['grayscale', 'grayscale_ignore_orientation']: |
|
img = img.convert('L') |
|
array = np.array(img) |
|
else: |
|
raise ValueError( |
|
'flag must be "color", "grayscale", "unchanged", ' |
|
f'"color_ignore_orientation" or "grayscale_ignore_orientation"' |
|
f' but got {flag}') |
|
return array |
|
|
|
|
|
def imread(img_or_path, flag='color', channel_order='bgr', backend=None): |
|
"""Read an image. |
|
|
|
Args: |
|
img_or_path (ndarray or str or Path): Either a numpy array or str or |
|
pathlib.Path. If it is a numpy array (loaded image), then |
|
it will be returned as is. |
|
flag (str): Flags specifying the color type of a loaded image, |
|
candidates are `color`, `grayscale`, `unchanged`, |
|
`color_ignore_orientation` and `grayscale_ignore_orientation`. |
|
By default, `cv2` and `pillow` backend would rotate the image |
|
according to its EXIF info unless called with `unchanged` or |
|
`*_ignore_orientation` flags. `turbojpeg` and `tifffile` backend |
|
always ignore image's EXIF info regardless of the flag. |
|
The `turbojpeg` backend only supports `color` and `grayscale`. |
|
channel_order (str): Order of channel, candidates are `bgr` and `rgb`. |
|
backend (str | None): The image decoding backend type. Options are |
|
`cv2`, `pillow`, `turbojpeg`, `tifffile`, `None`. |
|
If backend is None, the global imread_backend specified by |
|
``mmcv.use_backend()`` will be used. Default: None. |
|
|
|
Returns: |
|
ndarray: Loaded image array. |
|
""" |
|
|
|
if backend is None: |
|
backend = imread_backend |
|
if backend not in supported_backends: |
|
raise ValueError(f'backend: {backend} is not supported. Supported ' |
|
"backends are 'cv2', 'turbojpeg', 'pillow'") |
|
if isinstance(img_or_path, Path): |
|
img_or_path = str(img_or_path) |
|
|
|
if isinstance(img_or_path, np.ndarray): |
|
return img_or_path |
|
elif is_str(img_or_path): |
|
check_file_exist(img_or_path, |
|
f'img file does not exist: {img_or_path}') |
|
if backend == 'turbojpeg': |
|
with open(img_or_path, 'rb') as in_file: |
|
img = jpeg.decode(in_file.read(), |
|
_jpegflag(flag, channel_order)) |
|
if img.shape[-1] == 1: |
|
img = img[:, :, 0] |
|
return img |
|
elif backend == 'pillow': |
|
img = Image.open(img_or_path) |
|
img = _pillow2array(img, flag, channel_order) |
|
return img |
|
elif backend == 'tifffile': |
|
img = tifffile.imread(img_or_path) |
|
return img |
|
else: |
|
flag = imread_flags[flag] if is_str(flag) else flag |
|
img = cv2.imread(img_or_path, flag) |
|
if flag == IMREAD_COLOR and channel_order == 'rgb': |
|
cv2.cvtColor(img, cv2.COLOR_BGR2RGB, img) |
|
return img |
|
else: |
|
raise TypeError('"img" must be a numpy array or a str or ' |
|
'a pathlib.Path object') |
|
|
|
|
|
def imfrombytes(content, flag='color', channel_order='bgr', backend=None): |
|
"""Read an image from bytes. |
|
|
|
Args: |
|
content (bytes): Image bytes got from files or other streams. |
|
flag (str): Same as :func:`imread`. |
|
backend (str | None): The image decoding backend type. Options are |
|
`cv2`, `pillow`, `turbojpeg`, `None`. If backend is None, the |
|
global imread_backend specified by ``mmcv.use_backend()`` will be |
|
used. Default: None. |
|
|
|
Returns: |
|
ndarray: Loaded image array. |
|
""" |
|
|
|
if backend is None: |
|
backend = imread_backend |
|
if backend not in supported_backends: |
|
raise ValueError(f'backend: {backend} is not supported. Supported ' |
|
"backends are 'cv2', 'turbojpeg', 'pillow'") |
|
if backend == 'turbojpeg': |
|
img = jpeg.decode(content, _jpegflag(flag, channel_order)) |
|
if img.shape[-1] == 1: |
|
img = img[:, :, 0] |
|
return img |
|
elif backend == 'pillow': |
|
buff = io.BytesIO(content) |
|
img = Image.open(buff) |
|
img = _pillow2array(img, flag, channel_order) |
|
return img |
|
else: |
|
img_np = np.frombuffer(content, np.uint8) |
|
flag = imread_flags[flag] if is_str(flag) else flag |
|
img = cv2.imdecode(img_np, flag) |
|
if flag == IMREAD_COLOR and channel_order == 'rgb': |
|
cv2.cvtColor(img, cv2.COLOR_BGR2RGB, img) |
|
return img |
|
|
|
|
|
def imwrite(img, file_path, params=None, auto_mkdir=True): |
|
"""Write image to file. |
|
|
|
Args: |
|
img (ndarray): Image array to be written. |
|
file_path (str): Image file path. |
|
params (None or list): Same as opencv :func:`imwrite` interface. |
|
auto_mkdir (bool): If the parent folder of `file_path` does not exist, |
|
whether to create it automatically. |
|
|
|
Returns: |
|
bool: Successful or not. |
|
""" |
|
if auto_mkdir: |
|
dir_name = osp.abspath(osp.dirname(file_path)) |
|
mkdir_or_exist(dir_name) |
|
return cv2.imwrite(file_path, img, params) |
|
|