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的模板匹配技术,用这种方法测试未发现识别不到问题,需要用到小人的模板(用自己手机截取屏幕并裁剪出来)


小人模板:

跳一跳外挂python 跳一跳辅助代码_人工智能

 识别结果(蓝色方框):

跳一跳外挂python 跳一跳辅助代码_ci_02




 部分代码:




  • 获取底部小人坐标  


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.上一次没跳中中心,因此没出现白点,需分开处理) 

模板一:色差大的中心白点模板

跳一跳外挂python 跳一跳辅助代码_人工智能_03

模板二:色差小的中心白点模板

跳一跳外挂python 跳一跳辅助代码_ci_04




获取目标坐标部分代码(为了防止周围干扰,这里把相似度阈值限制到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多分 不过没什么用


第一次写博客,写得挺乱,将就看吧====