#!/usr/bin/env python # -*- coding: utf-8 -*- """ 诊断 pic0 误匹配:找出为何 pic0.png 会匹配到 草稿箱 区域而非正确缩略图。 用法: 在项目根目录执行 python python/scripts/test_match_pic0_screenshot.py 输出: 不同 scale_min/threshold 下的匹配位置与相关系数,并生成标注图。 """ from __future__ import print_function import os import sys import json SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__)) PROJECT_ROOT = os.path.normpath(os.path.join(SCRIPT_DIR, '..', '..')) if PROJECT_ROOT not in sys.path: sys.path.insert(0, PROJECT_ROOT) PROCESS_DIR = os.path.join(PROJECT_ROOT, 'static', 'process', 'GenerateNote') TMP_DIR = os.path.join(PROCESS_DIR, 'tmp') SCREENSHOT_PATH = os.path.join(TMP_DIR, 'Screenshot-pic0.png') TEMPLATE_PATH = os.path.join(TMP_DIR, 'pic0.png') MARKED_WRONG = os.path.join(TMP_DIR, 'Screenshot-pic0-marked-wrong.png') # 当前错误匹配 MARKED_BEST = os.path.join(TMP_DIR, 'Screenshot-pic0-marked-best.png') # 推荐参数匹配 try: import cv2 import numpy as np except ImportError: print('请安装: pip install opencv-python numpy') sys.exit(1) import importlib.util _image_match_path = os.path.join(SCRIPT_DIR, 'image-match.py') _spec = importlib.util.spec_from_file_location('image_match', _image_match_path) _image_match = importlib.util.module_from_spec(_spec) _spec.loader.exec_module(_image_match) load_image = _image_match.load_image multi_scale_template_match = _image_match.multi_scale_template_match def multi_scale_with_correlation(screenshot, template, threshold=0.50, scale_min=0.05, scale_max=2.0, steps=60): """多尺度模板匹配,返回 (x, y, w, h, center_x, center_y, best_correlation) 或 None。""" gray_screen = cv2.cvtColor(screenshot, cv2.COLOR_BGR2GRAY) gray_tpl = cv2.cvtColor(template, cv2.COLOR_BGR2GRAY) sh, sw = screenshot.shape[:2] t_h, t_w = template.shape[:2] best = None best_val = threshold step = max(0.02, (scale_max - scale_min) / float(steps)) for scale in np.arange(scale_min, scale_max + step * 0.5, step): w = max(8, int(round(t_w * scale))) h = max(8, int(round(t_h * scale))) if h > sh or w > sw: continue resized = cv2.resize(gray_tpl, (w, h), interpolation=cv2.INTER_AREA) result = cv2.matchTemplate(gray_screen, resized, cv2.TM_CCOEFF_NORMED) min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(result) if max_val > best_val: best_val = max_val x, y = int(max_loc[0]), int(max_loc[1]) center_x = x + w // 2 center_y = y + h // 2 best = (x, y, w, h, center_x, center_y, best_val) return best def crop_template_square(template, percent=0.8, base='w'): """与 image-match.py 一致的方形中心裁剪。""" t_h, t_w = template.shape[:2] side_raw = (t_w if base == 'w' else t_h) * percent side = min(max(1, int(round(side_raw))), t_w, t_h) x0 = (t_w - side) // 2 y0 = (t_h - side) // 2 return template[y0:y0 + side, x0:x0 + side].copy() def draw_match(screenshot, x, y, w, h, center_x, center_y, out_path, label='match'): img = screenshot.copy() cv2.rectangle(img, (x, y), (x + w, y + h), (0, 255, 0), 2) cv2.circle(img, (center_x, center_y), 8, (0, 0, 255), 2) cv2.putText(img, label, (x, y - 5), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 255), 1) out_dir = os.path.dirname(out_path) if out_dir: os.makedirs(out_dir, exist_ok=True) cv2.imwrite(out_path, img) print('已保存:', out_path) def main(): screenshot_path = SCREENSHOT_PATH template_path = TEMPLATE_PATH if len(sys.argv) >= 3: screenshot_path = sys.argv[1] template_path = sys.argv[2] if not os.path.isfile(screenshot_path): print('截图不存在:', screenshot_path) sys.exit(1) if not os.path.isfile(template_path): print('模板不存在:', template_path) sys.exit(1) screenshot = load_image(screenshot_path) template = load_image(template_path) if screenshot is None or template is None: print('无法加载图片') sys.exit(1) # 与 process 一致:使用 [0.8, "w"] 裁剪模板 template = crop_template_square(template, percent=0.8, base='w') sh, sw = screenshot.shape[:2] t_h, t_w = template.shape[:2] print('截图尺寸: {}x{}, 裁剪后模板: {}x{}'.format(sw, sh, t_w, t_h)) print('') # 草稿箱通常在右上角;正确缩略图在内容区(偏左/中,偏上) def region_label(cx, cy): if cx >= sw * 0.65 and cy <= sh * 0.25: return '疑似草稿箱' return '内容区' scale_min_list = [0.05, 0.10, 0.15, 0.20, 0.25] threshold_list = [0.40, 0.44, 0.48, 0.52] results = [] for scale_min in scale_min_list: for th in threshold_list: r = multi_scale_with_correlation( screenshot, template, threshold=th, scale_min=scale_min, scale_max=2.0, steps=60 ) if r is not None: x, y, w, h, cx, cy, corr = r label = region_label(cx, cy) results.append({ 'scale_min': scale_min, 'threshold': th, 'x': x, 'y': y, 'w': w, 'h': h, 'center_x': cx, 'center_y': cy, 'corr': corr, 'label': label }) if not results: print('未找到任何匹配,请降低阈值或扩大 scale 范围。') sys.exit(1) # 按相关系数从高到低排序 results.sort(key=lambda r: -r['corr']) print('===== 所有匹配(按相关系数降序)=====') print('scale_min threshold center_x center_y corr 区域') print('-' * 60) for r in results[:20]: print(' {:.2f} {:.2f} {:4d} {:4d} {:.3f} {}'.format( r['scale_min'], r['threshold'], r['center_x'], r['center_y'], r['corr'], r['label'])) # 当前逻辑:scale_min=0.05,阈值 0.40~0.52 中第一个命中的 current = None for th in (0.52, 0.48, 0.44, 0.40): r = multi_scale_with_correlation(screenshot, template, threshold=th, scale_min=0.05, scale_max=2.0, steps=60) if r is not None: x, y, w, h, cx, cy, corr = r current = (x, y, w, h, cx, cy, corr) break if current: x, y, w, h, cx, cy, corr = current draw_match(screenshot, x, y, w, h, cx, cy, MARKED_WRONG, 'current(scale_min=0.05) corr=%.3f' % corr) print('') print('【当前逻辑】scale_min=0.05 时匹配到: center=({}, {}), corr={:.3f}, 区域={}'.format( cx, cy, corr, region_label(cx, cy))) # 推荐:取「内容区」且相关系数最高的匹配 best_content = None for r in results: if r['label'] == '内容区': best_content = r break if best_content: r = best_content draw_match(screenshot, r['x'], r['y'], r['w'], r['h'], r['center_x'], r['center_y'], MARKED_BEST, 'best(scale_min=%.2f) corr=%.3f' % (r['scale_min'], r['corr'])) print('') print('【推荐】内容区最佳: scale_min={:.2f}, threshold={:.2f}, center=({}, {}), corr={:.3f}'.format( r['scale_min'], r['threshold'], r['center_x'], r['center_y'], r['corr'])) print('') print('建议: 在 image-match.py 中,对相册缩略图(pic) 将 scale_min_use 改为 min(scale_min, 0.15) 或 min(scale_min, 0.20),') print(' 避免 scale_min=0.05 时小尺度模板误匹配到右上角 草稿箱 区域。') else: print('') print('未找到内容区匹配,请检查截图与模板是否一致。') # 诊断结论 print('') print('===== 诊断结论 =====') if current and region_label(current[4], current[5]) == '疑似草稿箱': print('原因: scale_min=0.05 时,模板被缩得很小,与右上角 草稿箱 区域产生较高相关系数,被选为最佳匹配。') print('解决: 提高相册缩略图的最小缩放 scale_min(如 0.15 或 0.20),只在该尺度以上搜索,避免误匹配。') else: print('当前参数下匹配位置正常,若仍出现误匹配可适当提高 threshold 或 scale_min。') if __name__ == '__main__': main()