用Python 玩轉微信跳一跳(帶源碼)

python+opencv實現微信跳一跳輔助

微信上線的小程序遊戲“跳一跳”很趣,玩法簡單,上手很快,但是想要拿到高分不容易,當然本人寫這個輔助不是爲了獲取高分而寫,排行榜什麼的都是浮雲,主要是想搞清楚其實現原理、實現方法。
 
實現思路:從遊戲可以知道,小人與目標的距離越遠,按壓屏幕的時間就要越長,因此可以通過計算小人位置和目標中心的距離,把計算結果乘以一個係數(跳躍係數,通過多次測試可以得到一個穩定的常數)即可得到按壓時間(press_time=距離*係數),然後通過命令以指定按壓時間模擬按壓手機屏幕,則小人即可跳躍到指定位置。由於分析小人位置需要分析小人和目標的位置,所以每次在跳躍前需要把遊戲截屏-->發送到電腦-->分析,由於跳躍係數是常數,因此跳躍的精度就只和計算的距離有關,距離可根據兩點間最短距離公式計算得到,所以,現在的問題就變成了求小人和目標中心的座標。實現步驟:
 
  1. 在PC端實現手機截屏並複製到PC中用到兩條命令:
    adb shell screencap -p /sdcard/0.jpg 
    adb pull /sdcard/0.png ./op_screencap/jump.jpg
    os.system(命令)#執行命令(需要引入 os)
     執行完命令後,圖片理論上已經保存到了項目根目錄下op_screencap文件夾上,準備工作已經做好,下面開始分析圖片:
  •    獲取小人位置,這裏使用了opencv的模板匹配技術,用這種方法測試未發現識別不到問題,需要用到小人的模板(用自己手機截取屏幕並裁剪出來)
小人模板: 識別結果(藍色方框):
 
 部分代碼:
 
  • 獲取底部小人座標  
    def get_people_loc(self,img):
        '''
        獲取小人的位置範圍
        :param img:
        :return:返回帶有小人範圍的圖和小人座標
        '''
        #定義小人位置
        peo_loc=[0,0]
        #與模板進行匹配識別 獲取識別結果矩形範圍
        min_val, max_val, min_loc, max_loc = self.matchTemplate(img,self.template)
        #繪製識別結果
        draw_peo_result = cv2.rectangle(img, max_loc, (max_loc[0] + self.w, max_loc[1] + self.h), color=(255,0,0), lineType=8,thickness=3)
        #計算小人底部座標
        peo_bottom_x=int(max_loc[0]+(self.w)/2)
        peo_bottom_y=int((max_loc[1]+self.h-19))
        peo_loc=(peo_bottom_x,peo_bottom_y)
        return (draw_peo_result,peo_loc)
  • 獲取目標座標(這裏也是用到模板匹配,需要用到中心白點的模板,由於遊戲中白點存在三種情況:1.白點和背景顏色相差大 2.白點和背景顏色很接近如白色方塊 3.上一次沒跳中中心,因此沒出現白點,需分開處理) 
模板一:色差大的中心白點模板模板二:色差小的中心白點模板
 
獲取目標座標部分代碼(爲了防止周圍干擾,這裏把相似度閾值限制到80%以上):
 
    def get_target_loc(self,cv_img):
        '''
        獲取目標位置座標
        :param img:
        :return:識別結果的圖片和目標座標(失敗返回源圖像和(0,0))
        '''
        cv_img_gray=cv2.cvtColor(cv_img, cv2.COLOR_BGR2GRAY)
        min_val2, max_val2, min_loc2, max_loc2 =self.matchTemplate(cv_img_gray,self.template_center_gray)

        print('模板一匹配中心點 匹配度:%d%% 設定匹配閾值:80%%' % (max_val2 * 100), end=' ')
        # 計算中心座標
        cen_x = int(max_loc2[0] + (self.w2 / 2))
        cen_y = int(max_loc2[1] + self.h2 / 2)
        draw_cen_result = cv2.circle(cv_img, (cen_x, cen_y), 2, color=(255, 0, 0), thickness=3)

        #預防分數干擾 把分數部分設置爲全黑色
        draw_cen_result[0:500]=[0,0,0]

        if(max_val2>0.8):
            print('模板一匹配中心點成功')
            return (draw_cen_result,(cen_x,cen_y))
        else:
            cv_img_gray= cv2.cvtColor(cv_img, cv2.COLOR_BGR2GRAY)
            center_white_template_gray= cv2.cvtColor(self.center_white_template2, cv2.COLOR_BGR2GRAY)
            min_val3, max_val3, min_loc3, max_loc3=self.matchTemplate(cv_img_gray,center_white_template_gray)

            # 計算中心座標
            cen_x3 = int(max_loc3[0] + (self.w2 / 2))
            cen_y3 = int(max_loc3[1] + self.h2 / 2)
            #print('cen_x3:%d cen_y3:%d' % (cen_x3, cen_y3), end=' ')
            draw_cen_result = cv2.circle(cv_img, (cen_x3, cen_y3), 2, color=(255, 0, 0), thickness=3)
            print('模板一匹配中心點失敗\n模板二匹配中心點 匹配度:%d%% 設定匹配閾值:80%%' % (max_val3 * 100), end=' ')
            if (max_val3 > 0.8):
                print('模板二匹配中心點成功')
                return (draw_cen_result, (cen_x3, cen_y3))
            else:
                print('模板二匹配中心點失敗')
                return (draw_cen_result,(0,0))
上面只處理了存在中心白點的情況,如果沒有中心白點出現,或者匹配失敗,這裏使用了第三中獲取方法,通過matplotlib庫把圖片show到figure中,使用鼠標輔助獲取到目標的位置(比較笨拙的方法==)代碼省略====
 

完整代碼如下(大神請繞道、、、不喜勿噴、、變量的名字是隨便命名的,大家自行理解、、、、):
 

 
  • ImgHelper.py
import matplotlib.pyplot as plt
from PIL import Image
import adbOperate
import image_analysis
import time
import random
import cv2

class ImageHelprt:
    #定義小人座標
    peo_loc=(0,0)
    #定義跳躍目標的座標
    tar_loc=(0,0)
    #定義鼠標點擊類型
    click_count=1
    reg_event=True

    def __init__(self):
        self.figure=plt.figure()
        self.tempPath=''
        self.count=0
        self.img_analysis=image_analysis.IMG_Aanlysis()
        self.CLICK_SOURCE=(500,1514)
        self.exit=False
        
        #設置限制跳躍次數
        self.limit_count=50

    def OnBtn_pressEvent(self, event):
        '''
        鼠標點擊圖片時觸發的時事件
        :param event:
        :return:
        '''
        eX=event.xdata
        eY=event.ydata

        if(self.click_count==0):
            self.click_count=1
        else:
            print('點擊位置 X:', eX, '   Y:', eY)

            #第二次點擊計算距離
            self.tar_loc=(eX,eY)
            self.click_count=1

            distance=self.cal_distance(self.peo_loc,self.tar_loc)
            click_loc=self.cal_click_loc(self.CLICK_SOURCE)
            #操作ADB 控制手機跳躍
            adbOperate.jump(distance,click_loc=click_loc)
            self.draw_result(self.cv_img, self.peo_loc, (0,0),(int(self.tar_loc[0]),int(self.tar_loc[1])) , click_loc)
            plt.close()

    def show_img(self):
        '''
        顯示照片到當前figure中
        :param img: 要顯示的圖片
        :return:
        '''
        #讀取截圖
        self.img= Image.open(self.tempPath)
        self.cv_img=cv2.imread(self.tempPath)

        if(self.jump_count>=self.limit_count):self.exit=True

        #清空GDI容器
        self.figure.clear()
        #註冊鼠標點擊事件
        if(self.reg_event):
            self.figure.canvas.mpl_connect('button_press_event', self.OnBtn_pressEvent)

        #分析圖片
        #獲取小人位置
        (cv_img,peo_loc)=self.img_analysis.get_people_loc(self.cv_img)

        # 獲取目標位置
        (cv_img,target_loc)=self.img_analysis.get_target_loc(self.cv_img)
        if(target_loc[0]==target_loc[1]==0):
            self.figure=plt.figure(num='請用鼠標點擊目標棋子中心')
            #說明沒有識別到下一跳中心位置,彈出手動選擇
            self.figure.canvas.mpl_connect('button_press_event', self.OnBtn_pressEvent)
            print('請手動選擇目標!')
            self.peo_loc=peo_loc
            plt.imshow(self.cv_img,animated= True)
            plt.show()
            print('分析中...')
        else:
            #自動跳躍
            #計算距離
            distance=self.cal_distance(peo_loc=peo_loc,tar_loc=target_loc)
            #print('距離:%d' %distance)
            click_loc = self.cal_click_loc(self.CLICK_SOURCE)
            adbOperate.jump(distance,click_loc)
            self.draw_result(cv_img,peo_loc,target_loc,(0,0),click_loc)
        self.pull_screenshot()

    def cal_distance(self,peo_loc,tar_loc):
        '''
        計算小人與目標方塊中心的最短距離
        :param peo_loc:小人位置座標
        :param tar_loc:目標方塊位置座標
        :return:計算結果
        '''
        distance = (tar_loc[0] - peo_loc[0]) ** 2 + (tar_loc[1] - peo_loc[1]) ** 2
        distance = distance ** 0.5
        return distance

    def cal_click_loc(self,source_loc):
        '''
        隨機產生一個點擊位置
        :param source_loc:源座標
        :return: 源座標+隨機數
        '''
        click_x=500+random.randint(1,30)
        click_y=1514+random.randint(1,30)
        return (int(click_x),int(click_y))

    def pull_screenshot(self):
        '''
        調用adb操作類執行截圖 保留圖片路徑 繼續分析下一跳
        :return:
        '''
        time.sleep(1.5+random.randint(1,50)/100)
        self.tempPath = adbOperate.pull_screenshot(self)

        #是否結束遊戲
        if(self.exit):
            print('************到達限制次數:%d 自動結束跳躍************' %self.limit_count)
            return
        else:
            # 分析下一跳的圖片
            self.show_img()

    def draw_result(self,src_img,peo_loc,tar_loc,click_tar_loc,click_loc):
        '''
        保存識別結果
        :param src_img:源圖像
        :param peo_loc:人位置
        :param tar_loc:識別的目標
        :param click_tar_loc:點擊的目標位置
        :param click_loc:模擬點擊的位置
        :return:
        '''
        draw_img=src_img
        #人
        draw_img=cv2.circle(draw_img,peo_loc, 3, color=(0, 255, 0), thickness=3)
        #識別的目標
        draw_img =cv2.circle(draw_img,tar_loc,3,color=(255,0,0),thickness=3)
        #點擊目標的位置
        draw_img = cv2.circle(draw_img, click_tar_loc, 3, color=(0, 0, 255), thickness=3)
        #模擬點擊的位置
        draw_img = cv2.circle(draw_img,click_loc , 6, color=(255, 255, 0), thickness=3)
        draw_img = cv2.circle(draw_img, click_loc, 12, color=(255, 0, 0), thickness=3)
        cv2.imwrite(filename=self.tempPath,img=draw_img)
 
  • image_analysis.py
import cv2

class IMG_Aanlysis:

    def __init__(self):
        #加載小人模板
        self.template = cv2.imread('.\\template\\peo_template.jpg')
        #加載中心白點模板
        self.center_white_template=cv2.imread('.\\template\\white_center_template.png')
        #加載中心白點模板2
        self.center_white_template2=cv2.imread('.\\template\\white_center_template1.png')
        #二值化模板
        self.template_gray = cv2.cvtColor(self.template, cv2.COLOR_BGR2GRAY)
        self.template_center_gray = cv2.cvtColor(self.center_white_template, cv2.COLOR_BGR2GRAY)
        #獲取模板的寬高
        self.w,self. h = self.template_gray.shape[::-1]
        self.w2, self.h2 = self.template_center_gray.shape[::-1]
        print('小人模板寬高:'+str(self.w)+':'+str(self.h))
        print('中心點模板寬高:' + str(self.w2) + ':' + str(self.h2))

    def get_people_loc(self,img):
        '''
        獲取小人的位置範圍
        :param img:
        :return:返回小人範圍圖和小人座標
        '''
        #定義小人位置
        peo_loc=[0,0]
        #與模板進行匹配識別 獲取識別結果矩形範圍
        min_val, max_val, min_loc, max_loc = self.matchTemplate(img,self.template)

        #繪製識別結果
        draw_peo_result = cv2.rectangle(img, max_loc, (max_loc[0] + self.w, max_loc[1] + self.h), color=(255,0,0), lineType=8,
                             thickness=3)
        #計算小人底部座標
        peo_bottom_x=int(max_loc[0]+(self.w)/2)
        peo_bottom_y=int((max_loc[1]+self.h-19))
        peo_loc=(peo_bottom_x,peo_bottom_y)

        draw_peo_result= cv2.circle(draw_peo_result,(peo_bottom_x,peo_bottom_y),2,color=(0,255,0),thickness=3)

        return (draw_peo_result,peo_loc)

    def get_target_loc(self,cv_img):
        '''
        獲取目標位置座標
        :param img:
        :return:
        '''
        cv_img_gray=cv2.cvtColor(cv_img, cv2.COLOR_BGR2GRAY)
        min_val2, max_val2, min_loc2, max_loc2 =self.matchTemplate(cv_img_gray,self.template_center_gray)

        print('模板一匹配中心點 匹配度:%d%% 設定匹配閾值:80%%' % (max_val2 * 100), end=' ')
        # 計算中心座標
        cen_x = int(max_loc2[0] + (self.w2 / 2))
        cen_y = int(max_loc2[1] + self.h2 / 2)
        draw_cen_result = cv2.circle(cv_img, (cen_x, cen_y), 2, color=(255, 0, 0), thickness=3)

        #預防分數干擾 把分數部分設置爲全黑色
        #draw_cen_result[0:500]=[0,0,0]

        if(max_val2>0.8):
            print('模板一匹配中心點成功')
            return (draw_cen_result,(cen_x,cen_y))
        else:
            cv_img_gray= cv2.cvtColor(cv_img, cv2.COLOR_BGR2GRAY)
            center_white_template_gray= cv2.cvtColor(self.center_white_template2, cv2.COLOR_BGR2GRAY)
            min_val3, max_val3, min_loc3, max_loc3=self.matchTemplate(cv_img_gray,center_white_template_gray)

            # 計算中心座標
            cen_x3 = int(max_loc3[0] + (self.w2 / 2))
            cen_y3 = int(max_loc3[1] + self.h2 / 2)
            #print('cen_x3:%d cen_y3:%d' % (cen_x3, cen_y3), end=' ')
            draw_cen_result = cv2.circle(cv_img, (cen_x3, cen_y3), 2, color=(255, 0, 0), thickness=3)
            print('模板一匹配中心點失敗\n模板二匹配中心點 匹配度:%d%% 設定匹配閾值:80%%' % (max_val3 * 100), end=' ')
            if (max_val3 > 0.8):
                print('模板二匹配中心點成功')
                return (draw_cen_result, (cen_x3, cen_y3))
            else:
                print('模板二匹配中心點失敗')
                return (draw_cen_result,(0,0))


    def matchTemplate(self,img,template):
        res = cv2.matchTemplate(img, template,
                                cv2.TM_CCOEFF_NORMED)
        return cv2.minMaxLoc(res)

    def show_result(self,img):
        cv2.namedWindow('AnaResult', cv2.WINDOW_GUI_EXPANDED)
        cv2.imshow('AnaResult', img)
        cv2.waitKey(0)
 
  • adbOperate.py
import os

#定義截圖名稱
img_name='j.png'
img_out_path='./op_screencap/'

def pull_screenshot(self):
    '''
    截屏並存放到電腦中
    :return:
    '''
    self.count=  self.count+1
    self.jump_count=self.jump_count+1
    if(self.count>30):
        self.count=1
        print('將覆蓋舊圖')
    os.system('adb shell screencap -p /sdcard/'+img_name)
    os.system('adb pull /sdcard/j.png '+img_out_path+str(self.count)+'_'+img_name)
    self.temp_path=img_out_path+str(self.count)+'_'+img_name
    return  self.temp_path

def jump(distance,click_loc):#由於測試結果發現每次跳躍 係數都會在一定範圍波動 因此這裏分段設置跳躍係數
    cons=1.475
    if(distance<=168):
        cons=1.57
    elif(distance>168 and distance<=175):
        cons=1.5
    elif(distance>175 and distance<=313):
        cons=1.62
    elif(distance>313 and distance<400):
        cons=1.52
    elif(distance>=400 and distance<511):
        cons=1.50
    elif(distance>=511 and distance<540):
        cons = 1.49
    elif(distance>=540 and distance<659):
        cons=1.45
    elif(distance>=659 and distance<670):
        cons=1.47
    elif(distance>=670 and distance<692):
        cons=1.45
    elif(distance>=692 and distance<700):
        cons=1.38
    elif(distance>=698):
        cons=1.40
    press_time = distance * cons
    press_time = str(int(press_time))
    x1=str(click_loc[0])
    y1=str(click_loc[1])
    #cmd = 'adb shell input swipe 540 1514 540 1514 ' + str(press_time)
    cmd = 'adb shell input swipe {0} {1} {2} {3} {4}'
    cmd= cmd.format(click_loc[0],click_loc[1],click_loc[1],click_loc[0],press_time)
    #print('[distance:'+str(distance)+'][press_time:'+str(press_time)+']ms [cmd:'+cmd+']')
    print('跳躍函數:\n-----使用跳躍係數:%1.3f\n-----跳躍距離:%d\n-----按壓時間:%s ms' %(cons,int(distance),press_time))
    os.system(cmd)
main.py
import adbOperate
import ImgHelper

class Main:
    def __init__(self):
        print('主程序初始化...')
        imHelper = ImgHelper.ImageHelprt()
        imHelper.tempPath= adbOperate.pull_screenshot(imHelper)
        imHelper.show_img()
if __name__ == '__main__':
    Main()
以上代碼在小米5上測試完美運行,測試時請打開遊戲 開始遊戲然後截屏把小人模板*1、中心點模板*2(白色方塊和非白色方塊)裁剪好放到項目根目錄下的template文件夾中,相關環境自行搭建,只要想跳就不會跳完 本人最高跳到7000多分 不過沒什麼用
第一次寫博客,寫得挺亂,將就看吧====

 


 
    
 
 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章