img-reg.py 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224
  1. """
  2. 鍥惧儚鍖归厤妯″潡
  3. 鍔熻兘锛氳瘑鍒浘鐗?鏄惁鍦ㄥ浘鐗?涓紝濡傛灉鍦ㄥ垯杩斿洖鍥剧墖2鍦ㄥ浘鐗?涓殑鍧愭爣
  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 涓婂彲鑳芥棤娉曠洿鎺ュ鐞嗕腑鏂囪矾寰? 鐩存帴浣跨敤 imdecode 鏂规硶锛岄伩鍏?imread 鐨勮鍛? """
  14. # 灏嗚矾寰勮浆鎹负缁濆璺緞骞惰鑼冨寲
  15. abs_path = os.path.abspath(str(image_path))
  16. # 鐩存帴浣跨敤鏂囦欢璇诲彇 + imdecode锛岄伩鍏?imread 鍦ㄤ腑鏂囪矾寰勪笂鐨勯棶棰? # 杩欐牱鍙互閬垮厤 cv2.imread 杈撳嚭鐨勮鍛婁俊鎭? try:
  17. with open(abs_path, 'rb') as f:
  18. image_data = f.read()
  19. # 灏嗗瓧鑺傛祦瑙g爜涓哄浘鐗? img_array = np.frombuffer(image_data, np.uint8)
  20. img = cv2.imdecode(img_array, cv2.IMREAD_COLOR)
  21. if img is None:
  22. raise ValueError(f"cv2.imdecode 鏃犳硶瑙g爜鍥剧墖: {abs_path}")
  23. return img
  24. except FileNotFoundError:
  25. raise FileNotFoundError(f"鍥剧墖鏂囦欢涓嶅瓨鍦? {abs_path}")
  26. except Exception as e:
  27. raise ValueError(f"鏃犳硶璇诲彇鍥剧墖: {abs_path}, 閿欒: {e}")
  28. def match_image(
  29. image1_path: str,
  30. image2_path: str,
  31. image1_size: Tuple[int, int] = None
  32. ) -> Optional[Tuple[int, int, int, int]]:
  33. """
  34. 璇嗗埆鍥剧墖2鏄惁鍦ㄥ浘鐗?涓紝濡傛灉鍦ㄥ垯杩斿洖鍧愭爣
  35. Args:
  36. image1_path: 鍥剧墖1鐨勬湰鍦板湴鍧€锛堝ぇ鍥?涓诲浘锛? image2_path: 鍥剧墖2鐨勬湰鍦板湴鍧€锛堟ā鏉垮浘/瑕佹煡鎵剧殑鍥撅級
  37. image1_size: 鍥剧墖1鐨勫垎杈ㄧ巼灏哄 (width, height)锛屽鏋滄彁渚涘垯浼氬皢鍥剧墖1缂╂斁鍒拌灏哄
  38. Returns:
  39. 濡傛灉鎵惧埌鍥剧墖2锛岃繑鍥?(x, y, width, height) 琛ㄧず鍥剧墖2鍦ㄥ浘鐗?涓殑浣嶇疆鍜屽昂瀵? 濡傛灉鏈壘鍒帮紝杩斿洖 None
  40. """
  41. # 妫€鏌ユ枃浠舵槸鍚﹀瓨鍦? img1_path = Path(image1_path)
  42. img2_path = Path(image2_path)
  43. if not img1_path.exists():
  44. raise FileNotFoundError(f"鍥剧墖1涓嶅瓨鍦? {image1_path}")
  45. if not img2_path.exists():
  46. raise FileNotFoundError(f"鍥剧墖2涓嶅瓨鍦? {image2_path}")
  47. # 璇诲彇鍥剧墖锛堜娇鐢ㄦ敮鎸佷腑鏂囪矾寰勭殑鏂规硶锛? img1 = read_image_with_unicode_path(img1_path)
  48. img2 = read_image_with_unicode_path(img2_path)
  49. # 濡傛灉鎻愪緵浜嗗浘鐗?鐨勫昂瀵革紝鍒欑缉鏀惧浘鐗?
  50. if image1_size is not None:
  51. target_width, target_height = image1_size
  52. img1 = cv2.resize(img1, (target_width, target_height))
  53. # 杞崲涓虹伆搴﹀浘锛堟ā鏉垮尮閰嶉€氬父鍦ㄧ伆搴﹀浘涓婅繘琛岋級
  54. img1_gray = cv2.cvtColor(img1, cv2.COLOR_BGR2GRAY)
  55. img2_gray = cv2.cvtColor(img2, cv2.COLOR_BGR2GRAY)
  56. # 鑾峰彇妯℃澘灏哄
  57. template_height, template_width = img2_gray.shape
  58. # 濡傛灉妯℃澘鍥炬瘮涓诲浘澶э紝鍒欐棤娉曞尮閰? if template_height > img1_gray.shape[0] or template_width > img1_gray.shape[1]:
  59. return None
  60. # 浣跨敤妯℃澘鍖归厤鏂规硶
  61. # cv2.TM_CCOEFF_NORMED 杩斿洖褰掍竴鍖栫殑鐩稿叧绯绘暟锛屽€艰秺澶у尮閰嶅害瓒婇珮
  62. result = cv2.matchTemplate(img1_gray, img2_gray, cv2.TM_CCOEFF_NORMED)
  63. # 鑾峰彇鏈€浣冲尮閰嶄綅缃? min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(result)
  64. # 璁剧疆鍖归厤闃堝€硷紙鍙互鏍规嵁闇€瑕佽皟鏁达紝0.8 琛ㄧず80%鐩镐技搴︼級
  65. threshold = 0.8
  66. if max_val >= threshold:
  67. # 鎵惧埌鍖归厤浣嶇疆
  68. top_left = max_loc
  69. x, y = top_left
  70. # 杩斿洖鍧愭爣鍜屽昂瀵?(x, y, width, height)
  71. return (x, y, template_width, template_height)
  72. else:
  73. # 鏈壘鍒板尮閰? return None
  74. def match_image_multiple(
  75. image1_path: str,
  76. image2_path: str,
  77. image1_size: Tuple[int, int] = None,
  78. threshold: float = 0.8
  79. ) -> list:
  80. """
  81. 璇嗗埆鍥剧墖2鍦ㄥ浘鐗?涓殑鎵€鏈夊嚭鐜颁綅缃紙鍙兘鏈夊澶勫尮閰嶏級
  82. Args:
  83. image1_path: 鍥剧墖1鐨勬湰鍦板湴鍧€
  84. image2_path: 鍥剧墖2鐨勬湰鍦板湴鍧€
  85. image1_size: 鍥剧墖1鐨勫垎杈ㄧ巼灏哄 (width, height)
  86. threshold: 鍖归厤闃堝€硷紝榛樿0.8
  87. Returns:
  88. 杩斿洖鎵€鏈夊尮閰嶄綅缃殑鍒楄〃锛屾瘡涓厓绱犱负 (x, y, width, height)
  89. """
  90. # 妫€鏌ユ枃浠舵槸鍚﹀瓨鍦? img1_path = Path(image1_path)
  91. img2_path = Path(image2_path)
  92. if not img1_path.exists():
  93. raise FileNotFoundError(f"鍥剧墖1涓嶅瓨鍦? {image1_path}")
  94. if not img2_path.exists():
  95. raise FileNotFoundError(f"鍥剧墖2涓嶅瓨鍦? {image2_path}")
  96. # 璇诲彇鍥剧墖锛堜娇鐢ㄦ敮鎸佷腑鏂囪矾寰勭殑鏂规硶锛? img1 = read_image_with_unicode_path(img1_path)
  97. img2 = read_image_with_unicode_path(img2_path)
  98. # 濡傛灉鎻愪緵浜嗗浘鐗?鐨勫昂瀵革紝鍒欑缉鏀惧浘鐗?
  99. if image1_size is not None:
  100. target_width, target_height = image1_size
  101. img1 = cv2.resize(img1, (target_width, target_height))
  102. # 杞崲涓虹伆搴﹀浘
  103. img1_gray = cv2.cvtColor(img1, cv2.COLOR_BGR2GRAY)
  104. img2_gray = cv2.cvtColor(img2, cv2.COLOR_BGR2GRAY)
  105. # 鑾峰彇妯℃澘灏哄
  106. template_height, template_width = img2_gray.shape
  107. # 濡傛灉妯℃澘鍥炬瘮涓诲浘澶э紝鍒欐棤娉曞尮閰? if template_height > img1_gray.shape[0] or template_width > img1_gray.shape[1]:
  108. return []
  109. # 浣跨敤妯℃澘鍖归厤
  110. result = cv2.matchTemplate(img1_gray, img2_gray, cv2.TM_CCOEFF_NORMED)
  111. # 鎵惧埌鎵€鏈夎秴杩囬槇鍊肩殑鍖归厤浣嶇疆
  112. locations = np.where(result >= threshold)
  113. matches = []
  114. # 灏嗗尮閰嶄綅缃浆鎹负鍧愭爣鍒楄〃
  115. for pt in zip(*locations[::-1]): # Switch x and y coordinates
  116. x, y = pt
  117. matches.append((x, y, template_width, template_height))
  118. # 鍘婚櫎閲嶅鍜岀浉杩戠殑鍖归厤锛堥潪鏋佸ぇ鍊兼姂鍒讹級
  119. # 绠€鍗曞疄鐜帮細濡傛灉涓や釜鍖归厤浣嶇疆澶繎锛屽彧淇濈暀缃俊搴︽洿楂樼殑
  120. if len(matches) > 1:
  121. filtered_matches = []
  122. for match in matches:
  123. x, y, w, h = match
  124. is_duplicate = False
  125. for existing in filtered_matches:
  126. ex, ey, _, _ = existing
  127. # 濡傛灉涓や釜鍖归厤涓績璺濈灏忎簬妯℃澘灏哄鐨勪竴鍗婏紝璁や负鏄噸澶? distance = np.sqrt((x - ex) ** 2 + (y - ey) ** 2)
  128. if distance < min(w, h) / 2:
  129. is_duplicate = True
  130. break
  131. if not is_duplicate:
  132. filtered_matches.append(match)
  133. matches = filtered_matches
  134. return matches
  135. if __name__ == "__main__":
  136. # 娴嬭瘯绀轰緥
  137. import sys
  138. import os
  139. # 璁剧疆 UTF-8 缂栫爜锛岀‘淇濊兘姝g‘澶勭悊涓枃璺緞
  140. if sys.platform == 'win32':
  141. import codecs
  142. sys.stdout = codecs.getwriter('utf-8')(sys.stdout.buffer, 'strict')
  143. sys.stderr = codecs.getwriter('utf-8')(sys.stderr.buffer, 'strict')
  144. if len(sys.argv) < 3:
  145. print("鐢ㄦ硶: python img-reg.py <鍥剧墖1璺緞> <鍥剧墖2璺緞> [鍥剧墖1瀹藉害] [鍥剧墖1楂樺害]")
  146. print("绀轰緥: python img-reg.py screenshot.png template.png 1920 1080")
  147. sys.exit(1)
  148. # 澶勭悊璺緞锛氬皢姝f枩鏉犺浆鎹㈠洖鍙嶆枩鏉狅紙Windows锛夛紝骞剁‘淇濊矾寰勬纭? img1_path = sys.argv[1].replace('/', os.sep)
  149. img2_path = sys.argv[2].replace('/', os.sep)
  150. # 纭繚璺緞鏄粷瀵硅矾寰勶紝骞惰鑼冨寲璺緞
  151. if not os.path.isabs(img1_path):
  152. img1_path = os.path.abspath(img1_path)
  153. if not os.path.isabs(img2_path):
  154. img2_path = os.path.abspath(img2_path)
  155. # 瑙勮寖鍖栬矾寰勶紙绉婚櫎澶氫綑鐨勬枩鏉犵瓑锛? img1_path = os.path.normpath(img1_path)
  156. img2_path = os.path.normpath(img2_path)
  157. image1_size = None
  158. if len(sys.argv) >= 5:
  159. try:
  160. width = int(sys.argv[3])
  161. height = int(sys.argv[4])
  162. image1_size = (width, height)
  163. except ValueError:
  164. print("璀﹀憡: 鏃犳硶瑙f瀽鍥剧墖1灏哄锛屽皢浣跨敤鍘熷灏哄")
  165. # 楠岃瘉鏂囦欢鏄惁瀛樺湪
  166. if not os.path.exists(img1_path):
  167. raise FileNotFoundError(f"鍥剧墖1涓嶅瓨鍦? {img1_path}")
  168. if not os.path.exists(img2_path):
  169. raise FileNotFoundError(f"鍥剧墖2涓嶅瓨鍦? {img2_path}")
  170. try:
  171. result = match_image(img1_path, img2_path, image1_size)
  172. if result:
  173. x, y, w, h = result
  174. print(f"鎵惧埌鍖归厤锛佸潗鏍? x={x}, y={y}, 瀹藉害={w}, 楂樺害={h}")
  175. print(f"JSON鏍煎紡: {{\"x\": {x}, \"y\": {y}, \"width\": {w}, \"height\": {h}}}")
  176. else:
  177. print("鏈壘鍒板尮閰?)
  178. except Exception as e:
  179. print(f"閿欒: {e}")
  180. sys.exit(1)