img-reg.py 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242
  1. """
  2. 图像匹配模块
  3. 功能:识别图片2是否在图片1中,如果在则返回图片2在图片1中的坐标
  4. """
  5. import cv2
  6. import numpy as np
  7. from pathlib import Path
  8. from typing import Tuple, Optional
  9. import os
  10. def read_image_with_unicode_path(image_path):
  11. """
  12. 读取包含 Unicode 字符(如中文)的图片路径
  13. OpenCV 的 imread 在 Windows 上可能无法直接处理中文路径
  14. 直接使用 imdecode 方法,避免 imread 的警告
  15. """
  16. # 将路径转换为绝对路径并规范化
  17. abs_path = os.path.abspath(str(image_path))
  18. # 直接使用文件读取 + imdecode,避免 imread 在中文路径上的问题
  19. # 这样可以避免 cv2.imread 输出的警告信息
  20. try:
  21. with open(abs_path, 'rb') as f:
  22. image_data = f.read()
  23. # 将字节流解码为图片
  24. img_array = np.frombuffer(image_data, np.uint8)
  25. img = cv2.imdecode(img_array, cv2.IMREAD_COLOR)
  26. if img is None:
  27. raise ValueError(f"cv2.imdecode 无法解码图片: {abs_path}")
  28. return img
  29. except FileNotFoundError:
  30. raise FileNotFoundError(f"图片文件不存在: {abs_path}")
  31. except Exception as e:
  32. raise ValueError(f"无法读取图片: {abs_path}, 错误: {e}")
  33. def match_image(
  34. image1_path: str,
  35. image2_path: str,
  36. image1_size: Tuple[int, int] = None
  37. ) -> Optional[Tuple[int, int, int, int]]:
  38. """
  39. 识别图片2是否在图片1中,如果在则返回坐标
  40. Args:
  41. image1_path: 图片1的本地地址(大图/主图)
  42. image2_path: 图片2的本地地址(模板图/要查找的图)
  43. image1_size: 图片1的分辨率尺寸 (width, height),如果提供则会将图片1缩放到该尺寸
  44. Returns:
  45. 如果找到图片2,返回 (x, y, width, height) 表示图片2在图片1中的位置和尺寸
  46. 如果未找到,返回 None
  47. """
  48. # 检查文件是否存在
  49. img1_path = Path(image1_path)
  50. img2_path = Path(image2_path)
  51. if not img1_path.exists():
  52. raise FileNotFoundError(f"图片1不存在: {image1_path}")
  53. if not img2_path.exists():
  54. raise FileNotFoundError(f"图片2不存在: {image2_path}")
  55. # 读取图片(使用支持中文路径的方法)
  56. img1 = read_image_with_unicode_path(img1_path)
  57. img2 = read_image_with_unicode_path(img2_path)
  58. # 如果提供了图片1的尺寸,则缩放图片1
  59. if image1_size is not None:
  60. target_width, target_height = image1_size
  61. img1 = cv2.resize(img1, (target_width, target_height))
  62. # 转换为灰度图(模板匹配通常在灰度图上进行)
  63. img1_gray = cv2.cvtColor(img1, cv2.COLOR_BGR2GRAY)
  64. img2_gray = cv2.cvtColor(img2, cv2.COLOR_BGR2GRAY)
  65. # 获取模板尺寸
  66. template_height, template_width = img2_gray.shape
  67. # 如果模板图比主图大,则无法匹配
  68. if template_height > img1_gray.shape[0] or template_width > img1_gray.shape[1]:
  69. return None
  70. # 使用模板匹配方法
  71. # cv2.TM_CCOEFF_NORMED 返回归一化的相关系数,值越大匹配度越高
  72. result = cv2.matchTemplate(img1_gray, img2_gray, cv2.TM_CCOEFF_NORMED)
  73. # 获取最佳匹配位置
  74. min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(result)
  75. # 设置匹配阈值(可以根据需要调整,0.8 表示80%相似度)
  76. threshold = 0.8
  77. if max_val >= threshold:
  78. # 找到匹配位置
  79. top_left = max_loc
  80. x, y = top_left
  81. # 返回坐标和尺寸 (x, y, width, height)
  82. return (x, y, template_width, template_height)
  83. else:
  84. # 未找到匹配
  85. return None
  86. def match_image_multiple(
  87. image1_path: str,
  88. image2_path: str,
  89. image1_size: Tuple[int, int] = None,
  90. threshold: float = 0.8
  91. ) -> list:
  92. """
  93. 识别图片2在图片1中的所有出现位置(可能有多处匹配)
  94. Args:
  95. image1_path: 图片1的本地地址
  96. image2_path: 图片2的本地地址
  97. image1_size: 图片1的分辨率尺寸 (width, height)
  98. threshold: 匹配阈值,默认0.8
  99. Returns:
  100. 返回所有匹配位置的列表,每个元素为 (x, y, width, height)
  101. """
  102. # 检查文件是否存在
  103. img1_path = Path(image1_path)
  104. img2_path = Path(image2_path)
  105. if not img1_path.exists():
  106. raise FileNotFoundError(f"图片1不存在: {image1_path}")
  107. if not img2_path.exists():
  108. raise FileNotFoundError(f"图片2不存在: {image2_path}")
  109. # 读取图片(使用支持中文路径的方法)
  110. img1 = read_image_with_unicode_path(img1_path)
  111. img2 = read_image_with_unicode_path(img2_path)
  112. # 如果提供了图片1的尺寸,则缩放图片1
  113. if image1_size is not None:
  114. target_width, target_height = image1_size
  115. img1 = cv2.resize(img1, (target_width, target_height))
  116. # 转换为灰度图
  117. img1_gray = cv2.cvtColor(img1, cv2.COLOR_BGR2GRAY)
  118. img2_gray = cv2.cvtColor(img2, cv2.COLOR_BGR2GRAY)
  119. # 获取模板尺寸
  120. template_height, template_width = img2_gray.shape
  121. # 如果模板图比主图大,则无法匹配
  122. if template_height > img1_gray.shape[0] or template_width > img1_gray.shape[1]:
  123. return []
  124. # 使用模板匹配
  125. result = cv2.matchTemplate(img1_gray, img2_gray, cv2.TM_CCOEFF_NORMED)
  126. # 找到所有超过阈值的匹配位置
  127. locations = np.where(result >= threshold)
  128. matches = []
  129. # 将匹配位置转换为坐标列表
  130. for pt in zip(*locations[::-1]): # Switch x and y coordinates
  131. x, y = pt
  132. matches.append((x, y, template_width, template_height))
  133. # 去除重复和相近的匹配(非极大值抑制)
  134. # 简单实现:如果两个匹配位置太近,只保留置信度更高的
  135. if len(matches) > 1:
  136. filtered_matches = []
  137. for match in matches:
  138. x, y, w, h = match
  139. is_duplicate = False
  140. for existing in filtered_matches:
  141. ex, ey, _, _ = existing
  142. # 如果两个匹配中心距离小于模板尺寸的一半,认为是重复
  143. distance = np.sqrt((x - ex) ** 2 + (y - ey) ** 2)
  144. if distance < min(w, h) / 2:
  145. is_duplicate = True
  146. break
  147. if not is_duplicate:
  148. filtered_matches.append(match)
  149. matches = filtered_matches
  150. return matches
  151. if __name__ == "__main__":
  152. # 测试示例
  153. import sys
  154. import os
  155. # 设置 UTF-8 编码,确保能正确处理中文路径
  156. if sys.platform == 'win32':
  157. import codecs
  158. sys.stdout = codecs.getwriter('utf-8')(sys.stdout.buffer, 'strict')
  159. sys.stderr = codecs.getwriter('utf-8')(sys.stderr.buffer, 'strict')
  160. if len(sys.argv) < 3:
  161. print("用法: python img-reg.py <图片1路径> <图片2路径> [图片1宽度] [图片1高度]")
  162. print("示例: python img-reg.py screenshot.png template.png 1920 1080")
  163. sys.exit(1)
  164. # 处理路径:将正斜杠转换回反斜杠(Windows),并确保路径正确
  165. img1_path = sys.argv[1].replace('/', os.sep)
  166. img2_path = sys.argv[2].replace('/', os.sep)
  167. # 确保路径是绝对路径,并规范化路径
  168. if not os.path.isabs(img1_path):
  169. img1_path = os.path.abspath(img1_path)
  170. if not os.path.isabs(img2_path):
  171. img2_path = os.path.abspath(img2_path)
  172. # 规范化路径(移除多余的斜杠等)
  173. img1_path = os.path.normpath(img1_path)
  174. img2_path = os.path.normpath(img2_path)
  175. image1_size = None
  176. if len(sys.argv) >= 5:
  177. try:
  178. width = int(sys.argv[3])
  179. height = int(sys.argv[4])
  180. image1_size = (width, height)
  181. except ValueError:
  182. print("警告: 无法解析图片1尺寸,将使用原始尺寸")
  183. # 验证文件是否存在
  184. if not os.path.exists(img1_path):
  185. raise FileNotFoundError(f"图片1不存在: {img1_path}")
  186. if not os.path.exists(img2_path):
  187. raise FileNotFoundError(f"图片2不存在: {img2_path}")
  188. try:
  189. result = match_image(img1_path, img2_path, image1_size)
  190. if result:
  191. x, y, w, h = result
  192. print(f"找到匹配!坐标: x={x}, y={y}, 宽度={w}, 高度={h}")
  193. print(f"JSON格式: {{\"x\": {x}, \"y\": {y}, \"width\": {w}, \"height\": {h}}}")
  194. else:
  195. print("未找到匹配")
  196. except Exception as e:
  197. print(f"错误: {e}")
  198. sys.exit(1)