用OpenCV-python的微信跳一跳外掛腳本

微信跳一跳好像火了很久了茄唐,我才開始接觸,作為一個(gè)手殘黨, 玩了幾次不到20分驹暑,打算直接放棄了或衡。但作為一個(gè)技術(shù)宅,下一反應(yīng)肯定是“上腳本”宣增。最初想法是用arduino(不會(huì)可以學(xué)懊蛋颉)+樹梅派,樹梅派用OpenCV處理攝像頭爹脾,讀取手機(jī)屏幕帖旨,判斷跳躍距離,發(fā)命令給arduino控制機(jī)械手臂(觸控筆)灵妨,任務(wù)完成解阅。但問(wèn)題來(lái)了

  • 不會(huì)arduino,也沒(méi)有arduino和觸控筆泌霍。
  • 沒(méi)有樹梅派用的攝像頭货抄。
  • 不會(huì)OpenCV

arduino控制觸控筆,估計(jì)很麻煩朱转,但同時(shí)肯定很有意思蟹地。就算入手arduino,學(xué)會(huì)控制機(jī)械手臂藤为,難度好像是整個(gè)程序中最難的怪与。然后退一步,用adb給屏幕發(fā)送模擬觸屏命令缅疟,暫時(shí)解決第一部分分别。樹梅派的攝像頭也要買,暫時(shí)用筆記本攝像頭代替(把手機(jī)舉在攝像頭前真的很二)存淫,再退一步耘斩,用adb截屏讀取圖像,處理圖像比視頻流要簡(jiǎn)單些纫雁。然后開始作煌往,哎倾哺,我肯定不是第一個(gè)想到這些的轧邪,去搜搜,果然就找到了這里3羞海。具體做法是:

  1. 用adb讀取游戲截屏
  2. 根據(jù)顏色差判斷棋子和下一跳棋盤的位置忌愚。計(jì)算兩點(diǎn)之間的距離
  3. 找到合適的參數(shù),得到屏幕按壓時(shí)間却邓,用adb把命令發(fā)過(guò)去硕糊。

這里最關(guān)鍵的地方是判斷棋子和下一跳棋盤的位置,因?yàn)樽畛蹙褪且蒙螼penCV,他人處理顏色的代碼看了兩眼覺(jué)得太麻煩就直接放棄了简十。所以這篇日志的最主要的地方就是怎么用OpenCV找到這兩個(gè)點(diǎn)檬某。

從最開始寫這個(gè)腳本到開始寫個(gè)記錄,不斷搜索到新的方法來(lái)找這兩個(gè)點(diǎn)螟蝙。最近的發(fā)現(xiàn)2, 這里用了Tensorflow恢恼,對(duì)此代碼我還理解不能。我打算放棄寫這篇記錄胰默,認(rèn)真看看TF场斑。 轉(zhuǎn)念一想,我的方法雖然沒(méi)有其他人的高大上牵署,也沒(méi)什么效率漏隐,但總算是一種解決方式,所以還是記下來(lái)吧奴迅。

OpenCV的全稱是Open Source Computer Vision Library青责,是一個(gè)跨平臺(tái)的計(jì)算機(jī)視覺(jué)庫(kù),可用于開發(fā)實(shí)時(shí)的圖像處理取具、計(jì)算機(jī)視覺(jué)以及模式識(shí)別程序1爽柒。聽介紹就知道用在這里很合適。根據(jù)OpenCV的官方教程4者填, 初步確定尋找所需的兩個(gè)點(diǎn)的方法:

  1. 將圖像的色彩空間由RGB轉(zhuǎn)換HSV
  2. 確定圖形邊緣及輪廓
  3. 假定其中一個(gè)輪廓的中心就是所需的點(diǎn)浩村。
找到所需點(diǎn)的步驟圖

為什么要把色彩空間轉(zhuǎn)化為HSV,而不是使用原始的RGB或者灰度占哟? 據(jù)說(shuō)HSV比RGB能更好的處理顏色心墅,處理一個(gè)值H比三個(gè)RGB要簡(jiǎn)單,飽和度S和明度V能幫助處理光照和陰影榨乎。下圖可以看出怎燥,只有轉(zhuǎn)成HSV色彩空間的在后面的處理中可以正確的檢測(cè)到方塊的頂面邊緣,同樣的閾值條件下蜜暑,RGB模式對(duì)頂面和右側(cè)面的邊緣檢測(cè)能力較弱铐姚,灰度模式下頂面和兩個(gè)側(cè)面都分不清楚。而降噪處理在這張圖沒(méi)什么明顯的變化肛捍,但對(duì)于有些圖像--比如有木頭紋理的桌子--還是有幫助的隐绵,所以需要保留,高斯模糊應(yīng)該就足夠了拙毫,Bilateral Filtering is highly effective in noise removal while keeping edges sharp. But the operation is slower compared to other filters依许, 所以沒(méi)必要。


不同色彩空間對(duì)邊緣檢測(cè)處理的影響對(duì)比

根據(jù)上面的圖缀蹄,即便有了邊緣點(diǎn)峭跳,得到很多個(gè)輪廓的中心膘婶,仍然無(wú)法確定哪個(gè)中心點(diǎn)是棋子。根據(jù)經(jīng)驗(yàn)蛀醉,發(fā)現(xiàn)棋子總是某種黑紫色悬襟,而不論是下一跳的棋盤或者背景都不會(huì)跟這種顏色相近,所以根據(jù)顏色特征找到棋子更簡(jiǎn)單拯刁。所以接下來(lái)就是找到這個(gè)顏色值的上下限古胆,面向Google編程,我找到了別人的一個(gè)方法筛璧,自己修改后放gist逸绎。

def get_start_point(image):
    chess = image.copy()
    blurred = cv2.GaussianBlur(chess, (3, 3), 1)
    mask = cv2.inRange(blurred, lower_purple, upper_purple)
    mask_1 = cv2.inRange(blurred, lower_purple_1, upper_purple_1)
    masked = cv2.bitwise_and(blurred, blurred, mask=mask+mask_1)
    chess_edges = cv2.Canny(masked, 100, 200)
    thresh = cv2.adaptiveThreshold(
        chess_edges,
        255,
        cv2.ADAPTIVE_THRESH_GAUSSIAN_C,
        cv2.THRESH_BINARY,
        7,
        1)
    _, cnts, _ = cv2.findContours(
        thresh, cv2.RETR_CCOMP, cv2.CHAIN_APPROX_SIMPLE)

    cs = list()
    for cnt in cnts:
        (x, y), r = cv2.minEnclosingCircle(cnt)
        r = int(r)
        # print(r)
        if 25 < r < 40:
            cs.append((int(x), int(y)))
    mx = min(cs, key=operator.itemgetter(1))
    return mx

邊緣檢測(cè)函數(shù)就是上面的Canny(),該方法的參數(shù)有三個(gè)夭谤,第一個(gè)是要處理的圖像棺牧,后兩個(gè)是閾值的上下限。Canny之前的是標(biāo)準(zhǔn)的選取顏色方法朗儒。有了邊緣值再用adaptiveThreshold函數(shù)進(jìn)行二值化處理颊乘,然后用findContours尋找棋子的輪廓, 輪廓(Contour)在這里其實(shí)就是一系列邊緣點(diǎn)的列表,根據(jù)這些點(diǎn)就可以計(jì)算輪廓的很多特性醉锄,比如形狀乏悄,面積等,OpenCV有很多函數(shù)幫助處理這些特性恳不, 在這里可以用boundingRect(包括了所有輪廓點(diǎn)的長(zhǎng)方形)檩小,minEnclosingCircle(包括了所有輪廓點(diǎn)的圓)或 moments(輪廓的幾何重心),。因?yàn)槠灞P頂了個(gè)球烟勋,并且minEnclosingCircle同時(shí)返回圓心坐標(biāo)和半徑规求,在這里最合適不過(guò)了。由于findContours是返回的所有輪廓卵惦,很是雜亂阻肿,如圖右下里的紅圈,所以要過(guò)濾下沮尿,根據(jù)半徑過(guò)濾是最直接的想法丛塌,因?yàn)榍虻陌霃娇梢詼y(cè)出來(lái),在1080p的屏幕上大概為30個(gè)像素畜疾。這樣就能得到球心坐標(biāo)了赴邻,如同左下的綠圈,注意:1. 即使過(guò)濾后也可能有多個(gè)坐標(biāo)庸疾,這里取最上面的乍楚,就是mx = min(cs, key=operator.itemgetter(1))当编。2.這個(gè)坐標(biāo)不是起始點(diǎn)的坐標(biāo)届慈,起始點(diǎn)應(yīng)該為棋子底部圓心徒溪,y坐標(biāo)應(yīng)加上120px(圖的左上為坐標(biāo)原點(diǎn))。

尋找棋子.png

有了棋子的位置坐標(biāo)金顿,接下來(lái)就是找到下一跳的棋盤的頂部中心坐標(biāo)了臊泌,方法大概是這樣的:如果前一跳正好落在了當(dāng)前棋盤的中心,下一跳棋盤的中心會(huì)有個(gè)白點(diǎn)(這樣每一跳的分?jǐn)?shù)也會(huì)遞增揍拆,朋友圈排行上數(shù)k的分?jǐn)?shù)都是外掛刷來(lái)的)渠概,只要找到這個(gè)白點(diǎn)就ok了。尋找這個(gè)白點(diǎn)跟找棋子一樣也是通過(guò)顏色特征white_dot = np.array([0, 0, 245])嫂拴。但沒(méi)有這個(gè)白點(diǎn)之前還是要用檢測(cè)邊緣的辦法播揪,然后計(jì)算棋盤的頂面中心,如果沒(méi)有噪音筒狠,這個(gè)方法和直接找到白點(diǎn)坐標(biāo)應(yīng)該是一樣的猪狈,如果沒(méi)有噪音。處理圖像的步驟還是一樣辩恼,有個(gè)細(xì)節(jié)可以減少計(jì)算量:棋子和下一跳的棋盤總是在屏幕中心的兩側(cè)雇庙,利用這點(diǎn)可以把棋盤從圖上挖出來(lái)

def get_end_point(image, start_point):
    global half_width
    global cutted

    cutted = image.copy()
    if start_point[0] < half_width:
        #棋子在屏幕左側(cè),取棋子坐標(biāo)右側(cè)的圖
        cutted = cutted[0:start_point[1] +
                        chess_height, start_point[0]+chess_width:]
    else:
        cutted = cutted[0:start_point[1] +
                        chess_height, :start_point[0]-chess_width]

    w_dot = _get_end_dot(cutted)

    if w_dot is None:
        print("get by edges")
        w_dot = _get_end_by_edges(cutted)

    x, y = w_dot
    ### 最后得到坐標(biāo)要還原回去
    if start_point[0] < half_width:
        x += start_point[0]+chess_width

    return (x, y)

上面_get_end_dot()get_start_point類似灶伊,_get_end_by_edges是在沒(méi)找到白點(diǎn)的情況下的笨方法疆前,_get_ul方法來(lái)自這里findContours方法也和前面不太一樣聘萨,返回輪廓的同時(shí)還返回了輪廓的層次結(jié)構(gòu)竹椒,因?yàn)橹恍枰獌?nèi)側(cè)的輪廓,用ch[2] < 0過(guò)濾出來(lái)米辐。關(guān)于這個(gè)的 解釋碾牌。 最后還要處理一部分噪音造成的太小的輪廓圓及整個(gè)棋盤+陰影造成的太大的圓。

def _get_ul(c, sigma=0.15):
    l = int(max(0, (1.0 - sigma) * c))
    u = int(min(255, (1.0 + sigma) * c))
    return (l, u)


def _get_end_by_edges(image):
    image = cv2.GaussianBlur(image, (1, 1), 0)
    bg = image[:20, :]
    mid = np.median(bg)
    l, u = _get_ul(mid)
    end_edges = cv2.Canny(image, l, u)
    end_thresh = cv2.adaptiveThreshold(
        end_edges,
        255,
        cv2.ADAPTIVE_THRESH_GAUSSIAN_C,
        cv2.THRESH_BINARY,
        5,
        1)
    _, cnts, hierarchy = cv2.findContours(
        end_thresh, cv2.RETR_CCOMP, cv2.CHAIN_APPROX_SIMPLE)

    hierarchy = hierarchy[0]
    ms = list()
    for cpnt in zip(cnts, hierarchy):
        cnt = cpnt[0]
        ch = cpnt[1]
        if ch[2] < 0:
            (x, y), r = cv2.minEnclosingCircle(cnt)
            r = int(r)
            # print(r)
            if 25 < r < 230:
                ms.append((int(x), int(y)))
    mx = min(ms, key=operator.itemgetter(1))
    return mx

利用檢測(cè)邊緣的方法尋找中心點(diǎn)的效果如下:?jiǎn)紊倚螤钜?guī)則些的可以找到正中心或偏差不大儡循。奇形怪狀的連能不能跳到上面都不保證舶吗。


Figure_1-5.png

Figure_1-7.png

主要方法就是這些了,整個(gè)代碼可以去我的gist查看择膝。整個(gè)代碼還是會(huì)有些問(wèn)題誓琼,有些能改進(jìn)的地方,但是作為一個(gè)演示程序應(yīng)該夠了肴捉。我測(cè)試了幾次腹侣,都沒(méi)到1k,汗齿穗。但是不管跑多少分傲隶,都會(huì)遇到下圖(當(dāng)然我的歷史最高分也是腳本跑了,只是當(dāng)時(shí)還沒(méi)有現(xiàn)在的外掛檢測(cè))窃页,反正除了測(cè)試改進(jìn)代碼跺株,我是不會(huì)用這個(gè)跑分了复濒。
sa.png

如果有興趣,我可能會(huì)增加點(diǎn)關(guān)于python的環(huán)境配置乒省。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末巧颈,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子袖扛,更是在濱河造成了極大的恐慌砸泛,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,755評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件蛆封,死亡現(xiàn)場(chǎng)離奇詭異唇礁,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)惨篱,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門垒迂,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人妒蛇,你說(shuō)我怎么就攤上這事机断。” “怎么了绣夺?”我有些...
    開封第一講書人閱讀 165,138評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵吏奸,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我陶耍,道長(zhǎng)奋蔚,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,791評(píng)論 1 295
  • 正文 為了忘掉前任烈钞,我火速辦了婚禮泊碑,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘毯欣。我一直安慰自己馒过,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,794評(píng)論 6 392
  • 文/花漫 我一把揭開白布酗钞。 她就那樣靜靜地躺著腹忽,像睡著了一般。 火紅的嫁衣襯著肌膚如雪砚作。 梳的紋絲不亂的頭發(fā)上窘奏,一...
    開封第一講書人閱讀 51,631評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音葫录,去河邊找鬼着裹。 笑死,一個(gè)胖子當(dāng)著我的面吹牛米同,可吹牛的內(nèi)容都是我干的骇扇。 我是一名探鬼主播摔竿,決...
    沈念sama閱讀 40,362評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼匠题!你這毒婦竟也來(lái)了拯坟?” 一聲冷哼從身側(cè)響起但金,我...
    開封第一講書人閱讀 39,264評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤韭山,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后冷溃,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體钱磅,經(jīng)...
    沈念sama閱讀 45,724評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評(píng)論 3 336
  • 正文 我和宋清朗相戀三年似枕,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了盖淡。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,040評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡凿歼,死狀恐怖褪迟,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情答憔,我是刑警寧澤味赃,帶...
    沈念sama閱讀 35,742評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站虐拓,受9級(jí)特大地震影響心俗,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜蓉驹,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,364評(píng)論 3 330
  • 文/蒙蒙 一城榛、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧态兴,春花似錦狠持、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至敢订,卻和暖如春王污,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背楚午。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工昭齐, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人矾柜。 一個(gè)月前我還...
    沈念sama閱讀 48,247評(píng)論 3 371
  • 正文 我出身青樓阱驾,卻偏偏與公主長(zhǎng)得像就谜,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子里覆,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,979評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容