From b03b147baeede591551c4c14e734c3785468402f Mon Sep 17 00:00:00 2001 From: iperov Date: Tue, 26 Mar 2019 11:09:44 +0400 Subject: [PATCH] refactoring --- converters/ConverterImage.py | 1 - converters/ConverterMasked.py | 12 +- facelib/LandmarksProcessor.py | 6 +- imagelib/__init__.py | 18 ++ imagelib/color_transfer.py | 191 ++++++++++++ imagelib/draw.py | 13 + imagelib/equalize_and_stack_square.py | 45 +++ imagelib/morph.py | 37 +++ imagelib/reduce_colors.py | 14 + imagelib/text.py | 63 ++++ imagelib/warp.py | 51 ++++ mainscripts/Converter.py | 1 - mainscripts/Extractor.py | 4 +- mainscripts/Sorter.py | 1 - mainscripts/Trainer.py | 4 +- models/ModelBase.py | 6 +- samples/SampleProcessor.py | 8 +- utils/image_utils.py | 424 -------------------------- 18 files changed, 452 insertions(+), 447 deletions(-) create mode 100644 imagelib/color_transfer.py create mode 100644 imagelib/draw.py create mode 100644 imagelib/equalize_and_stack_square.py create mode 100644 imagelib/morph.py create mode 100644 imagelib/reduce_colors.py create mode 100644 imagelib/text.py create mode 100644 imagelib/warp.py delete mode 100644 utils/image_utils.py diff --git a/converters/ConverterImage.py b/converters/ConverterImage.py index c507b6c..a3dcc22 100644 --- a/converters/ConverterImage.py +++ b/converters/ConverterImage.py @@ -4,7 +4,6 @@ from facelib import FaceType import cv2 import numpy as np -from utils import image_utils ''' predictor_func: diff --git a/converters/ConverterMasked.py b/converters/ConverterMasked.py index aea8db3..a5fb7eb 100644 --- a/converters/ConverterMasked.py +++ b/converters/ConverterMasked.py @@ -5,7 +5,7 @@ from facelib import FaceType from facelib import FANSegmentator import cv2 import numpy as np -from utils import image_utils +import imagelib from interact import interact as io ''' default_mode = {1:'overlay', @@ -262,7 +262,7 @@ class ConverterMasked(Converter): if debug: debugs += [ np.clip( cv2.warpAffine( prd_face_bgr, face_output_mat, img_size, np.zeros(img_bgr.shape, dtype=np.float32), cv2.WARP_INVERSE_MAP | cv2.INTER_LANCZOS4, cv2.BORDER_TRANSPARENT ), 0, 1.0) ] - prd_face_bgr = image_utils.reinhard_color_transfer ( np.clip( (prd_face_bgr*255).astype(np.uint8), 0, 255), + prd_face_bgr = imagelib.reinhard_color_transfer ( np.clip( (prd_face_bgr*255).astype(np.uint8), 0, 255), np.clip( (dst_face_bgr*255).astype(np.uint8), 0, 255), source_mask=prd_face_mask_a, target_mask=prd_face_mask_a) prd_face_bgr = np.clip( prd_face_bgr.astype(np.float32) / 255.0, 0.0, 1.0) @@ -275,7 +275,7 @@ class ConverterMasked(Converter): if debug: debugs += [ np.clip( cv2.warpAffine( prd_face_bgr, face_output_mat, img_size, np.zeros(img_bgr.shape, dtype=np.float32), cv2.WARP_INVERSE_MAP | cv2.INTER_LANCZOS4, cv2.BORDER_TRANSPARENT ), 0, 1.0) ] - prd_face_bgr = image_utils.linear_color_transfer (prd_face_bgr, dst_face_bgr) + prd_face_bgr = imagelib.linear_color_transfer (prd_face_bgr, dst_face_bgr) prd_face_bgr = np.clip( prd_face_bgr, 0.0, 1.0) if debug: @@ -300,7 +300,7 @@ class ConverterMasked(Converter): hist_match_2 = dst_face_bgr*hist_mask_a + (1.0-hist_mask_a)* np.ones ( prd_face_bgr.shape[:2] + (1,) , dtype=np.float32) hist_match_2[ hist_match_1 > 1.0 ] = 1.0 - prd_face_bgr = image_utils.color_hist_match(hist_match_1, hist_match_2, self.hist_match_threshold ) + prd_face_bgr = imagelib.color_hist_match(hist_match_1, hist_match_2, self.hist_match_threshold ) if self.mode == 'hist-match-bw': prd_face_bgr = prd_face_bgr.astype(dtype=np.float32) @@ -334,14 +334,14 @@ class ConverterMasked(Converter): if self.mode == 'seamless-hist-match': out_face_bgr = cv2.warpAffine( out_img, face_mat, (self.output_size, self.output_size) ) - new_out_face_bgr = image_utils.color_hist_match(out_face_bgr, dst_face_bgr, self.hist_match_threshold) + new_out_face_bgr = imagelib.color_hist_match(out_face_bgr, dst_face_bgr, self.hist_match_threshold) new_out = cv2.warpAffine( new_out_face_bgr, face_mat, img_size, img_bgr.copy(), cv2.WARP_INVERSE_MAP | cv2.INTER_LANCZOS4, cv2.BORDER_TRANSPARENT ) out_img = np.clip( img_bgr*(1-img_mask_blurry_aaa) + (new_out*img_mask_blurry_aaa) , 0, 1.0 ) if self.final_image_color_degrade_power != 0: if debug: debugs += [out_img.copy()] - out_img_reduced = image_utils.reduce_colors(out_img, 256) + out_img_reduced = imagelib.reduce_colors(out_img, 256) if self.final_image_color_degrade_power == 100: out_img = out_img_reduced else: diff --git a/facelib/LandmarksProcessor.py b/facelib/LandmarksProcessor.py index 12b91f2..1ec4607 100644 --- a/facelib/LandmarksProcessor.py +++ b/facelib/LandmarksProcessor.py @@ -3,8 +3,8 @@ import cv2 import numpy as np from enum import IntEnum import mathlib +import imagelib from mathlib.umeyama import umeyama -from utils import image_utils from facelib import FaceType import math @@ -315,11 +315,11 @@ def draw_landmarks (image, image_landmarks, color=(0,255,0), transparent_mask=Fa def draw_rect_landmarks (image, rect, image_landmarks, face_size, face_type, transparent_mask=False, landmarks_color=(0,255,0) ): draw_landmarks(image, image_landmarks, color=landmarks_color, transparent_mask=transparent_mask) - image_utils.draw_rect (image, rect, (255,0,0), 2 ) + imagelib.draw_rect (image, rect, (255,0,0), 2 ) image_to_face_mat = get_transform_mat (image_landmarks, face_size, face_type) points = transform_points ( [ (0,0), (0,face_size-1), (face_size-1, face_size-1), (face_size-1,0) ], image_to_face_mat, True) - image_utils.draw_polygon (image, points, (0,0,255), 2) + imagelib.draw_polygon (image, points, (0,0,255), 2) def calc_face_pitch(landmarks): if not isinstance(landmarks, np.ndarray): diff --git a/imagelib/__init__.py b/imagelib/__init__.py index 52e480f..2f674e1 100644 --- a/imagelib/__init__.py +++ b/imagelib/__init__.py @@ -1 +1,19 @@ from .estimate_sharpness import estimate_sharpness +from .equalize_and_stack_square import equalize_and_stack_square + +from .text import get_text_image +from .text import get_draw_text_lines + +from .draw import draw_polygon +from .draw import draw_rect + +from .morph import morph_by_points + +from .warp import gen_warp_params +from .warp import warp_by_params + +from .reduce_colors import reduce_colors + +from .color_transfer import color_hist_match +from .color_transfer import reinhard_color_transfer +from .color_transfer import linear_color_transfer diff --git a/imagelib/color_transfer.py b/imagelib/color_transfer.py new file mode 100644 index 0000000..eb66074 --- /dev/null +++ b/imagelib/color_transfer.py @@ -0,0 +1,191 @@ +import numpy as np +import cv2 + +def reinhard_color_transfer(target, source, clip=False, preserve_paper=False, source_mask=None, target_mask=None): + """ + Transfers the color distribution from the source to the target + image using the mean and standard deviations of the L*a*b* + color space. + + This implementation is (loosely) based on to the "Color Transfer + between Images" paper by Reinhard et al., 2001. + + Parameters: + ------- + source: NumPy array + OpenCV image in BGR color space (the source image) + target: NumPy array + OpenCV image in BGR color space (the target image) + clip: Should components of L*a*b* image be scaled by np.clip before + converting back to BGR color space? + If False then components will be min-max scaled appropriately. + Clipping will keep target image brightness truer to the input. + Scaling will adjust image brightness to avoid washed out portions + in the resulting color transfer that can be caused by clipping. + preserve_paper: Should color transfer strictly follow methodology + layed out in original paper? The method does not always produce + aesthetically pleasing results. + If False then L*a*b* components will scaled using the reciprocal of + the scaling factor proposed in the paper. This method seems to produce + more consistently aesthetically pleasing results + + Returns: + ------- + transfer: NumPy array + OpenCV image (w, h, 3) NumPy array (uint8) + """ + + + # convert the images from the RGB to L*ab* color space, being + # sure to utilizing the floating point data type (note: OpenCV + # expects floats to be 32-bit, so use that instead of 64-bit) + source = cv2.cvtColor(source, cv2.COLOR_BGR2LAB).astype(np.float32) + target = cv2.cvtColor(target, cv2.COLOR_BGR2LAB).astype(np.float32) + + # compute color statistics for the source and target images + src_input = source if source_mask is None else source*source_mask + tgt_input = target if target_mask is None else target*target_mask + (lMeanSrc, lStdSrc, aMeanSrc, aStdSrc, bMeanSrc, bStdSrc) = lab_image_stats(src_input) + (lMeanTar, lStdTar, aMeanTar, aStdTar, bMeanTar, bStdTar) = lab_image_stats(tgt_input) + + # subtract the means from the target image + (l, a, b) = cv2.split(target) + l -= lMeanTar + a -= aMeanTar + b -= bMeanTar + + if preserve_paper: + # scale by the standard deviations using paper proposed factor + l = (lStdTar / lStdSrc) * l + a = (aStdTar / aStdSrc) * a + b = (bStdTar / bStdSrc) * b + else: + # scale by the standard deviations using reciprocal of paper proposed factor + l = (lStdSrc / lStdTar) * l + a = (aStdSrc / aStdTar) * a + b = (bStdSrc / bStdTar) * b + + # add in the source mean + l += lMeanSrc + a += aMeanSrc + b += bMeanSrc + + # clip/scale the pixel intensities to [0, 255] if they fall + # outside this range + l = _scale_array(l, clip=clip) + a = _scale_array(a, clip=clip) + b = _scale_array(b, clip=clip) + + # merge the channels together and convert back to the RGB color + # space, being sure to utilize the 8-bit unsigned integer data + # type + transfer = cv2.merge([l, a, b]) + transfer = cv2.cvtColor(transfer.astype(np.uint8), cv2.COLOR_LAB2BGR) + + # return the color transferred image + return transfer + +def linear_color_transfer(target_img, source_img, mode='pca', eps=1e-5): + ''' + Matches the colour distribution of the target image to that of the source image + using a linear transform. + Images are expected to be of form (w,h,c) and float in [0,1]. + Modes are chol, pca or sym for different choices of basis. + ''' + mu_t = target_img.mean(0).mean(0) + t = target_img - mu_t + t = t.transpose(2,0,1).reshape(3,-1) + Ct = t.dot(t.T) / t.shape[1] + eps * np.eye(t.shape[0]) + mu_s = source_img.mean(0).mean(0) + s = source_img - mu_s + s = s.transpose(2,0,1).reshape(3,-1) + Cs = s.dot(s.T) / s.shape[1] + eps * np.eye(s.shape[0]) + if mode == 'chol': + chol_t = np.linalg.cholesky(Ct) + chol_s = np.linalg.cholesky(Cs) + ts = chol_s.dot(np.linalg.inv(chol_t)).dot(t) + if mode == 'pca': + eva_t, eve_t = np.linalg.eigh(Ct) + Qt = eve_t.dot(np.sqrt(np.diag(eva_t))).dot(eve_t.T) + eva_s, eve_s = np.linalg.eigh(Cs) + Qs = eve_s.dot(np.sqrt(np.diag(eva_s))).dot(eve_s.T) + ts = Qs.dot(np.linalg.inv(Qt)).dot(t) + if mode == 'sym': + eva_t, eve_t = np.linalg.eigh(Ct) + Qt = eve_t.dot(np.sqrt(np.diag(eva_t))).dot(eve_t.T) + Qt_Cs_Qt = Qt.dot(Cs).dot(Qt) + eva_QtCsQt, eve_QtCsQt = np.linalg.eigh(Qt_Cs_Qt) + QtCsQt = eve_QtCsQt.dot(np.sqrt(np.diag(eva_QtCsQt))).dot(eve_QtCsQt.T) + ts = np.linalg.inv(Qt).dot(QtCsQt).dot(np.linalg.inv(Qt)).dot(t) + matched_img = ts.reshape(*target_img.transpose(2,0,1).shape).transpose(1,2,0) + matched_img += mu_s + matched_img[matched_img>1] = 1 + matched_img[matched_img<0] = 0 + return matched_img + +def lab_image_stats(image): + # compute the mean and standard deviation of each channel + (l, a, b) = cv2.split(image) + (lMean, lStd) = (l.mean(), l.std()) + (aMean, aStd) = (a.mean(), a.std()) + (bMean, bStd) = (b.mean(), b.std()) + + # return the color statistics + return (lMean, lStd, aMean, aStd, bMean, bStd) + +def _scale_array(arr, clip=True): + if clip: + return np.clip(arr, 0, 255) + + mn = arr.min() + mx = arr.max() + scale_range = (max([mn, 0]), min([mx, 255])) + + if mn < scale_range[0] or mx > scale_range[1]: + return (scale_range[1] - scale_range[0]) * (arr - mn) / (mx - mn) + scale_range[0] + + return arr + +def channel_hist_match(source, template, hist_match_threshold=255, mask=None): + # Code borrowed from: + # https://stackoverflow.com/questions/32655686/histogram-matching-of-two-images-in-python-2-x + masked_source = source + masked_template = template + + if mask is not None: + masked_source = source * mask + masked_template = template * mask + + oldshape = source.shape + source = source.ravel() + template = template.ravel() + masked_source = masked_source.ravel() + masked_template = masked_template.ravel() + s_values, bin_idx, s_counts = np.unique(source, return_inverse=True, + return_counts=True) + t_values, t_counts = np.unique(template, return_counts=True) + ms_values, mbin_idx, ms_counts = np.unique(source, return_inverse=True, + return_counts=True) + mt_values, mt_counts = np.unique(template, return_counts=True) + + s_quantiles = np.cumsum(s_counts).astype(np.float64) + s_quantiles = hist_match_threshold * s_quantiles / s_quantiles[-1] + t_quantiles = np.cumsum(t_counts).astype(np.float64) + t_quantiles = 255 * t_quantiles / t_quantiles[-1] + interp_t_values = np.interp(s_quantiles, t_quantiles, t_values) + + return interp_t_values[bin_idx].reshape(oldshape) + +def color_hist_match(src_im, tar_im, hist_match_threshold=255): + h,w,c = src_im.shape + matched_R = channel_hist_match(src_im[:,:,0], tar_im[:,:,0], hist_match_threshold, None) + matched_G = channel_hist_match(src_im[:,:,1], tar_im[:,:,1], hist_match_threshold, None) + matched_B = channel_hist_match(src_im[:,:,2], tar_im[:,:,2], hist_match_threshold, None) + + to_stack = (matched_R, matched_G, matched_B) + for i in range(3, c): + to_stack += ( src_im[:,:,i],) + + + matched = np.stack(to_stack, axis=-1).astype(src_im.dtype) + return matched diff --git a/imagelib/draw.py b/imagelib/draw.py new file mode 100644 index 0000000..3de1191 --- /dev/null +++ b/imagelib/draw.py @@ -0,0 +1,13 @@ +import numpy as np +import cv2 + +def draw_polygon (image, points, color, thickness = 1): + points_len = len(points) + for i in range (0, points_len): + p0 = tuple( points[i] ) + p1 = tuple( points[ (i+1) % points_len] ) + cv2.line (image, p0, p1, color, thickness=thickness) + +def draw_rect(image, rect, color, thickness=1): + l,t,r,b = rect + draw_polygon (image, [ (l,t), (r,t), (r,b), (l,b ) ], color, thickness) diff --git a/imagelib/equalize_and_stack_square.py b/imagelib/equalize_and_stack_square.py new file mode 100644 index 0000000..31c435a --- /dev/null +++ b/imagelib/equalize_and_stack_square.py @@ -0,0 +1,45 @@ +import numpy as np +import cv2 + +def equalize_and_stack_square (images, axis=1): + max_c = max ([ 1 if len(image.shape) == 2 else image.shape[2] for image in images ] ) + + target_wh = 99999 + for i,image in enumerate(images): + if len(image.shape) == 2: + h,w = image.shape + c = 1 + else: + h,w,c = image.shape + + if h < target_wh: + target_wh = h + + if w < target_wh: + target_wh = w + + for i,image in enumerate(images): + if len(image.shape) == 2: + h,w = image.shape + c = 1 + else: + h,w,c = image.shape + + if c < max_c: + if c == 1: + if len(image.shape) == 2: + image = np.expand_dims ( image, -1 ) + image = np.concatenate ( (image,)*max_c, -1 ) + elif c == 2: #GA + image = np.expand_dims ( image[...,0], -1 ) + image = np.concatenate ( (image,)*max_c, -1 ) + else: + image = np.concatenate ( (image, np.ones((h,w,max_c - c))), -1 ) + + if h != target_wh or w != target_wh: + image = cv2.resize ( image, (target_wh, target_wh) ) + h,w,c = image.shape + + images[i] = image + + return np.concatenate ( images, axis = 1 ) \ No newline at end of file diff --git a/imagelib/morph.py b/imagelib/morph.py new file mode 100644 index 0000000..8aa5114 --- /dev/null +++ b/imagelib/morph.py @@ -0,0 +1,37 @@ +import numpy as np +import cv2 +from scipy.spatial import Delaunay + + +def applyAffineTransform(src, srcTri, dstTri, size) : + warpMat = cv2.getAffineTransform( np.float32(srcTri), np.float32(dstTri) ) + return cv2.warpAffine( src, warpMat, (size[0], size[1]), None, flags=cv2.INTER_LINEAR, borderMode=cv2.BORDER_REFLECT_101 ) + +def morphTriangle(dst_img, src_img, st, dt) : + (h,w,c) = dst_img.shape + sr = np.array( cv2.boundingRect(np.float32(st)) ) + dr = np.array( cv2.boundingRect(np.float32(dt)) ) + sRect = st - sr[0:2] + dRect = dt - dr[0:2] + d_mask = np.zeros((dr[3], dr[2], c), dtype = np.float32) + cv2.fillConvexPoly(d_mask, np.int32(dRect), (1.0,)*c, 8, 0); + imgRect = src_img[sr[1]:sr[1] + sr[3], sr[0]:sr[0] + sr[2]] + size = (dr[2], dr[3]) + warpImage1 = applyAffineTransform(imgRect, sRect, dRect, size) + + if c == 1: + warpImage1 = np.expand_dims( warpImage1, -1 ) + + dst_img[dr[1]:dr[1]+dr[3], dr[0]:dr[0]+dr[2]] = dst_img[dr[1]:dr[1]+dr[3], dr[0]:dr[0]+dr[2]]*(1-d_mask) + warpImage1 * d_mask + +def morph_by_points (image, sp, dp): + if sp.shape != dp.shape: + raise ValueError ('morph_by_points() sp.shape != dp.shape') + (h,w,c) = image.shape + + result_image = np.zeros(image.shape, dtype = image.dtype) + + for tri in Delaunay(dp).simplices: + morphTriangle(result_image, image, sp[tri], dp[tri]) + + return result_image \ No newline at end of file diff --git a/imagelib/reduce_colors.py b/imagelib/reduce_colors.py new file mode 100644 index 0000000..4300d30 --- /dev/null +++ b/imagelib/reduce_colors.py @@ -0,0 +1,14 @@ +import numpy as np +import cv2 +from PIL import Image + +#n_colors = [0..256] +def reduce_colors (img_bgr, n_colors): + img_rgb = (cv2.cvtColor(img_bgr, cv2.COLOR_BGR2RGB) * 255.0).astype(np.uint8) + img_rgb_pil = Image.fromarray(img_rgb) + img_rgb_pil_p = img_rgb_pil.convert('P', palette=Image.ADAPTIVE, colors=n_colors) + + img_rgb_p = img_rgb_pil_p.convert('RGB') + img_bgr = cv2.cvtColor( np.array(img_rgb_p, dtype=np.float32) / 255.0, cv2.COLOR_RGB2BGR ) + + return img_bgr diff --git a/imagelib/text.py b/imagelib/text.py new file mode 100644 index 0000000..0a486af --- /dev/null +++ b/imagelib/text.py @@ -0,0 +1,63 @@ +import localization +import numpy as np +from PIL import Image, ImageDraw, ImageFont + +pil_fonts = {} +def _get_pil_font (font, size): + global pil_fonts + try: + font_str_id = '%s_%d' % (font, size) + if font_str_id not in pil_fonts.keys(): + pil_fonts[font_str_id] = ImageFont.truetype(font + ".ttf", size=size, encoding="unic") + pil_font = pil_fonts[font_str_id] + return pil_font + except: + return ImageFont.load_default() + +def get_text_image( shape, text, color=(1,1,1), border=0.2, font=None): + try: + size = shape[1] + pil_font = _get_pil_font( localization.get_default_ttf_font_name() , size) + text_width, text_height = pil_font.getsize(text) + + canvas = Image.new('RGB', shape[0:2], (0,0,0) ) + draw = ImageDraw.Draw(canvas) + offset = ( 0, 0) + draw.text(offset, text, font=pil_font, fill=tuple((np.array(color)*255).astype(np.int)) ) + + result = np.asarray(canvas) / 255 + if shape[2] != 3: + result = np.concatenate ( (result, np.ones ( (shape[1],) + (shape[0],) + (shape[2]-3,)) ), axis=2 ) + + return result + except: + return np.zeros ( (shape[1], shape[0], shape[2]), dtype=np.float32 ) + +def draw_text( image, rect, text, color=(1,1,1), border=0.2, font=None): + h,w,c = image.shape + + l,t,r,b = rect + l = np.clip (l, 0, w-1) + r = np.clip (r, 0, w-1) + t = np.clip (t, 0, h-1) + b = np.clip (b, 0, h-1) + + image[t:b, l:r] += get_text_image ( (r-l,b-t,c) , text, color, border, font ) + + +def draw_text_lines (image, rect, text_lines, color=(1,1,1), border=0.2, font=None): + text_lines_len = len(text_lines) + if text_lines_len == 0: + return + + l,t,r,b = rect + h = b-t + h_per_line = h // text_lines_len + + for i in range(0, text_lines_len): + draw_text (image, (l, i*h_per_line, r, (i+1)*h_per_line), text_lines[i], color, border, font) + +def get_draw_text_lines ( image, rect, text_lines, color=(1,1,1), border=0.2, font=None): + image = np.zeros ( image.shape, dtype=np.float ) + draw_text_lines ( image, rect, text_lines, color, border, font) + return image diff --git a/imagelib/warp.py b/imagelib/warp.py new file mode 100644 index 0000000..c1f575f --- /dev/null +++ b/imagelib/warp.py @@ -0,0 +1,51 @@ +import numpy as np +import cv2 +from utils import random_utils + +def gen_warp_params (source, flip, rotation_range=[-10,10], scale_range=[-0.5, 0.5], tx_range=[-0.05, 0.05], ty_range=[-0.05, 0.05] ): + h,w,c = source.shape + if (h != w) or (w != 64 and w != 128 and w != 256 and w != 512 and w != 1024): + raise ValueError ('TrainingDataGenerator accepts only square power of 2 images.') + + rotation = np.random.uniform( rotation_range[0], rotation_range[1] ) + scale = np.random.uniform(1 +scale_range[0], 1 +scale_range[1]) + tx = np.random.uniform( tx_range[0], tx_range[1] ) + ty = np.random.uniform( ty_range[0], ty_range[1] ) + + #random warp by grid + cell_size = [ w // (2**i) for i in range(1,4) ] [ np.random.randint(3) ] + cell_count = w // cell_size + 1 + + grid_points = np.linspace( 0, w, cell_count) + mapx = np.broadcast_to(grid_points, (cell_count, cell_count)).copy() + mapy = mapx.T + + mapx[1:-1,1:-1] = mapx[1:-1,1:-1] + random_utils.random_normal( size=(cell_count-2, cell_count-2) )*(cell_size*0.24) + mapy[1:-1,1:-1] = mapy[1:-1,1:-1] + random_utils.random_normal( size=(cell_count-2, cell_count-2) )*(cell_size*0.24) + + half_cell_size = cell_size // 2 + + mapx = cv2.resize(mapx, (w+cell_size,)*2 )[half_cell_size:-half_cell_size-1,half_cell_size:-half_cell_size-1].astype(np.float32) + mapy = cv2.resize(mapy, (w+cell_size,)*2 )[half_cell_size:-half_cell_size-1,half_cell_size:-half_cell_size-1].astype(np.float32) + + #random transform + random_transform_mat = cv2.getRotationMatrix2D((w // 2, w // 2), rotation, scale) + random_transform_mat[:, 2] += (tx*w, ty*w) + + params = dict() + params['mapx'] = mapx + params['mapy'] = mapy + params['rmat'] = random_transform_mat + params['w'] = w + params['flip'] = flip and np.random.randint(10) < 4 + + return params + +def warp_by_params (params, img, warp, transform, flip, is_border_replicate): + if warp: + img = cv2.remap(img, params['mapx'], params['mapy'], cv2.INTER_CUBIC ) + if transform: + img = cv2.warpAffine( img, params['rmat'], (params['w'], params['w']), borderMode=(cv2.BORDER_REPLICATE if is_border_replicate else cv2.BORDER_CONSTANT), flags=cv2.INTER_CUBIC ) + if flip and params['flip']: + img = img[:,::-1,:] + return img \ No newline at end of file diff --git a/mainscripts/Converter.py b/mainscripts/Converter.py index db80e16..c5bdae0 100644 --- a/mainscripts/Converter.py +++ b/mainscripts/Converter.py @@ -7,7 +7,6 @@ import cv2 from utils.DFLPNG import DFLPNG from utils.DFLJPG import DFLJPG from utils.cv2_utils import * -from utils import image_utils import shutil import numpy as np import time diff --git a/mainscripts/Extractor.py b/mainscripts/Extractor.py index 82b8a0a..ab1023f 100644 --- a/mainscripts/Extractor.py +++ b/mainscripts/Extractor.py @@ -7,11 +7,11 @@ import shutil from pathlib import Path import numpy as np import mathlib +import imagelib import cv2 from utils import Path_utils from utils.DFLJPG import DFLJPG from utils.cv2_utils import * -from utils import image_utils import facelib from facelib import FaceType from facelib import LandmarksProcessor @@ -378,7 +378,7 @@ class ExtractSubprocessor(Subprocessor): if self.cache_text_lines_img[0] == sh: self.text_lines_img = self.cache_text_lines_img[1] else: - self.text_lines_img = (image_utils.get_draw_text_lines ( self.image, sh, + self.text_lines_img = (imagelib.get_draw_text_lines ( self.image, sh, [ '[Mouse click] - lock/unlock selection', '[Mouse wheel] - change rect', '[Enter] / [Space] - confirm / skip frame', diff --git a/mainscripts/Sorter.py b/mainscripts/Sorter.py index a5c2a78..72a9040 100644 --- a/mainscripts/Sorter.py +++ b/mainscripts/Sorter.py @@ -6,7 +6,6 @@ import cv2 from shutil import copyfile from pathlib import Path from utils import Path_utils -from utils import image_utils from utils.DFLPNG import DFLPNG from utils.DFLJPG import DFLJPG from utils.cv2_utils import * diff --git a/mainscripts/Trainer.py b/mainscripts/Trainer.py index ce574d4..1cc642b 100644 --- a/mainscripts/Trainer.py +++ b/mainscripts/Trainer.py @@ -7,7 +7,7 @@ import numpy as np import itertools from pathlib import Path from utils import Path_utils -from utils import image_utils +import imagelib import cv2 import models from interact import interact as io @@ -235,7 +235,7 @@ def main(args, device_args): for i in range(0, len(head_lines)): t = i*head_line_height b = (i+1)*head_line_height - head[t:b, 0:w] += image_utils.get_text_image ( (w,head_line_height,c) , head_lines[i], color=[0.8]*c ) + head[t:b, 0:w] += imagelib.get_text_image ( (w,head_line_height,c) , head_lines[i], color=[0.8]*c ) final = head diff --git a/models/ModelBase.py b/models/ModelBase.py index b68c350..d243e87 100644 --- a/models/ModelBase.py +++ b/models/ModelBase.py @@ -4,10 +4,10 @@ import time import inspect import pickle import colorsys +import imagelib from pathlib import Path from utils import Path_utils from utils import std_utils -from utils import image_utils from utils.cv2_utils import * import numpy as np import cv2 @@ -351,7 +351,7 @@ class ModelBase(object): if len(batch.shape) == 4: images.append( batch[0] ) - return image_utils.equalize_and_stack_square (images) + return imagelib.equalize_and_stack_square (images) def generate_next_sample(self): return [next(generator) for generator in self.generator_list] @@ -488,5 +488,5 @@ class ModelBase(object): lh_text = 'Iter: %d' % (iter) if iter != 0 else '' - lh_img[last_line_t:last_line_b, 0:w] += image_utils.get_text_image ( (w,last_line_b-last_line_t,c), lh_text, color=[0.8]*c ) + lh_img[last_line_t:last_line_b, 0:w] += imagelib.get_text_image ( (w,last_line_b-last_line_t,c), lh_text, color=[0.8]*c ) return lh_img diff --git a/samples/SampleProcessor.py b/samples/SampleProcessor.py index f10f5e9..9e5ddff 100644 --- a/samples/SampleProcessor.py +++ b/samples/SampleProcessor.py @@ -1,7 +1,7 @@ from enum import IntEnum import numpy as np import cv2 -from utils import image_utils +import imagelib from facelib import LandmarksProcessor from facelib import FaceType @@ -56,7 +56,7 @@ class SampleProcessor(object): if debug and close_sample_bgr is not None: LandmarksProcessor.draw_landmarks (close_sample_bgr, close_sample.landmarks, (0, 1, 0)) - params = image_utils.gen_warp_params(sample_bgr, sample_process_options.random_flip, rotation_range=sample_process_options.rotation_range, scale_range=sample_process_options.scale_range, tx_range=sample_process_options.tx_range, ty_range=sample_process_options.ty_range ) + params = imagelib.gen_warp_params(sample_bgr, sample_process_options.random_flip, rotation_range=sample_process_options.rotation_range, scale_range=sample_process_options.scale_range, tx_range=sample_process_options.tx_range, ty_range=sample_process_options.ty_range ) images = [[None]*3 for _ in range(30)] @@ -144,7 +144,7 @@ class SampleProcessor(object): d_landmarks = d_landmarks[idxs] s_landmarks = np.concatenate ( [s_landmarks, [ [0,0], [ res // 2, 0], [ res-1, 0], [0, res//2], [res-1, res//2] ,[0,res-1] ,[res//2, res-1] ,[res-1,res-1] ] ] ) d_landmarks = np.concatenate ( [d_landmarks, [ [0,0], [ res // 2, 0], [ res-1, 0], [0, res//2], [res-1, res//2] ,[0,res-1] ,[res//2, res-1] ,[res-1,res-1] ] ] ) - img = image_utils.morph_by_points (sample_bgr, s_landmarks, d_landmarks) + img = imagelib.morph_by_points (sample_bgr, s_landmarks, d_landmarks) cur_sample = close_sample else: img = sample_bgr @@ -159,7 +159,7 @@ class SampleProcessor(object): mask[mask > 0.0] = 1.0 img = np.concatenate( (img, mask ), -1 ) - images[img_type][face_mask_type] = image_utils.warp_by_params (params, img, (img_type==1 or img_type==2), (img_type==2 or img_type==3), img_type != 0, face_mask_type == 0) + images[img_type][face_mask_type] = imagelib.warp_by_params (params, img, (img_type==1 or img_type==2), (img_type==2 or img_type==3), img_type != 0, face_mask_type == 0) img = images[img_type][face_mask_type] diff --git a/utils/image_utils.py b/utils/image_utils.py deleted file mode 100644 index 1afffde..0000000 --- a/utils/image_utils.py +++ /dev/null @@ -1,424 +0,0 @@ -import sys -from utils import random_utils -import numpy as np -import cv2 -import localization -from scipy.spatial import Delaunay -from PIL import Image, ImageDraw, ImageFont - -def reinhard_color_transfer(target, source, clip=False, preserve_paper=False, source_mask=None, target_mask=None): - """ - Transfers the color distribution from the source to the target - image using the mean and standard deviations of the L*a*b* - color space. - - This implementation is (loosely) based on to the "Color Transfer - between Images" paper by Reinhard et al., 2001. - - Parameters: - ------- - source: NumPy array - OpenCV image in BGR color space (the source image) - target: NumPy array - OpenCV image in BGR color space (the target image) - clip: Should components of L*a*b* image be scaled by np.clip before - converting back to BGR color space? - If False then components will be min-max scaled appropriately. - Clipping will keep target image brightness truer to the input. - Scaling will adjust image brightness to avoid washed out portions - in the resulting color transfer that can be caused by clipping. - preserve_paper: Should color transfer strictly follow methodology - layed out in original paper? The method does not always produce - aesthetically pleasing results. - If False then L*a*b* components will scaled using the reciprocal of - the scaling factor proposed in the paper. This method seems to produce - more consistently aesthetically pleasing results - - Returns: - ------- - transfer: NumPy array - OpenCV image (w, h, 3) NumPy array (uint8) - """ - - - # convert the images from the RGB to L*ab* color space, being - # sure to utilizing the floating point data type (note: OpenCV - # expects floats to be 32-bit, so use that instead of 64-bit) - source = cv2.cvtColor(source, cv2.COLOR_BGR2LAB).astype(np.float32) - target = cv2.cvtColor(target, cv2.COLOR_BGR2LAB).astype(np.float32) - - # compute color statistics for the source and target images - src_input = source if source_mask is None else source*source_mask - tgt_input = target if target_mask is None else target*target_mask - (lMeanSrc, lStdSrc, aMeanSrc, aStdSrc, bMeanSrc, bStdSrc) = lab_image_stats(src_input) - (lMeanTar, lStdTar, aMeanTar, aStdTar, bMeanTar, bStdTar) = lab_image_stats(tgt_input) - - # subtract the means from the target image - (l, a, b) = cv2.split(target) - l -= lMeanTar - a -= aMeanTar - b -= bMeanTar - - if preserve_paper: - # scale by the standard deviations using paper proposed factor - l = (lStdTar / lStdSrc) * l - a = (aStdTar / aStdSrc) * a - b = (bStdTar / bStdSrc) * b - else: - # scale by the standard deviations using reciprocal of paper proposed factor - l = (lStdSrc / lStdTar) * l - a = (aStdSrc / aStdTar) * a - b = (bStdSrc / bStdTar) * b - - # add in the source mean - l += lMeanSrc - a += aMeanSrc - b += bMeanSrc - - # clip/scale the pixel intensities to [0, 255] if they fall - # outside this range - l = _scale_array(l, clip=clip) - a = _scale_array(a, clip=clip) - b = _scale_array(b, clip=clip) - - # merge the channels together and convert back to the RGB color - # space, being sure to utilize the 8-bit unsigned integer data - # type - transfer = cv2.merge([l, a, b]) - transfer = cv2.cvtColor(transfer.astype(np.uint8), cv2.COLOR_LAB2BGR) - - # return the color transferred image - return transfer - -def linear_color_transfer(target_img, source_img, mode='pca', eps=1e-5): - ''' - Matches the colour distribution of the target image to that of the source image - using a linear transform. - Images are expected to be of form (w,h,c) and float in [0,1]. - Modes are chol, pca or sym for different choices of basis. - ''' - mu_t = target_img.mean(0).mean(0) - t = target_img - mu_t - t = t.transpose(2,0,1).reshape(3,-1) - Ct = t.dot(t.T) / t.shape[1] + eps * np.eye(t.shape[0]) - mu_s = source_img.mean(0).mean(0) - s = source_img - mu_s - s = s.transpose(2,0,1).reshape(3,-1) - Cs = s.dot(s.T) / s.shape[1] + eps * np.eye(s.shape[0]) - if mode == 'chol': - chol_t = np.linalg.cholesky(Ct) - chol_s = np.linalg.cholesky(Cs) - ts = chol_s.dot(np.linalg.inv(chol_t)).dot(t) - if mode == 'pca': - eva_t, eve_t = np.linalg.eigh(Ct) - Qt = eve_t.dot(np.sqrt(np.diag(eva_t))).dot(eve_t.T) - eva_s, eve_s = np.linalg.eigh(Cs) - Qs = eve_s.dot(np.sqrt(np.diag(eva_s))).dot(eve_s.T) - ts = Qs.dot(np.linalg.inv(Qt)).dot(t) - if mode == 'sym': - eva_t, eve_t = np.linalg.eigh(Ct) - Qt = eve_t.dot(np.sqrt(np.diag(eva_t))).dot(eve_t.T) - Qt_Cs_Qt = Qt.dot(Cs).dot(Qt) - eva_QtCsQt, eve_QtCsQt = np.linalg.eigh(Qt_Cs_Qt) - QtCsQt = eve_QtCsQt.dot(np.sqrt(np.diag(eva_QtCsQt))).dot(eve_QtCsQt.T) - ts = np.linalg.inv(Qt).dot(QtCsQt).dot(np.linalg.inv(Qt)).dot(t) - matched_img = ts.reshape(*target_img.transpose(2,0,1).shape).transpose(1,2,0) - matched_img += mu_s - matched_img[matched_img>1] = 1 - matched_img[matched_img<0] = 0 - return matched_img - -def lab_image_stats(image): - # compute the mean and standard deviation of each channel - (l, a, b) = cv2.split(image) - (lMean, lStd) = (l.mean(), l.std()) - (aMean, aStd) = (a.mean(), a.std()) - (bMean, bStd) = (b.mean(), b.std()) - - # return the color statistics - return (lMean, lStd, aMean, aStd, bMean, bStd) - -def _scale_array(arr, clip=True): - if clip: - return np.clip(arr, 0, 255) - - mn = arr.min() - mx = arr.max() - scale_range = (max([mn, 0]), min([mx, 255])) - - if mn < scale_range[0] or mx > scale_range[1]: - return (scale_range[1] - scale_range[0]) * (arr - mn) / (mx - mn) + scale_range[0] - - return arr - -def channel_hist_match(source, template, hist_match_threshold=255, mask=None): - # Code borrowed from: - # https://stackoverflow.com/questions/32655686/histogram-matching-of-two-images-in-python-2-x - masked_source = source - masked_template = template - - if mask is not None: - masked_source = source * mask - masked_template = template * mask - - oldshape = source.shape - source = source.ravel() - template = template.ravel() - masked_source = masked_source.ravel() - masked_template = masked_template.ravel() - s_values, bin_idx, s_counts = np.unique(source, return_inverse=True, - return_counts=True) - t_values, t_counts = np.unique(template, return_counts=True) - ms_values, mbin_idx, ms_counts = np.unique(source, return_inverse=True, - return_counts=True) - mt_values, mt_counts = np.unique(template, return_counts=True) - - s_quantiles = np.cumsum(s_counts).astype(np.float64) - s_quantiles = hist_match_threshold * s_quantiles / s_quantiles[-1] - t_quantiles = np.cumsum(t_counts).astype(np.float64) - t_quantiles = 255 * t_quantiles / t_quantiles[-1] - interp_t_values = np.interp(s_quantiles, t_quantiles, t_values) - - return interp_t_values[bin_idx].reshape(oldshape) - -def color_hist_match(src_im, tar_im, hist_match_threshold=255): - h,w,c = src_im.shape - matched_R = channel_hist_match(src_im[:,:,0], tar_im[:,:,0], hist_match_threshold, None) - matched_G = channel_hist_match(src_im[:,:,1], tar_im[:,:,1], hist_match_threshold, None) - matched_B = channel_hist_match(src_im[:,:,2], tar_im[:,:,2], hist_match_threshold, None) - - to_stack = (matched_R, matched_G, matched_B) - for i in range(3, c): - to_stack += ( src_im[:,:,i],) - - - matched = np.stack(to_stack, axis=-1).astype(src_im.dtype) - return matched - - -pil_fonts = {} -def _get_pil_font (font, size): - global pil_fonts - try: - font_str_id = '%s_%d' % (font, size) - if font_str_id not in pil_fonts.keys(): - pil_fonts[font_str_id] = ImageFont.truetype(font + ".ttf", size=size, encoding="unic") - pil_font = pil_fonts[font_str_id] - return pil_font - except: - return ImageFont.load_default() - -def get_text_image( shape, text, color=(1,1,1), border=0.2, font=None): - try: - size = shape[1] - pil_font = _get_pil_font( localization.get_default_ttf_font_name() , size) - text_width, text_height = pil_font.getsize(text) - - canvas = Image.new('RGB', shape[0:2], (0,0,0) ) - draw = ImageDraw.Draw(canvas) - offset = ( 0, 0) - draw.text(offset, text, font=pil_font, fill=tuple((np.array(color)*255).astype(np.int)) ) - - result = np.asarray(canvas) / 255 - if shape[2] != 3: - result = np.concatenate ( (result, np.ones ( (shape[1],) + (shape[0],) + (shape[2]-3,)) ), axis=2 ) - - return result - except: - return np.zeros ( (shape[1], shape[0], shape[2]), dtype=np.float32 ) - -def draw_text( image, rect, text, color=(1,1,1), border=0.2, font=None): - h,w,c = image.shape - - l,t,r,b = rect - l = np.clip (l, 0, w-1) - r = np.clip (r, 0, w-1) - t = np.clip (t, 0, h-1) - b = np.clip (b, 0, h-1) - - image[t:b, l:r] += get_text_image ( (r-l,b-t,c) , text, color, border, font ) - -def draw_text_lines (image, rect, text_lines, color=(1,1,1), border=0.2, font=None): - text_lines_len = len(text_lines) - if text_lines_len == 0: - return - - l,t,r,b = rect - h = b-t - h_per_line = h // text_lines_len - - for i in range(0, text_lines_len): - draw_text (image, (l, i*h_per_line, r, (i+1)*h_per_line), text_lines[i], color, border, font) - -def get_draw_text_lines ( image, rect, text_lines, color=(1,1,1), border=0.2, font=None): - image = np.zeros ( image.shape, dtype=np.float ) - draw_text_lines ( image, rect, text_lines, color, border, font) - return image - - -def draw_polygon (image, points, color, thickness = 1): - points_len = len(points) - for i in range (0, points_len): - p0 = tuple( points[i] ) - p1 = tuple( points[ (i+1) % points_len] ) - cv2.line (image, p0, p1, color, thickness=thickness) - -def draw_rect(image, rect, color, thickness=1): - l,t,r,b = rect - draw_polygon (image, [ (l,t), (r,t), (r,b), (l,b ) ], color, thickness) - -def rectContains(rect, point) : - return not (point[0] < rect[0] or point[0] >= rect[2] or point[1] < rect[1] or point[1] >= rect[3]) - -def applyAffineTransform(src, srcTri, dstTri, size) : - warpMat = cv2.getAffineTransform( np.float32(srcTri), np.float32(dstTri) ) - return cv2.warpAffine( src, warpMat, (size[0], size[1]), None, flags=cv2.INTER_LINEAR, borderMode=cv2.BORDER_REFLECT_101 ) - -def morphTriangle(dst_img, src_img, st, dt) : - (h,w,c) = dst_img.shape - sr = np.array( cv2.boundingRect(np.float32(st)) ) - dr = np.array( cv2.boundingRect(np.float32(dt)) ) - sRect = st - sr[0:2] - dRect = dt - dr[0:2] - d_mask = np.zeros((dr[3], dr[2], c), dtype = np.float32) - cv2.fillConvexPoly(d_mask, np.int32(dRect), (1.0,)*c, 8, 0); - imgRect = src_img[sr[1]:sr[1] + sr[3], sr[0]:sr[0] + sr[2]] - size = (dr[2], dr[3]) - warpImage1 = applyAffineTransform(imgRect, sRect, dRect, size) - - if c == 1: - warpImage1 = np.expand_dims( warpImage1, -1 ) - - dst_img[dr[1]:dr[1]+dr[3], dr[0]:dr[0]+dr[2]] = dst_img[dr[1]:dr[1]+dr[3], dr[0]:dr[0]+dr[2]]*(1-d_mask) + warpImage1 * d_mask - -def morph_by_points (image, sp, dp): - if sp.shape != dp.shape: - raise ValueError ('morph_by_points() sp.shape != dp.shape') - (h,w,c) = image.shape - - result_image = np.zeros(image.shape, dtype = image.dtype) - - for tri in Delaunay(dp).simplices: - morphTriangle(result_image, image, sp[tri], dp[tri]) - - return result_image - -def equalize_and_stack_square (images, axis=1): - max_c = max ([ 1 if len(image.shape) == 2 else image.shape[2] for image in images ] ) - - target_wh = 99999 - for i,image in enumerate(images): - if len(image.shape) == 2: - h,w = image.shape - c = 1 - else: - h,w,c = image.shape - - if h < target_wh: - target_wh = h - - if w < target_wh: - target_wh = w - - for i,image in enumerate(images): - if len(image.shape) == 2: - h,w = image.shape - c = 1 - else: - h,w,c = image.shape - - if c < max_c: - if c == 1: - if len(image.shape) == 2: - image = np.expand_dims ( image, -1 ) - image = np.concatenate ( (image,)*max_c, -1 ) - elif c == 2: #GA - image = np.expand_dims ( image[...,0], -1 ) - image = np.concatenate ( (image,)*max_c, -1 ) - else: - image = np.concatenate ( (image, np.ones((h,w,max_c - c))), -1 ) - - if h != target_wh or w != target_wh: - image = cv2.resize ( image, (target_wh, target_wh) ) - h,w,c = image.shape - - images[i] = image - - return np.concatenate ( images, axis = 1 ) - -def bgr2hsv (img): - return cv2.cvtColor(img, cv2.COLOR_BGR2HSV) - -def hsv2bgr (img): - return cv2.cvtColor(img, cv2.COLOR_HSV2BGR) - -def bgra2hsva (img): - return np.concatenate ( (cv2.cvtColor(img[...,0:3], cv2.COLOR_BGR2HSV ), np.expand_dims (img[...,3], -1)), -1 ) - -def bgra2hsva_list (imgs): - return [ bgra2hsva(img) for img in imgs ] - -def hsva2bgra (img): - return np.concatenate ( (cv2.cvtColor(img[...,0:3], cv2.COLOR_HSV2BGR ), np.expand_dims (img[...,3], -1)), -1 ) - -def hsva2bgra_list (imgs): - return [ hsva2bgra(img) for img in imgs ] - -def gen_warp_params (source, flip, rotation_range=[-10,10], scale_range=[-0.5, 0.5], tx_range=[-0.05, 0.05], ty_range=[-0.05, 0.05] ): - h,w,c = source.shape - if (h != w) or (w != 64 and w != 128 and w != 256 and w != 512 and w != 1024): - raise ValueError ('TrainingDataGenerator accepts only square power of 2 images.') - - rotation = np.random.uniform( rotation_range[0], rotation_range[1] ) - scale = np.random.uniform(1 +scale_range[0], 1 +scale_range[1]) - tx = np.random.uniform( tx_range[0], tx_range[1] ) - ty = np.random.uniform( ty_range[0], ty_range[1] ) - - #random warp by grid - cell_size = [ w // (2**i) for i in range(1,4) ] [ np.random.randint(3) ] - cell_count = w // cell_size + 1 - - grid_points = np.linspace( 0, w, cell_count) - mapx = np.broadcast_to(grid_points, (cell_count, cell_count)).copy() - mapy = mapx.T - - mapx[1:-1,1:-1] = mapx[1:-1,1:-1] + random_utils.random_normal( size=(cell_count-2, cell_count-2) )*(cell_size*0.24) - mapy[1:-1,1:-1] = mapy[1:-1,1:-1] + random_utils.random_normal( size=(cell_count-2, cell_count-2) )*(cell_size*0.24) - - half_cell_size = cell_size // 2 - - mapx = cv2.resize(mapx, (w+cell_size,)*2 )[half_cell_size:-half_cell_size-1,half_cell_size:-half_cell_size-1].astype(np.float32) - mapy = cv2.resize(mapy, (w+cell_size,)*2 )[half_cell_size:-half_cell_size-1,half_cell_size:-half_cell_size-1].astype(np.float32) - - #random transform - random_transform_mat = cv2.getRotationMatrix2D((w // 2, w // 2), rotation, scale) - random_transform_mat[:, 2] += (tx*w, ty*w) - - params = dict() - params['mapx'] = mapx - params['mapy'] = mapy - params['rmat'] = random_transform_mat - params['w'] = w - params['flip'] = flip and np.random.randint(10) < 4 - - return params - -def warp_by_params (params, img, warp, transform, flip, is_border_replicate): - if warp: - img = cv2.remap(img, params['mapx'], params['mapy'], cv2.INTER_CUBIC ) - if transform: - img = cv2.warpAffine( img, params['rmat'], (params['w'], params['w']), borderMode=(cv2.BORDER_REPLICATE if is_border_replicate else cv2.BORDER_CONSTANT), flags=cv2.INTER_CUBIC ) - if flip and params['flip']: - img = img[:,::-1,:] - return img - -#n_colors = [0..256] -def reduce_colors (img_bgr, n_colors): - img_rgb = (cv2.cvtColor(img_bgr, cv2.COLOR_BGR2RGB) * 255.0).astype(np.uint8) - img_rgb_pil = Image.fromarray(img_rgb) - img_rgb_pil_p = img_rgb_pil.convert('P', palette=Image.ADAPTIVE, colors=n_colors) - - img_rgb_p = img_rgb_pil_p.convert('RGB') - img_bgr = cv2.cvtColor( np.array(img_rgb_p, dtype=np.float32) / 255.0, cv2.COLOR_RGB2BGR ) - - return img_bgr