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