""" 分析拼消消测试输出,检测卡片透明区域并溯源问题成因。 用法: python tools/analyze_puzzle_clear_output.py python tools/analyze_puzzle_clear_output.py --dir path/to/output python tools/analyze_puzzle_clear_output.py --detail # 详细逐卡输出 """ import os import sys import argparse from collections import defaultdict from PIL import Image def scan_transparent_pixels(img_path): """扫描图片,返回 (总像素数, 透明像素数, 边缘透明列比例)""" img = Image.open(img_path).convert("RGBA") w, h = img.size pixels = img.load() total = w * h transparent = 0 edge_cols_with_transparent = 0 edge_rows_with_transparent = 0 # 统计透明像素 for y in range(h): for x in range(w): if pixels[x, y][3] < 128: transparent += 1 # 检测四边是否有透明像素(边缘列/行透明占比 > 10%) for x in range(w): col_transparent = sum(1 for y in range(h) if pixels[x, y][3] < 128) if col_transparent > h * 0.1: edge_cols_with_transparent += 1 for y in range(h): row_transparent = sum(1 for x in range(w) if pixels[x, y][3] < 128) if row_transparent > w * 0.1: edge_rows_with_transparent += 1 ratio = transparent / total * 100 if total > 0 else 0 has_edge = edge_cols_with_transparent > 0 or edge_rows_with_transparent > 0 return total, transparent, ratio, has_edge, edge_cols_with_transparent, edge_rows_with_transparent def analyze_sheet_cleaned(sheet_path): """分析去背后的 sheet 图,检查各 group 区域的透明情况""" img = Image.open(sheet_path).convert("RGBA") w, h = img.size pixels = img.load() # 统计整体透明像素 total = w * h transparent = sum(1 for y in range(h) for x in range(w) if pixels[x, y][3] < 128) return total, transparent, transparent / total * 100 if total > 0 else 0 def main(): parser = argparse.ArgumentParser(description="分析拼消消测试输出") parser.add_argument("--dir", default="", help="输出目录路径") parser.add_argument("--detail", action="store_true", help="详细逐卡输出") args = parser.parse_args() # 自动查找输出目录 if args.dir: base = args.dir else: script_dir = os.path.dirname(os.path.abspath(__file__)) repo_root = os.path.dirname(script_dir) candidates = [ os.path.join(repo_root, "server-rs", "crates", "api-server", "target", "test-output", "puzzle-clear-real"), os.path.join(repo_root, "server-rs", "target", "test-output", "puzzle-clear-real"), ] base = None for c in candidates: if os.path.isdir(c): base = c break if not base: print("未找到测试输出目录。请用 --dir 指定路径。") sys.exit(1) sheets_dir = os.path.join(base, "sheets") cards_dir = os.path.join(base, "cards") if not os.path.isdir(cards_dir): print(f"cards 目录不存在: {cards_dir}") sys.exit(1) # ==================== 阶段 1: 分析卡片 ==================== print("=" * 70) print("阶段 1: 卡片透明像素分析") print("=" * 70) card_results = [] # (sheet, card_name, total, transparent, ratio, has_edge, edge_cols, edge_rows) problem_cards = [] for sheet_name in sorted(os.listdir(cards_dir)): sheet_dir = os.path.join(cards_dir, sheet_name) if not os.path.isdir(sheet_dir): continue for card_name in sorted(os.listdir(sheet_dir)): if not card_name.endswith(".png"): continue card_path = os.path.join(sheet_dir, card_name) total, trans, ratio, has_edge, ec, er = scan_transparent_pixels(card_path) card_results.append((sheet_name, card_name, total, trans, ratio, has_edge, ec, er)) if ratio > 5 or has_edge: problem_cards.append((sheet_name, card_name, total, trans, ratio, has_edge, ec, er)) # 按 sheet 汇总 by_sheet = defaultdict(list) for r in card_results: by_sheet[r[0]].append(r) print(f"\n总卡片数: {len(card_results)}") print(f"问题卡片数 (透明>5% 或 有边缘透明): {len(problem_cards)}") print() for sheet_name in sorted(by_sheet.keys()): cards = by_sheet[sheet_name] problem_count = sum(1 for r in cards if r[4] > 5 or r[5]) print(f" {sheet_name}: {len(cards)} cards, {problem_count} problems") if problem_cards: print(f"\n--- 问题卡片详情 ---") problem_cards.sort(key=lambda r: -r[4]) # sort by ratio desc for sheet, name, total, trans, ratio, has_edge, ec, er in problem_cards: group_id = name.split("-part-")[0] edge_info = f", 边缘透明列={ec} 行={er}" if has_edge else "" print(f" {sheet}/{name} group={group_id} transparent={ratio:.1f}% ({trans}/{total}){edge_info}") # ==================== 阶段 2: 溯源分析 ==================== print() print("=" * 70) print("阶段 2: 溯源 — 对比原始 sheet 与去背后 sheet") print("=" * 70) if os.path.isdir(sheets_dir): for fname in sorted(os.listdir(sheets_dir)): if not fname.endswith(".png"): continue sheet_path = os.path.join(sheets_dir, fname) total, trans, ratio = analyze_sheet_cleaned(sheet_path) is_cleaned = "-cleaned" in fname label = "去背后" if is_cleaned else "原始" print(f" {fname} ({label}): {trans}/{total} 透明像素 ({ratio:.1f}%)") # ==================== 阶段 3: 问题溯源推理 ==================== print() print("=" * 70) print("阶段 3: 问题成因分析") print("=" * 70) if not problem_cards: print(" 无问题卡片,管线正常。") return # 分析问题卡片的 group 分布 problem_groups = defaultdict(list) for r in problem_cards: group_id = r[1].split("-part-")[0] problem_groups[group_id].append(r) print(f"\n 涉及 {len(problem_groups)} 个 group:") for group_id in sorted(problem_groups.keys()): cards = problem_groups[group_id] avg_ratio = sum(r[4] for r in cards) / len(cards) edge_count = sum(1 for r in cards if r[5]) print(f" {group_id}: {len(cards)} cells, avg透明={avg_ratio:.1f}%, {edge_count} cells有边缘透明") # 检查原始 sheet 和去背后 sheet 的差异 if os.path.isdir(sheets_dir): cleaned_files = [f for f in os.listdir(sheets_dir) if "cleaned" in f] raw_files = [f for f in os.listdir(sheets_dir) if "cleaned" not in f and f.endswith(".png")] if cleaned_files and raw_files: for raw_f in sorted(raw_files): raw_p = os.path.join(sheets_dir, raw_f) cleaned_f = raw_f.replace(".png", "-cleaned.png") cleaned_p = os.path.join(sheets_dir, cleaned_f) if not os.path.exists(cleaned_p): continue _, raw_trans, raw_ratio = analyze_sheet_cleaned(raw_p) _, cleaned_trans, cleaned_ratio = analyze_sheet_cleaned(cleaned_p) print(f"\n {raw_f}:") print(f" 原始透明: {raw_ratio:.1f}%") print(f" 去后透明: {cleaned_ratio:.1f}%") delta = cleaned_ratio - raw_ratio if delta > 1: print(f" ** 去背增加了 {delta:.1f}% 透明像素 — 可能误删了主体内容") print() print(" 可能原因:") print(" 1. AI 未将内容画满整个 group 区域(内容在组内偏移)") print(" 2. 洋红去背误将主题内近似洋红的像素也变透明") print(" 3. find_non_transparent_bounds 扫描范围包括了相邻 group 的透明间隙") print(" 4. group resize 后未完全覆盖目标尺寸(内容比例与目标比例不匹配)") if __name__ == "__main__": main()