"""示例场景图片管理 — 优先使用真实图片,缺失时生成合成图片 真实图片位于 test_imgs/complex/ 目录(通过 Git LFS 推送) 文本场景示例由合成生成(模拟合同文档) """ import os from PIL import Image, ImageDraw, ImageFont, ImageFilter def _get_font(size): try: return ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", size) except Exception: try: return ImageFont.truetype("DejaVuSans.ttf", size) except Exception: return ImageFont.load_default() def _generate_text_scene(path1, path2): """生成文本场景示例:模拟合同文档 两张图内容相同,但图2做了亮度调整和水平偏移,模拟不同角度拍摄 """ w, h = 600, 800 for path, brightness, offset_x in [(path1, 0, 0), (path2, 30, 5)]: img = Image.new('RGB', (w, h), (255, 255, 255)) draw = ImageDraw.Draw(img) # 标题区域 draw.rectangle([50 + offset_x, 40, 550 + offset_x, 80], fill=(30, 30, 30)) # 正文行 for i in range(25): y = 120 + i * 25 x_start = 50 + offset_x x_end = 550 - (i % 5) * 20 + offset_x draw.rectangle([x_start, y, x_end, y + 12], fill=(60, 60, 60)) # 模拟印章(红色圆形) draw.ellipse([400 + offset_x, 650, 500 + offset_x, 750], outline=(180, 30, 30), width=3) draw.text((420 + offset_x, 680), "SEAL", fill=(180, 30, 30), font=_get_font(20)) # 亮度调整 if brightness > 0: import numpy as np arr = np.array(img).astype(int) + brightness arr = np.clip(arr, 0, 255).astype('uint8') img = Image.fromarray(arr) img.save(path, 'JPEG', quality=85) def _scan_real_images(directory): """扫描目录中的真实图片文件""" if not os.path.isdir(directory): return [] exts = {'.jpg', '.jpeg', '.png', '.bmp', '.webp'} imgs = [] for f in sorted(os.listdir(directory)): if os.path.splitext(f)[1].lower() in exts: imgs.append(os.path.join(directory, f)) return imgs def ensure_demo_images(base_dir): """确保示例图片存在,优先使用真实图片 Args: base_dir: 项目根目录 Returns: dict: {'text': [path1, path2], 'complex': [path1, path2]} 或 None """ demo_dir = os.path.join(base_dir, 'test_imgs') os.makedirs(demo_dir, exist_ok=True) # === 复杂场景:优先使用真实图片 === complex_dir = os.path.join(demo_dir, 'complex') real_complex = _scan_real_images(complex_dir) if len(real_complex) >= 2: complex_imgs = real_complex[:2] else: # Fallback:生成合成图片 c1 = os.path.join(demo_dir, 'demo_complex_1.jpg') c2 = os.path.join(demo_dir, 'demo_complex_2.jpg') if not os.path.exists(c1): try: _generate_complex_scene(c1, c2) print("[示例] 已生成合成复杂场景图片") except Exception as e: print(f"[示例] 合成复杂场景生成失败: {e}") return None complex_imgs = [c1, c2] # === 文本场景:优先使用真实图片 === text_dir = os.path.join(demo_dir, 'text') real_text = _scan_real_images(text_dir) if len(real_text) >= 2: text_imgs = real_text[:2] else: # Fallback:生成合成图片 t1 = os.path.join(demo_dir, 'demo_text_1.jpg') t2 = os.path.join(demo_dir, 'demo_text_2.jpg') if not os.path.exists(t1): try: _generate_text_scene(t1, t2) print("[示例] 已生成合成文本场景图片") except Exception as e: print(f"[示例] 合成文本场景生成失败: {e}") return None text_imgs = [t1, t2] return { 'text': text_imgs, 'complex': complex_imgs, } def _generate_complex_scene(path1, path2): """生成复杂场景示例:模拟实景照片(fallback 用)""" w, h = 600, 450 for path, shift_x, color_shift in [(path1, 0, 0), (path2, 15, 20)]: img = Image.new('RGB', (w, h)) draw = ImageDraw.Draw(img) # 天空渐变 for y in range(h // 2): r = int(100 + y * 0.3 + color_shift) g = int(150 + y * 0.2) b = int(200 + y * 0.15) draw.line([(0, y), (w, y)], fill=(min(r, 255), min(g, 255), min(b, 255))) # 地面渐变 for y in range(h // 2, h): r = int(80 + (y - h // 2) * 0.3) g = int(120 + (y - h // 2) * 0.2) b = int(60 + (y - h // 2) * 0.1) draw.line([(0, y), (w, y)], fill=(min(r, 255), min(g, 255), min(b, 255))) # 太阳 sun_x = 450 + shift_x draw.ellipse([sun_x - 40, 50, sun_x + 40, 130], fill=(255, 220, 100)) # 山脉 draw.polygon([(0, 225), (150 + shift_x, 150), (300 + shift_x, 200), (450 + shift_x, 160), (600, 220), (600, 225)], fill=(80, 100, 80)) # 建筑 draw.rectangle([100 + shift_x, 250, 200 + shift_x, 400], fill=(120, 100, 90)) draw.rectangle([250 + shift_x, 280, 350 + shift_x, 400], fill=(100, 110, 100)) draw.rectangle([400 + shift_x, 260, 480 + shift_x, 400], fill=(90, 80, 100)) # 轻微模糊模拟不同焦距 if shift_x > 0: img = img.filter(ImageFilter.GaussianBlur(radius=0.8)) img.save(path, 'JPEG', quality=85)