diff --git a/utils/poster_notes_creator.py b/utils/poster_notes_creator.py index 96bda6d..6f82c00 100644 --- a/utils/poster_notes_creator.py +++ b/utils/poster_notes_creator.py @@ -177,9 +177,27 @@ class PosterNotesCreator: num_additional_images: int = 3, output_filename_template: str = "additional_{index}.jpg", variation_strength: str = "medium", - extra_effects: bool = True + extra_effects: bool = True, + collage_style: str = None ) -> List[str]: - """选择未被海报使用的图像作为额外配图,并处理为3:4比例""" + """ + 选择未被海报使用的图像作为额外配图,处理为3:4比例的拼图 + + Args: + run_id: 运行ID + topic_index: 主题索引 + variant_index: 变体索引 + poster_metadata_path: 海报元数据路径 + source_image_dir: 源图像目录 + num_additional_images: 要使用的额外图像数量,默认为3 + output_filename_template: 输出文件名模板 + variation_strength: 变化强度,可以是 'low', 'medium', 'high' + extra_effects: 是否应用额外效果 + collage_style: 拼图风格,可以是 'grid', 'mosaic', 'polaroid', 'slice', 'random' 或 None(不使用拼图) + + Returns: + List[str]: 保存的笔记图像路径列表 + """ logger.info(f"开始为主题 {topic_index} 变体 {variant_index} 选择额外配图") # 获取候选图像 @@ -220,7 +238,8 @@ class PosterNotesCreator: output_filename_template.format(index=i+1), image_seed, variation_strength, - extra_effects + extra_effects, + collage_style ) future_to_image[future] = (i, image_filename) @@ -313,51 +332,357 @@ class PosterNotesCreator: output_filename, seed, variation_strength, - extra_effects + extra_effects, + collage_style=None ): """处理单张图像 - 此方法可在独立进程中运行""" try: - # 加载图像 + # 加载图像 image = Image.open(image_path) - - # 处理图像为3:4比例,并添加微小变化 - processed_image = self.optimized_process_image( + + # 处理图像,优先使用拼图处理而不是数字指纹模块 + if collage_style: + # 如果指定了拼图风格,则使用拼图处理 + processed_image = self.create_collage_image( + image, + (3, 4), + style=collage_style, + seed=seed + ) + else: + # 调整为目标比例但不使用数字指纹模块 + processed_image = self.optimized_process_image( image, (3, 4), add_variation=True, - seed=seed, + seed=seed, variation_strength=variation_strength, - extra_effects=extra_effects + extra_effects=False # 不使用高强度数字指纹模块 ) - - # 创建元数据 + + # 创建元数据 additional_metadata = { - "original_image": image_filename, + "original_image": image_filename, "additional_index": index + 1, "source_dir": source_dir, - "is_additional_image": True, - "processed": True, - "aspect_ratio": "3:4", - "variation_applied": True, - "variation_strength": variation_strength, - "extra_effects": extra_effects - } - - # 使用输出处理器保存图像 + "is_additional_image": True, + "processed": True, + "aspect_ratio": "3:4", + "variation_applied": True, + "variation_strength": variation_strength, + "collage_style": collage_style if collage_style else "none" + } + + # 使用输出处理器保存图像 return self.output_handler.handle_generated_image( - run_id, - topic_index, - variant_index, - 'additional', # 图像类型为additional - processed_image, - output_filename, - additional_metadata - ) + run_id, + topic_index, + variant_index, + 'additional', # 图像类型为additional + processed_image, + output_filename, + additional_metadata + ) except Exception as e: logger.error(f"处理图像时出错 '{image_filename}': {e}") logger.error(traceback.format_exc()) return None + def create_collage_image( + self, + image: Image.Image, + target_ratio: Tuple[int, int] = (3, 4), + style: str = "grid", + seed: int = None + ) -> Image.Image: + """ + 创建拼图图像 + + Args: + image: 原始图像 + target_ratio: 目标宽高比,默认为(3, 4) + style: 拼图风格, 可以是 "grid", "mosaic", "polaroid", "slice", "random" + seed: 随机种子 + + Returns: + Image.Image: 拼图后的图像 + """ + logger.info(f"创建拼图图像,风格: {style}") + + # 设置随机种子 + if seed is not None: + random.seed(seed) + np.random.seed(seed) + + # 调整图像为目标比例 + width, height = image.size + current_ratio = width / height + target_ratio_value = target_ratio[0] / target_ratio[1] + + # 调整大小 + if current_ratio > target_ratio_value: # 图片较宽 + new_height = 1200 + new_width = int(new_height * current_ratio) + else: # 图片较高 + new_width = 900 + new_height = int(new_width / current_ratio) + + # 高效调整尺寸 + resized_image = image.resize((new_width, new_height), Image.LANCZOS) + + # 裁剪为目标比例 + resized_width, resized_height = resized_image.size + if resized_width / resized_height > target_ratio_value: + crop_width = int(resized_height * target_ratio_value) + crop_x = (resized_width - crop_width) // 2 + image_base = resized_image.crop((crop_x, 0, crop_x + crop_width, resized_height)) + else: + crop_height = int(resized_width / target_ratio_value) + crop_y = (resized_height - crop_height) // 2 + image_base = resized_image.crop((0, crop_y, resized_width, crop_y + crop_height)) + + # 确保base是RGB模式 + image_base = image_base.convert('RGB') + base_width, base_height = image_base.size + + # 根据指定风格创建拼图 + if style == "random": + # 随机选择一种风格 + style = random.choice(["grid", "mosaic", "polaroid", "slice"]) + + if style == "grid": + # 网格风格: 将图像分割为网格并随机调整每个网格块 + grid_size = random.randint(2, 4) # 网格大小 + cell_width = base_width // grid_size + cell_height = base_height // grid_size + + # 创建空白画布 + collage = Image.new('RGB', (base_width, base_height), (255, 255, 255)) + + # 处理每个网格 + for row in range(grid_size): + for col in range(grid_size): + # 计算当前网格的位置 + left = col * cell_width + top = row * cell_height + right = left + cell_width + bottom = top + cell_height + + # 裁剪当前网格块 + cell = image_base.crop((left, top, right, bottom)) + + # 随机应用效果 + effect = random.choice(["rotate", "brightness", "contrast", "none"]) + if effect == "rotate": + angle = random.uniform(-5, 5) + cell = cell.rotate(angle, resample=Image.BICUBIC, expand=False) + elif effect == "brightness": + factor = random.uniform(0.9, 1.1) + enhancer = ImageEnhance.Brightness(cell) + cell = enhancer.enhance(factor) + elif effect == "contrast": + factor = random.uniform(0.9, 1.1) + enhancer = ImageEnhance.Contrast(cell) + cell = enhancer.enhance(factor) + + # 添加细小边框 + border_size = random.randint(1, 3) + bordered_cell = Image.new('RGB', (cell.width + border_size*2, cell.height + border_size*2), (255, 255, 255)) + bordered_cell.paste(cell, (border_size, border_size)) + + # 粘贴回拼图 + collage.paste(bordered_cell.resize((cell_width, cell_height)), (left, top)) + + elif style == "mosaic": + # 马赛克风格: 创建不规则大小的方块组成的拼图 + collage = Image.new('RGB', (base_width, base_height), (255, 255, 255)) + + # 创建不同大小的块 + blocks = [] + min_size = min(base_width, base_height) // 6 + max_size = min(base_width, base_height) // 3 + + # 生成一些随机块 + for _ in range(20): # 尝试放置20个块 + block_width = random.randint(min_size, max_size) + block_height = random.randint(min_size, max_size) + left = random.randint(0, base_width - block_width) + top = random.randint(0, base_height - block_height) + blocks.append((left, top, left + block_width, top + block_height)) + + # 按面积排序块,大的先放 + blocks.sort(key=lambda b: (b[2]-b[0])*(b[3]-b[1]), reverse=True) + + # 放置每个块 + for block in blocks: + left, top, right, bottom = block + + # 裁剪原图对应区域 + cell = image_base.crop(block) + + # 随机应用滤镜 + filter_type = random.choice(["none", "blur", "sharpen", "edge_enhance"]) + if filter_type == "blur": + cell = cell.filter(ImageFilter.GaussianBlur(radius=random.uniform(0.3, 0.8))) + elif filter_type == "sharpen": + cell = cell.filter(ImageFilter.SHARPEN) + elif filter_type == "edge_enhance": + cell = cell.filter(ImageFilter.EDGE_ENHANCE) + + # 添加边框 + border_size = random.randint(2, 5) + bordered_cell = Image.new('RGB', (cell.width + border_size*2, cell.height + border_size*2), (240, 240, 240)) + bordered_cell.paste(cell, (border_size, border_size)) + + # 粘贴到拼图上 + collage.paste(bordered_cell, (left, top)) + + elif style == "polaroid": + # 宝丽来风格: 模拟拍立得照片排列 + collage = Image.new('RGB', (base_width, base_height), (245, 245, 245)) + + # 创建2-4张宝丽来照片 + num_photos = random.randint(2, 4) + + # 根据照片数量确定大小 + if num_photos == 2: + photo_width = base_width // 2 - 20 + photo_height = int(photo_width * 0.9) # 稍扁一些 + elif num_photos == 3: + photo_width = base_width // 3 - 15 + photo_height = int(photo_width * 0.9) + else: # 4 + photo_width = base_width // 2 - 15 + photo_height = base_height // 2 - 15 + + # 根据照片数量计算布局 + layouts = [] + if num_photos == 2: + # 水平放置两张 + layouts = [ + (10, (base_height - photo_height) // 2), + (base_width - photo_width - 10, (base_height - photo_height) // 2 + random.randint(-10, 10)) + ] + elif num_photos == 3: + # 水平放置三张 + layouts = [ + (10, (base_height - photo_height) // 2 + random.randint(-15, 15)), + ((base_width - photo_width) // 2, (base_height - photo_height) // 2 + random.randint(-5, 5)), + (base_width - photo_width - 10, (base_height - photo_height) // 2 + random.randint(-15, 15)) + ] + else: # 4 + # 2x2网格放置 + layouts = [ + (10, 10), + (base_width - photo_width - 10, 10 + random.randint(-5, 5)), + (10 + random.randint(-5, 5), base_height - photo_height - 10), + (base_width - photo_width - 10, base_height - photo_height - 10 + random.randint(-5, 5)) + ] + + # 创建并放置每张宝丽来照片 + for i, (left, top) in enumerate(layouts): + # 为每张照片选择不同区域 + src_left = random.randint(0, base_width - photo_width) + src_top = random.randint(0, base_height - photo_height) + photo = image_base.crop((src_left, src_top, src_left + photo_width, src_top + photo_height)) + + # 应用宝丽来效果 + # 1. 提高对比度 + enhancer = ImageEnhance.Contrast(photo) + photo = enhancer.enhance(1.2) + + # 2. 轻微增加亮度 + enhancer = ImageEnhance.Brightness(photo) + photo = enhancer.enhance(1.05) + + # 3. 创建宝丽来边框 + border_width = 10 + frame = Image.new('RGB', (photo_width + border_width*2, photo_height + border_width*4), (255, 255, 255)) + frame.paste(photo, (border_width, border_width)) + + # 4. 随机旋转 + angle = random.uniform(-5, 5) + frame = frame.rotate(angle, resample=Image.BICUBIC, expand=False) + + # 粘贴到拼图中 + collage.paste(frame, (left, top)) + + elif style == "slice": + # 切片风格: 水平或垂直切片并错开 + direction = random.choice(["horizontal", "vertical"]) + collage = Image.new('RGB', (base_width, base_height), (255, 255, 255)) + + if direction == "horizontal": + # 水平切片 + num_slices = random.randint(4, 8) + slice_height = base_height // num_slices + + for i in range(num_slices): + # 切出原图片片 + top = i * slice_height + bottom = min(top + slice_height, base_height) + slice_img = image_base.crop((0, top, base_width, bottom)) + + # 确定偏移量 + offset = random.randint(-30, 30) if i % 2 == 1 else 0 + # 确保偏移后不超出边界 + offset = max(-base_width // 4, min(base_width // 4, offset)) + + # 粘贴到拼图,带偏移 + paste_left = offset + if paste_left < 0: + # 如果左侧偏移超出边界,需要裁剪 + slice_img = slice_img.crop((-paste_left, 0, slice_img.width, slice_img.height)) + paste_left = 0 + elif paste_left + slice_img.width > base_width: + # 如果右侧偏移超出边界,需要裁剪 + slice_img = slice_img.crop((0, 0, base_width - paste_left, slice_img.height)) + + collage.paste(slice_img, (paste_left, top)) + else: + # 垂直切片 + num_slices = random.randint(4, 8) + slice_width = base_width // num_slices + + for i in range(num_slices): + # 切出原图片片 + left = i * slice_width + right = min(left + slice_width, base_width) + slice_img = image_base.crop((left, 0, right, base_height)) + + # 确定偏移量 + offset = random.randint(-30, 30) if i % 2 == 1 else 0 + # 确保偏移后不超出边界 + offset = max(-base_height // 4, min(base_height // 4, offset)) + + # 粘贴到拼图,带偏移 + paste_top = offset + if paste_top < 0: + # 如果上侧偏移超出边界,需要裁剪 + slice_img = slice_img.crop((0, -paste_top, slice_img.width, slice_img.height)) + paste_top = 0 + elif paste_top + slice_img.height > base_height: + # 如果下侧偏移超出边界,需要裁剪 + slice_img = slice_img.crop((0, 0, slice_img.width, base_height - paste_top)) + + collage.paste(slice_img, (left, paste_top)) + + else: + # 默认情况,直接返回调整后的图像 + logger.warning(f"未知的拼图风格: {style},使用原始图像") + collage = image_base + + # 清理元数据 + result = self.strip_metadata(collage) + + # 重置随机种子 + if seed is not None: + random.seed() + np.random.seed() + + logger.info(f"拼图图像创建完成,风格: {style}") + return result + def add_dct_noise(self, image: Image.Image, intensity: float = 0.1, block_size: int = 8) -> Image.Image: """ 在DCT域添加噪声以对抗pHash (需要Scipy) - 强化版 @@ -1457,10 +1782,27 @@ def select_additional_images( output_handler: OutputHandler, output_filename_template: str = "additional_{index}.jpg", variation_strength: str = "medium", - extra_effects: bool = True + extra_effects: bool = True, + collage_style: str = None ) -> List[str]: """ - 选择未被海报使用的图像作为额外配图,并处理为3:4比例 + 选择未被海报使用的图像作为额外配图,并处理为3:4比例的拼图 + + Args: + run_id: 运行ID + topic_index: 主题索引 + variant_index: 变体索引 + poster_metadata_path: 海报元数据路径 + source_image_dir: 源图像目录 + num_additional_images: 要使用的额外图像数量 + output_handler: 输出处理器 + output_filename_template: 输出文件名模板 + variation_strength: 变化强度 + extra_effects: 是否应用额外效果 + collage_style: 拼图风格,可以是 'grid', 'mosaic', 'polaroid', 'slice', 'random' 或 None(不使用拼图) + + Returns: + List[str]: 保存的笔记图像路径列表 """ logger.info(f"开始为主题 {topic_index} 变体 {variant_index} 选择额外配图") @@ -1472,7 +1814,7 @@ def select_additional_images( # 创建处理器实例 creator = PosterNotesCreator(output_handler) - # 使用优化后的方法处理图像 + # 使用修改后的方法处理图像 return creator.create_additional_images( run_id, topic_index, @@ -1482,5 +1824,6 @@ def select_additional_images( num_additional_images, output_filename_template, variation_strength, - extra_effects + extra_effects, + collage_style ) \ No newline at end of file