我用Python合成大西瓜!
作者?小雨姑娘 康尼狄格大學(xué) 來源 Datawhale
頂級程序員 | 公眾號 Topcoding

這周五沒漂亮妹妹帶我出去玩了嗚嗚,無聊在家扣手機,發(fā)現(xiàn)大家都在合成大西瓜 。作為一個未來年輕無為的計算機科學(xué)家(或許是人民藝術(shù)家),我是不屑于玩這種浪費時間又無聊的游戲的(因為玩了四小時才合成了第一個大西瓜),但為了投身到人民群眾中去,我決定嘗試寫一個程序掛機跑一下。
寫了一下午,我的第一版Python大西瓜終于寫完了,雖然還是挺智障的,但至少打敗了50%的人,也算通過圖靈測試了哈哈哈哈哈哈。其實是怕過兩天我寫個最終版,大家都玩夠了,小丑就是我自己了,為了吃一口熱乎的s**t,我就現(xiàn)在發(fā)了哈哈哈哈。
如果下周五還有人玩這游戲,我還會繼續(xù)寫的。
本文章分為六個部分(如果我能堅持寫完不睡著的話):
圖像捕捉 水果檢測 水果識別 去除噪聲 點擊控制 游戲策略
話不多說,開沖!

1. 圖像捕捉
作為一個計算機程序,合成大西瓜的第一步就是能夠看到大西瓜

這一步超級簡單了,只需要使用python自帶的windows圖形界面接口win32gui就可以操作。其中最重要的環(huán)節(jié)就是獲取瀏覽器窗口的上下左右四個頂點的坐標(biāo),調(diào)用FindWindow方法獲取窗口對象,然后把窗口對象傳給GetWindowRect獲取頂點坐標(biāo)。
這地方我卡了很久,因為FindWindow需要首先知道窗口的名字,我試了試“Edge”不對,“合成大西瓜”也不對,最后機智的我打開了Alt + del,發(fā)現(xiàn)它叫“小游戲:合成大西瓜 - 個人 - Microsoft\u200b Edge”仿佛在對我說:“我不叫喂,我叫……”
然后再用PIL庫的ImageGrab方法截圖屏幕窗口內(nèi)的像素,轉(zhuǎn)化成numpy數(shù)組,就可以進(jìn)行后續(xù)處理啦。
2. 水果檢測
在我們獲取到圖像后,下一步就是識別水果的位置了,一個自然的思路就是識別圓,因為這里的水果都是圓的(我真謝謝作者沒有加個香蕉。什么?你說甘蔗也是水果……)
然后我還想吐槽一下,下面這個到底是什么東西啊?是兩排牙齒嗎?有沒有人和我一樣覺得莫名很惡心?而且這玩意轉(zhuǎn)換成灰度圖以后更惡心……

Ok 基于這個思路,我們只需要找到圖里面的圓形就好啦!
這里的思路是OpenCV里面已經(jīng)實現(xiàn)好的經(jīng)典圓形識別方法——霍夫圓檢測法(看清楚,不是霍夫曼,不是霍夫曼,不是霍夫曼)。基本思路就是找?guī)讉€邊緣點,然后邊走邊畫圓,最后看圓心是不是在一起吧……我不是搞CV的,如果有錯誤請大佬們評論區(qū)指正。

這一步主要有兩個難點吧。第一個就是水果都疊在一起,兩個圓形很容易連通在一起,從而找不到圓。我是通過設(shè)置一個13乘13的高斯濾波器,先把邊緣模糊一下,然后再提取會好很多。
第二個難點是cv2.HoughCircles有三四個要調(diào)整的參數(shù),怎么選對最后的識別效果影響很大……我是通過GridSearch遍歷參數(shù)看識別效果選定的最后參數(shù),老深度學(xué)習(xí)煉丹師了哈哈哈。
最后我們獲取的是很多圓形的坐標(biāo)以及半徑。
3. 水果識別
下一步我們拿到一堆圓形后,就是要知道每個圓形對應(yīng)的是哪種水果了。然后我想采訪一下游戲的開發(fā)者,為什么橙子 和檸檬 要設(shè)計成一樣大?拉瓦錫的棺材板快要壓不住了(物質(zhì)守恒定律)。

所以我不能夠通過圓形的半徑確定是哪個水果了(大哭),而且霍夫圓檢測的結(jié)果擾動也挺大的,同一個水果兩次檢測的半徑可能有一定差別。
到這里,一個聰明的靚仔就會輸入import pytorch摩拳擦掌準(zhǔn)備模式識別了,我這種憨憨選擇的是把圓內(nèi)部的平均RGB值求出來,然后加上半徑一起對比,找相似度最大的。
覺得有點對不起初中數(shù)學(xué)老師,忘了根據(jù)圓心和半徑怎么求圓內(nèi)坐標(biāo)了,求個內(nèi)接矩形內(nèi)像素的RGB平均值……
最后就建立了這么一個從RGB平均值+半徑到水果種類的映射:
fruit_type = {
'GRAPE' : [133.68415638 ,42.41563786,112.84156379, 18],
'KIWI' : [132.0420593,201.00264307,64.44633418, 53],
'CHERRY' : [238.33213966,39.17905103,56.09982095, 28],
'ORANGE' : [246.2261046,129.05342651,21.69071235, 37],
'LEMON' : [237.19926471,216.40716912,65.56176471, 43],
'TOMATO' : [238.18209682 ,89.91402075,95.00730902, 66],
'PEACH' : [235.99086897,165.13803074,97.37832902, 65],
'PINEAPPLE' : [248.87955751,219.50704342,88.25265164,89],
'COCONUT' : [230.9732507,225.98286918,211.98437795, 101],
'WATERMELON' : [236.62388536,100.24692594,121.0164732,143],
}
4. 去除噪聲
就當(dāng)我以為可以進(jìn)行下一步時,我又發(fā)現(xiàn)了很多問題,主要是因為大菠蘿 和椰子 ,我愛椰汁 ,我恨椰子。

這菠蘿和椰子有兩層,本身就含有兩個圓,所以識別出來的結(jié)果經(jīng)常出來很多重合的圓,例如下面這樣:

所以我還得想辦法去除重合的圓,這里我選擇的方法是對所有的圓心兩兩比較,如果他們的距離的和小于兩個圓半徑的和,那么就是重合了。那么我們?nèi)绾闻袛嗄膫€圓是正確的呢?繼續(xù)比較相似度吧…跟標(biāo)準(zhǔn)圖形相似度大的就是正確的。
雖然方法很樸實無華,但它貌似確實很有用。
5. 點擊控制
這個其實也很簡單,沒什么難點

使用Python內(nèi)置的win32api.mouse_event方法模擬鼠標(biāo)點擊事件就可以,只有一個需要注意的地方是,它是會強制控制你的鼠標(biāo)……所以如果你的手速不夠快的話,可能點擊不了右上角關(guān)閉程序的那個叉叉,只能眼睜睜看著你的水果疊到最上面……

6. 游戲策略
實際上今天我主要是在摸索怎么識別和控制了,自己想的算法都沒有實現(xiàn),為了吃一口熱乎s**t,現(xiàn)在只是一個智障版本。

策略是如果有一樣的就往那里落,如果沒有就落在最中間。
這個策略顯然是非常爛的,因為一樣的話會變大可能對結(jié)構(gòu)更加不利。如果沒有一樣的,落在不同位置也會導(dǎo)致整體結(jié)構(gòu)不同……我覺得首先應(yīng)該是避免小的在大的上面,類似于2048盡量別往上滑的策略,因為如果大的在上面小的可能永遠(yuǎn)無法合成了。
應(yīng)該去如何維持一個良好的結(jié)構(gòu),是不是要像計算機體系結(jié)構(gòu)一樣設(shè)計個多級緩存?能不能為每一步設(shè)計一個評估指標(biāo),對算法自動進(jìn)行迭代優(yōu)化?每次刷新的水果順序是不是相同的,以及是否有一定統(tǒng)計規(guī)律?如果我們能夠預(yù)先知道后續(xù)刷新的水果,我們就可以使用A*或者其他啟發(fā)式算法進(jìn)行搜索了。甚至我們能不能用一些AI方法例如強化學(xué)習(xí)來做?
有好多好多可以嘗試的有趣的Idea,如果這游戲下周五還有人玩,我很愿意嘗試一些新的思路,這個合成大西瓜系列也會繼續(xù)更新哈哈。但是現(xiàn)在,真的,我搞了一整天,滿腦子都是山竹+山竹=櫻桃……再讓我看一眼我就吐了

7. 關(guān)于作者
介紹一下小雨姑娘吧,年輕無為的計算機博士生,未來的人民藝術(shù)家,現(xiàn)居青島。基本平時時間都在看書和做Research了,有時間會來知乎寫寫文章。大家可以關(guān)注下我的兩個專欄:小雨姑娘的機器學(xué)習(xí)筆記和小雨姑娘的算法筆記,認(rèn)真寫知識的那種,跟這篇文章不一樣的,認(rèn)真的,哈哈。
困到睜不開眼,還是堅持寫完了……?那么,我再去玩兩盤大西瓜。

—?完?—
一鍵三連「分享」、「點贊」和「在看」
技術(shù)干貨與你天天見~
