想必大家多多少少都会玩微信跳一跳,鉴于别的语言实现自动玩游戏,出于好奇,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:[email protected] % 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