1. 程式人生 > >跳一跳python輔助軟體思路及原始碼解析

跳一跳python輔助軟體思路及原始碼解析

跳一跳python輔助軟體思路及影象識別原始碼解析

本文將梳理github上最火的wechat_jump_game的實現思路,並解析其影象處理部分原始碼

首先廢話少說先看效果
執行效果圖

核心思想

獲取棋子到下一個方塊的中心點的距離
計算觸控式螢幕幕的時間
點選螢幕

重要方法

計算棋子到下一個方塊中心點的距離

  • 使用 adb shell screencap -p 命令獲取手機當前螢幕畫面
  • 再通過影象上的資訊找出棋子的座標和下一個方塊中心點的座標
  • 然後通過兩點間距離公式計算出距離

計算觸控式螢幕幕的時間

  • T=A * S
    其中S為上步算出的畫素距離,T為按壓時間(ms),A為一個係數這個係數會隨著螢幕解析度的變化而變化,在1920*1080的螢幕下這個係數為1.35,在2560*1440的螢幕下這個係數為1.475

點選螢幕

  • adb shell input swipe x y x y time(ms)
    這條命令能夠點選手機螢幕x,y位置time(ms)

影象處理部分原始碼解析

影象處理部分程式碼都在 find_piece_and_board(im) 方法中

通過輸入的影象im計算出棋子的座標點以及下一個方塊中心的座標點

在find_piece_and_board的方法中一進來就是下面的兩個巢狀在一起的for迴圈:

    for i in range(int(h / 3), int(h * 2 / 3), 50):
        last_pixel = im_pixel[0, i]
        for
j in range(1, w): pixel = im_pixel[j, i] # 不是純色的線,則記錄 scan_start_y 的值,準備跳出迴圈 if pixel[0] != last_pixel[0] or pixel[1] != last_pixel[1] or pixel[2] != last_pixel[2]: scan_start_y = i - 50 break if scan_start_y: break

這段程式碼的作用就是從螢幕2/3的位置向下尋找不是純色的線。並將找到位置的縱座標-50作為,尋找棋子和方塊的起始座標。這樣可以簡化以後搜尋的工作量,因為在這個橫座標以上是沒有東西的。

接下來是查詢棋子座標的程式碼

# 查詢棋子座標
    # piece_x_sum 橫座標總量 piece_x_c 點的個數 piece_y_max 縱座標最大值
    # 從 scan_start_y 開始往下掃描,棋子應位於螢幕上半部分,這裡暫定不超過 2/3
    for i in range(scan_start_y, int(h * 2 / 3)):
        for j in range(scan_x_border, w - scan_x_border):  # 橫座標方面也減少了一部分掃描開銷
            pixel = im_pixel[j, i]
            # 根據棋子的最低行的顏色判斷,找最後一行那些點的平均值,這個顏色這樣應該 OK,暫時不提出來
            if (50 < pixel[0] < 60) and (53 < pixel[1] < 63) and (95 < pixel[2] < 110):
                piece_x_sum += j
                piece_x_c += 1
                piece_y_max = max(i, piece_y_max)

    if not all((piece_x_sum, piece_x_c)):
        return 0, 0, 0, 0

    # 平均橫座標
    piece_x = int(piece_x_sum / piece_x_c)
    # 縱座標最大值-底座一半的高度
    piece_y = piece_y_max - piece_base_height_1_2  # 上移棋子底盤高度的一半

查詢棋子的重要依據就是棋子的顏色較為單一併且和方塊的顏色有較大差距。如果一個畫素點的RGB畫素值在B(50, 60), G(53, 63), R(95, 110)範圍內那麼就認為這個畫素點是屬於棋子的。根據以上資訊就能計算出棋子的平均橫座標,以及最大的縱座標值。

所以不難計算出棋子座標(棋子平均橫座標, 棋子最大縱座標 - 底座一半的高度)其中底座一半的高度因手機解析度而異。需要提前配置好。

最後是查詢下一個方塊中心點的座標的程式碼

# 尋找最高的棋盤
    # 棋盤不會和棋子在同一側
    # 限制棋盤掃描的橫座標,避免音符 bug
    if piece_x < w / 2:
        board_x_start = piece_x
        board_x_end = w
    else:
        board_x_start = 0
        board_x_end = piece_x

    for i in range(int(h / 3), int(h * 2 / 3)):
        last_pixel = im_pixel[0, i]
        if board_x or board_y:
            break
        board_x_sum = 0
        board_x_c = 0

        for j in range(int(board_x_start), int(board_x_end)):
            pixel = im_pixel[j, i]

            # 下一個棋盤緊貼著棋子
            # 修掉腦袋比下一個小格子還高的情況的 bug
            if abs(j - piece_x) < piece_body_width:
                continue

            # 修掉圓頂的時候一條線導致的小 bug,這個顏色判斷應該 OK,暫時不提出來
            if abs(pixel[0] - last_pixel[0]) + abs(pixel[1] - last_pixel[1]) + abs(pixel[2] - last_pixel[2]) > 10:
                board_x_sum += j
                board_x_c += 1
        if board_x_sum:
            # 最高棋盤的平均橫座標
            board_x = board_x_sum / board_x_c
    last_pixel = im_pixel[board_x, i]

程式碼開頭通過棋子所在的螢幕位置限制搜尋的寬度,如果棋子在螢幕左邊那麼就在螢幕右邊搜尋方塊,反之亦然。因為方塊和棋子不會在螢幕同一側。

然後就是自上而下得搜尋方塊的上頂點。
方塊上頂點座標( 平均橫座標,當前行的縱座標)

然後再往下縱座標+247的位置開始向上找顏色與上頂點一樣的點,為下頂點。
當然此方法有一點侷限性對於純色的平面效果很好但是對於非純色的平面。可能會判斷出錯。

如果上一跳命中中間,則下個目標中心會出現 r245 g245 b245 的點,利用這個屬性彌補上一段程式碼可能存在的判斷錯誤
若上一跳由於某種原因沒有跳到正中間,而下一跳恰好有無法正確識別花紋,則有可能遊戲失敗,由於花紋面積通常比較大,失敗概率較低

可改進方案

首先是目前方案對於多解析度需要多個配置檔案來記錄不同解析度下的係數以及棋子底盤一半的高度。隨機測試了6臺手機其中有兩臺手機因沒有配飾而無法正常運作

首先是係數A,觀察方程T=A * S,A就是一個可訓練量,利用機器學習框架比如TensorFlow,對這個一元一次方程進行擬合。

觀察棋子底盤一半的高度在程式碼中的作用。不難發現是為了求出棋子底盤中心的縱座標。而棋子底盤中心的位置恰恰是棋子最寬的地方。所以可以通過找出棋子最寬處的縱座標的方式找到棋子底盤中心的縱座標。這樣就擺脫了對配置檔案的依賴,能讓程式碼在任何手機上正常執行。

其次是對於方塊中心座標位置的判斷方法出錯率較高,雖然有中心白點可以彌補但是在大量跳躍的過程中還是會出現錯誤。3太手機不停運行了一下午,最高分只有2009分。

現方法出錯率高的原因是使用純顏色方法判斷,但是在實際遊戲中顏色豐富的方塊也不少。如果想改變就不能依賴顏色方法判斷,而應該通過幾何影象的形狀來計算方塊的位置。不難發現遊戲中方塊只有稜形和圓形兩種形狀。

首先通過canny或其他輪廓查詢運算元提取出影象的輪廓,然後通過霍夫變換提取出圓形和稜形的中心座標。

以上是我個人的一些見解。歡迎大家評論區一起探討學習