|
|
|
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)
|
|
|