helper.py 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219
  1. import cv2
  2. import numpy as np
  3. import os
  4. import threading
  5. from io import BytesIO
  6. from PIL import Image
  7. from ppadb.client import Client as AdbClient
  8. from dotenv import load_dotenv
  9. with_cuda = 0
  10. if cv2.cuda.getCudaEnabledDeviceCount() > 0:
  11. print("CUDA is available")
  12. with_cuda = 1
  13. else:
  14. print("CUDA is not available")
  15. with_cuda = 0
  16. load_dotenv()
  17. android_address = os.getenv("ANDROID_ADDRESS")
  18. def get_current_screen():
  19. return current_screen
  20. def capture_current_screen(timeout=5): # Timeout in seconds
  21. def target():
  22. global current_screen
  23. current_screen = device.screencap()
  24. capture_thread = threading.Thread(target=target)
  25. capture_thread.start()
  26. capture_thread.join(timeout)
  27. if capture_thread.is_alive():
  28. print("Screen capture timed out")
  29. # Handle the timeout situation, e.g., by retrying or aborting
  30. capture_thread.join()
  31. return current_screen
  32. def find_center(x1, y1, x2, y2):
  33. centerX = round(x1 + (x2 - x1) / 2)
  34. centerY = round(y1 + (y2 - y1) / 2)
  35. return centerX, centerY
  36. def call_device_shell(action, timeout=10):
  37. def target():
  38. device.shell(action)
  39. thread = threading.Thread(target=target)
  40. thread.start()
  41. thread.join(timeout)
  42. if thread.is_alive():
  43. print("ran into timeout")
  44. thread.join()
  45. def tap(x, y=None):
  46. # Check if x is an int
  47. if isinstance(x, int):
  48. if not isinstance(y, int):
  49. raise ValueError("y must be an int when x is an int")
  50. # Construct the location string from both x and y
  51. location = f"{x} {y}"
  52. # Check if x is a string
  53. elif isinstance(x, str):
  54. location = x
  55. elif isinstance(x, tuple):
  56. location = f"{x[0]} {x[1]}"
  57. else:
  58. raise TypeError("x must be either an int or a string")
  59. # Assuming 'device' is a previously defined object with a 'shell' method
  60. action = f"input tap {location}"
  61. print(action)
  62. call_device_shell(action, timeout=5)
  63. def tap_button(template):
  64. button = find_template(template)
  65. if len(button) == 0:
  66. return
  67. tap(f"{button[0][0]} {button[0][1]}")
  68. def swipe(start, end, duration=1000):
  69. action = f"input swipe {start} {end} {duration}"
  70. print(action)
  71. call_device_shell(action, timeout=5)
  72. def look_for_templates(templates):
  73. for name, template in templates.items():
  74. locations = find_template(template)
  75. if len(locations) > 0:
  76. return name, locations
  77. return None, None
  78. def first_template(template_image):
  79. result = find_template(template_image)
  80. if len(result) > 0:
  81. return result[0]
  82. return None
  83. def find_template(template_image):
  84. if with_cuda == 1:
  85. # Ensure the images are in the correct format (BGR for OpenCV)
  86. target_image = capture_current_screen()
  87. # Upload images to GPU
  88. target_image_gpu = cv2.cuda_GpuMat()
  89. template_image_gpu = cv2.cuda_GpuMat()
  90. target_image_gpu.upload(target_image)
  91. template_image_gpu.upload(template_image)
  92. # Perform template matching on the GPU
  93. result_gpu = cv2.cuda.createTemplateMatching(cv2.CV_8UC3, cv2.TM_CCOEFF_NORMED)
  94. result = result_gpu.match(target_image_gpu, template_image_gpu)
  95. # Download result from GPU to CPU
  96. result = result.download()
  97. else:
  98. target_image = Image.open(BytesIO(get_current_screen()))
  99. # Convert the image to a NumPy array and then to BGR format (which OpenCV uses)
  100. target_image = np.array(target_image)
  101. target_image = cv2.cvtColor(target_image, cv2.COLOR_RGB2BGR)
  102. h, w = template_image.shape[:-1]
  103. # Template matching
  104. result = cv2.matchTemplate(target_image, template_image, cv2.TM_CCOEFF_NORMED)
  105. # Define a threshold
  106. threshold = 0.9 # Adjust this threshold based on your requirements
  107. # Finding all locations where match exceeds threshold
  108. locations = np.where(result >= threshold)
  109. locations = list(zip(*locations[::-1]))
  110. # Create list of rectangles
  111. rectangles = [(*loc, loc[0] + w, loc[1] + h) for loc in locations]
  112. # Apply non-maximum suppression to remove overlaps
  113. rectangles = non_max_suppression(rectangles, 0.3)
  114. # Initialize an empty list to store coordinates
  115. coordinates = []
  116. for startX, startY, endX, endY in rectangles:
  117. # Append the coordinate pair to the list
  118. coordinates.append(find_center(startX, startY, endX, endY))
  119. # Sort the coordinates by y value in ascending order
  120. return sorted(coordinates, key=lambda x: x[1])
  121. def non_max_suppression(boxes, overlapThresh):
  122. if len(boxes) == 0:
  123. return []
  124. # Convert to float
  125. boxes = np.array(boxes, dtype="float")
  126. # Initialize the list of picked indexes
  127. pick = []
  128. # Grab the coordinates of the bounding boxes
  129. x1 = boxes[:, 0]
  130. y1 = boxes[:, 1]
  131. x2 = boxes[:, 2]
  132. y2 = boxes[:, 3]
  133. # Compute the area of the bounding boxes and sort by bottom-right y-coordinate
  134. area = (x2 - x1 + 1) * (y2 - y1 + 1)
  135. idxs = np.argsort(y2)
  136. # Keep looping while some indexes still remain in the indexes list
  137. while len(idxs) > 0:
  138. # Grab the last index in the indexes list and add the index value to the list of picked indexes
  139. last = len(idxs) - 1
  140. i = idxs[last]
  141. pick.append(i)
  142. # Find the largest (x, y) coordinates for the start of the bounding box and the smallest (x, y)
  143. # coordinates for the end of the bounding box
  144. xx1 = np.maximum(x1[i], x1[idxs[:last]])
  145. yy1 = np.maximum(y1[i], y1[idxs[:last]])
  146. xx2 = np.minimum(x2[i], x2[idxs[:last]])
  147. yy2 = np.minimum(y2[i], y2[idxs[:last]])
  148. # Compute the width and height of the bounding box
  149. w = np.maximum(0, xx2 - xx1 + 1)
  150. h = np.maximum(0, yy2 - yy1 + 1)
  151. # Compute the ratio of overlap
  152. overlap = (w * h) / area[idxs[:last]]
  153. # Delete all indexes from the index list that have overlap greater than the threshold
  154. idxs = np.delete(
  155. idxs, np.concatenate(([last], np.where(overlap > overlapThresh)[0]))
  156. )
  157. # Return only the bounding boxes that were picked
  158. return boxes[pick].astype("int")
  159. client = AdbClient(host="127.0.0.1", port=5037)
  160. device = client.device(android_address)
  161. current_screen = capture_current_screen()