歡迎光臨
每天分享高質量文章

AI 玩微信跳一跳的正確姿勢:跳一跳 Auto-Jump 演演算法詳解

作者丨安捷 & 肖泰洪

學校丨北京大學碩士生

研究方向丨計算機視覺

本文經授權轉載自知乎專欄「學術興趣小組」。

最近,微信小遊戲跳一跳可以說是火遍了全國,從小孩子到大孩子彷彿每一個人都在刷跳一跳,作為無(zhi)所(hui)不(ban)能(zhuan)的 AI 程式員,我們在想,能不能用人工智慧(AI)和計算機視覺(CV)的方法來玩一玩這個遊戲?


於是,我們開發了微信跳一跳 Auto-Jump 演演算法,重新定義了玩跳一跳的正確姿勢,我們的演演算法不僅遠遠超越了人類的水平,在速度和準確度上也遠遠超越了目前已知的所有演演算法,可以說是跳一跳界的 state-of-the-art,下麵我們詳細介紹我們的演演算法。


演演算法的第一步是獲取手機螢幕的截圖並可以控制手機的觸控操作,我們的 github 倉庫裡詳細介紹了針對 Android 和 IOS 手機的配置方法。


Github 地址:


https://github.com/Prinsphield/Wechat_AutoJump


你只需要按照將手機連線電腦,按照教程執行就可以完成配置。在獲取到螢幕截圖之後,就是個簡單的視覺問題。我們需要找的就是小人的位置和下一次需要跳的臺面的中心。


如圖所示,綠色的點代表小人當前的位置,紅點代表標的位置。


多尺度搜索 Multiscale Search


這個問題可以有非常多的方法去解,為了糙快猛地刷上榜,我一開始用的方式是多尺度搜索。我隨便找了一張圖,把小人摳出來,就像下麵這樣。

另外,我註意到小人在螢幕的不同位置,大小略有不同,所以我設計了多尺度的搜尋,用不同大小的進行匹配,最後選取置信度(confidence score)最高的。 


多尺度搜索的程式碼長這樣:

def multi_scale_search(pivot, screen, range=0.3, num=10):
   H, W = screen.shape[:2]
   h, w = pivot.shape[:2]

   found = None
   for scale in np.linspace(1-range, 1+range, num)[::-1]:
       resized = cv2.resize(screen, (int(W * scale), int(H * scale)))
       r = W / float(resized.shape[1])
       if resized.shape[0] < h or resized.shape[1] < w:
           break
       res = cv2.matchTemplate(resized, pivot, cv2.TM_CCOEFF_NORMED)

       loc = np.where(res >= res.max())
       pos_h, pos_w = list(zip(*loc))[0]

       if found is None or res.max() > found[-1]:
           found = (pos_h, pos_w, r, res.max())

   if found is None: return (0,0,0,0,0)
   pos_h, pos_w, r, score = found
   start_h, start_w = int(pos_h * r), int(pos_w * r)
   end_h, end_w = int((pos_h + h) * r), int((pos_w + w) * r)
   return [start_h, start_w, end_h, end_w, score]

我們來試一試,效果還不錯,應該說是又快又好,我所有的實驗中找小人從來沒有失誤。


不過這裡的位置框的底部中心並不是小人的位置,真實的位置是在那之上一些。


同理,標的臺面也可以用這種辦法搜尋,但是我們需要收集一些不同的臺面,有圓形的,方形的,便利店,井蓋,稜柱等等。由於數量一多,加上多尺度的原因,速度上會慢下來。


這時候,我們就需要想辦法加速了。首先可以註意到標的位置始終在小人的位置的上面,所以可以操作的一點就是在找到小人位置之後把小人位置以下的部分都捨棄掉,這樣可以減少搜尋空間。


但是這還是不夠,我們需要進一步去挖掘遊戲裡的故事。小人和標的臺面基本上是關於螢幕中心對稱的位置的。這提供了一個非常好的思路去縮小搜尋空間。


假設螢幕解析度是(1280,720)的,小人底部的位置是(h1, w1),那麼關於中心對稱點的位置就是(1280 – h1, 720 – w1),以這個點為中心的一個邊長 300 的正方形內,我們再去多尺度搜索標的位置,就會又快有準了。


效果見下圖,藍色框是(300,300)的搜尋區域,紅色框是搜到的臺面,矩形中心就是標的點的坐標了。


加速的奇技淫巧(Fast-Search)


玩遊戲需要細心觀察。我們可以發現,小人上一次如果跳到臺面中心,那麼下一次標的臺面的中心會有一個白點,就像剛才所展示的圖裡的。


更加細心的人會發現,白點的 RGB 值是(245,245,245),這就讓我找到了一個非常簡單並且高效的方式,就是直接去搜索這個白點,註意到白點是一個連通區域,畫素值為(245,245,245)的畫素個數穩定在 280-310 之間,所以我們可以利用這個去直接找到標的的位置。


這種方式只在前一次跳到中心的時候可以用,不過沒有關係,我們每次都可以試一試這個不花時間的方法,不行再考慮多尺度搜索。


講到這裡,我們的方法已經可以執行的非常出色了,基本上是一個永動機。下麵是用我的手機玩了一個半小時左右,跳了 859 次的狀態,我們的方法正確的計算出來了小人的位置和標的位置,不過我選擇狗帶了,因為手機卡的已經不行了。

以下是效果演示:

到這裡就結束了嗎?那我們和業餘玩家有什麼區別?下麵進入正經的學術時間,非戰鬥人員請迅速撤離。


CNN Coarse-to-Fine 模型


考慮到 iOS 裝置由於螢幕抓取方案的限制(WebDriverAgent 獲得的截圖經過了壓縮,影象畫素受損,不再是原來的畫素值,原因不詳,歡迎瞭解詳情的小夥伴提出改進意見)無法使用 fast-search,同時為了相容多解析度裝置,我們使用摺積神經網路構建了一個更快更魯棒的標的檢測模型。


下麵分資料採集與預處理,coarse 模型,fine 模型,cascade 四部分介紹我們的演演算法。

資料採集與預處理

基於我們非常準確的 multiscale-search 和 fast-search 模型,我們採集了 7 次實驗資料,共計大約 3000 張螢幕截圖,每一張截圖均帶有標的位置標註,對於每一張圖,我們進行了兩種不同的預處理方式,並分別用於訓練 coarse 模型和 fine 模型,下麵分別介紹兩種不同的預處理方式。

Coarse 模型資料預處理

由於每一張影象中真正對於當前判斷有意義的區域只在螢幕中央位置,即人和標的物體所在的位置,因此,每一張截圖的上下兩部分都是沒有意義的。


於是,我們將採集到的大小為 1280*720 的影象沿 x 方向上下各截去 320*720 大小,只保留中心 640*720 的影象作為訓練資料。


我們觀察到,遊戲中,每一次當小人落在標的物中心位置時,下一個標的物的中心會出現一個白色的圓點。

考慮到訓練資料中 fast-search 會產生大量有白點的資料,為了杜絕白色圓點對網路訓練的幹擾,我們對每一張圖進行了去白點操作,具體做法是,用白點周圍的純色畫素填充白點區域。

Fine 模型資料預處理

為了進一步提升模型的精度,我們為 fine 模型建立了資料集,對訓練集中的每一張圖,在標的點附近擷取 320*320 大小的一塊作為訓練資料。

為了防止網路學到 trivial 的結果,我們對每一張圖增加了 50 畫素的隨機偏移。fine 模型資料同樣進行了去白點操作。


Coarse 模型

我們把這一問題看成了回歸問題,coarse 模型使用一個摺積神經網路回歸標的的位置。

def forward(self, img, is_training, keep_prob, name='coarse'):
   with tf.name_scope(name):
       with tf.variable_scope(name):
           out = self.conv2d('conv1', img, [3, 3, self.input_channle, 16], 2)
           # out = tf.layers.batch_normalization(out, name='bn1', training=is_training)
           out = tf.nn.relu(out, name='relu1')

           out = self.make_conv_bn_relu('conv2', out, [3, 3, 16, 32], 1, is_training)
           out = tf.nn.max_pool(out, [1, 2, 2, 1], [1, 2, 2, 1], padding='SAME')

           out = self.make_conv_bn_relu('conv3', out, [5, 5, 32, 64], 1, is_training)
           out = tf.nn.max_pool(out, [1, 2, 2, 1], [1, 2, 2, 1], padding='SAME')

           out = self.make_conv_bn_relu('conv4', out, [7, 7, 64, 128], 1, is_training)
           out = tf.nn.max_pool(out, [1, 2, 2, 1], [1, 2, 2, 1], padding='SAME')

           out = self.make_conv_bn_relu('conv5', out, [9, 9, 128, 256], 1, is_training)
           out = tf.nn.max_pool(out, [1, 2, 2, 1], [1, 2, 2, 1], padding='SAME')

           out = tf.reshape(out, [-1, 256 * 20 * 23])
           out = self.make_fc('fc1', out, [256 * 20 * 23, 256], keep_prob)
           out = self.make_fc('fc2', out, [256, 2], keep_prob)

   return out

經過十小時的訓練,coarse 模型在測試集上達到了 6 畫素的精度,實際測試精度大約為 10 畫素,在測試機器(MacBook Pro Retina, 15-inch, Mid 2015, 2.2 GHz Intel Core i7)上 inference 時間 0.4 秒。


這一模型可以很輕鬆的拿到超過 1k 的分數,這已經遠遠超過了人類水平和絕大多數自動演演算法的水平,日常娛樂完全夠用,不過,你認為我們就此為止那就大錯特錯了。


Fine 模型

Fine 模型結構與 coarse 模型類似,引數量稍大,fine 模型作為對 coarse 模型的 refine 操作。

def forward(self, img, is_training, keep_prob, name='fine'):
   with tf.name_scope(name):
       with tf.variable_scope(name):
           out = self.conv2d('conv1', img, [3, 3, self.input_channle, 16], 2)
           # out = tf.layers.batch_normalization(out, name='bn1', training=is_training)
           out = tf.nn.relu(out, name='relu1')

           out = self.make_conv_bn_relu('conv2', out, [3, 3, 16, 64], 1, is_training)
           out = tf.nn.max_pool(out, [1, 2, 2, 1], [1, 2, 2, 1], padding='SAME')

           out = self.make_conv_bn_relu('conv3', out, [5, 5, 64, 128], 1, is_training)
           out = tf.nn.max_pool(out, [1, 2, 2, 1], [1, 2, 2, 1], padding='SAME')

           out = self.make_conv_bn_relu('conv4', out, [7, 7, 128, 256], 1, is_training)
           out = tf.nn.max_pool(out, [1, 2, 2, 1], [1, 2, 2, 1], padding='SAME')

           out = self.make_conv_bn_relu('conv5', out, [9, 9, 256, 512], 1, is_training)
           out = tf.nn.max_pool(out, [1, 2, 2, 1], [1, 2, 2, 1], padding='SAME')

           out = tf.reshape(out, [-1, 512 * 10 * 10])
           out = self.make_fc('fc1', out, [512 * 10 * 10, 512], keep_prob)
           out = self.make_fc('fc2', out, [512, 2], keep_prob)

   return out

經過十小時訓練,fine 模型測試集精度達到了 0.5 畫素,實際測試精度大約為 1 畫素,在測試機器上的 inference 時間 0.2 秒。

Cascade


總體精度 1 畫素左右,時間 0.6 秒。


總結

針對這一問題,我們利用 AI 和 CV 技術,提出了合適適用於 iOS 和 Android 裝置的完整解決方案,稍有技術背景的使用者都可以實現成功配置、執行。


我們提出了 Multiscale-Search,Fast-Search 和 CNN Coarse-to-Fine 三種解決這一問題的演演算法,三種演演算法相互配合,可以實現快速準確的搜尋、跳躍,使用者針對自己的裝置稍加調整跳躍引數即可接近實現“永動機”。


講到這裡,似乎可以宣佈,我們的工作 terminate 了這個問題,微信小遊戲跳一跳 Game Over! 


友情提示:適度遊戲益腦,沉迷遊戲傷身,技術手段的樂趣在於技術本身而不在遊戲排名,希望大家理性對待遊戲排名和本文提出的技術,用遊戲娛樂自己的生活。


宣告:本文提出的演演算法及開原始碼符合 MIT 開源協議,以商業目的使用該演演算法造成的一切後果須由使用者本人承擔。



  我是彩蛋 


解鎖新功能:熱門職位推薦!

PaperWeekly小程式升級啦

今日arXiv√猜你喜歡√熱門職位

找全職找實習都不是問題

 

 解鎖方式 

1. 識別下方二維碼開啟小程式

2. 用PaperWeekly社群賬號進行登陸

3. 登陸後即可解鎖所有功能

 職位釋出 

請新增小助手微信(pwbot01)進行諮詢

 

長按識別二維碼,使用小程式

*點選閱讀原文即可註冊

           

關於PaperWeekly


PaperWeekly 是一個推薦、解讀、討論、報道人工智慧前沿論文成果的學術平臺。如果你研究或從事 AI 領域,歡迎在公眾號後臺點選「交流群」,小助手將把你帶入 PaperWeekly 的交流群裡。


贊(0)

分享創造快樂