cv_common.py 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192
  1. import asyncio
  2. import time
  3. import cv2
  4. from cv import detect
  5. from DrissionPage import ChromiumPage
  6. from DrissionPage.common import Actions,Keys
  7. from sqlmodel import SQLModel, Field, Column, JSON
  8. from sqlalchemy.types import LargeBinary
  9. from typing import List, Optional,Coroutine
  10. class ImageMatchResult(SQLModel, table=True):
  11. id: Optional[int] = Field(default=None, primary_key=True)
  12. screen_img_data: bytes = Field(sa_column=Column(LargeBinary))
  13. template_img_path: str
  14. template_img_h: int
  15. template_img_w: int
  16. threshold: float
  17. match_min_val: float
  18. match_max_val: float
  19. min_location: list = Field(sa_column=Column(JSON))
  20. max_location: list = Field(sa_column=Column(JSON))
  21. max_location_center: List[int] = Field(sa_column=Column(JSON))
  22. class CVPage:
  23. def __init__(self, tab:ChromiumPage=None,) -> None:
  24. self.tab = tab
  25. @classmethod
  26. def match_img_in_screen(cls, screen, target_path) -> ImageMatchResult:
  27. screen_img = detect.read_image(screen)
  28. template_img = detect.read_image(target_path)
  29. # 获取模板图像的宽度和高度
  30. w, h = template_img.shape[1], template_img.shape[0]
  31. # 模板匹配
  32. result = cv2.matchTemplate(screen_img, template_img, cv2.TM_CCOEFF_NORMED)
  33. min_val, max_val, min_location, max_location = cv2.minMaxLoc(result)
  34. match_result = ImageMatchResult(
  35. screen_img_path=screen_img,
  36. template_img_path=target_path,
  37. template_img_h=h,
  38. template_img_w=w,
  39. match_min_val=min_val,
  40. match_max_val=max_val,
  41. min_location=min_location,
  42. max_location=max_location,
  43. max_location_center= [max_location[0] + w // 2, max_location[1] + h // 2]
  44. )
  45. return match_result
  46. @classmethod
  47. def match_img_in_screen_region(cls, screen, target_path, region=None) -> ImageMatchResult:
  48. screen_img: cv2.typing.MatLike = detect.read_image(screen)
  49. template_img: cv2.typing.MatLike = detect.read_image(target_path)
  50. # 获取屏幕图像的尺寸
  51. screen_h, screen_w = screen_img.shape[:2]
  52. # 如果指定了region,计算实际的匹配区域
  53. if region:
  54. x1, y1, x2, y2 = region
  55. x1, y1 = int(x1 * screen_w), int(y1 * screen_h)
  56. x2, y2 = int(x2 * screen_w), int(y2 * screen_h)
  57. roi = screen_img[y1:y2, x1:x2]
  58. else:
  59. roi = screen_img
  60. # 获取模板图像的宽度和高度
  61. w, h = template_img.shape[1], template_img.shape[0]
  62. # cv2.imwrite('test1.png', roi)
  63. # 模板匹配
  64. result = cv2.matchTemplate(roi, template_img, cv2.TM_CCOEFF_NORMED)
  65. min_val, max_val, min_location, max_location = cv2.minMaxLoc(result)
  66. match_result = ImageMatchResult(
  67. screen_img_path=screen if type(screen) == str else None,
  68. template_img_path=target_path,
  69. template_img_h=h,
  70. template_img_w=w,
  71. match_min_val=min_val,
  72. match_max_val=max_val,
  73. min_location=min_location,
  74. max_location=max_location,
  75. max_location_center=[max_location[0] + w // 2, max_location[1] + h // 2]
  76. )
  77. return match_result
  78. def show_match_result(screen, match_result:ImageMatchResult, threshold=0.8, imshow=True):
  79. # 读取源图像和目标图像
  80. source_img: cv2.typing.MatLike = detect.read_image(screen)
  81. w = match_result.template_img_w
  82. h = match_result.template_img_h
  83. # 获取匹配结果
  84. max_val = match_result.match_max_val
  85. max_loc = match_result.max_location
  86. if max_val >= threshold: # 如果最大相似度大于或等于阈值
  87. top_left = max_loc
  88. bottom_right = (top_left[0] + w, top_left[1] + h)
  89. label = f"Similarity: {max_val:.2f}"
  90. cv2.putText(source_img, label, (top_left[0], top_left[1] - 10),
  91. cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 2)
  92. # 绘制矩形框
  93. cv2.rectangle(source_img, top_left, bottom_right, (0, 255, 0), 2)
  94. # print(f"匹配位置: {top_left,bottom_right}, 相似度: {max_val}")
  95. # else:
  96. # print("没有找到相似度足够高的匹配.")
  97. if imshow:
  98. # 创建一个窗口来显示结果
  99. cv2.namedWindow('Detected', cv2.WINDOW_NORMAL)
  100. cv2.imshow('Detected', source_img)
  101. # 等待按键
  102. cv2.waitKey(0)
  103. cv2.destroyAllWindows()
  104. else:
  105. return source_img
  106. def wait_for_image_match(self, target_path, threshold=0.9, timeout=30, interval=0.3, raise_error=False):
  107. """
  108. 等待直到图片匹配度超过给定阈值或超时。
  109. :param target_path: 目标图片的路径
  110. :param threshold: 匹配度阈值,默认为0.8
  111. :param timeout: 等待超时时间(秒),默认为30秒
  112. :param interval: 每次检查之间的间隔时间(秒),默认为0.5秒
  113. :return: 如果找到匹配的图片,则返回ImageMatchResult,否则返回None
  114. """
  115. end_time = time.time() + timeout
  116. while time.time() < end_time:
  117. match_result = self.match_img_in_screen(target_path)
  118. if match_result.match_max_val >= threshold:
  119. return match_result
  120. time.sleep(interval)
  121. if raise_error:
  122. raise Exception(f'超时未找到匹配的图片 {target_path} ,阈值为{threshold}')
  123. return None # 超时未找到符合条件的匹配
  124. async def wait_for_image_match_async(self, screen, target_path, threshold=0.9, timeout=30, interval=0.3, raise_error=False)->ImageMatchResult:
  125. """
  126. 异步等待直到图片匹配度超过给定阈值或超时。
  127. """
  128. end_time = time.time() + timeout
  129. while time.time() < end_time:
  130. if isinstance(screen, Coroutine):
  131. print(screen)
  132. screen = await screen()
  133. match_result = self.match_img_in_screen(screen, target_path)
  134. if match_result.match_max_val >= threshold:
  135. return match_result
  136. await asyncio.sleep(interval)
  137. if raise_error:
  138. raise Exception(f'超时未找到匹配的图片 {target_path} ,阈值为{threshold}')
  139. return None # 超时未找到符合条件的匹配
  140. def wait_for_image_disappear(self, target_path, threshold=0.9, timeout=30, interval=0.3, raise_error=False):
  141. """
  142. 等待直到目标图片从屏幕上消失或超时。
  143. :param target_path: 目标图片的路径
  144. :param threshold: 匹配度阈值,当匹配度低于此值时认为图片已消失,默认为0.3
  145. :param timeout: 等待超时时间(秒),默认为30秒
  146. :param interval: 每次检查之间的间隔时间(秒),默认为0.5秒
  147. :return: 如果图片消失,则返回True,否则返回False
  148. """
  149. end_time = time.time() + timeout
  150. while time.time() < end_time:
  151. match_result = self.match_img_in_screen(target_path)
  152. if match_result.match_max_val < threshold:
  153. return True # 图片已消失
  154. time.sleep(interval)
  155. if raise_error:
  156. raise Exception(f'超时未找到匹配的图片 {target_path} ,阈值为{threshold}')
  157. return None # 超时未找到符合条件的匹配
  158. async def wait_for_image_disappear_async(self, screen, target_path, threshold=0.9, timeout=30, interval=0.3, raise_error=False):
  159. """
  160. 异步等待直到目标图片从屏幕上消失或超时。
  161. """
  162. end_time = time.time() + timeout
  163. while time.time() < end_time:
  164. match_result = self.match_img_in_screen(screen, target_path)
  165. if match_result.match_max_val < threshold:
  166. return True # 图片已消失
  167. await asyncio.sleep(interval)
  168. if raise_error:
  169. raise Exception(f'超时未找到匹配的图片 {target_path} ,阈值为{threshold}')
  170. return False # 超时未找到符合条件的匹配