Spaces:
Running
Running
import os | |
import imghdr | |
import numbers | |
import warnings | |
from io import BytesIO | |
import cv2 | |
import khandy | |
import numpy as np | |
from PIL import Image | |
def imread(file_or_buffer, flags=-1): | |
"""Improvement on cv2.imread, make it support filename including chinese character. | |
""" | |
try: | |
if isinstance(file_or_buffer, bytes): | |
return cv2.imdecode(np.frombuffer(file_or_buffer, dtype=np.uint8), flags) | |
else: | |
# support type: file or str or Path | |
return cv2.imdecode(np.fromfile(file_or_buffer, dtype=np.uint8), flags) | |
except Exception as e: | |
print(e) | |
return None | |
def imread_cv(file_or_buffer, flags=-1): | |
warnings.warn('khandy.imread_cv will be deprecated, use khandy.imread instead!') | |
return imread(file_or_buffer, flags) | |
def imwrite(filename, image, params=None): | |
"""Improvement on cv2.imwrite, make it support filename including chinese character. | |
""" | |
cv2.imencode(os.path.splitext(filename)[-1], image, params)[1].tofile(filename) | |
def imwrite_cv(filename, image, params=None): | |
warnings.warn('khandy.imwrite_cv will be deprecated, use khandy.imwrite instead!') | |
return imwrite(filename, image, params) | |
def imread_pil(file_or_buffer, to_mode=None): | |
"""Improvement on Image.open to avoid ResourceWarning. | |
""" | |
try: | |
if isinstance(file_or_buffer, bytes): | |
buffer = BytesIO() | |
buffer.write(file_or_buffer) | |
buffer.seek(0) | |
file_or_buffer = buffer | |
if hasattr(file_or_buffer, 'read'): | |
image = Image.open(file_or_buffer) | |
if to_mode is not None: | |
image = image.convert(to_mode) | |
else: | |
# open path as file to avoid ResourceWarning (https://github.com/python-pillow/Pillow/issues/835) | |
with open(file_or_buffer, 'rb') as f: | |
image = Image.open(f) | |
# If convert outside with statement, will raise "seek of closed file" as | |
# https://github.com/microsoft/Swin-Transformer/issues/66 | |
if to_mode is not None: | |
image = image.convert(to_mode) | |
return image | |
except Exception as e: | |
print(e) | |
return None | |
def imwrite_bytes(filename, image_bytes: bytes, update_extension: bool = True): | |
"""Write image bytes to file. | |
Args: | |
filename: str | |
filename which image_bytes is written into. | |
image_bytes: bytes | |
image content to be written. | |
update_extension: bool | |
whether update extension according to image_bytes or not. | |
the cost of update extension is smaller than update image format. | |
""" | |
extension = imghdr.what('', image_bytes) | |
file_extension = khandy.get_path_extension(filename) | |
# imghdr.what fails to determine image format sometimes! | |
# so when its return value is None, never update extension. | |
if extension is None: | |
image = cv2.imdecode(np.frombuffer(image_bytes, np.uint8), -1) | |
image_bytes = cv2.imencode(file_extension, image)[1] | |
elif (extension.lower() != file_extension.lower()[1:]): | |
if update_extension: | |
filename = khandy.replace_path_extension(filename, extension) | |
else: | |
image = cv2.imdecode(np.frombuffer(image_bytes, np.uint8), -1) | |
image_bytes = cv2.imencode(file_extension, image)[1] | |
with open(filename, "wb") as f: | |
f.write(image_bytes) | |
return filename | |
def rescale_image(image: np.ndarray, rescale_factor='auto', dst_dtype=np.float32): | |
"""Rescale image by rescale_factor. | |
Args: | |
img (ndarray): Image to be rescaled. | |
rescale_factor (str, int or float, *optional*, defaults to `'auto'`): | |
rescale the image by the specified scale factor. When is `'auto'`, | |
rescale the image to [0, 1). | |
dtype (np.dtype, *optional*, defaults to `np.float32`): | |
The dtype of the output image. Defaults to `np.float32`. | |
Returns: | |
ndarray: The rescaled image. | |
""" | |
if rescale_factor == 'auto': | |
if np.issubdtype(image.dtype, np.unsignedinteger): | |
rescale_factor = 1. / np.iinfo(image.dtype).max | |
else: | |
raise TypeError(f'Only support uint dtype ndarray when `rescale_factor` is `auto`, got {image.dtype}') | |
elif issubclass(rescale_factor, (int, float)): | |
pass | |
else: | |
raise TypeError('rescale_factor must be "auto", int or float') | |
image = image.astype(dst_dtype, copy=True) | |
image *= rescale_factor | |
image = image.astype(dst_dtype) | |
return image | |
def normalize_image_value(image: np.ndarray, mean, std, rescale_factor=None): | |
"""Normalize an image with mean and std, rescale optionally. | |
Args: | |
image (ndarray): Image to be normalized. | |
mean (int, float, Sequence[int], Sequence[float], ndarray): The mean to be used for normalize. | |
std (int, float, Sequence[int], Sequence[float], ndarray): The std to be used for normalize. | |
rescale_factor (None, 'auto', int or float, *optional*, defaults to `None`): | |
rescale the image by the specified scale factor. When is `'auto'`, | |
rescale the image to [0, 1); When is `None`, do not rescale. | |
Returns: | |
ndarray: The normalized image which dtype is np.float32. | |
""" | |
dst_dtype = np.float32 | |
mean = np.array(mean, dtype=dst_dtype).flatten() | |
std = np.array(std, dtype=dst_dtype).flatten() | |
if rescale_factor == 'auto': | |
if np.issubdtype(image.dtype, np.unsignedinteger): | |
mean *= np.iinfo(image.dtype).max | |
std *= np.iinfo(image.dtype).max | |
else: | |
raise TypeError(f'Only support uint dtype ndarray when `rescale_factor` is `auto`, got {image.dtype}') | |
elif isinstance(rescale_factor, (int, float)): | |
mean *= rescale_factor | |
std *= rescale_factor | |
image = image.astype(dst_dtype, copy=True) | |
image -= mean | |
image /= std | |
return image | |
def normalize_image_dtype(image, keep_num_channels=False): | |
"""Normalize image dtype to uint8 (usually for visualization). | |
Args: | |
image : ndarray | |
Input image. | |
keep_num_channels : bool, optional | |
If this is set to True, the result is an array which has | |
the same shape as input image, otherwise the result is | |
an array whose channels number is 3. | |
Returns: | |
out: ndarray | |
Image whose dtype is np.uint8. | |
""" | |
assert (image.ndim == 3 and image.shape[-1] in [1, 3]) or (image.ndim == 2) | |
image = image.astype(np.float32) | |
image = khandy.minmax_normalize(image, axis=None, copy=False) | |
image = np.array(image * 255, dtype=np.uint8) | |
if not keep_num_channels: | |
if image.ndim == 2: | |
image = np.expand_dims(image, -1) | |
if image.shape[-1] == 1: | |
image = np.tile(image, (1,1,3)) | |
return image | |
def normalize_image_channel(image, swap_rb=False): | |
"""Normalize image channel number and order to RGB or BGR. | |
Args: | |
image : ndarray | |
Input image. | |
swap_rb : bool, optional | |
whether swap red and blue channel or not | |
Returns: | |
out: ndarray | |
Image whose shape is (..., 3). | |
""" | |
if image.ndim == 2: | |
image = cv2.cvtColor(image, cv2.COLOR_GRAY2BGR) | |
elif image.ndim == 3: | |
num_channels = image.shape[-1] | |
if num_channels == 1: | |
image = cv2.cvtColor(image, cv2.COLOR_GRAY2BGR) | |
elif num_channels == 3: | |
if swap_rb: | |
image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB) | |
elif num_channels == 4: | |
if swap_rb: | |
image = cv2.cvtColor(image, cv2.COLOR_BGRA2RGB) | |
else: | |
image = cv2.cvtColor(image, cv2.COLOR_BGRA2BGR) | |
else: | |
raise ValueError(f'Unsupported image channel number, only support 1, 3 and 4, got {num_channels}!') | |
else: | |
raise ValueError(f'Unsupported image ndarray ndim, only support 2 and 3, got {image.ndim}!') | |
return image | |
def normalize_image_shape(image, swap_rb=False): | |
warnings.warn('khandy.normalize_image_shape will be deprecated, use khandy.normalize_image_channel instead!') | |
return normalize_image_channel(image, swap_rb) | |
def stack_image_list(image_list, dtype=np.float32): | |
"""Join a sequence of image along a new axis before first axis. | |
References: | |
`im_list_to_blob` in `py-faster-rcnn-master/lib/utils/blob.py` | |
""" | |
assert isinstance(image_list, (tuple, list)) | |
max_dimension = np.array([image.ndim for image in image_list]).max() | |
assert max_dimension in [2, 3] | |
max_shape = np.array([image.shape[:2] for image in image_list]).max(axis=0) | |
num_channels = [] | |
for image in image_list: | |
if image.ndim == 2: | |
num_channels.append(1) | |
else: | |
num_channels.append(image.shape[-1]) | |
assert len(set(num_channels) - set([1])) in [0, 1] | |
max_num_channels = np.max(num_channels) | |
blob = np.empty((len(image_list), max_shape[0], max_shape[1], max_num_channels), dtype=dtype) | |
for k, image in enumerate(image_list): | |
blob[k, :image.shape[0], :image.shape[1], :] = np.atleast_3d(image).astype(dtype, copy=False) | |
if max_dimension == 2: | |
blob = np.squeeze(blob, axis=-1) | |
return blob | |
def is_numpy_image(image): | |
return isinstance(image, np.ndarray) and image.ndim in {2, 3} | |
def is_gray_image(image, tol=3): | |
assert is_numpy_image(image) | |
if image.ndim == 2: | |
return True | |
elif image.ndim == 3: | |
num_channels = image.shape[-1] | |
if num_channels == 1: | |
return True | |
elif num_channels == 3: | |
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) | |
gray3 = cv2.cvtColor(gray, cv2.COLOR_GRAY2BGR) | |
mae = np.mean(cv2.absdiff(image, gray3)) | |
return mae <= tol | |
elif num_channels == 4: | |
rgb = cv2.cvtColor(image, cv2.COLOR_BGRA2BGR) | |
gray = cv2.cvtColor(rgb, cv2.COLOR_BGR2GRAY) | |
gray3 = cv2.cvtColor(gray, cv2.COLOR_GRAY2BGR) | |
mae = np.mean(cv2.absdiff(rgb, gray3)) | |
return mae <= tol | |
else: | |
return False | |
else: | |
return False | |
def is_solid_color_image(image, tol=4): | |
assert is_numpy_image(image) | |
mean = np.array(cv2.mean(image)[:-1], dtype=np.float32) | |
if image.ndim == 2: | |
mae = np.mean(np.abs(image - mean[0])) | |
return mae <= tol | |
elif image.ndim == 3: | |
num_channels = image.shape[-1] | |
if num_channels == 1: | |
mae = np.mean(np.abs(image - mean[0])) | |
return mae <= tol | |
elif num_channels == 3: | |
mae = np.mean(np.abs(image - mean)) | |
return mae <= tol | |
elif num_channels == 4: | |
mae = np.mean(np.abs(image[:,:,:-1] - mean)) | |
return mae <= tol | |
else: | |
return False | |
else: | |
return False | |
def create_solid_color_image(image_width, image_height, color, dtype=None): | |
if isinstance(color, numbers.Real): | |
image = np.full((image_height, image_width), color, dtype=dtype) | |
elif isinstance(color, (tuple, list)): | |
if len(color) == 1: | |
image = np.full((image_height, image_width), color[0], dtype=dtype) | |
elif len(color) in (3, 4): | |
image = np.full((1, 1, len(color)), color, dtype=dtype) | |
image = cv2.copyMakeBorder(image, 0, image_height-1, 0, image_width-1, | |
cv2.BORDER_CONSTANT, value=color) | |
else: | |
color = np.asarray(color, dtype=dtype) | |
image = np.empty((image_height, image_width, len(color)), dtype=dtype) | |
image[:] = color | |
else: | |
raise TypeError(f'Invalid type {type(color)} for `color`.') | |
return image | |