From 64116844f2b63d3fd7de2d2ff408ddea933440a2 Mon Sep 17 00:00:00 2001 From: iperov Date: Thu, 11 Nov 2021 23:15:51 +0400 Subject: [PATCH] refactoring --- __dev_archived/archived.zip | Bin 2060093 -> 2080725 bytes apps/DeepFaceLive/backend/FaceAligner.py | 2 +- apps/DeepFaceLive/backend/FaceMarker.py | 2 +- apps/DeepFaceLive/backend/FaceMerger.py | 4 +- apps/DeepFaceLive/backend/FaceSwapper.py | 4 +- apps/DeepFaceLive/backend/FrameAdjuster.py | 4 +- main.py | 20 +- xlib/face/FLandmarks2D.py | 19 +- xlib/image/ImageProcessor.py | 379 ++++++++++++++++++--- 9 files changed, 360 insertions(+), 74 deletions(-) diff --git a/__dev_archived/archived.zip b/__dev_archived/archived.zip index aeec5f3c15a6ca8e1beb06cc82cdd98f65b3af76..557282cee92c95d05b1be392bad52b9d27ab6b8e 100644 GIT binary patch delta 20847 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 y7O@ub7Ks+g7O58L7MT{=7P&3*S+3iUO_blSDZ Tuple[Affine2DMat, Affine2DUniMat]: + y_offset : float = 0) -> Tuple[np.ndarray, Affine2DUniMat]: """ Cut the face to square of output_size from img using landmarks with given parameters diff --git a/xlib/image/ImageProcessor.py b/xlib/image/ImageProcessor.py index 2897311..d8e87af 100644 --- a/xlib/image/ImageProcessor.py +++ b/xlib/image/ImageProcessor.py @@ -39,7 +39,7 @@ class ImageProcessor: """ """ ip = ImageProcessor.__new__(ImageProcessor) - ip._img = self._img + ip._img = self._img.copy() return ip def get_dims(self) -> Tuple[int,int,int,int]: @@ -53,18 +53,24 @@ class ImageProcessor: def get_dtype(self): return self._img.dtype - def adjust_gamma(self, red : float, green : float, blue : float) -> 'ImageProcessor': + def gamma(self, red : float, green : float, blue : float, mask=None) -> 'ImageProcessor': dtype = self.get_dtype() self.to_ufloat32() - img = self._img - np.power(img, np.array([1.0 / blue, 1.0 / green, 1.0 / red], np.float32), out=img) + 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) -> 'ImageProcessor': + def apply(self, func, mask=None) -> 'ImageProcessor': """ apply your own function on internal image @@ -76,12 +82,16 @@ class ImageProcessor: .apply( lambda img: img-[102,127,63] ) """ - img = self._img - dtype = img.dtype - new_img = func(self._img).astype(dtype) - if new_img.ndim != 4: + 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') - self._img = new_img + + 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: @@ -147,7 +157,7 @@ class ImageProcessor: img[h] = high_val return self - def degrade_resize(self, power : float, interpolation : 'ImageProcessor.Interpolation' = None) -> 'ImageProcessor': + def reresize(self, power : float, interpolation : 'ImageProcessor.Interpolation' = None, mask = None) -> 'ImageProcessor': """ power float 0 .. 1.0 @@ -159,7 +169,7 @@ class ImageProcessor: if interpolation is None: interpolation = ImageProcessor.Interpolation.LINEAR - img = self._img + img = orig_img = self._img N,H,W,C = img.shape W_lr = max(4, int(W*(1.0-power))) @@ -168,41 +178,196 @@ class ImageProcessor: 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 median_blur(self, size : int, power : float) -> 'ImageProcessor': + def box_sharpen(self, size : int, power : float, mask = None) -> 'ImageProcessor': """ - size int median kernel size + size int kernel size - power float 0 .. 1.0 + power float 0 .. 1.0 (or higher) """ - power = min(1, max(0, power)) + 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 = self._img + 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) - img = ne.evaluate('img*(1.0-power) + img_blur*power') - + + 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, mask = None) -> 'ImageProcessor': + """ + size int median kernel size + + opacity float 0 .. 1.0 + """ + 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 image + apply erode and blur to the mask image erode int != 0 blur int > 0 @@ -244,7 +409,126 @@ class ImageProcessor: 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 = [-360.0 .. 360.0] + s_diff = [-1.0 .. 1.0] + s_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 + + 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 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 rotate90(self) -> 'ImageProcessor': self._img = np.rot90(self._img, k=1, axes=(1,2) ) return self @@ -305,18 +589,6 @@ class ImageProcessor: self._img = img return self - def sharpen(self, factor : float, kernel_size=3) -> 'ImageProcessor': - img = self._img - - N,H,W,C = img.shape - img = img.transpose( (1,2,0,3) ).reshape( (H,W,N*C) ) - blur = cv2.GaussianBlur(img, (kernel_size, kernel_size) , 0) - img = cv2.addWeighted(img, 1.0 + (0.5 * factor), blur, -(0.5 * factor), 0) - img = img.reshape( (H,W,N,C) ).transpose( (2,0,1,3) ) - - self._img = img - return self - def get_image(self, format) -> np.ndarray: """ returns image with desired format @@ -395,7 +667,7 @@ class ImageProcessor: return self - def resize(self, size : Tuple, interpolation : 'ImageProcessor.Interpolation' = None, new_ip=False ) -> 'ImageProcessor': + def resize(self, size : Tuple, interpolation : 'ImageProcessor.Interpolation' = None ) -> 'ImageProcessor': """ resize to (W,H) """ @@ -411,14 +683,11 @@ class ImageProcessor: img = cv2.resize (img, (TW, TH), interpolation=_cv_inter[interpolation]) img = img.reshape( (TH,TW,N,C) ).transpose( (2,0,1,3) ) - if new_ip: - return ImageProcessor(img) - self._img = img return self - def warpAffine(self, mat, out_width, out_height, interpolation : 'ImageProcessor.Interpolation' = None ) -> 'ImageProcessor': + def warp_affine(self, mat, out_width, out_height, interpolation : 'ImageProcessor.Interpolation' = None ) -> 'ImageProcessor': """ img HWC """ @@ -489,12 +758,36 @@ class ImageProcessor: 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): - LINEAR = 0 - CUBIC = 1 + NEAREST = 0, + LINEAR = 1 + CUBIC = 2, LANCZOS4 = 4 -_cv_inter = { ImageProcessor.Interpolation.LINEAR : cv2.INTER_LINEAR, +_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, } \ No newline at end of file