微信跳一跳 -Matlab也能玩~

     想必大家多多少少都会玩微信跳一跳,鉴于别的语言实现自动玩游戏,出于好奇,Matlab当然能做的更好!集标注-->训练-->测试-->最终自动(或手动)于一身,而且也不需要那么多代码+手机需要root等麻烦!为了每跳一步获得稳定的截图画面,我设置停顿了3秒,程序识别速度还是很快的(如果不考虑屏幕稳定,除去停顿3秒没来得及更新就有结果了),基本上不用for循环,尽量用matlab自带的函数及功能。


运行软件需求:

1、MATLAB R2017a或最新版本。

2、电脑端下载安装TeamView远程控制软件。

3、安卓手机端下载QuickSupport APP+你的机型Add on插件(用于电脑实时控制手机)。


思路:

通过识别游戏画面投掷子和棋子方块之间的距离推算时间,然后交给TeamView实时控制手机按压屏幕操作来实现跳一跳。识别的图像ROI选取的是手机屏幕离上下边缘1/5高度内,宽度为屏幕宽度。当然有很多方法去识别棋盘方格,投掷子,下面用的算法有ACF,cascade,模板匹配,附带识别颜色,边缘等细节特征识别。如果其中某个检测器没检测到就让另外一个检测器检测,万一都没检测到,就换成手动取两者中心点坐标获得距离,然后通过系数乘以距离得到按压时间,最后调用Windows API函数控制鼠标到屏幕上完成固定时间的按压动作。


步骤:

1、图像标注

(附录我已经给出下载链接,这步读者可跳过)

先从自己手机上截图若干张游戏画面存储到电脑磁盘上,我这里大概获取了245张图像。导入到Matlab的training Image Labeler/ imageLabeler APP中,标注两种类型的ROI,如图1所示,标注完导出到工作空间,结果如图2所示。保存标注结果为weChartlabel.mat。


图1 图像标注


图2 标注结果

2、图像训练

训练2个对象类型,投掷子dice、棋块方格targetObject。我选用的是dice用cascade去训练,附带训练用ACF,模板匹配等方法,因为每次投掷子dice大小和颜色都比较稳定;棋块方格用ACF,边缘检测等特征,因为下一次要跳的方块永远在屏幕最上面,通过边缘检测基本上都能找到棋块的上顶点。ACF训练如图3几行代码,cascade训练如图4的代码。

ACF训练投掷子和棋块方格:

    load weChartlabel.mat %标注结果文件,里面存储的变量mylabel
    Detector =trainACFObjectDetector(mylabel(1:100,[1,2])); %训练投掷子dice
    Detector2 =trainACFObjectDetector(mylabel(1:100,[1,3]));% 训练目标棋块targetObject
    save weChartDetector.mat Detector Detector2

 cascade训练投掷子:

%% prepare
load weChartlabel.mat
positiveSamples = mylabel(1:200,1:2);
negativeSamples = imageDatastore('./negativeSamples');

%% train
trainCascadeObjectDetector('diceDetector.xml',positiveSamples, ...
    negativeSamples,'FalseAlarmRate',0.1,'NumCascadeStages',5);

上面ACF训练我只用了100个样本,cascade用了200个样本,大家根据自己情况选择。训练完成后各类检测器保存在当前目录下weChartDetector.mat和diceDetector.xml文件中。另外投掷子比较单一,用模板匹配方法也可以的,想到matlab里面用模板匹配vision.TemplateMatcher,逐像素块计算方法比较耗时。故调用OpenCV的库函数来实现,这部分已通过mexFunction实现。

3、图像测试

主要给出投掷子的模板匹配图和棋块的边缘检测图。图3为原图ROI,图4我边缘检测图,图5我投掷子匹配的图。

                              

图3 ROI原图

                       

图4 边缘检测

                              

图5 模板匹配结果图

4、自动检测/手动检测

设置了保存中间每一步图像的过程,每次运行前会清空文件夹里面的图像,运行后会自动保存结果原图和标注图。这2个文件夹分别是当前目录下的save_Imgs和save_RGB。其中的示例图像如图6所示。手动标注如图7所示。

                                       

                                     

                                         

图6 自动检测


图7 手动标记中心

主要源码:

% author:cuixing
% email:cuixing158@foxmail.com
% date:2018-01-7
%

%% main
addpath('./matchTemplateDice')

%% 设置参数
screenSize = get(groot,'ScreenSize');% 获取电脑屏幕尺寸
flagAUTO = true; % 是否自动,false为手动点击两者中心获取距离
rate_time = 800/195; % 大概距离与时间的对应关系为:800ms==195像素,自己可以调整
videoObj = vision.VideoPlayer();
numFrame = 1;
cursorXRange = [262,411];% 电脑屏幕坐标,模拟手指在此范围内按下,可以根据get(groot,'PointerLocation')初步估计
cursorYRange = [119,252];% 电脑屏幕坐标,模拟手指在此范围内按下,可以根据get(groot,'PointerLocation')初步估计
xMag = cursorXRange(2)- cursorXRange(1);
yMag = cursorYRange(2)- cursorYRange(1);
templateImg = imread('./matchTemplateDice/template.jpg');

%% 准备工作
figurePosition = [screenSize(3)/2,screenSize(4)/3,screenSize(4)/2,screenSize(4)/2];
h = figure('Name','RGB','position',figurePosition);
saveImgPath = './save_Imgs';% 仅供查看使用
saveRGBPath = './save_RGB';% 仅供查看使用
if ~exist(saveImgPath,'dir')
    mkdir(saveImgPath)
end
if ~exist(saveRGBPath,'dir')
    mkdir(saveRGBPath)
end

% 删除上次存储的图像
s1 = struct2cell(dir([saveImgPath,'/','*.jpg']))';
s2 = struct2cell(dir([saveRGBPath,'/','*.jpg']))';
if ~isempty(s1)
    cellTem = s1(:,1);
    cellfun(@(x)deleteImgs(x,saveImgPath),cellTem);
end
if ~isempty(s2)
    cellTem = s2(:,1);
    cellfun(@(x)deleteImgs(x,saveRGBPath),cellTem);
end

%% 主程序
constWidth = 396;% 设置图像固定宽度,这个跟模板图像有关
while isvalid(h) % 想退出循环关闭窗口即可
    % 截图,获取手机屏幕画面
    set(groot,'PointerLocation',[410,65]);%鼠标所在的截图窗口位置,自己设定
    flag1 =mouseclick(0); % 0表示单击,左键单击选中当前鼠标位置, mex编译函数
    Img = cropFunction(1);% 没有参数则截取整个屏幕,一个参数就选择窗口截取
    imwrite(Img,['save_Imgs/',...
        datestr(now,'yyyy-mm-dd-HH-MM-SS'),...
        '_snopshot_',num2str(numFrame),'.jpg']);
    
    %% 根据当前手机屏幕图像并计算距离
    rows = size(Img,1);
    cols = size(Img,2);
    ROI = [0,round(rows/5),cols,round(3*rows/5)];
    targetImg = imcrop(Img,ROI);
    targetHight = size(targetImg,1);
    targetWidth = size(targetImg,2);
    constHight = round(targetHight*constWidth/targetWidth); %按比例
    targetImg = imresize(targetImg,[constHight,constWidth]);% 固定大小
    if flagAUTO
        [currentD,RGB,flag] = getD_jump(targetImg,templateImg);
        if flag % 投掷子或者棋子没检测到,这时候用鼠标选择点
            imshow(targetImg);
            [x,y] = ginput(2);
             message = sprintf('%s\n%s\n','请用鼠标先点击投掷子中心,',...
                '然后点击方格棋块中心');
            msgbox(message,'投掷子或者棋块没检测到!');
            currentD = pdist([x,y],'euclidean');
            RGB = targetImg;
            close(gcf)
        end
    else
        h.Position = figurePosition;
        imshow(targetImg);
        [x,y] = ginput(2);
         message = sprintf('%s\n%s\n','请用鼠标先点击投掷子中心,',...
            '然后点击棋子方块中心');
        msgbox(message,'手动操作!');
        currentD = pdist([x,y],'euclidean');
        RGB = targetImg;
        close(gcf)
    end
    
    imwrite(RGB,['save_RGB/',...
        datestr(now,'yyyy-mm-dd-HH-MM-SS'),...
        '_RGB_',num2str(numFrame),'.jpg']);
    h = figure(1);
    h.Position = figurePosition;
    imshow(RGB);
    fprintf('第%d张画面色子的distance:%f像素...\n',...
        numFrame,currentD);
    
    % 模拟手指点击屏幕在一定范围,因为人不能高精度点击一个点
    position = set(groot,'PointerLocation',...
        [cursorXRange(1)+xMag*rand(),cursorYRange(1)+yMag*rand()]);%设置鼠标位置在截图窗口
    
    flag2 = mouseclick(3,rate_time*currentD); % 3表示左键单击不动,第二个参数表示停顿时间毫秒;mex编译函数
    pause(3) % 停顿3秒待下一个画面稳定
    numFrame = numFrame+1;
end

%%
rmpath('./matchTemplateDice');
getD_jump.m如下:

function [distance,RGB,flag] = getD_jump(image,templateImg)
% 输入1张手机屏幕截图image,输出色子到下一个棋子的距离distance
% 和标记后的图像RGB
% flag 标志位,为0表示正常检测到,为1表示投掷子没检测到,为2为方格没检测到

%% prepare data
% path = 'F:\imagesData\微信跳一跳';
% imds = imageDatastore(path,'includesubfolders',true,...
%     'fileExtensions',{'.png'},...
%     'LabelSource','none');

%% train
load weChartDetector.mat %检测器文件,里面存储的是投掷子的Detector,棋块的Detector2
if ~isvalid(Detector)
    load weChartlabel.mat %标注结果文件,里面存储的变量mylabel
    Detector =trainACFObjectDetector(mylabel(1:100,[1,2])); %训练投掷子dice
    Detector2 =trainACFObjectDetector(mylabel(1:100,[1,3]));% 训练目标棋块targetObject
    save weChartDetector.mat Detector Detector2
end

%% detect
% 检测振子
diceDetector = vision.CascadeObjectDetector('diceDetector.xml');
bboxes = step(diceDetector,image);% 默认cascade
if isempty(bboxes) % 用模板匹配,或者acf检测
%     ROI = matchTemplateDice(image,templateImg);
%     box = ROI;
    [bboxes,scores] = detect(Detector,image);
    [~,maxIndex] = max(scores);
    box = bboxes(maxIndex,:);
else
    box = bboxes(1,:);
end
if isempty(box)
    fprintf('投掷子没有检测到!\n')
    flag = 1;
    distance = 0;
    RGB = image;
    return;
end
center1 = [box(1)+box(3)/2,box(2)+box(4)/2];

% 检测棋子
[bboxes2,~] = detect(Detector2,image);
[score2,minIndex2] = min(bboxes2(:,2));% 最上面的那个棋子方格
box2 = bboxes2(minIndex2,:);
if isempty(box2) % acf没检测到方块,就用canny检测
    gray = rgb2gray(image);
    edgeLines = edge(gray,'canny');
    
    % 找到最上面的白点坐标
    [y,x] = find(edgeLines==1);
    [~,index] = min(y);
    upperPoint = [x(index),y(index)];% 最上面的点,这个点每次找的比较可靠
    center2 = [upperPoint(1),upperPoint(2)+20];%大概向下20个像素
    box2 = [center2(1)-10,center2(2)-10,20,20];
    score2 = 1;
else
    center2 = [box2(1)+box2(3)/2,box2(2)+box2(4)/2];
end
% MinSize=[116,166];
% MaxSize=[193,193];
if isempty(box2)|| center2(2)>= center1(2)||box2(3)>193||box2(4)>116
    fprintf('棋子没有检测到!\n')
    flag = 2;
    distance = 0;
    RGB = image;
    return;
end

%% 标注保存
labels = cell(1,1);
labels{1} = sprintf('%s','dice');
labels2 = cell(1,1);
labels2{1} = sprintf('%s %s','Pier,',['score:',num2str(score2)]);

distance = pdist([center1;center2],'euclidean');
putString = sprintf('%s\n',['Next Line,',...
    'distance:',num2str(distance)]);
RGB = insertText(image,[20,20],putString,'TextColor','red','FontSize',25);
RGB = insertShape(RGB,'line',[center1,center2],...
    'color','green','LineWidth',3);% 画路线,绿色
RGB = insertObjectAnnotation(RGB,'rectangle',...
    box,labels,'color','blue','LineWidth',3);% 画投掷子,蓝色框
RGB = insertObjectAnnotation(RGB,'rectangle',...
    box2,labels2,'color','red','LineWidth',3);% 画棋子,红色框
flag = 0;% 表示正常都检测到





目前我设计的程序没有用到ADB工具,想到一种简单的工具实时获取手机界面即可,目前用的是Teamviewer QS(即QuickSupport APP),安装简单,方便,傻瓜式操作~如要获得高分数不仅电脑与手机实时通讯要好,上面系数rateTime选择恰当。matlab与Windows API控制鼠标函数已通过mex编译完成,直接可以在matlab里面当做函数调用。当然如果是其他平台,如Linux、mac可以重新mex cpp文件,不同平台编译后缀不一样,其他使用方法一样,祝玩的愉快~

附:链接给出所有程序及文件的下载链接,链接:https://pan.baidu.com/s/1ht5IMyC 密码: 8c74







  • 8
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值