test_match_pic1_screenshot.py 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182
  1. #!/usr/bin/env python
  2. # -*- coding: utf-8 -*-
  3. """
  4. 临时测试脚本:对 pic1.png 与 Screenshot-pic1.png 做多组参数匹配,直到找到匹配并在截图上标出。
  5. 用法: 在项目根目录执行
  6. python python/scripts/test_match_pic1_screenshot.py
  7. cd static/process/GenerateNote && python ../../../python/scripts/test_match_pic1_screenshot.py
  8. 输出: 控制台打印最佳参数与坐标,并生成 Screenshot-pic1-marked.png 标出匹配框与中心点。
  9. """
  10. from __future__ import print_function
  11. import os
  12. import sys
  13. import json
  14. # 项目根目录(本脚本在 python/scripts/)
  15. SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
  16. PROJECT_ROOT = os.path.normpath(os.path.join(SCRIPT_DIR, '..', '..'))
  17. if PROJECT_ROOT not in sys.path:
  18. sys.path.insert(0, PROJECT_ROOT)
  19. # 默认路径:流程目录下的 tmp
  20. PROCESS_DIR = os.path.join(PROJECT_ROOT, 'static', 'process', 'GenerateNote')
  21. TMP_DIR = os.path.join(PROCESS_DIR, 'tmp')
  22. SCREENSHOT_PATH = os.path.join(TMP_DIR, 'Screenshot-pic1.png')
  23. TEMPLATE_PATH = os.path.join(TMP_DIR, 'pic1.png')
  24. MARKED_OUT_PATH = os.path.join(TMP_DIR, 'Screenshot-pic1-marked.png')
  25. try:
  26. import cv2
  27. import numpy as np
  28. except ImportError:
  29. print('请安装: pip install opencv-python numpy')
  30. sys.exit(1)
  31. # 动态加载 image-match 模块(文件名带横杠)
  32. import importlib.util
  33. _image_match_path = os.path.join(SCRIPT_DIR, 'image-match.py')
  34. _spec = importlib.util.spec_from_file_location('image_match', _image_match_path)
  35. _image_match = importlib.util.module_from_spec(_spec)
  36. _spec.loader.exec_module(_image_match)
  37. load_image = _image_match.load_image
  38. multi_scale_template_match = _image_match.multi_scale_template_match
  39. match_by_features = _image_match.match_by_features
  40. HAS_ROMA = getattr(_image_match, 'HAS_ROMA', False)
  41. match_by_roma = getattr(_image_match, 'match_by_roma', lambda *a, **k: None)
  42. def multi_scale_with_correlation(screenshot, template, threshold=0.50, scale_min=0.4, scale_max=1.65, steps=80):
  43. """多尺度模板匹配并返回最佳相关系数。返回 (x, y, w, h, center_x, center_y, best_val) 或 None。"""
  44. gray_screen = cv2.cvtColor(screenshot, cv2.COLOR_BGR2GRAY)
  45. gray_tpl = cv2.cvtColor(template, cv2.COLOR_BGR2GRAY)
  46. sh, sw = screenshot.shape[:2]
  47. t_h, t_w = template.shape[:2]
  48. best = None
  49. best_val = threshold
  50. step = max(0.02, (scale_max - scale_min) / float(steps))
  51. for scale in np.arange(scale_min, scale_max + step * 0.5, step):
  52. w = max(8, int(round(t_w * scale)))
  53. h = max(8, int(round(t_h * scale)))
  54. if h > sh or w > sw:
  55. continue
  56. resized = cv2.resize(gray_tpl, (w, h), interpolation=cv2.INTER_AREA)
  57. result = cv2.matchTemplate(gray_screen, resized, cv2.TM_CCOEFF_NORMED)
  58. min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(result)
  59. if max_val > best_val:
  60. best_val = max_val
  61. x, y = int(max_loc[0]), int(max_loc[1])
  62. center_x = x + w // 2
  63. center_y = y + h // 2
  64. best = (x, y, w, h, center_x, center_y, best_val)
  65. if best is None:
  66. return None
  67. return best
  68. def draw_match(screenshot, x, y, w, h, center_x, center_y, out_path):
  69. """在截图上画匹配框和中心点并保存。"""
  70. img = screenshot.copy()
  71. cv2.rectangle(img, (x, y), (x + w, y + h), (0, 255, 0), 2)
  72. cv2.circle(img, (center_x, center_y), 8, (0, 0, 255), 2)
  73. cv2.putText(img, 'center', (center_x + 10, center_y), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 255), 1)
  74. cv2.imwrite(out_path, img)
  75. print('已保存标注图:', out_path)
  76. def main():
  77. screenshot_path = SCREENSHOT_PATH
  78. template_path = TEMPLATE_PATH
  79. if len(sys.argv) >= 3:
  80. screenshot_path = sys.argv[1]
  81. template_path = sys.argv[2]
  82. if not os.path.isfile(screenshot_path):
  83. print('截图不存在:', screenshot_path)
  84. sys.exit(1)
  85. if not os.path.isfile(template_path):
  86. print('模板不存在:', template_path)
  87. sys.exit(1)
  88. screenshot = load_image(screenshot_path)
  89. template = load_image(template_path)
  90. if screenshot is None:
  91. print('无法加载截图')
  92. sys.exit(1)
  93. if template is None:
  94. print('无法加载模板')
  95. sys.exit(1)
  96. sh, sw = screenshot.shape[:2]
  97. t_h, t_w = template.shape[:2]
  98. print('截图尺寸: {}x{}, 模板尺寸: {}x{}'.format(sw, sh, t_w, t_h))
  99. # 1) 尝试 RoMa(若可用),仅当中心在上半区时才采用
  100. if HAS_ROMA:
  101. roma_result = match_by_roma(screenshot, template, min_matches=4)
  102. if roma_result is not None:
  103. x, y, w, h, center_x, center_y = roma_result
  104. if center_y <= sh * 0.6:
  105. print('[RoMa] 匹配成功: center=({}, {})'.format(center_x, center_y))
  106. draw_match(screenshot, x, y, w, h, center_x, center_y, MARKED_OUT_PATH)
  107. print('推荐 process.json: 保持 [0.15, 2.0], 方形裁剪如 [0.8, "w"]')
  108. sys.exit(0)
  109. # 2) 收集多尺度模板匹配结果(参数网格),优先取「中心在画面上半区」的匹配(格子图通常在上方)
  110. sh, sw = screenshot.shape[:2]
  111. upper_ratio = 0.6 # 中心 y < sh * upper_ratio 视为上半区(格子区域)
  112. candidates = []
  113. # 3) 多尺度模板匹配:参数网格(缩略图通常 scale 约 0.1~0.4)
  114. scale_min_list = [0.05, 0.10, 0.15]
  115. scale_max_list = [1.0, 1.5, 2.0]
  116. threshold_list = [0.40, 0.46, 0.52]
  117. best_result = None
  118. best_corr = 0.0
  119. best_params = None
  120. for scale_min in scale_min_list:
  121. for scale_max in scale_max_list:
  122. if scale_max <= scale_min:
  123. continue
  124. for th in threshold_list:
  125. r = multi_scale_with_correlation(
  126. screenshot, template,
  127. threshold=th, scale_min=scale_min, scale_max=scale_max, steps=50
  128. )
  129. if r is not None:
  130. x, y, w, h, cx, cy, corr = r
  131. candidates.append((corr, (x, y, w, h, cx, cy), (scale_min, scale_max, th)))
  132. # 优先选中心在上半区的最高相关匹配,否则选全局最高相关
  133. best_result = None
  134. best_corr = 0.0
  135. best_params = None
  136. for corr, rect, params in sorted(candidates, key=lambda t: -t[0]):
  137. x, y, w, h, cx, cy = rect
  138. if cy <= sh * upper_ratio and corr > best_corr:
  139. best_corr = corr
  140. best_result = rect
  141. best_params = params
  142. if best_result is None and candidates:
  143. best_corr, best_result, best_params = max(candidates, key=lambda t: t[0])
  144. print('(未在上半区找到匹配,使用全局最高相关;请查看标注图确认是否为底部缩略图)')
  145. if best_result is None:
  146. print('未找到任何匹配。可尝试: 降低 threshold、扩大 scale_min~scale_max、或检查模板是否在截图内。')
  147. sys.exit(1)
  148. x, y, w, h, center_x, center_y = best_result
  149. scale_min, scale_max, th = best_params
  150. print('')
  151. print('[多尺度模板] 匹配成功 (最佳相关系数 {:.3f}, 中心在上半区)'.format(best_corr))
  152. print(' 中心坐标: ({}, {})'.format(center_x, center_y))
  153. print(' 矩形: x={} y={} w={} h={}'.format(x, y, w, h))
  154. print(' 推荐 process.json inVars:')
  155. print(' inVars: ["tmp/pic{{idx}}.png", [{}, {}], [0.8, "w"]]'.format(scale_min, scale_max))
  156. print(' 并在 image-match.py 中 fallback 阈值可设为 <= {:.2f}'.format(th))
  157. print('')
  158. draw_match(screenshot, x, y, w, h, center_x, center_y, MARKED_OUT_PATH)
  159. if __name__ == '__main__':
  160. main()