From 2a50e7040e3963e7c4b8386fa411d5e93de33a48 Mon Sep 17 00:00:00 2001 From: zcr Date: Wed, 7 Jan 2026 16:22:19 +0800 Subject: [PATCH] =?UTF-8?q?feat=20:=20design=20overall=20print=20=E6=96=B0?= =?UTF-8?q?=E5=A2=9E=E5=B9=B3=E9=93=BA=E9=97=B4=E8=B7=9D=E5=92=8C=E6=97=8B?= =?UTF-8?q?=E8=BD=AC=E8=A7=92=E5=BA=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../pipeline/no_seg_print_painting.py | 187 +++++++---- .../design_fast/pipeline/print_painting.py | 300 ++++++++---------- 2 files changed, 262 insertions(+), 225 deletions(-) diff --git a/app/service/design_fast/pipeline/no_seg_print_painting.py b/app/service/design_fast/pipeline/no_seg_print_painting.py index 732ac36..efc3f12 100644 --- a/app/service/design_fast/pipeline/no_seg_print_painting.py +++ b/app/service/design_fast/pipeline/no_seg_print_painting.py @@ -9,7 +9,6 @@ from app.service.utils.new_oss_client import oss_get_image class NoSegPrintPainting: def __init__(self, minio_client): - self.random_seed = random.randint(0, 1000) self.minio_client = minio_client def __call__(self, result): @@ -21,16 +20,8 @@ class NoSegPrintPainting: if overall_print['print_path_list']: painting_dict = {'dim_image_h': result['pattern_image'].shape[0], 'dim_image_w': result['pattern_image'].shape[1]} - if "print_angle_list" in overall_print.keys() and overall_print['print_angle_list'][0] != 0: - painting_dict = self.painting_collection(painting_dict, overall_print, print_trigger=True) - painting_dict['tile_print'] = self.rotate_crop_image(img=painting_dict['tile_print'], angle=-overall_print['print_angle_list'][0], crop=True) - painting_dict['mask_inv_print'] = self.rotate_crop_image(img=painting_dict['mask_inv_print'], angle=-overall_print['print_angle_list'][0], crop=True) - - # resize 到sketch大小 - painting_dict['tile_print'] = self.resize_and_crop(img=painting_dict['tile_print'], target_width=painting_dict['dim_image_w'], target_height=painting_dict['dim_image_h']) - painting_dict['mask_inv_print'] = self.resize_and_crop(img=painting_dict['mask_inv_print'], target_width=painting_dict['dim_image_w'], target_height=painting_dict['dim_image_h']) - else: - painting_dict = self.painting_collection(painting_dict, overall_print, print_trigger=True, is_single=False) + # 获取平铺 + 旋转 的overall print + painting_dict = self.painting_collection(painting_dict, overall_print) result['no_seg_sketch_overall'] = result['no_seg_sketch_print'] = self.printpaint(result, painting_dict, print_=True) result['pattern_image'] = result['no_seg_sketch_overall'] @@ -151,7 +142,6 @@ class NoSegPrintPainting: temp_fg = np.expand_dims(result['mask'], axis=2).repeat(3, axis=2) tmp2 = (result['final_image'] * (temp_fg / 255)).astype(np.uint8) result['no_seg_sketch_print'] = cv2.add(tmp1, tmp2) - return result @staticmethod @@ -166,26 +156,21 @@ class NoSegPrintPainting: print_background = img1_bg + img2_fg return print_background - def painting_collection(self, painting_dict, print_dict, print_trigger=False, is_single=False): - if print_trigger: - print_ = self.get_print(print_dict) - painting_dict['Trigger'] = not is_single - painting_dict['location'] = print_['location'] - single_mask_inv_print = self.get_mask_inv(print_['image']) - dim_max = max(painting_dict['dim_image_h'], painting_dict['dim_image_w']) - dim_pattern = (int(dim_max * print_['scale'] / 5), int(dim_max * print_['scale'] / 5)) - if not is_single: - # 如果print 模式为overall 且 有角度的话 , 组合的print为正方形,方便裁剪 - if "print_angle_list" in print_dict.keys() and print_dict['print_angle_list'][0] != 0: - painting_dict['mask_inv_print'] = self.tile_image(single_mask_inv_print, dim_pattern, print_['scale'], dim_max, dim_max, painting_dict['location'], trigger=True) - painting_dict['tile_print'] = self.tile_image(print_['image'], dim_pattern, print_['scale'], dim_max, dim_max, painting_dict['location'], trigger=True) - else: - painting_dict['mask_inv_print'] = self.tile_image(single_mask_inv_print, dim_pattern, print_['scale'], painting_dict['dim_image_h'], painting_dict['dim_image_w'], painting_dict['location'], trigger=True) - painting_dict['tile_print'] = self.tile_image(print_['image'], dim_pattern, print_['scale'], painting_dict['dim_image_h'], painting_dict['dim_image_w'], painting_dict['location'], trigger=True) - else: - painting_dict['mask_inv_print'] = self.tile_image(single_mask_inv_print, dim_pattern, print_['scale'], painting_dict['dim_image_h'], painting_dict['dim_image_w'], painting_dict['location']) - painting_dict['tile_print'] = self.tile_image(print_['image'], dim_pattern, print_['scale'], painting_dict['dim_image_h'], painting_dict['dim_image_w'], painting_dict['location']) - painting_dict['dim_print_h'], painting_dict['dim_print_w'] = dim_pattern + def painting_collection(self, painting_dict, print_dict): + print_ = self.get_print(print_dict) + painting_dict['location'] = print_['location'] + dim_max = max(painting_dict['dim_image_h'], painting_dict['dim_image_w']) + dim_pattern = (int(dim_max * print_['scale'] / 5), int(dim_max * print_['scale'] / 5)) + gap = print_dict.get('gap', [0, 0])[0] + painting_dict['tile_print'] = tile_image(pattern=print_['image'], + dim=dim_pattern, + gap_x=gap[0], + gap_y=gap[1], + canvas_h=painting_dict['dim_image_h'], + canvas_w=painting_dict['dim_image_w'], + location=painting_dict['location'], + angle=45) + painting_dict['mask_inv_print'] = np.zeros(painting_dict['tile_print'].shape[:2], dtype=np.uint8) return painting_dict def tile_image(self, pattern, dim, scale, dim_image_h, dim_image_w, location, trigger=False): @@ -219,33 +204,32 @@ class NoSegPrintPainting: @staticmethod def printpaint(result, painting_dict, print_=False): - - if print_ and painting_dict['Trigger']: + if print_: print_mask = cv2.bitwise_and(result['mask'], cv2.bitwise_not(painting_dict['mask_inv_print'])) img_fg = cv2.bitwise_and(painting_dict['tile_print'], painting_dict['tile_print'], mask=print_mask) else: print_mask = result['mask'] img_fg = result['final_image'] - if print_ and not painting_dict['Trigger']: - index_ = None - try: - index_ = len(painting_dict['location']) - except: - assert f'there must be parameter of location if choose IfSingle' - - for i in range(index_): - start_h, start_w = int(painting_dict['location'][i][1]), int(painting_dict['location'][i][0]) - - length_h = min(start_h + painting_dict['dim_print_h'], img_fg.shape[0]) - length_w = min(start_w + painting_dict['dim_print_w'], img_fg.shape[1]) - - change_region = img_fg[start_h: length_h, start_w: length_w, :] - # problem in change_mask - change_mask = print_mask[start_h: length_h, start_w: length_w] - # get real part into change mask - _, change_mask = cv2.threshold(change_mask, 220, 255, cv2.THRESH_BINARY) - cv2.bitwise_not(painting_dict['mask_inv_print']) - img_fg[start_h:start_h + painting_dict['dim_print_h'], start_w:start_w + painting_dict['dim_print_w'], :] = change_region + # if print_ and not painting_dict['Trigger']: + # index_ = None + # try: + # index_ = len(painting_dict['location']) + # except: + # assert f'there must be parameter of location if choose IfSingle' + # + # for i in range(index_): + # start_h, start_w = int(painting_dict['location'][i][1]), int(painting_dict['location'][i][0]) + # + # length_h = min(start_h + painting_dict['dim_print_h'], img_fg.shape[0]) + # length_w = min(start_w + painting_dict['dim_print_w'], img_fg.shape[1]) + # + # change_region = img_fg[start_h: length_h, start_w: length_w, :] + # # problem in change_mask + # change_mask = print_mask[start_h: length_h, start_w: length_w] + # # get real part into change mask + # _, change_mask = cv2.threshold(change_mask, 220, 255, cv2.THRESH_BINARY) + # cv2.bitwise_not(painting_dict['mask_inv_print']) + # img_fg[start_h:start_h + painting_dict['dim_print_h'], start_w:start_w + painting_dict['dim_print_w'], :] = change_region clothes_mask_print = cv2.bitwise_not(print_mask) @@ -277,8 +261,6 @@ class NoSegPrintPainting: print_w = print_shape[1] print_h = print_shape[0] - random.seed(self.random_seed) - # 1.拿到偏移量后和resize后的print宽高取余 得到真正偏移量 # 偏移量增加2分之print.w 使坐标位于图中间 如果要位于左上角删除+ print_w // 2 即可 x_offset = print_w - int(location[0][1] % print_w) + print_w // 2 @@ -420,3 +402,96 @@ class NoSegPrintPainting: cropped_img = resized_img[start_y:start_y + target_height, :] return cropped_img + + +def tile_image(pattern, dim, gap_x, gap_y, canvas_h, canvas_w, location, angle=0): + """ + 按照指定的 X/Y 间距平铺印花,并支持旋转 + :param angle: 旋转角度 (度数, 逆时针) + """ + # 1. 确保输入是 RGBA + if pattern.shape[2] == 3: + pattern = cv2.cvtColor(pattern, cv2.COLOR_BGR2BGRA) + + # 2. 缩放与旋转印花 + resized_p = cv2.resize(pattern, dim, interpolation=cv2.INTER_AREA) + rotated_p = rotate_image(resized_p, angle) + p_h, p_w = rotated_p.shape[:2] + + # 3. 创建透明单元格 + cell_h, cell_w = p_h + gap_y, p_w + gap_x + unit_cell = np.zeros((cell_h, cell_w, 4), dtype=np.uint8) + unit_cell[:p_h, :p_w, :] = rotated_p + + # 4. 执行平铺 + tiles_y = (canvas_h // cell_h) + 2 + tiles_x = (canvas_w // cell_w) + 2 + full_tiled = np.tile(unit_cell, (tiles_y, tiles_x, 1)) + + # 5. 裁剪平铺层 + offset_x = int(location[0][1] % cell_w) + offset_y = int(location[0][0] % cell_h) + tiled_layer = full_tiled[offset_y: offset_y + canvas_h, + offset_x: offset_x + canvas_w] + + # 6. 创建纯白色背景并合成 + # 创建一个纯白色的 BGR 画布 + white_background = np.full((canvas_h, canvas_w, 3), 255, dtype=np.uint8) + + # 分离平铺层的颜色通道和 Alpha 通道 + tiled_bgr = tiled_layer[:, :, :3] + alpha_mask = tiled_layer[:, :, 3] / 255.0 # 归一化到 0-1 + alpha_mask = cv2.merge([alpha_mask, alpha_mask, alpha_mask]) # 扩展到 3 通道 + + # 执行 Alpha 混合:结果 = 平铺层 * alpha + 背景 * (1 - alpha) + result = (tiled_bgr * alpha_mask + white_background * (1 - alpha_mask)).astype(np.uint8) + + return result + + +def rotate_image(image, angle): + """ + 旋转图片并保持完整内容(自动扩大画布) + """ + if angle == 0: + return image + + (h, w) = image.shape[:2] + (cX, cY) = (w // 2, h // 2) + + # 获取旋转矩阵 + M = cv2.getRotationMatrix2D((cX, cY), angle, 1.0) + + # 计算旋转后新边界的 sine 和 cosine + cos = np.abs(M[0, 0]) + sin = np.abs(M[0, 1]) + + # 计算新的画布尺寸 + nW = int((h * sin) + (w * cos)) + nH = int((h * cos) + (w * sin)) + + # 调整旋转矩阵以考虑平移 + M[0, 2] += (nW / 2) - cX + M[1, 2] += (nH / 2) - cY + + # 执行旋转 + return cv2.warpAffine(image, M, (nW, nH)) + + +def crop_image(image, image_size_h, image_size_w, location, print_shape): + print_w = print_shape[1] + print_h = print_shape[0] + + # 1.拿到偏移量后和resize后的print宽高取余 得到真正偏移量 + # 偏移量增加2分之print.w 使坐标位于图中间 如果要位于左上角删除+ print_w // 2 即可 + x_offset = print_w - int(location[0][1] % print_w) + print_w // 2 + y_offset = print_h - int(location[0][0] % print_h) + print_h // 2 + + # y_offset = int(location[0][0]) + # x_offset = int(location[0][1]) + + if len(image.shape) == 2: + image = image[x_offset: x_offset + image_size_h, y_offset: y_offset + image_size_w] + elif len(image.shape) == 3: + image = image[x_offset: x_offset + image_size_h, y_offset: y_offset + image_size_w, :] + return image diff --git a/app/service/design_fast/pipeline/print_painting.py b/app/service/design_fast/pipeline/print_painting.py index e84d9be..aa7337a 100644 --- a/app/service/design_fast/pipeline/print_painting.py +++ b/app/service/design_fast/pipeline/print_painting.py @@ -9,7 +9,6 @@ from app.service.utils.new_oss_client import oss_get_image class PrintPainting: def __init__(self, minio_client): - self.random_seed = None self.minio_client = minio_client def __call__(self, result): @@ -39,23 +38,14 @@ class PrintPainting: overall_print['location'][0] = [x * y for x, y in zip(overall_print['location'][0], result['resize_scale'])] painting_dict = {'dim_image_h': result['pattern_image'].shape[0], 'dim_image_w': result['pattern_image'].shape[1]} result['print_image'] = result['pattern_image'] - if "print_angle_list" in overall_print.keys() and overall_print['print_angle_list'][0] != 0: - painting_dict = self.painting_collection(painting_dict, overall_print, print_trigger=True) - painting_dict['tile_print'] = self.rotate_crop_image(img=painting_dict['tile_print'], angle=-overall_print['print_angle_list'][0], crop=True) - painting_dict['mask_inv_print'] = self.rotate_crop_image(img=painting_dict['mask_inv_print'], angle=-overall_print['print_angle_list'][0], crop=True) - - # resize 到sketch大小 - painting_dict['tile_print'] = self.resize_and_crop(img=painting_dict['tile_print'], target_width=painting_dict['dim_image_w'], target_height=painting_dict['dim_image_h']) - painting_dict['mask_inv_print'] = self.resize_and_crop(img=painting_dict['mask_inv_print'], target_width=painting_dict['dim_image_w'], target_height=painting_dict['dim_image_h']) - else: - painting_dict = self.painting_collection(painting_dict, overall_print, print_trigger=True, is_single=False) + # 获取平铺 + 旋转 的overall print + painting_dict = self.painting_collection(painting_dict, overall_print) result['print_image'] = self.printpaint(result, painting_dict, print_=True) result['single_image'] = result['final_image'] = result['pattern_image'] = result['print_image'] if single_print['print_path_list']: # 2025-9-19 印花调整 印花坐标按照sketch的缩放比调整 sketch_resize_scale = result['resize_scale'] - print_background = np.zeros((result['pattern_image'].shape[0], result['pattern_image'].shape[1], 3), dtype=np.uint8) mask_background = np.zeros((result['pattern_image'].shape[0], result['pattern_image'].shape[1], 3), dtype=np.uint8) for i in range(len(single_print['print_path_list'])): @@ -78,75 +68,6 @@ class PrintPainting: print_background = cv2.cvtColor(np.array(source_image_pil), cv2.COLOR_RGBA2BGR) mask_background = cv2.cvtColor(np.array(source_image_pil_mask), cv2.COLOR_RGBA2BGR) ret, mask_background = cv2.threshold(mask_background, 124, 255, cv2.THRESH_BINARY) - # else: - # mask = self.get_mask_inv(image) - # mask = np.expand_dims(mask, axis=2) - # mask = cv2.cvtColor(mask, cv2.COLOR_GRAY2BGR) - # mask = cv2.bitwise_not(mask) - # - # mask = cv2.resize(mask, (int(result['final_image'].shape[1] * single_print['print_scale_list'][i][0]), int(result['final_image'].shape[0] * single_print['print_scale_list'][i][1]))) - # image = cv2.resize(image, (int(result['final_image'].shape[1] * single_print['print_scale_list'][i][0]), int(result['final_image'].shape[0] * single_print['print_scale_list'][i][1]))) - # # 旋转后的坐标需要重新算 - # rotate_mask, _ = self.img_rotate(mask, single_print['print_angle_list'][i]) - # rotate_image, rotated_new_size = self.img_rotate(image, single_print['print_angle_list'][i]) - # # x, y = int(result['print']['location'][i][0] - rotated_new_size[0] - (rotate_mask.shape[0] - image.shape[0]) / 2), int(result['print']['location'][i][1] - rotated_new_size[1] - (rotate_mask.shape[1] - image.shape[1]) / 2) - # x, y = int(single_print['location'][i][0] - rotated_new_size[0]), int(single_print['location'][i][1] - rotated_new_size[1]) - # - # image_x = print_background.shape[1] # 底图宽 - # image_y = print_background.shape[0] # 底图高 - # print_x = rotate_image.shape[1] #印花宽 - # print_y = rotate_image.shape[0] #印花高 - # - # # 有bug - # # if x + print_x > image_x: - # # rotate_image = rotate_image[:, :x + print_x - image_x] - # # rotate_mask = rotate_mask[:, :x + print_x - image_x] - # # # - # # if y + print_y > image_y: - # # rotate_image = rotate_image[:y + print_y - image_y] - # # rotate_mask = rotate_mask[:y + print_y - image_y] - # - # # 不能是并行 - # # 当前第一轮的if (108以及115)是判断有没有过下界和右界。第二轮的是判断左上有没有超出。 如果这个样子的话,先裁了右边,再左移,region就会有问题 - # # 先挪 再判断 最后裁剪 - # - # # 如果print旋转了 或者 print贴边了 则需要判断 判断左界和上界是否小于0 - # if x <= 0: # 如果X轴偏移量小于0,说明印花需要被裁剪至合适大小 或当X轴偏移量大于印花宽度时,裁剪后的印花宽度为0 - # rotate_image = rotate_image[:, abs(x):] - # rotate_mask = rotate_mask[:, abs(x):] - # start_x = x = 0 - # else: - # start_x = x - # - # if y <= 0: # 如果X轴偏移量大于0,说明印花需要被裁剪至合适大小 或当Y轴偏移量大于印花宽度时,裁剪后的印花宽度为0 - # rotate_image = rotate_image[abs(y):, :] - # rotate_mask = rotate_mask[abs(y):, :] - # start_y = y = 0 - # else: - # start_y = y - # - # # ------------------ - # # 如果print-size大于image-size 则需要裁剪print - # - # if x + print_x > image_x: - # rotate_image = rotate_image[:, :image_x - x] - # rotate_mask = rotate_mask[:, :image_x - x] - # - # if y + print_y > image_y: - # rotate_image = rotate_image[:image_y - y, :] - # rotate_mask = rotate_mask[:image_y - y, :] - # - # # mask_background[start_y:y + rotate_mask.shape[0], start_x:x + rotate_mask.shape[1]] = cv2.bitwise_xor(mask_background[start_y:y + rotate_mask.shape[0], start_x:x + rotate_mask.shape[1]], rotate_mask) - # # print_background[start_y:y + rotate_image.shape[0], start_x:x + rotate_image.shape[1]] = cv2.add(print_background[start_y:y + rotate_image.shape[0], start_x:x + rotate_image.shape[1]], rotate_image) - # - # # mask_background[start_y:y + rotate_mask.shape[0], start_x:x + rotate_mask.shape[1]] = rotate_mask - # # print_background[start_y:y + rotate_image.shape[0], start_x:x + rotate_image.shape[1]] = rotate_image - # mask_background = self.stack_prin(mask_background, result['pattern_image'], rotate_mask, start_y, y, start_x, x) - # print_background = self.stack_prin(print_background, result['pattern_image'], rotate_image, start_y, y, start_x, x) - - # gray_image = cv2.cvtColor(mask_background, cv2.COLOR_BGR2GRAY) - # print_background = cv2.bitwise_and(print_background, print_background, mask=gray_image) - print_mask = cv2.bitwise_and(result['mask'], cv2.cvtColor(mask_background, cv2.COLOR_BGR2GRAY)) img_fg = cv2.bitwise_or(print_background, print_background, mask=print_mask) img_bg = cv2.bitwise_and(result['pattern_image'], result['pattern_image'], mask=cv2.bitwise_not(print_mask)) @@ -166,7 +87,6 @@ class PrintPainting: if element_print['element_path_list']: # 2025-9-19 印花调整 印花坐标按照sketch的缩放比调整 sketch_resize_scale = result['resize_scale'] - print_background = np.zeros((result['final_image'].shape[0], result['final_image'].shape[1], 3), dtype=np.uint8) mask_background = np.zeros((result['final_image'].shape[0], result['final_image'].shape[1], 3), dtype=np.uint8) for i in range(len(element_print['element_path_list'])): @@ -207,20 +127,6 @@ class PrintPainting: print_x = rotate_image.shape[1] print_y = rotate_image.shape[0] - # 有bug - # if x + print_x > image_x: - # rotate_image = rotate_image[:, :x + print_x - image_x] - # rotate_mask = rotate_mask[:, :x + print_x - image_x] - # # - # if y + print_y > image_y: - # rotate_image = rotate_image[:y + print_y - image_y] - # rotate_mask = rotate_mask[:y + print_y - image_y] - - # 不能是并行 - # 当前第一轮的if (108以及115)是判断有没有过下界和右界。第二轮的是判断左上有没有超出。 如果这个样子的话,先裁了右边,再左移,region就会有问题 - # 先挪 再判断 最后裁剪 - - # 如果print旋转了 或者 print贴边了 则需要判断 判断左界和上界是否小于0 if x <= 0: rotate_image = rotate_image[:, -x:] rotate_mask = rotate_mask[:, -x:] @@ -235,9 +141,6 @@ class PrintPainting: else: start_y = y - # ------------------ - # 如果print-size大于image-size 则需要裁剪print - if x + print_x > image_x: rotate_image = rotate_image[:, :image_x - x] rotate_mask = rotate_mask[:, :image_x - x] @@ -246,11 +149,6 @@ class PrintPainting: rotate_image = rotate_image[:image_y - y, :] rotate_mask = rotate_mask[:image_y - y, :] - # mask_background[start_y:y + rotate_mask.shape[0], start_x:x + rotate_mask.shape[1]] = cv2.bitwise_xor(mask_background[start_y:y + rotate_mask.shape[0], start_x:x + rotate_mask.shape[1]], rotate_mask) - # print_background[start_y:y + rotate_image.shape[0], start_x:x + rotate_image.shape[1]] = cv2.add(print_background[start_y:y + rotate_image.shape[0], start_x:x + rotate_image.shape[1]], rotate_image) - - # mask_background[start_y:y + rotate_mask.shape[0], start_x:x + rotate_mask.shape[1]] = rotate_mask - # print_background[start_y:y + rotate_image.shape[0], start_x:x + rotate_image.shape[1]] = rotate_image mask_background = self.stack_prin(mask_background, result['pattern_image'], rotate_mask, start_y, y, start_x, x) print_background = self.stack_prin(print_background, result['pattern_image'], rotate_image, start_y, y, start_x, x) @@ -298,12 +196,8 @@ class PrintPainting: ret, mask_background = cv2.threshold(mask_background, 124, 255, cv2.THRESH_BINARY) print_mask = cv2.bitwise_and(result['mask'], cv2.cvtColor(mask_background, cv2.COLOR_BGR2GRAY)) img_fg = cv2.bitwise_or(print_background, print_background, mask=print_mask) - # TODO element 丢失信息 three_channel_image = cv2.merge([cv2.bitwise_not(print_mask), cv2.bitwise_not(print_mask), cv2.bitwise_not(print_mask)]) img_bg = cv2.bitwise_and(result['final_image'], three_channel_image) - # mask_mo = np.expand_dims(print_mask, axis=2).repeat(3, axis=2) - # gray_mo = np.expand_dims(result['gray'], axis=2).repeat(3, axis=2) - # img_fg = (img_fg * (mask_mo / 255) * (gray_mo / 255)).astype(np.uint8) result['final_image'] = cv2.add(img_bg, img_fg) canvas = np.full_like(result['final_image'], 255) temp_bg = np.expand_dims(cv2.bitwise_not(result['mask']), axis=2).repeat(3, axis=2) @@ -325,27 +219,21 @@ class PrintPainting: print_background = img1_bg + img2_fg return print_background - def painting_collection(self, painting_dict, print_dict, print_trigger=False, is_single=False): - if print_trigger: - print_ = self.get_print(print_dict) - painting_dict['Trigger'] = not is_single - painting_dict['location'] = print_['location'] - single_mask_inv_print = self.get_mask_inv(print_['image']) - dim_max = max(painting_dict['dim_image_h'], painting_dict['dim_image_w']) - dim_pattern = (int(dim_max * print_['scale'] / 5), int(dim_max * print_['scale'] / 5)) - if not is_single: - self.random_seed = random.randint(0, 1000) - # 如果print 模式为overall 且 有角度的话 , 组合的print为正方形,方便裁剪 - if "print_angle_list" in print_dict.keys() and print_dict['print_angle_list'][0] != 0: - painting_dict['mask_inv_print'] = self.tile_image(single_mask_inv_print, dim_pattern, print_['scale'], dim_max, dim_max, painting_dict['location'], trigger=True) - painting_dict['tile_print'] = self.tile_image(print_['image'], dim_pattern, print_['scale'], dim_max, dim_max, painting_dict['location'], trigger=True) - else: - painting_dict['mask_inv_print'] = self.tile_image(single_mask_inv_print, dim_pattern, print_['scale'], painting_dict['dim_image_h'], painting_dict['dim_image_w'], painting_dict['location'], trigger=True) - painting_dict['tile_print'] = self.tile_image(print_['image'], dim_pattern, print_['scale'], painting_dict['dim_image_h'], painting_dict['dim_image_w'], painting_dict['location'], trigger=True) - else: - painting_dict['mask_inv_print'] = self.tile_image(single_mask_inv_print, dim_pattern, print_['scale'], painting_dict['dim_image_h'], painting_dict['dim_image_w'], painting_dict['location']) - painting_dict['tile_print'] = self.tile_image(print_['image'], dim_pattern, print_['scale'], painting_dict['dim_image_h'], painting_dict['dim_image_w'], painting_dict['location']) - painting_dict['dim_print_h'], painting_dict['dim_print_w'] = dim_pattern + def painting_collection(self, painting_dict, print_dict): + print_ = self.get_print(print_dict) + painting_dict['location'] = print_['location'] + dim_max = max(painting_dict['dim_image_h'], painting_dict['dim_image_w']) + dim_pattern = (int(dim_max * print_['scale'] / 5), int(dim_max * print_['scale'] / 5)) + gap = print_dict.get('gap', [0, 0])[0] + painting_dict['tile_print'] = tile_image(pattern=print_['image'], + dim=dim_pattern, + gap_x=gap[0], + gap_y=gap[1], + canvas_h=painting_dict['dim_image_h'], + canvas_w=painting_dict['dim_image_w'], + location=painting_dict['location'], + angle=45) + painting_dict['mask_inv_print'] = np.zeros(painting_dict['tile_print'].shape[:2], dtype=np.uint8) return painting_dict def tile_image(self, pattern, dim, scale, dim_image_h, dim_image_w, location, trigger=False): @@ -374,51 +262,37 @@ class PrintPainting: mask_inv = cv2.inRange(print_tile, lower, upper) return mask_inv else: - # bg_color = cv2.cvtColor(print_, cv2.COLOR_BGR2LAB)[0][0] - # print_tile = cv2.cvtColor(print_, cv2.COLOR_BGR2LAB) - # bg_l, bg_a, bg_b = bg_color[0], bg_color[1], bg_color[2] - # bg_L_high, bg_L_low = self.get_low_high_lab(bg_l, L=True) - # bg_a_high, bg_a_low = self.get_low_high_lab(bg_a) - # bg_b_high, bg_b_low = self.get_low_high_lab(bg_b) - # lower = np.array([bg_L_low, bg_a_low, bg_b_low]) - # upper = np.array([bg_L_high, bg_a_high, bg_b_high]) - - # print_tile = cv2.cvtColor(print_, cv2.COLOR_BGR2LAB) - # mask_inv = cv2.cvtColor(print_tile, cv2.COLOR_BGR2GRAY) - - # mask_inv = cv2.cvtColor(print_, cv2.COLOR_BGR2GRAY) mask_inv = np.zeros(print_.shape[:2], dtype=np.uint8) return mask_inv @staticmethod def printpaint(result, painting_dict, print_=False): - - if print_ and painting_dict['Trigger']: + if print_: print_mask = cv2.bitwise_and(result['mask'], cv2.bitwise_not(painting_dict['mask_inv_print'])) img_fg = cv2.bitwise_and(painting_dict['tile_print'], painting_dict['tile_print'], mask=print_mask) else: print_mask = result['mask'] img_fg = result['final_image'] - if print_ and not painting_dict['Trigger']: - index_ = None - try: - index_ = len(painting_dict['location']) - except: - assert f'there must be parameter of location if choose IfSingle' - - for i in range(index_): - start_h, start_w = int(painting_dict['location'][i][1]), int(painting_dict['location'][i][0]) - - length_h = min(start_h + painting_dict['dim_print_h'], img_fg.shape[0]) - length_w = min(start_w + painting_dict['dim_print_w'], img_fg.shape[1]) - - change_region = img_fg[start_h: length_h, start_w: length_w, :] - # problem in change_mask - change_mask = print_mask[start_h: length_h, start_w: length_w] - # get real part into change mask - _, change_mask = cv2.threshold(change_mask, 220, 255, cv2.THRESH_BINARY) - cv2.bitwise_not(painting_dict['mask_inv_print']) - img_fg[start_h:start_h + painting_dict['dim_print_h'], start_w:start_w + painting_dict['dim_print_w'], :] = change_region + # if print_ and not painting_dict['Trigger']: + # index_ = None + # try: + # index_ = len(painting_dict['location']) + # except: + # assert f'there must be parameter of location if choose IfSingle' + # + # for i in range(index_): + # start_h, start_w = int(painting_dict['location'][i][1]), int(painting_dict['location'][i][0]) + # + # length_h = min(start_h + painting_dict['dim_print_h'], img_fg.shape[0]) + # length_w = min(start_w + painting_dict['dim_print_w'], img_fg.shape[1]) + # + # change_region = img_fg[start_h: length_h, start_w: length_w, :] + # # problem in change_mask + # change_mask = print_mask[start_h: length_h, start_w: length_w] + # # get real part into change mask + # _, change_mask = cv2.threshold(change_mask, 220, 255, cv2.THRESH_BINARY) + # cv2.bitwise_not(painting_dict['mask_inv_print']) + # img_fg[start_h:start_h + painting_dict['dim_print_h'], start_w:start_w + painting_dict['dim_print_w'], :] = change_region clothes_mask_print = cv2.bitwise_not(print_mask) @@ -450,11 +324,6 @@ class PrintPainting: print_w = print_shape[1] print_h = print_shape[0] - random.seed(self.random_seed) - # logging.info(f'overall print location : {location}') - # x_offset = random.randint(0, image.shape[0] - image_size_h) - # y_offset = random.randint(0, image.shape[1] - image_size_w) - # 1.拿到偏移量后和resize后的print宽高取余 得到真正偏移量 # 偏移量增加2分之print.w 使坐标位于图中间 如果要位于左上角删除+ print_w // 2 即可 x_offset = print_w - int(location[0][1] % print_w) + print_w // 2 @@ -596,3 +465,96 @@ class PrintPainting: cropped_img = resized_img[start_y:start_y + target_height, :] return cropped_img + + +def tile_image(pattern, dim, gap_x, gap_y, canvas_h, canvas_w, location, angle=0): + """ + 按照指定的 X/Y 间距平铺印花,并支持旋转 + :param angle: 旋转角度 (度数, 逆时针) + """ + # 1. 确保输入是 RGBA + if pattern.shape[2] == 3: + pattern = cv2.cvtColor(pattern, cv2.COLOR_BGR2BGRA) + + # 2. 缩放与旋转印花 + resized_p = cv2.resize(pattern, dim, interpolation=cv2.INTER_AREA) + rotated_p = rotate_image(resized_p, angle) + p_h, p_w = rotated_p.shape[:2] + + # 3. 创建透明单元格 + cell_h, cell_w = p_h + gap_y, p_w + gap_x + unit_cell = np.zeros((cell_h, cell_w, 4), dtype=np.uint8) + unit_cell[:p_h, :p_w, :] = rotated_p + + # 4. 执行平铺 + tiles_y = (canvas_h // cell_h) + 2 + tiles_x = (canvas_w // cell_w) + 2 + full_tiled = np.tile(unit_cell, (tiles_y, tiles_x, 1)) + + # 5. 裁剪平铺层 + offset_x = int(location[0][1] % cell_w) + offset_y = int(location[0][0] % cell_h) + tiled_layer = full_tiled[offset_y: offset_y + canvas_h, + offset_x: offset_x + canvas_w] + + # 6. 创建纯白色背景并合成 + # 创建一个纯白色的 BGR 画布 + white_background = np.full((canvas_h, canvas_w, 3), 255, dtype=np.uint8) + + # 分离平铺层的颜色通道和 Alpha 通道 + tiled_bgr = tiled_layer[:, :, :3] + alpha_mask = tiled_layer[:, :, 3] / 255.0 # 归一化到 0-1 + alpha_mask = cv2.merge([alpha_mask, alpha_mask, alpha_mask]) # 扩展到 3 通道 + + # 执行 Alpha 混合:结果 = 平铺层 * alpha + 背景 * (1 - alpha) + result = (tiled_bgr * alpha_mask + white_background * (1 - alpha_mask)).astype(np.uint8) + + return result + + +def rotate_image(image, angle): + """ + 旋转图片并保持完整内容(自动扩大画布) + """ + if angle == 0: + return image + + (h, w) = image.shape[:2] + (cX, cY) = (w // 2, h // 2) + + # 获取旋转矩阵 + M = cv2.getRotationMatrix2D((cX, cY), angle, 1.0) + + # 计算旋转后新边界的 sine 和 cosine + cos = np.abs(M[0, 0]) + sin = np.abs(M[0, 1]) + + # 计算新的画布尺寸 + nW = int((h * sin) + (w * cos)) + nH = int((h * cos) + (w * sin)) + + # 调整旋转矩阵以考虑平移 + M[0, 2] += (nW / 2) - cX + M[1, 2] += (nH / 2) - cY + + # 执行旋转 + return cv2.warpAffine(image, M, (nW, nH)) + + +def crop_image(image, image_size_h, image_size_w, location, print_shape): + print_w = print_shape[1] + print_h = print_shape[0] + + # 1.拿到偏移量后和resize后的print宽高取余 得到真正偏移量 + # 偏移量增加2分之print.w 使坐标位于图中间 如果要位于左上角删除+ print_w // 2 即可 + x_offset = print_w - int(location[0][1] % print_w) + print_w // 2 + y_offset = print_h - int(location[0][0] % print_h) + print_h // 2 + + # y_offset = int(location[0][0]) + # x_offset = int(location[0][1]) + + if len(image.shape) == 2: + image = image[x_offset: x_offset + image_size_h, y_offset: y_offset + image_size_w] + elif len(image.shape) == 3: + image = image[x_offset: x_offset + image_size_h, y_offset: y_offset + image_size_w, :] + return image