DeepFaceLive/xlib/image/ImageProcessor.py
2022-09-15 22:15:35 +04:00

919 lines
28 KiB
Python

from enum import IntEnum
from typing import Tuple, Union
import cv2
import numexpr as ne
import numpy as np
class ImageProcessor:
"""
Generic image processor for numpy images
arguments
img np.ndarray HW (2 ndim)
HWC (3 ndim)
NHWC (4 ndim)
"""
def __init__(self, img : np.ndarray, copy=False):
if copy:
img = img.copy()
ndim = img.ndim
if ndim not in [2,3,4]:
raise ValueError(f'img.ndim must be 2,3,4, not {ndim}.')
# Make internal image as NHWC
if ndim == 2:
N, (H,W), C = 0, img.shape, 0
img = img[None,:,:,None]
elif ndim == 3:
N, (H,W,C) = 0, img.shape
img = img[None,...]
else:
N,H,W,C = img.shape
self._img : np.ndarray = img
def copy(self) -> 'ImageProcessor':
"""
"""
ip = ImageProcessor.__new__(ImageProcessor)
ip._img = self._img.copy()
return ip
def get_dims(self) -> Tuple[int,int,int,int]:
"""
returns dimensions of current working image
returns N,H,W,C (ints) , each >= 1
"""
return self._img.shape
def get_dtype(self):
return self._img.dtype
def gamma(self, red : float, green : float, blue : float, mask=None) -> 'ImageProcessor':
dtype = self.get_dtype()
self.to_ufloat32()
img = orig_img = self._img
img = np.power(img, np.array([1.0 / blue, 1.0 / green, 1.0 / red], np.float32) )
np.clip(img, 0, 1.0, out=img)
if mask is not None:
mask = self._check_normalize_mask(mask)
img = ne.evaluate('orig_img*(1-mask) + img*mask')
self._img = img
self.to_dtype(dtype)
return self
def apply(self, func, mask=None) -> 'ImageProcessor':
"""
apply your own function on internal image
image has NHWC format. Do not change format, but dims can be changed.
func callable (img) -> img
example:
.apply( lambda img: img-[102,127,63] )
"""
img = orig_img = self._img
img = func(img).astype(orig_img.dtype)
if img.ndim != 4:
raise Exception('func used in ImageProcessor.apply changed format of image')
if mask is not None:
mask = self._check_normalize_mask(mask)
img = ne.evaluate('orig_img*(1-mask) + img*mask').astype(orig_img.dtype)
self._img = img
return self
def fit_in (self, TW = None, TH = None, pad_to_target : bool = False, allow_upscale : bool = False, interpolation : 'ImageProcessor.Interpolation' = None) -> float:
"""
fit image in w,h keeping aspect ratio
TW,TH int/None target width,height
pad_to_target bool pad remain area with zeros
allow_upscale bool if image smaller than TW,TH it will be upscaled
interpolation ImageProcessor.Interpolation. value
returns scale float value
"""
#if interpolation is None:
# interpolation = ImageProcessor.Interpolation.LINEAR
img = self._img
N,H,W,C = img.shape
if TW is not None and TH is None:
scale = TW / W
elif TW is None and TH is not None:
scale = TH / H
elif TW is not None and TH is not None:
SW = W / TW
SH = H / TH
scale = 1.0
if SW > 1.0 or SH > 1.0 or (SW < 1.0 and SH < 1.0):
scale /= max(SW, SH)
else:
raise ValueError('TW or TH should be specified')
if not allow_upscale and scale > 1.0:
scale = 1.0
if scale != 1.0:
img = img.transpose( (1,2,0,3) ).reshape( (H,W,N*C) )
img = cv2.resize (img, ( int(W*scale), int(H*scale) ), interpolation=ImageProcessor.Interpolation.LINEAR)
H,W = img.shape[0:2]
img = img.reshape( (H,W,N,C) ).transpose( (2,0,1,3) )
if pad_to_target:
w_pad = (TW-W) if TW is not None else 0
h_pad = (TH-H) if TH is not None else 0
if w_pad != 0 or h_pad != 0:
img = np.pad(img, ( (0,0), (0,h_pad), (0,w_pad), (0,0) ))
self._img = img
return scale
def clip(self, min, max) -> 'ImageProcessor':
np.clip(self._img, min, max, out=self._img)
return self
def clip2(self, low_check, low_val, high_check, high_val) -> 'ImageProcessor':
img = self._img
l, h = img < low_check, img > high_check
img[l] = low_val
img[h] = high_val
return self
def reresize(self, power : float, interpolation : 'ImageProcessor.Interpolation' = None, mask = None) -> 'ImageProcessor':
"""
power float 0 .. 1.0
"""
power = min(1, max(0, power))
if power == 0:
return self
if interpolation is None:
interpolation = ImageProcessor.Interpolation.LINEAR
img = orig_img = self._img
N,H,W,C = img.shape
W_lr = max(4, int(W*(1.0-power)))
H_lr = max(4, int(H*(1.0-power)))
img = img.transpose( (1,2,0,3) ).reshape( (H,W,N*C) )
img = cv2.resize (img, (W_lr,H_lr), interpolation=_cv_inter[interpolation])
img = cv2.resize (img, (W,H) , interpolation=_cv_inter[interpolation])
img = img.reshape( (H,W,N,C) ).transpose( (2,0,1,3) )
if mask is not None:
mask = self._check_normalize_mask(mask)
img = ne.evaluate('orig_img*(1-mask) + img*mask').astype(orig_img.dtype)
self._img = img
return self
def box_sharpen(self, size : int, power : float, mask = None) -> 'ImageProcessor':
"""
size int kernel size
power float 0 .. 1.0 (or higher)
"""
power = max(0, power)
if power == 0:
return self
if size % 2 == 0:
size += 1
dtype = self.get_dtype()
self.to_ufloat32()
img = orig_img = self._img
N,H,W,C = img.shape
img = img.transpose( (1,2,0,3) ).reshape( (H,W,N*C) )
kernel = np.zeros( (size, size), dtype=np.float32)
kernel[ size//2, size//2] = 1.0
box_filter = np.ones( (size, size), dtype=np.float32) / (size**2)
kernel = kernel + (kernel - box_filter) * (power)
img = cv2.filter2D(img, -1, kernel)
img = np.clip(img, 0, 1, out=img)
img = img.reshape( (H,W,N,C) ).transpose( (2,0,1,3) )
if mask is not None:
mask = self._check_normalize_mask(mask)
img = ne.evaluate('orig_img*(1-mask) + img*mask')
self._img = img
self.to_dtype(dtype)
return self
def gaussian_sharpen(self, sigma : float, power : float, mask = None) -> 'ImageProcessor':
"""
sigma float
power float 0 .. 1.0 and higher
"""
sigma = max(0, sigma)
if sigma == 0:
return self
dtype = self.get_dtype()
self.to_ufloat32()
img = orig_img = self._img
N,H,W,C = img.shape
img = img.transpose( (1,2,0,3) ).reshape( (H,W,N*C) )
img = cv2.addWeighted(img, 1.0 + power,
cv2.GaussianBlur(img, (0, 0), sigma), -power, 0)
img = np.clip(img, 0, 1, out=img)
img = img.reshape( (H,W,N,C) ).transpose( (2,0,1,3) )
if mask is not None:
mask = self._check_normalize_mask(mask)
img = ne.evaluate('orig_img*(1-mask) + img*mask')
self._img = img
self.to_dtype(dtype)
return self
def gaussian_blur(self, sigma : float, opacity : float = 1.0, mask = None) -> 'ImageProcessor':
"""
sigma float
opacity float 0 .. 1.0
"""
sigma = max(0, sigma)
if sigma == 0:
return self
opacity = np.float32( min(1, max(0, opacity)) )
if opacity == 0:
return self
dtype = self.get_dtype()
self.to_ufloat32()
img = orig_img = self._img
N,H,W,C = img.shape
img = img.transpose( (1,2,0,3) ).reshape( (H,W,N*C) )
img_blur = cv2.GaussianBlur(img, (0,0), sigma)
f32_1 = np.float32(1.0)
img = ne.evaluate('img*(f32_1-opacity) + img_blur*opacity')
img = np.clip(img, 0, 1, out=img)
img = img.reshape( (H,W,N,C) ).transpose( (2,0,1,3) )
if mask is not None:
mask = self._check_normalize_mask(mask)
img = ne.evaluate('orig_img*(1-mask) + img*mask')
self._img = img
self.to_dtype(dtype)
return self
def median_blur(self, size : int, opacity : float = 1.0, mask = None) -> 'ImageProcessor':
"""
size int median kernel size
opacity float 0 .. 1.0
"""
if size % 2 == 0:
size += 1
size = max(1, size)
opacity = min(1, max(0, opacity))
if opacity == 0:
return self
dtype = self.get_dtype()
self.to_ufloat32()
img = orig_img = self._img
N,H,W,C = img.shape
img = img.transpose( (1,2,0,3) ).reshape( (H,W,N*C) )
img_blur = cv2.medianBlur(img, size)
f32_1 = np.float32(1.0)
img = ne.evaluate('img*(f32_1-opacity) + img_blur*opacity')
img = np.clip(img, 0, 1, out=img)
img = img.reshape( (H,W,N,C) ).transpose( (2,0,1,3) )
if mask is not None:
mask = self._check_normalize_mask(mask)
img = ne.evaluate('orig_img*(1-mask) + img*mask')
self._img = img
self.to_dtype(dtype)
return self
def motion_blur( self, size, angle, mask=None ):
"""
size [1..]
angle degrees
mask H,W
H,W,C
N,H,W,C int/float 0-1 will be applied
"""
if size % 2 == 0:
size += 1
dtype = self.get_dtype()
self.to_ufloat32()
img = orig_img = self._img
N,H,W,C = img.shape
img = img.transpose( (1,2,0,3) ).reshape( (H,W,N*C) )
k = np.zeros((size, size), dtype=np.float32)
k[ (size-1)// 2 , :] = np.ones(size, dtype=np.float32)
k = cv2.warpAffine(k, cv2.getRotationMatrix2D( (size / 2 -0.5 , size / 2 -0.5 ) , angle, 1.0), (size, size) )
k = k * ( 1.0 / np.sum(k) )
img = cv2.filter2D(img, -1, k)
img = np.clip(img, 0, 1, out=img)
img = img.reshape( (H,W,N,C) ).transpose( (2,0,1,3) )
if mask is not None:
mask = self._check_normalize_mask(mask)
img = ne.evaluate('orig_img*(1-mask) + img*mask')
self._img = img
self.to_dtype(dtype)
return self
def erode_blur(self, erode : int, blur : int, fade_to_border : bool = False) -> 'ImageProcessor':
"""
apply erode and blur to the mask image
erode int != 0
blur int > 0
fade_to_border(False) clip the image in order
to fade smoothly to the border with specified blur amount
"""
erode, blur = int(erode), int(blur)
img = self._img
N,H,W,C = img.shape
img = img.transpose( (1,2,0,3) ).reshape( (H,W,N*C) )
img = np.pad (img, ( (H,H), (W,W), (0,0) ) )
if erode > 0:
el = np.asarray(cv2.getStructuringElement(cv2.MORPH_ELLIPSE,(3,3)))
iterations = max(1,erode//2)
img = cv2.erode(img, el, iterations = iterations )
elif erode < 0:
el = np.asarray(cv2.getStructuringElement(cv2.MORPH_ELLIPSE,(3,3)))
iterations = max(1,-erode//2)
img = cv2.dilate(img, el, iterations = iterations )
if fade_to_border:
h_clip_size = H + blur // 2
w_clip_size = W + blur // 2
img[:h_clip_size,:] = 0
img[-h_clip_size:,:] = 0
img[:,:w_clip_size] = 0
img[:,-w_clip_size:] = 0
if blur > 0:
sigma = blur * 0.125 * 2
img = cv2.GaussianBlur(img, (0, 0), sigma)
img = img[H:-H,W:-W]
img = img.reshape( (H,W,N,C) ).transpose( (2,0,1,3) )
self._img = img
return self
def levels(self, in_bwg_out_bw, mask = None) -> 'ImageProcessor':
"""
in_bwg_out_bw ( [N],[C], 5)
optional per channel/batch input black,white,gamma and out black,white floats
in black = [0.0 .. 1.0] default:0.0
in white = [0.0 .. 1.0] default:1.0
in gamma = [0.0 .. 2.0++] default:1.0
out black = [0.0 .. 1.0] default:0.0
out white = [0.0 .. 1.0] default:1.0
"""
dtype = self.get_dtype()
self.to_ufloat32()
img = orig_img = self._img
N,H,W,C = img.shape
v = np.array(in_bwg_out_bw, np.float32)
if v.ndim == 1:
v = v[None,None,...]
v = np.tile(v, (N,C,1))
elif v.ndim == 2:
v = v[None,...]
v = np.tile(v, (N,1,1))
elif v.ndim > 3:
raise ValueError('in_bwg_out_bw.ndim > 3')
VN, VC, VD = v.shape
if N != VN or C != VC or VD != 5:
raise ValueError('wrong in_bwg_out_bw size. Must have 5 floats at last dim.')
v = v[:,None,None,:,:]
img = np.clip( (img - v[...,0]) / (v[...,1] - v[...,0]), 0, 1 )
img = ( img ** (1/v[...,2]) ) * (v[...,4] - v[...,3]) + v[...,3]
img = np.clip(img, 0, 1, out=img)
if mask is not None:
mask = self._check_normalize_mask(mask)
img = ne.evaluate('orig_img*(1-mask) + img*mask')
self._img = img
self.to_dtype(dtype)
return self
def hsv(self, h_diff : float, s_diff : float, v_diff : float, mask = None) -> 'ImageProcessor':
"""
apply HSV modification for BGR image
h_diff = [-1.0 .. 1.0]
s_diff = [-1.0 .. 1.0]
v_diff = [-1.0 .. 1.0]
"""
dtype = self.get_dtype()
self.to_ufloat32()
img = orig_img = self._img
N,H,W,C = img.shape
if C != 3:
raise Exception('Image channels must be == 3')
img = img.reshape( (N*H,W,C) )
h, s, v = cv2.split(cv2.cvtColor(img, cv2.COLOR_BGR2HSV))
h = ( h + h_diff*360.0 ) % 360
s += s_diff
np.clip (s, 0, 1, out=s )
v += v_diff
np.clip (v, 0, 1, out=v )
img = np.clip( cv2.cvtColor(cv2.merge([h, s, v]), cv2.COLOR_HSV2BGR) , 0, 1 )
img = img.reshape( (N,H,W,C) )
if mask is not None:
mask = self._check_normalize_mask(mask)
img = ne.evaluate('orig_img*(1-mask) + img*mask')
self._img = img
self.to_dtype(dtype)
return self
def to_lab(self) -> 'ImageProcessor':
"""
"""
img = self._img
N,H,W,C = img.shape
if C != 3:
raise Exception('Image channels must be == 3')
img = img.reshape( (N*H,W,C) )
img = cv2.cvtColor(img, cv2.COLOR_BGR2LAB)
img = img.reshape( (N,H,W,C) )
self._img = img
return self
def from_lab(self) -> 'ImageProcessor':
"""
"""
img = self._img
N,H,W,C = img.shape
if C != 3:
raise Exception('Image channels must be == 3')
img = img.reshape( (N*H,W,C) )
img = cv2.cvtColor(img, cv2.COLOR_LAB2BGR)
img = img.reshape( (N,H,W,C) )
self._img = img
return self
def jpeg_recompress(self, quality : int, mask = None ) -> 'ImageProcessor':
"""
quality 0-100
"""
dtype = self.get_dtype()
self.to_uint8()
img = orig_img = self._img
_,_,_,C = img.shape
if C != 3:
raise Exception('Image channels must be == 3')
new_imgs = []
for x in img:
ret, result = cv2.imencode('.jpg', x, [int(cv2.IMWRITE_JPEG_QUALITY), quality] )
if not ret:
raise Exception('unable to compress jpeg')
x = cv2.imdecode(result, flags=cv2.IMREAD_UNCHANGED)
new_imgs.append(x)
img = np.array(new_imgs)
if mask is not None:
mask = self._check_normalize_mask(mask)
img = ne.evaluate('orig_img*(1-mask) + img*mask').astype(np.uint8)
self._img = img
self.to_dtype(dtype)
return self
def patch_to_batch(self, patch_size : int) -> 'ImageProcessor':
img = self._img
N,H,W,C = img.shape
OH, OW = H // patch_size, W // patch_size
img = img.reshape(N,OH,patch_size,OW,patch_size,C)
img = img.transpose(0,2,4,1,3,5)
img = img.reshape(N*patch_size*patch_size,OH,OW,C)
self._img = img
return self
def patch_from_batch(self, patch_size : int) -> 'ImageProcessor':
img = self._img
N,H,W,C = img.shape
ON = N//(patch_size*patch_size)
img = img.reshape(ON,patch_size,patch_size,H,W,C )
img = img.transpose(0,3,1,4,2,5)
img = img.reshape(ON,H*patch_size,W*patch_size,C )
self._img = img
return self
def rct(self, like : np.ndarray, mask : np.ndarray = None, like_mask : np.ndarray = None, mask_cutoff=0.5) -> 'ImageProcessor':
"""
Transfer color using rct method.
like np.ndarray [N][HW][3C] np.uint8/np.float32
mask(None) np.ndarray [N][HW][1C] np.uint8/np.float32
like_mask(None) np.ndarray [N][HW][1C] np.uint8/np.float32
mask_cutoff(0.5) float
masks are used to limit the space where color statistics will be computed to adjust the image
reference: Color Transfer between Images https://www.cs.tau.ac.il/~turkel/imagepapers/ColorTransfer.pdf
"""
dtype = self.get_dtype()
self.to_ufloat32()
self.to_lab()
like_for_stat = ImageProcessor(like).to_ufloat32().to_lab().get_image('NHWC')
if like_mask is not None:
like_mask = ImageProcessor(like_mask).to_ufloat32().ch(1).get_image('NHW')
like_for_stat = like_for_stat.copy()
like_for_stat[like_mask < mask_cutoff] = [0,0,0]
img_for_stat = img = self._img
if mask is not None:
mask = ImageProcessor(mask).to_ufloat32().ch(1).get_image('NHW')
img_for_stat = img_for_stat.copy()
img_for_stat[mask < mask_cutoff] = [0,0,0]
source_l_mean, source_l_std, source_a_mean, source_a_std, source_b_mean, source_b_std, \
= img_for_stat[...,0].mean((1,2), keepdims=True), img_for_stat[...,0].std((1,2), keepdims=True), img_for_stat[...,1].mean((1,2), keepdims=True), img_for_stat[...,1].std((1,2), keepdims=True), img_for_stat[...,2].mean((1,2), keepdims=True), img_for_stat[...,2].std((1,2), keepdims=True)
like_l_mean, like_l_std, like_a_mean, like_a_std, like_b_mean, like_b_std, \
= like_for_stat[...,0].mean((1,2), keepdims=True), like_for_stat[...,0].std((1,2), keepdims=True), like_for_stat[...,1].mean((1,2), keepdims=True), like_for_stat[...,1].std((1,2), keepdims=True), like_for_stat[...,2].mean((1,2), keepdims=True), like_for_stat[...,2].std((1,2), keepdims=True)
# not as in the paper: scale by the standard deviations using reciprocal of paper proposed factor
source_l = img[...,0]
source_l = ne.evaluate('(source_l - source_l_mean) * like_l_std / source_l_std + like_l_mean')
source_a = img[...,1]
source_a = ne.evaluate('(source_a - source_a_mean) * like_a_std / source_a_std + like_a_mean')
source_b = img[...,2]
source_b = ne.evaluate('(source_b - source_b_mean) * like_b_std / source_b_std + like_b_mean')
np.clip(source_l, 0, 100, out=source_l)
np.clip(source_a, -127, 127, out=source_a)
np.clip(source_b, -127, 127, out=source_b)
self._img = np.stack([source_l,source_a,source_b], -1)
self.from_lab()
self.to_dtype(dtype)
return self
def rotate90(self) -> 'ImageProcessor':
self._img = np.rot90(self._img, k=1, axes=(1,2) )
return self
def rotate180(self) -> 'ImageProcessor':
self._img = np.rot90(self._img, k=2, axes=(1,2) )
return self
def rotate270(self) -> 'ImageProcessor':
self._img = np.rot90(self._img, k=3, axes=(1,2) )
return self
def flip_horizontal(self) -> 'ImageProcessor':
"""
"""
self._img = self._img[:,:,::-1,:]
return self
def flip_vertical(self) -> 'ImageProcessor':
"""
"""
self._img = self._img[:,::-1,:,:]
return self
def pad(self, t_h, b_h, l_w, r_w) -> 'ImageProcessor':
"""
"""
self._img = np.pad(self._img, ( (0,0), (t_h,b_h), (l_w,r_w), (0,0) ))
return self
def pad_to_next_divisor(self, dw=None, dh=None) -> 'ImageProcessor':
"""
pad image to next divisor of width/height
dw,dh int
"""
img = self._img
_,H,W,_ = img.shape
w_pad = 0
if dw is not None:
w_pad = W % dw
if w_pad != 0:
w_pad = dw - w_pad
h_pad = 0
if dh is not None:
h_pad = H % dh
if h_pad != 0:
h_pad = dh - h_pad
if w_pad != 0 or h_pad != 0:
img = np.pad(img, ( (0,0), (0,h_pad), (0,w_pad), (0,0) ))
self._img = img
return self
def get_image(self, format) -> np.ndarray:
"""
returns image with desired format
format str examples:
NHWC, HWCN, NHW
if symbol in format does not exist, it will be got from 0 index
zero dim will be set to 1
"""
format = format.upper()
img = self._img
# First slice missing dims
N_slice = 0 if 'N' not in format else slice(None)
H_slice = 0 if 'H' not in format else slice(None)
W_slice = 0 if 'W' not in format else slice(None)
C_slice = 0 if 'C' not in format else slice(None)
img = img[N_slice, H_slice, W_slice, C_slice]
f = ''
if 'N' in format: f += 'N'
if 'H' in format: f += 'H'
if 'W' in format: f += 'W'
if 'C' in format: f += 'C'
if f != format:
# Transpose to target
d = { s:i for i,s in enumerate(f) }
transpose_order = [ d[s] for s in format ]
img = img.transpose(transpose_order)
return np.ascontiguousarray(img)
def ch(self, TC : int) -> 'ImageProcessor':
"""
Clips or expands channel dimension to target channels
TC int >= 1
"""
img = self._img
N,H,W,C = img.shape
if TC <= 0:
raise ValueError(f'channels must be positive value, not {TC}')
if TC > C:
# Ch expand
img = img[...,0:1] # Clip to single ch first.
img = np.repeat (img, TC, -1) # Expand by repeat
elif TC < C:
# Ch reduction clip
img = img[...,:TC]
self._img = img
return self
def to_grayscale(self) -> 'ImageProcessor':
"""
Converts 3 ch bgr to grayscale.
"""
img = self._img
_,_,_,C = img.shape
if C != 1:
dtype = self.get_dtype()
if C == 2:
img = img[...,:1]
elif C >= 3:
img = img[...,:3]
img = np.dot(img, np.array([0.1140, 0.5870, 0.2989], np.float32)) [...,None]
img = img.astype(dtype)
self._img = img
return self
def resize(self, size : Tuple, interpolation : 'ImageProcessor.Interpolation' = None ) -> 'ImageProcessor':
"""
resize to (W,H)
"""
img = self._img
N,H,W,C = img.shape
TW,TH = size
if W != TW or H != TH:
if interpolation is None:
interpolation = ImageProcessor.Interpolation.LINEAR
img = img.transpose( (1,2,0,3) ).reshape( (H,W,N*C) )
img = cv2.resize (img, (TW, TH), interpolation=_cv_inter[interpolation])
img = img.reshape( (TH,TW,N,C) ).transpose( (2,0,1,3) )
self._img = img
return self
def warp_affine(self, mat, out_width, out_height, interpolation : 'ImageProcessor.Interpolation' = None ) -> 'ImageProcessor':
"""
img HWC
"""
img = self._img
N,H,W,C = img.shape
img = img.transpose( (1,2,0,3) ).reshape( (H,W,N*C) )
if interpolation is None:
interpolation = ImageProcessor.Interpolation.LINEAR
img = cv2.warpAffine(img, mat, (out_width, out_height), flags=_cv_inter[interpolation] )
img = img.reshape( (out_height,out_width,N,C) ).transpose( (2,0,1,3) )
self._img = img
return self
def swap_ch(self) -> 'ImageProcessor':
"""swaps order of channels"""
self._img = self._img[...,::-1]
return self
def as_float32(self) -> 'ImageProcessor':
"""
change image format to float32
"""
self._img = self._img.astype(np.float32)
return self
def as_uint8(self) -> 'ImageProcessor':
"""
change image format to uint8
"""
self._img = self._img.astype(np.uint8)
return self
def to_dtype(self, dtype, from_tanh=False) -> 'ImageProcessor':
if dtype == np.float32:
return self.to_ufloat32(from_tanh=from_tanh)
elif dtype == np.uint8:
return self.to_uint8(from_tanh=from_tanh)
else:
raise ValueError('unsupported dtype')
def to_ufloat32(self, as_tanh=False, from_tanh=False) -> 'ImageProcessor':
"""
Convert to uniform float32
"""
if self._img.dtype == np.uint8:
self._img = self._img.astype(np.float32)
if as_tanh:
self._img /= 127.5
self._img -= 1.0
else:
self._img /= 255.0
elif self._img.dtype in [np.float32, np.float64]:
if from_tanh:
self._img += 1.0
self._img /= 2.0
return self
def to_uint8(self, from_tanh=False) -> 'ImageProcessor':
"""
Convert to uint8
if current image dtype is float32/64, then image will be multiplied by *255
"""
img = self._img
if img.dtype in [np.float32, np.float64]:
if from_tanh:
img += 1.0
img /= 2.0
img *= 255.0
np.clip(img, 0, 255, out=img)
self._img = img.astype(np.uint8, copy=False)
return self
def _check_normalize_mask(self, mask : np.ndarray):
N,H,W,C = self._img.shape
if mask.ndim == 2:
mask = mask[None,...,None]
elif mask.ndim == 3:
mask = mask[None,...]
if mask.ndim != 4:
raise ValueError('mask must have ndim == 4')
MN, MH, MW, MC = mask.shape
if H != MH or W != MW:
raise ValueError('mask H,W, mismatch')
if MN != 1 and N != MN:
raise ValueError(f'mask N dim must be 1 or == {N}')
if MC != 1 and C != MC:
raise ValueError(f'mask C dim must be 1 or == {C}')
return mask
class Interpolation(IntEnum):
NEAREST = 0,
LINEAR = 1
CUBIC = 2,
LANCZOS4 = 4
_cv_inter = { ImageProcessor.Interpolation.NEAREST : cv2.INTER_NEAREST,
ImageProcessor.Interpolation.LINEAR : cv2.INTER_LINEAR,
ImageProcessor.Interpolation.CUBIC : cv2.INTER_CUBIC,
ImageProcessor.Interpolation.LANCZOS4 : cv2.INTER_LANCZOS4,
}