
1實驗環境
操作系統:Windows10
編碼語言:Python3.6
編譯平台:Pycharm
Python庫:os、datetime、matplotlib、opencv-python、time
2實驗代碼流程圖
3代碼運行步驟和結果等
3.1 手機和電腦用數據線連接
使用通過數據線連接手機,將開發者模式打開並授權
通過adb命令
adb devices |
可以查看連接的Android設備的信息
3.2 獲取手機相關的信息
查看Android手機的分辨率(第四行)
adb shell dumpsys window displays |
獲取屏幕密度
adb shell wm density |
獲取手機型號
adb shell getprop ro.product.device |
獲取Android系統的版本
adb shell getprop ro.build.version.release |
3.3 截屏
輸入如下命令:
adb shell screencap -p /sdcard/auto.png |
此時,截屏的圖片就保存到 /sdcard/auto.png文件中。
注意:/sdcard/和/data/目錄是可以寫入的。
可以通過命令查看sdcard目錄下所有的文件。
adb shell ls /sdcard/ -l |
通過如下命令把手機上的文件拷貝到電腦上
adb pull /sdcard/auto.png h:\ |
此時,圖片就會被拷貝到h:\根目錄下了。打開即可看到當前手機的屏幕信息。
3.4 屏幕點擊事件
通過如下命令模擬手機的滑動事件
adb shell input swipe x1 y1 x2 y2 duration |
通過adb shell input swipe命令進行滑動
l x1、y1:滑動開始的點。
l x2、y2:滑動結束的點。
l duration:持續的時間(單位ms)。
特殊情況下:如果不寫duration參數,就理解為點擊事件。如果寫duration,然后x1y1和x2y2是相同的點,就表示長按。
跳一跳關鍵是:duration的值的計算。
嘗試:
adb shell input swipe 100 100 100 100 700 |
嘗試修改duration的值,看看跳的效果。
求得可以拿到加分的中間值。比如555~871都可以拿到加分(555以下和871以上就不能拿到加分),此時則取中間值為(555+871)/2=713 作為后面計算的參考值。
3.5 duration值的計算
假設我們截屏的效果是如下:
從圖中可以看到,時間的值跟開始位置到結束位置的距離有關。
假設時間是t,距離是s。公式應該是s = at
基本思路:兩點之間的距離乘以一個時間系數。
所以要從截圖上識別出起跳位置的坐標(x1,y1)和目標位置的坐標(x2,y2)。
起跳位置的坐標:小人的底座中心點
目標位置的坐標:目標菱形的中心點
然后計算這兩點之間的距離(歐氏距離):sqrt((x1-x2)2+(y1-y2)2)
3.6 尋找關鍵坐標——起跳坐標
算法策略:獲取小人的底座中心點的值作為起跳點。
1 獲取小人的所有像素點中y坐標的最大值
2 在小人y坐標的最大值那些像素點中,計算出x的平均值,作為小人底座的x的值。
3 y坐標的最大值減去一個偏移值,就作為小人底座的y值。(注意:該偏移值不同的設備是不同的,同一台設備不同場景下是一樣的)
比如教師機的設備中最低點的值是(410,1162),中心值是(410,1142),從而計算出偏移值為1162-1142=20
3.7 獲取目標坐標的y值
取屏幕寬和高的一半(x=540和y=960)
我們會發現,目標格子的邊沿(x=560,y=980)和這個是差不多的(y的偏差是20,x的偏差是20)
以后每次跳動的時候,假如已經知道目標格子的邊沿,和目標坐標的x值,就可以很輕松計算出目標坐標的y值。
注意:每個格子的寬和高的比例是相同的。
方形:左:(560,848) 園:左:(251,876)
右:(1015,848) 右:(522,876)
上:(790,718) 上:(388,799)
下:(790,980) 下:(388,957)
中:(790,850) 中:(388,876)
高和寬的比例:(980-718)/(1015-560) =262/455=0.576。假設該值為p
最后,由已知的目標坐標的x值,求目標坐標的y值。
先附上運行結果,以及截圖信息:
在理解了跳一跳的基本思路之后,現在附上完整代碼(有注釋):
# main.py
# _*_ coding:utf-8 _*_ __author__ = 'WoLykos' from operations import * from draw import * from algorithm import * import time import random # 測試截屏 # def test_screen_cap(): # op = Operation() # op.screen_cap() # 測試顯示圖片 def test_show_pic(): draw = Draw() draw.show_pic("img/auto.png") # 測試計算歐式距離 def test_euclidean_distance(): algorithm = Algorithm() p1 = (3, 4) p2 = (6, 8) d = algorithm.euclidean_distance(p1, p2) print(d) # 測試尋找關鍵坐標 def test_find_point(): op = Operation() im = op.screen_cap() algorithm = Algorithm() start_x, start_y, end_x, end_y = algorithm.find_point(im) print("start_point:", start_x, start_y) print("end_point:", end_x, end_y) start_point = (start_x, start_y) end_point = (end_x, end_y) distance = algorithm.euclidean_distance(start_point, end_point) # print(distance) press_time = algorithm.distance_to_time(distance) op.jump(start_point, end_point, press_time) if __name__ == "__main__": # test_screen_cap() # test_show_pic() while True: # test_euclidean_distance() test_find_point() time.sleep(1 + 2*random.random())
# algorithm.py # _*_ coding:utf-8 _*_ __author__ = 'WoLykos' class Algorithm: # 構造器 def __int__(self): pass # 計算兩點之間的歐氏距離 # p1和p2表示兩個點 用元組來表示 def euclidean_distance(self, p1, p2): return ((p1[0]-p2[0])**2+(p1[1]-p2[1])**2)**0.5 # ((p1[0]-p2[0])**2+(p1[1]-p2[1])**2)**0.5 # 尋找關鍵坐標 # 返回值1,2 piece_x, piece_y 起跳點的坐標 170,555 # 返回值3,4 board_x, board_y 目標點的坐標 395,425 def find_point(self, im): # piece_x = piece_y = 0 # board_x = board_y = 0 # 圖像的大小 w, h = im.size # (1080,1920) # 加載圖像 im_pixel = im.load() # 記錄小人所有的點 points = [] # 記錄y的最大值 piece_y_max = 0 # 1 計算出起跳點 就是小人底座的中心點 # 1.1 獲取小人的所有像素點中y坐標的最大值 # 遍歷圖像中的每一個點 # 遍歷每一行 for i in range(h // 3, h * 2 // 3): # 遍歷每一列 for j in range(w): pixel = im_pixel[j, i] # print("i = ", i, ",j = ", j, "pixel = ", pixel) # 判斷pixel是否小人所在的位置 # 當該點的RGB值約為56,56,82的時候就可以認為是小人所在的像素點了 if (51 < pixel[0] < 61 and 51 < pixel[1] < 61 and 72 < pixel[2] < 102): # 把當前的點添加到points數組中 points.append((j, i)) # (x,y) # 記錄下y的值 if i > piece_y_max: piece_y_max = i # print("piece_y_max = %d" % (piece_y_max,)) # 1.2 在小人y坐標的最大值那些像素點中,計算出x的平均值,作為小人底座的x的值。 bottom_x = [] for x, y in points: if y == piece_y_max: bottom_x.append(x) piece_x = sum(bottom_x) // len(bottom_x) # print("piece_x = %d" % (piece_x,)) # 1.3 y坐標的最大值減去一個偏移值,就作為小人底座的y值。(注意:該偏移值不同的設備是不同的,同一台設備不同場景下是一樣的) piece_y = piece_y_max - 20 # 偏移值1130-110=20 # print("piece_y = %d" % (piece_y,)) # 2計算 目標格子的中心點 # 2.1計算目標格子的x值 points = [] # 只取中間1/3進行掃描 for i in range(h // 3, h * 2 // 3): if len(points) > 0: break # 取坐標的一個點作為背景的參照物 last_pixel = im_pixel[0, i] # 逐個掃描右邊的點 for j in range(w): pixel = im_pixel[j, i] # 把當前點與最左邊的點比較 如果RGB差異比較大 則認為是目標點 # 排除該點為小人像素點56,56,82的可能性,BUG if not (54 < pixel[0] < 141 and 54 < pixel[1] < 130 and 69 < pixel[2] < 172): if (abs(pixel[0] - last_pixel[0]) + abs(pixel[1] - last_pixel[1]) + abs(pixel[2] - last_pixel[2]) > 10): points.append((j, i)) top_x = [] for x, y in points: top_x.append(x) board_x = sum(top_x) // len(top_x) # print("board_x = %d" % (board_x,)) # 2.2計算目標格式子y值 # 屏幕中心的值 center_x = w / 2 + 20 # x的偏差是20 center_y = h / 2 + 20 # y的偏差是20,園 # 格子高和寬的比例 height_per_width = 262 / 455 # 計算出目標格子的y值(需要轉換成整數) # 從piece_x調到board_x 如果piece_x < board_x則表示從左往右跳 # 如果piece_x > board_x 則表示從右往左跳 if piece_x < board_x: board_y = int(center_y - height_per_width * (board_x - center_x)) else: # 從右往左跳 board_y = int(center_y + height_per_width * (board_x - center_x)) # print("board_y = %d" % (board_y,)) return piece_x, piece_y, board_x, board_y # 距離與時間的轉換 def distance_to_time(self, distance): # 當0分的時候 距離為 527.5234591939964 時間為713 p = 713 / 527.5234591939964 # 該算法后面待優化 press_time = distance * p return press_time
# operations.py # _*_ coding:utf-8 _*_ __author__ = 'WoLykos' import os import datetime from PIL import Image # 實現控制安卓 class Operation: # 構造方法 def __int__(self): pass # 截屏 def screen_cap(self): filename = time = datetime.datetime.now().strftime("%H%M%S")+".png" # 截屏並保存到手機 cmd = "adb shell screencap -p /sdcard/auto.png" os.system(cmd) # 拷貝到電腦 cmd = "adb pull /sdcard/auto.png "+"img/"+filename os.system(cmd) # 打開圖像文件 return Image.open("img/"+filename) # 控制屏幕進行跳動 def jump(self, src, dst, press_time): # print(press_time) press_time = int(press_time) cmd = "adb shell input swipe %d %d %d %d %d" % ( int(src[0]), int(src[1]), int(dst[0]), int(dst[1]), press_time ) print(cmd) os.system(cmd)
大功告成!!
謝謝各位。。