helper.py 5.6 KB

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