说明:最近微信的跳一跳小程序可谓火了一把,不是因为它本身多好玩,而是有大部分的程序员们加入其中,利用各种领域方法,实现了微信跳一跳的外挂,分数轻松上千或上万。之前也看了基于Python开源的代码,GitHub上现在的star已经快超过1W了,简直不敢想。趁着今天礼拜天,在实验室中也简单的实现了一下微信跳一跳的辅助工具,精度还不够高,我跑了一下才到90,纯属娱乐好玩的,后期再继续改进,主要是依赖C++来实现了一下。
环境: Win10+VS2012+Opencv2.4.10+ADB工具
环境的搭建请查阅相关资料!
主要思路:
通过adb图像获取部分大家可以查阅相关资料,代码也很简单:
adb shell screencap -p /sdcard/autojump.png
adb pull /sdcard/autojump.png
利用上面两行代码即可将手机当前的屏幕进行截图并且上传到工程文件路径下。
首先就是在上传的autojump.png图片上进行模板匹配,匹配出小人,并计算小人的坐标;如上图所示,框出来的用红色箭头所指即为小人的坐标!
然后就是通过Canny()函数进行图像的边缘检测,这里使用的阈值为5,10基本可以检测出所有边缘信息;
然后根据一般下一个要跳的地点始终在小人的左半屏或又半屏部分这一先验知识,来进行查找范围的确定,进行行扫描,扫描到的第一个值为255的即返回当前坐标值;然后通过计算与小人坐标的距离即可得到下一步要跳跃的距离,(注:本代码中在下一个坐标的纵坐标进行+50处理,由于本文中只利用了一个关键点进行测试的,这样做是显然不合理的,接下来可以再利用第二个关键点进行下一个跳跃目标中心点的计算),由于本人手机是1080*1920的所以再得到距离过后乘以一个跳跃系数1.35,(这里不同分辨率的手机系数是不一样的),这样就得到了跳跃按压时间,从而通过system()命令进行调用ADB工具进行与手机通讯实现模拟人的点击。本文仅仅是简单的实现了一下看看效果,如果想跑高分还得进行代码的优化与更改!其次因为每次按压的地点肯定是不一样的,而本文也采用简单的同一位置按压,这样做很容易被腾讯反作弊给查出来的,所以这里可以添加一个随机数从而可以简单的避免位置重复!
代码如下:
/*
时间:2018-1-7
地点:SHNU
功能:wechat简单跳一跳C++代码的实现,有待改进,仅供学习之用!欢迎大家提出新算法
*/
#include
#include
#include
using namespace cv;
using namespace std;
//全局变量定义区
Mat srcImage;
Mat dstImage;
Mat Character;
//get_screenshot();获取手机上的图像
void get_screenshot();
//Canny_Dec();边缘检测
void Canny_Dec(Mat& srcImage);
//获取Character坐标
Point get_Character_Loc(Mat& srcImage,Mat& Tem_img);
//获取下一个要跳的点
Point get_next_img_Loc(Mat& srcImage,Point& Character_Loc);
//计算距离
int get_distance(Point& first_point,Point& next_point);
//跳跃
void jump(int&g_distance);
int main(int argc,char** argv)
{
system("color 3F");
while (true)
{
get_screenshot();
srcImage = imread("autojump.png");
dstImage = srcImage.clone();
Character = imread("./Template/character.png");
//imshow("Character",Character);
//cvtColor(srcImage,srcImage,CV_BGR2GRAY);
Point next_p = get_Character_Loc(srcImage,Character);
cout<<"next_p:"<<1111<int g_distance = get_distance(next_p,get_next);
jump(g_distance);
//cout<<"get_next_img_Loc:"<
circle(dstImage,get_next,8,Scalar(0,221,2));
//imshow("test",dstImage);
imwrite("Canny.png",dstImage);
_sleep(1500);
}
return 0;
}
void get_screenshot()
{
system("adb shell screencap -p /sdcard/autojump.png");
system("adb pull /sdcard/autojump.png");
}
Point get_Character_Loc(Mat& srcImage,Mat& Tem_img)
{
matchTemplate(srcImage,Tem_img,dstImage,CV_TM_SQDIFF);
double minVal,maxVal;
Point minLoc,maxLoc,matchLoc;
minMaxLoc(dstImage,&minVal,&maxVal,&minLoc,&maxLoc,Mat());
matchLoc = minLoc; //matchLoc是最佳匹配的区域左上角点
rectangle(srcImage,Rect(matchLoc,Size(Character.cols,Character.rows)),Scalar(255,255,0),1,8,0);
//Canny(srcImage,srcImage,1,10);
putText(srcImage,"Wang",Point(matchLoc.x+Character.cols*0.5,matchLoc.y+Character.rows),1,2,Scalar(0,0,255));//画出Character小人的坐标
return Point(matchLoc.x+Character.cols*0.5,matchLoc.y+Character.rows);
}
Point get_next_img_Loc(Mat& srcImage,Point& Character_Loc)
{
cout<<"get_next_img_Loc"<5 ,10);
imwrite("get_next_img_Loc.png",srcImage);
//imshow("get",srcImage);
cout<<"Character_Loc.x:"<if(Character_Loc.x < 540)
{
for(int j = int(srcImage.rows*0.2);j<int(srcImage.rows*0.8);j++)
{
uchar* data = srcImage.ptr(j);
for(int i = 1079;i > 540 ;i--)
{
if(data[i] == 255)
{
return Point(i,j);
//cout<<"Point:"<
}
}
}
}
else
{
for(int j = int(srcImage.rows*0.2);j<int(srcImage.rows*0.8);j++)
{
uchar* data = srcImage.ptr(j);
for(int i = 0;i<540;i++)
{
if(data[i] == 255)
return Point(i,j);
}
}
}
}
int get_distance(Point& first_point,Point& next_point)
{
int A = first_point.x - next_point.x;
int B = first_point.y - (next_point.y+50);
return int(pow(pow(A,2)+pow(B,2),0.5));
}
void jump(int&g_distance)
{
char AA[50];
int distance_ = g_distance * 1.35;
sprintf(AA,"adb shell input swipe 320 410 320 410 %d",distance_);
cout<
下一个关键点定位:
上图中画出的小圈圈,不太清晰,将就着看下!小菜水平有限,仅仅是基于好玩就弄了下!
华丽的分割线————————————————
又来更新一下啦!
晚上不想看论文就想到了之前的跳一跳,经过一边显示命令窗口输出和一边显示Canny()边缘化处理终于找到了上次跑的分数低的原因啦!
主要原因如下所示:
如上所示,由于之前选取的Canny()中的阈值为1和10,这导致一旦要跳到的下一个目标物体的颜色和背景色很接近时就很容易使得边缘的梯度小于10,因此就不会被认为是边缘,从而导致上面的这种情况出现。索性今天就将阈值设为3和8,并在Canny()函数前面加上了一个高斯滤波器。如下:
GaussianBlur(srcImage,srcImage,Size(3,3),0);
还有就是上面的_sleep(1500)函数,这里如果闲时间比较久的话也是可以改为1000的,速度上有所提升。
同时加上按压位置的随机数,使得每次按压点都是在(320,410)—(350,460)之间。代码如下:
int rand_x = int(320+rand()%50); //加上随机数使得每次按压都是在点(320,410)-(370,460)之间
int rand_y = int(410+rand()%50);
sprintf(AA,"adb shell input swipe %d %d %d %d %d",rand_x,rand_y,rand_x,rand_y,distance_);
如下图所示:
由上图可知,每次按压的位置都是在变的。
完整版代码如下:
/*
时间:2018-1-7
地点:SHNU
功能:wechat简单跳一跳C++代码的实现,有待改进,仅供学习之用!欢迎大家提出新算法
*/
#include
#include
#include
#include//rand()随机数头文件
using namespace cv;
using namespace std;
//全局变量定义区
Mat srcImage;
Mat dstImage;
Mat Character;
static int i = 0;
//get_screenshot();获取手机上的图像
void get_screenshot();
//Canny_Dec();边缘检测
void Canny_Dec(Mat& srcImage);
//获取Character坐标
Point get_Character_Loc(Mat& srcImage,Mat& Tem_img);
//获取下一个要跳的点
Point get_next_img_Loc(Mat& srcImage,Point& Character_Loc);
//计算距离
int get_distance(Point& first_point,Point& next_point);
//跳跃
void jump(int&g_distance);
int main(int argc,char** argv)
{
system("color 3F");
while (true)
{
get_screenshot();
srcImage = imread("autojump.png");
dstImage = srcImage.clone();
Character = imread("./Template/character.png");
//imshow("Character",Character);
//cvtColor(srcImage,srcImage,CV_BGR2GRAY);
Point next_p = get_Character_Loc(srcImage,Character);
//cout<<"next_p:"<<1111<
Point get_next = get_next_img_Loc(srcImage,next_p);
int g_distance = get_distance(next_p,get_next);
jump(g_distance);
//cout<<"get_next_img_Loc:"<
circle(dstImage,get_next,8,Scalar(0,221,2));
//imshow("test",dstImage);
imwrite("Canny.png",dstImage);
_sleep(1000);
}
return 0;
}
void get_screenshot()
{
system("adb shell screencap -p /sdcard/autojump.png");
system("adb pull /sdcard/autojump.png");
}
Point get_Character_Loc(Mat& srcImage,Mat& Tem_img)
{
matchTemplate(srcImage,Tem_img,dstImage,CV_TM_SQDIFF);
double minVal,maxVal;
Point minLoc,maxLoc,matchLoc;
minMaxLoc(dstImage,&minVal,&maxVal,&minLoc,&maxLoc,Mat());
matchLoc = minLoc; //matchLoc是最佳匹配的区域左上角点
cout<<"maxVal:"<255 ,255,0),1,8,0);
//Canny(srcImage,srcImage,1,10);
putText(srcImage,"Wang",Point(matchLoc.x+Character.cols*0.5,matchLoc.y+Character.rows),1,2,Scalar(0,0,255));//画出Character小人的坐标
return Point(matchLoc.x+Character.cols*0.5,matchLoc.y+Character.rows);
}
Point get_next_img_Loc(Mat& srcImage,Point& Character_Loc)
{
cout<<"get_next_img_Loc"<3 ,3),0);
Canny(srcImage,srcImage,3,8);
char AA[30];
sprintf(AA,"get_next_img_Loc_%d.png",i);
cout<//imshow("get",srcImage);
cout<<"Character_Loc.x:"<if(Character_Loc.x < 540)
{
for(int j = int(srcImage.rows*0.2);j<int(srcImage.rows*0.8);j++)
{
uchar* data = srcImage.ptr(j);
for(int i = 1079;i > 540 ;i--)
{
if(data[i] == 255)
{
return Point(i,j);
//cout<<"Point:"<
}
}
}
}
else
{
for(int j = int(srcImage.rows*0.2);j<int(srcImage.rows*0.8);j++)
{
uchar* data = srcImage.ptr(j);
for(int i = 0;i<540;i++)
{
if(data[i] == 255)
return Point(i,j);
}
}
}
return Character_Loc;
}
int get_distance(Point& first_point,Point& next_point)
{
int A = first_point.x - next_point.x;
int B = first_point.y - (next_point.y+50);
return int(pow(pow(A,2)+pow(B,2),0.5));
}
void jump(int&g_distance)
{
char AA[50];
int distance_ = g_distance * 1.35;
int rand_x = int(320+rand()%50); //加上随机数使得每次按压都是在点(320,410)-(370,460)之间
int rand_y = int(410+rand()%50);
sprintf(AA,"adb shell input swipe %d %d %d %d %d",rand_x,rand_y,rand_x,rand_y,distance_);
cout<
通过测试效果如下:轻松得榜首,到五百多时程序依然可以一直在运行,我觉得时间太长,所以就直接弄挂了。不过并不影响榜首的位置!上图:
总结: 主要本人刚好也是视觉图像方向的,哈哈,就闲的无聊测试了一把,经过更改和测试。如果不遇到那种与背景色特别相近的,上榜首是没问题的! 嗯,说了这么多,这个博客也就到此结束啦,有问题欢迎留言!
附件:
说明:上图黄色方框,是扫描区域(一旦确定小人的坐标属于右半屏就扫描左半屏,之所以选择黄色区域是为了避免扫描到分数边缘和底下的一个分享按钮,这样做也加快了代码的运行速度。小人如果在左半屏,同理,只不过我选择的扫描方式是从右向左)
ADB工具和工程下载地址: 下载