import os import copy import time from service.generate_main_image.image_deal_base_func import * from PIL import Image, ImageDraw from blend_modes import multiply import os import settings from functools import wraps from service.multi_threaded_image_saving import ImageSaver from service.get_mask_by_green import GetMask # def time_it(func): @wraps(func) # 使用wraps来保留原始函数的元数据信息 def wrapper(*args, **kwargs): start_time = time.time() # 记录开始时间 result = func(*args, **kwargs) # 调用原始函数 end_time = time.time() # 记录结束时间 print( f"Executing {func.__name__} took {end_time - start_time:.4f} seconds." ) # 打印耗时 return result return wrapper class GeneratePic(object): def __init__(self, is_test=False): # self.logger = MyLogger() self.is_test = is_test self.saver = ImageSaver() pass @time_it def get_mask_and_config(self, im_jpg: Image, im_png: Image, curve_mask: bool): """ 步骤: 1、尺寸进行对应缩小 2、查找并设定鞋底阴影蒙版 3、自动色阶检查亮度 4、输出自动色阶参数、以及放大的尺寸蒙版 """ # ===================尺寸进行对应缩小(提升处理速度) im_jpg = to_resize(im_jpg, width=800) im_png = to_resize(im_png, width=800) x1, y1, x2, y2 = im_png.getbbox() cv2_png = pil_to_cv2(im_png) # =====================设定鞋底阴影图的蒙版 # 查找每列的最低非透明点 min_y_values = find_lowest_non_transparent_points(cv2_png) # 在鞋底最低处增加一条直线蒙版,蒙版宽度为有效区域大小 image_high = im_jpg.height print("图片高度:", image_high) cv2_jpg = pil_to_cv2(im_jpg) # 返回线条图片,以及最低位置 print("返回线条图片,以及最低位置") # crop_image_box=(x1, y1, x2, y2), if curve_mask: crop_image_box = None else: # 不需要曲线部分的蒙版 crop_image_box = (x1, y1, x2, y2) img_with_shifted_line, lowest_y = draw_shifted_line( image=cv2_jpg, min_y_values=min_y_values, shift_amount=15, one_line_pos=(x1, x2), line_color=(0, 0, 0), line_thickness=20, app=None, crop_image_box=crop_image_box, ) print("66 制作蒙版") # 制作蒙版 mask_line = cv2_to_pil(img_with_shifted_line) mask = mask_line.convert("L") # 转换为灰度图 mask = ImageOps.invert(mask) # 蒙版扩边 print("72 蒙版扩边") # 默认expansion_radius 65 blur_radius 45 mask = expand_or_shrink_mask( pil_image=mask, expansion_radius=50, blur_radius=35 ) # =============使用绿色蒙版进行处理 if settings.IS_GET_GREEN_MASK: print("============使用绿色蒙版进行处理") mask = mask.convert("RGB") white_bg = Image.new(mode="RGB", size=im_png.size, color=(0, 0, 0)) green_areas_mask_pil = GetMask().find_green_areas(cv2_jpg) green_areas_mask_pil = expand_or_shrink_mask( pil_image=green_areas_mask_pil, expansion_radius=15, blur_radius=5 ) mask.paste(white_bg, mask=green_areas_mask_pil.convert("L")) mask = mask.convert("L") # ====================生成新的图片 print("84 生成新的图片") bg = Image.new(mode="RGBA", size=im_png.size, color=(255, 255, 255, 255)) bg.paste(im_png, mask=im_png) bg.paste(im_jpg, mask=mask) # 粘贴有阴影的地方 if self.is_test: _bg = bg.copy() draw = ImageDraw.Draw(_bg) # 定义直线的起点和终点坐标 start_point = (0, lowest_y) # 直线的起始点 end_point = (_bg.width, lowest_y) # 直线的结束点 # 定义直线的颜色(R, G, B) line_color = (255, 0, 0) # 红色 _r = Image.new(mode="RGBA", size=im_png.size, color=(246, 147, 100, 255)) # mask_line = mask_line.convert('L') # 转换为灰度图 # mask_line = ImageOps.invert(mask_line) # _bg.paste(_r, mask=mask) # 绘制直线 draw.line([start_point, end_point], fill=line_color, width=1) _bg.show() # bg.save(r"C:\Users\gymmc\Desktop\data\bg.png") # bg.show() # ==================自动色阶处理====================== # 对上述拼接后的图片进行自动色阶处理 bg = bg.convert("RGB") _im = cv2.cvtColor(np.asarray(bg), cv2.COLOR_RGB2BGR) # 背景阴影 im_shadow = cv2.cvtColor(_im, cv2.COLOR_BGR2GRAY) print("image_high lowest_y", image_high, lowest_y) if lowest_y < 0 or lowest_y >= image_high: lowest_y = image_high - 1 print("image_high lowest_y", image_high, lowest_y) rows = [lowest_y] # 需要检查的像素行 print("copy.copy(im_shadow)") _im_shadow = copy.copy(im_shadow) Midtones = 0.7 Highlight = 235 k = copy.copy(settings.COLOR_GRADATION_CYCLES) print("循环识别") xunhuan = 0 while k: xunhuan += 1 k -= 1 Midtones += 0.035 if Midtones > 1.7: Midtones = 1.7 Highlight -= 3 _im_shadow = levels_adjust( img=im_shadow, Shadow=0, Midtones=Midtones, Highlight=Highlight, OutShadow=0, OutHighlight=255, Dim=3, ) brightness_list = calculate_average_brightness_opencv( img_gray=_im_shadow, rows_to_check=rows ) print( "循环识别:{},Midtones:{},Highlight:{},brightness_list:{}".format( xunhuan, Midtones, Highlight, brightness_list ) ) if brightness_list[0] >= settings.GRENERATE_MAIN_PIC_BRIGHTNESS: break im_shadow = cv2_to_pil(_im_shadow) # ======================================================== # 计算阴影的亮度,用于确保阴影不要太黑 # 1、图片预处理,只保留阴影 only_shadow_img = im_shadow.copy() only_shadow_img.paste( Image.new( mode="RGBA", size=only_shadow_img.size, color=(255, 255, 255, 255) ), mask=im_png, ) average_brightness = calculated_shadow_brightness(only_shadow_img) print("average_brightness:", average_brightness) config = { "Midtones": Midtones, "Highlight": Highlight, "average_brightness": average_brightness, } return mask, config def get_mask_and_config_1_2025_05_18(self, im_jpg: Image, im_png: Image): """ 步骤: 1、尺寸进行对应缩小 2、查找并设定鞋底阴影蒙版 3、自动色阶检查亮度 4、输出自动色阶参数、以及放大的尺寸蒙版 """ # ===================尺寸进行对应缩小(提升处理速度) im_jpg = to_resize(im_jpg, width=800) im_png = to_resize(im_png, width=800) x1, y1, x2, y2 = im_png.getbbox() cv2_png = pil_to_cv2(im_png) # =====================设定鞋底阴影图的蒙版 # 查找每列的最低非透明点 min_y_values = find_lowest_non_transparent_points(cv2_png) # 在鞋底最低处增加一条直线蒙版,蒙版宽度为有效区域大小 image_high = im_jpg.height print("图片高度:", image_high) # TODO 待移除 if settings.app: settings.app.processEvents() cv2_jpg = pil_to_cv2(im_jpg) # 返回线条图片,以及最低位置 print("返回线条图片,以及最低位置") img_with_shifted_line, lowest_y = draw_shifted_line( image=cv2_jpg, min_y_values=min_y_values, shift_amount=15, one_line_pos=(x1, x2), line_color=(0, 0, 0), line_thickness=20, app=settings.app, crop_image_box=(x1, y1, x2, y2), ) # TODO 待移除 if settings.app: settings.app.processEvents() print("66 制作蒙版") # 制作蒙版 mask_line = cv2_to_pil(img_with_shifted_line) mask = mask_line.convert("L") # 转换为灰度图 mask = ImageOps.invert(mask) # 蒙版扩边 print("72 蒙版扩边") # 默认expansion_radius 65 blur_radius 45 mask = expand_or_shrink_mask( pil_image=mask, expansion_radius=50, blur_radius=35 ) # mask1 = expand_mask(mask, expansion_radius=30, blur_radius=10) # mask1.save("mask1.png") # mask2 = expand_or_shrink_mask(pil_image=mask, expansion_radius=60, blur_radius=30) # mask2.save("mask2.png") # raise 11 # TODO 待移除 if settings.app: settings.app.processEvents() # ====================生成新的图片 print("84 生成新的图片") bg = Image.new(mode="RGBA", size=im_png.size, color=(255, 255, 255, 255)) bg.paste(im_png, mask=im_png) bg.paste(im_jpg, mask=mask) # 粘贴有阴影的地方 # TODO 待移除 if settings.app: settings.app.processEvents() if self.is_test: _bg = bg.copy() draw = ImageDraw.Draw(_bg) # 定义直线的起点和终点坐标 start_point = (0, lowest_y) # 直线的起始点 end_point = (_bg.width, lowest_y) # 直线的结束点 # 定义直线的颜色(R, G, B) line_color = (255, 0, 0) # 红色 # 绘制直线 draw.line([start_point, end_point], fill=line_color, width=1) # mask.show() # bg = pil_to_cv2(bg) # cv2.line(bg, (x1, lowest_y + 5), (x2, lowest_y + 5), color=(0, 0, 0),thickness=2) # bg = cv2_to_pil(bg) _r = Image.new(mode="RGBA", size=im_png.size, color=(246, 147, 100, 255)) mask_line = mask_line.convert("L") # 转换为灰度图 mask_line = ImageOps.invert(mask_line) _bg.paste(_r, mask=mask) _bg.show() # bg.save(r"C:\Users\gymmc\Desktop\data\bg.png") # bg.show() # ==================自动色阶处理====================== # 对上述拼接后的图片进行自动色阶处理 bg = bg.convert("RGB") _im = cv2.cvtColor(np.asarray(bg), cv2.COLOR_RGB2BGR) # 背景阴影 im_shadow = cv2.cvtColor(_im, cv2.COLOR_BGR2GRAY) print("image_high lowest_y", image_high, lowest_y) if lowest_y < 0 or lowest_y >= image_high: lowest_y = image_high - 1 print("image_high lowest_y", image_high, lowest_y) rows = [lowest_y] # 需要检查的像素行 print("copy.copy(im_shadow)") _im_shadow = copy.copy(im_shadow) Midtones = 0.7 Highlight = 235 k = 12 print("循环识别") while k: print("循环识别:{}".format(k)) if settings.app: settings.app.processEvents() k -= 1 Midtones += 0.1 if Midtones > 1: Midtones = 1 Highlight -= 3 _im_shadow = levels_adjust( img=im_shadow, Shadow=0, Midtones=Midtones, Highlight=Highlight, OutShadow=0, OutHighlight=255, Dim=3, ) brightness_list = calculate_average_brightness_opencv( img_gray=_im_shadow, rows_to_check=rows ) print(brightness_list) if brightness_list[0] >= settings.GRENERATE_MAIN_PIC_BRIGHTNESS: break print("Midtones,Highlight:", Midtones, Highlight) im_shadow = cv2_to_pil(_im_shadow) # ======================================================== # 计算阴影的亮度,用于确保阴影不要太黑 # 1、图片预处理,只保留阴影 only_shadow_img = im_shadow.copy() only_shadow_img.paste( Image.new( mode="RGBA", size=only_shadow_img.size, color=(255, 255, 255, 255) ), mask=im_png, ) average_brightness = calculated_shadow_brightness(only_shadow_img) print("average_brightness:", average_brightness) config = { "Midtones": Midtones, "Highlight": Highlight, "average_brightness": average_brightness, } return mask, config def my_test(self, **kwargs): if "output_queue" in kwargs: output_queue = kwargs["output_queue"] else: output_queue = None time.sleep(3) if output_queue is not None: output_queue.put(True) @time_it def run( self, image_path, cut_image_path, out_path, image_deal_mode=0, image_index=99, out_pic_size=1024, is_logo=True, out_process_path_1=None, out_process_path_2=None, resize_mode=None, max_box=None, logo_path="", curve_mask=False, **kwargs, ): # im 为cv对象 """ image_path:原始图 cut_image_path:抠图结果 与原始图尺寸相同 out_path:输出主图路径 image_deal_mode:图片处理模式,1表示需要镜像处理 image_index:图片顺序索引 out_pic_size:输出图片宽度大小 is_logo=True 是否要添加logo水印 out_process_path_1=None, 有阴影的图片,白底非透明 out_process_path_2=None, 已抠图的图片 resize_mode=0,1,2 主体缩小尺寸 curve_mask 为True时,表示为对鞋曲线部分的mask,不做剪裁 """ if "output_queue" in kwargs: output_queue = kwargs["output_queue"] else: output_queue = None # ==========先进行剪切原图 _s = time.time() orign_im = Image.open(image_path) # 原始图 print("242 need_time_1:{}".format(time.time() - _s)) orign_x, orign_y = orign_im.size time.sleep(3) cut_image = Image.open(cut_image_path) # 原始图的已扣图 try: cut_image, new_box = get_mini_crop_img(img=cut_image) im_shadow = orign_im.crop(new_box) # 切图 new_x, new_y = im_shadow.size except Exception as e: im_shadow = cut_image # ================自动色阶处理 _s = time.time() shadow_mask, config = self.get_mask_and_config( im_jpg=im_shadow, im_png=cut_image, curve_mask=curve_mask ) print("242 need_time_2:{}".format(time.time() - _s)) shadow_mask = shadow_mask.resize(im_shadow.size) # =====抠图,形成新的阴影背景图===== # TODO 待移除 # settings.app.processEvents() _new_im_shadow = Image.new( mode="RGBA", size=im_shadow.size, color=(255, 255, 255, 255) ) _new_im_shadow.paste(im_shadow, mask=shadow_mask) # 粘贴有阴影的地方 # _new_im_shadow.show() _new_im_shadow = pil_to_cv2(_new_im_shadow) _new_im_shadow = cv2.cvtColor(_new_im_shadow, cv2.COLOR_BGR2GRAY) _new_im_shadow = levels_adjust( img=_new_im_shadow, Shadow=0, Midtones=config["Midtones"], Highlight=config["Highlight"], OutShadow=0, OutHighlight=255, Dim=3, ) im_shadow = cv2_to_pil(_new_im_shadow) # ================处理阴影的亮度================== average_brightness = config["average_brightness"] if config["average_brightness"] < 180: # 调整阴影亮度 backdrop_prepped = np.asfarray( Image.new(mode="RGBA", size=im_shadow.size, color=(255, 255, 255, 255)) ) im_shadow = im_shadow.convert("RGBA") source_prepped = np.asfarray(im_shadow) # im_shadow.show() opacity = (average_brightness - 30) / 160 opacity = max(0.5, min(opacity, 1)) print("阴影透明度:{}%".format(int(opacity * 100))) blended_np = multiply( backdrop_prepped, source_prepped, opacity=int(opacity * 100) / 100 ) im_shadow = Image.fromarray(np.uint8(blended_np)).convert("RGB") # im_shadow.show() # 把原图粘贴回去,避免色差 im_shadow.paste(cut_image, (0, 0), mask=cut_image) # _new_im_shadow.show() # ===========处理其他==================== # 保存带有阴影的底图,没有logo if out_process_path_1: out_image_1 = im_shadow.copy() if image_deal_mode == 1: out_image_1 = out_image_1.transpose(Image.FLIP_LEFT_RIGHT) self.saver.save_image( image=out_image_1, file_path=out_process_path_1, save_mode="png" ) # save_image_by_thread(image=out_image_1, out_path=out_process_path_1) # out_image_1.save(out_process_path_1) # 保存抠图结果,没有底图,没有logo if out_process_path_2: out_image_2 = cut_image.copy() if image_deal_mode == 1: out_image_2 = out_image_2.transpose(Image.FLIP_LEFT_RIGHT) self.saver.save_image( image=out_image_2, file_path=out_process_path_2, save_mode="png" ) # save_image_by_thread(image=out_image_2, out_path=out_process_path_2, save_mode="png") # out_image_2.save(out_process_path_2) # 不生成主图时直接退出 if not out_path: return True # im_shadow.show() # =====================主图物体的缩放依据大小 if max_box: im_shadow = to_resize(_im=im_shadow, width=max_box[0], high=max_box[1]) cut_image = to_resize(_im=cut_image, width=max_box[0], high=max_box[1]) else: if resize_mode is None: im_shadow = to_resize(_im=im_shadow, width=1400, high=1400) cut_image = to_resize(_im=cut_image, width=1400, high=1400) elif resize_mode == 1: im_shadow = to_resize(_im=im_shadow, width=1400, high=1400) cut_image = to_resize(_im=cut_image, width=1400, high=1400) elif resize_mode == 2: # todo 兼容长筒靴等,将图片大小限制在一个指定的box内 im_shadow = to_resize(_im=im_shadow, width=650) cut_image = to_resize(_im=cut_image, width=650) # 再次检查需要约束缩小到一定高度,适应长筒靴 _im_x, _im_y = cut_image.size if _im_y > 1400: im_shadow = to_resize(_im=im_shadow, high=1400) cut_image = to_resize(_im=cut_image, high=1400) # if im_shadow.height <= im_shadow.width * 1.2: # im_shadow = to_resize(_im=im_shadow, width=650) # cut_image = to_resize(_im=cut_image, width=650) # else: # im_shadow = to_resize(_im=im_shadow, high=1400) # cut_image = to_resize(_im=cut_image, high=1400) if image_deal_mode == 1: # 翻转 im_shadow = im_shadow.transpose(Image.FLIP_LEFT_RIGHT) cut_image = cut_image.transpose(Image.FLIP_LEFT_RIGHT) # 创建底层背景 image_bg = Image.new("RGB", (1600, 1600), (255, 255, 255)) image_bg_x, image_bg_y = image_bg.size image_x, image_y = im_shadow.size _x = int((image_bg_x - image_x) / 2) _y = int((image_bg_y - image_y) / 2) image_bg.paste(im_shadow, (_x, _y)) image_bg.paste(cut_image, (_x, _y), cut_image) # 再叠加原图避免色差 if "小苏" in settings.Company: # 所有主图加logo is_logo = True if is_logo: # logo_path = "" # if settings.PROJECT == "红蜻蜓": # logo_path = r"resources\LOGO\HQT\logo.png" # elif settings.PROJECT == "惠利玛": # if "小苏" in settings.Company: # logo_path = r"resources\LOGO\xiaosushuoxie\logo.png" # elif "惠利玛" in settings.Company: # logo_path = r"resources\LOGO\HLM\logo.png" # else: # pass if not logo_path: logo_im = Image.new("RGBA", (1600, 1600), (0, 0, 0, 0)) else: if os.path.exists(logo_path): logo_im = Image.open(logo_path) else: logo_im = Image.new("RGBA", (1600, 1600), (0, 0, 0, 0)) image_bg.paste(logo_im, (0, 0), logo_im) # image_bg = image_bg.resize((out_pic_size, out_pic_size), Image.BICUBIC) if settings.OUT_PIC_FACTOR > 1.0: print("图片锐化处理") image_bg = sharpen_image(image_bg, factor=settings.OUT_PIC_FACTOR) if out_pic_size < 1600: image_bg = image_bg.resize( (out_pic_size, out_pic_size), resample=settings.RESIZE_IMAGE_MODE ) print("图片保存格式", settings.OUT_PIC_MODE) if settings.OUT_PIC_MODE == ".jpg": if settings.OUT_PIC_QUALITY == "普通": self.saver.save_image( image=image_bg, file_path=out_path, save_mode="jpg", quality=None, dpi=None, _format="JPEG", ) # save_image_by_thread(image_bg, out_path, save_mode="jpg", quality=None, dpi=None, _format="JPEG") # image_bg.save(out_path, format="JPEG") else: self.saver.save_image( image=image_bg, file_path=out_path, save_mode="jpg", quality=100, dpi=(300, 300), _format="JPEG", ) # save_image_by_thread(image_bg, out_path, save_mode="jpg", quality=100, dpi=(300, 300), _format="JPEG") # image_bg.save(out_path, quality=100, dpi=(300, 300), format="JPEG") elif settings.OUT_PIC_MODE == ".png": self.saver.save_image(image=image_bg, file_path=out_path, save_mode="png") # image_bg.save(out_path) else: new_format = settings.OUT_PIC_MODE.split(".")[-1] self.saver.save_image( image=image_bg, file_path=out_path, save_mode=new_format, # _format=new_format, ) if output_queue is not None: output_queue.put(True) return True