微信跳一跳辅助程序开发,基于C++与opencv图像识别

趁着期末这段时间,课程不多,在学习opencv,闲来无事,看到网上有大神用python实现了Wechat的跳一跳的辅助外挂,看了大概原理,似乎跟我最近学的opencv好像很沾边,但是鄙人实在不懂Python,于是想着用C++就把它给实现了。原文链接:https://github.com/wangshub/wechat_jump_game,感兴趣的同学们可以看一下大神版本。

步入正题

先说原理,我这里直接copy大神的:

将手机点击到《跳一跳》小程序界面,用 ADB 工具获取当前手机截图,并用 ADB 将截图传上来,靠棋子的颜色来识别棋子,靠底色和方块的色差来识别棋盘(重点),用 ADB 工具点击屏幕一跳。

开发环境:

所谓工欲善必先利其器,鄙人的平台是win7 64位+vs2015+opencv 3.4+adb工具+荣耀4X(分辨率:720x1280),只对此分辨率作适配过,其它的我试了一下,不可行,等考完试我再适配。环境配置网上很多,这里就不浪费时间了。adb工具直接放到工程文件夹下即可。

前提准备工作:把手机用数据线连到电脑,装好驱动,打开开发者模式,打开USB调试,下载adb工具,这里最好配个环境变量。不配也行,打开cmd命令窗口,转到adb目录,键入 adb devices 命令(坑爹的魅族手机,它就是连接不了,不得不借用同学的备用手机荣耀4x来测试),如下:



连接成功之后,开始写代码了

打开vs,新建工程键入头文件这些这些不多说了,同学们都会,

1.使用system命令,截图,并传上来,用imread读入到变量scree去

system("adb shell screencap -p /sdcard/autojump.jpg");//截图
system("adb pull /sdcard/autojump.jpg");              //上传到电脑
Mat scree = imread("autojump.jpg");		      //读入
2.对图像进行处理,分两步,一是先找出棋子的位置,二是找到物体的中心位置


1).找棋子。找规律,发现的棋子的颜色在游戏中好像是独一无二的,利用这点,我们很容易找到棋子的位置,用ps拾取棋子底部的RGB三个通道颜色(注意了,opencv的储存通道的顺序是BGR)作为参考点,然后从图像下方(逐行逐个像素)往上扫描(为什么是从下往上?因为我们要的是棋子的下方,如果是上方,找的是棋子的脑袋),如果扫描到的点的各通道的数值与参考点的作对比,如果各点的误差的值的和少于20(数值根据需要适当的调),那么认为是找到了。代码如下:

Point ptc, ptm;								//存储棋子底下与物体中心的坐标
void draw_color(Mat &img)
{	
	int average = 0;
	int channels = img.channels();
	int nRows = img.rows;
	int nCols = img.cols* channels;
	int i = (int)nRows * 1 / 3;					//不用整个图像都扫描,高的1/3就行

											
	int b_color = 102;						//定义棋子蓝色通道值
	int g_color = 54;						//定义棋子绿色通道值
	int r_color = 53;						//定义棋子红色通道值

	int man_x = 0;
	int man_y = 0;
	int fla = 0;
	for (man_y = i + 350; man_y>i + 100; man_y--)
	{
		for (man_x = 99; man_x < nCols - 99; man_x += 3)
		{
			
			if ((abs((int)img.at<uchar>(man_y, man_x) - b_color) +
				abs((int)img.at<uchar>(man_y, man_x + 1) - g_color) +
				abs((int)img.at<uchar>(man_y, man_x + 2) - r_color)) < 20) //判断
			{
				ptm.x = (int)man_x / 3.0;		//记录坐标,因为opencv存储图像点是以三通道存储的,所以除以3
				ptm.y = man_y - 5;			//下移,防止找到背景点的bug
				fla = 1;
				break;
			}
		}
		if (fla)
			break;
	}
	draw_garry(img, ptm.x);//调用另外一下函数,由另外的函数去找物体顶面上的中点
}
2).找到棋子的坐标坐标之后,我们要找物体的顶面上的中点。分两步,1是找物体的顶面的上界,2是找物体的顶面的下界。


首先从图像上方往下扫描,大概屏幕往下的1/4开始找,因为这部分区域基本上是背景,无其它的颜色,所以利用这点,我们以此处的第一行为参考,把一行中每一点的像素值加起来,与第一行作对比,如果比参考值大或者小于某个数值(适当的调整),则认为是有物体了(先把图像转换为灰度比较好处理)。然后再往右扫描,以第一个像素点作对比,然后比较他们的绝对差,大于某个值,就是找到了

代码如下

        Mat img;
	cvtColor(thr, img, COLOR_RGB2GRAY);

	int sum = 0;
	int average = 0;
	int channels = img.channels();
	int nRows = img.rows;
	int nCols = img.cols* channels;

	int k = 0;
	int f = 0;
	Rect r1;
	Rect r2;
	Point pt1, pt2;
	int stat_y = (int)(nRows*(2.0 / 6.0));
	pt2.x = nCols;
	pt1.x = 0;
	pt1.y = 0;
	pt2.y = 0;


	int i;
	int j;
	//找出物体的第一行
	//扫描每一行,如果下一行与第一行的像素总和相差正负30个,那么就不是纯色,有物体了
	for (i = stat_y; i < nRows; ++i)
	{
		for (j = 0; j<nCols; j++)
		{
			sum += img.at<uchar>(i, j);//把一行的像素值加起来

		}
		if (sum != 0)
			if (k == 0)
			{
				f = sum;
				k = 1;
			}
		
		if (sum <f - 30|| sum>f + 30)//如果这行与参考行的像素值相差正负30,那么认为是有物体
		{
			average = 1;
			break;

		}
		if (average)
			break;
		sum = 0;
	}
	//找出有物体的第一行后
	//接着找出非背景的第一点
	int fl = i + 273;//物体最大不会高于273行
	int i_x = 0;
	int i_y = 0;
	int kk = 0;
	for (; i < fl; ++i)
	{
		for (j = 0; j<nCols-1; j++)
		{
			//如果前一点与后一点的像素值相差大于5,那么就是找到了
			kk = abs((int)img.at<uchar>(i, j) - (int)img.at<uchar>(i, j+1));
			//为了防止棋子的脑袋超过物体,所以先找出棋子的x轴方向的位置,
			//如果这时找出的第一点与棋子的坐标相接近,则继续找
			if (abs(j - man_xx) < 30)
				continue;
			if (kk>5)
			{
				i_x = j;
				i_y = i;
				pt1.x = i_x;
				pt1.y = i_y;
				break;
			}




		}
		if (i_x != 0)
			break;
	}

同理,嗯嗯,我很喜欢这词,每当用这个词的时候可以省略好多字好多言语。我们取上界那个点的颜色作参考点,由下往上找,就能找到下界的点了

	int n;
	int n_x = 0;
	int n_y = 0;

	//找出物体的下界,以便求中点
	for (n = fl; n >= i; n--)
	{
			//用上面找到物体的那一点的像素值来判断,相差不大于10就是找到了
			if (abs((int)img.at<uchar>(i+1, j) - (int)img.at<uchar>(n, j)) < 10)
			{
				pt2.x = j;
				pt2.y = n;
				break;
			}

	}
上下界的点找到之后,物体的顶面的中点就很容易计算出来了

	ptc.x = j;
	ptc.y = (int)((n - i) /2) + i;//计算物体顶面中点
3.计算物体顶面中点到棋子底部的距离,不多说,两点距离公式,并乘上系数0.5再除以0.25,就是按压时间。

	//计算人到物体中心距离
	//两点距离公式,距离再乘以0.5再除以0.25就是按压时间
	int S = (int)sqrt((ptc.x - ptm.x)*(ptc.x - ptm.x) + (ptc.y - ptm.y)*(ptc.y - ptm.y))*0.5 / 0.25;
4.发送命令:adb shell input swipe 320 420 320 410,在坐标320,410到坐标320,410模拟滑到时间S毫秒,也就是按压S毫秒啦啦。

	char ch[50] = { " " };
	//把按压时间与命令放到一个字符串上
	sprintf(ch, "adb shell input swipe 320 410 310 410 %d", S);
	//用system命令输入
	system(ch);
所有代码:

main.cpp文件
#include "header.h"


int main()
{
	cout << "微信跳一跳辅助程序,彬彬移植于网络大神的python的版本\n此版本是用C++与opencv定的还有很多bug\n";
	cout << "请确保打开了手机微信跳一跳 y or n?\n";
	getchar();
	while(1)
	{
	system("adb shell screencap -p /sdcard/autojump.jpg");
	system("adb pull /sdcard/autojump.jpg");
	Mat scree = imread("autojump.jpg");
	draw_color(scree);
	Sleep(1500);//延时,不能太快
	}
	system("pause");
	return 0;
}
gray.cpp文件
#include "header.h"
Point ptc, ptm;								//存储棋子底下与物体中心的坐标

/************找出棋子的底下的坐标*****************

///判断当前点与人物底下的颜色相似
//对应的通道像素值作差取绝对值
//即参考的红色通道值-扫描点的红色通道点等三个通道分别减取绝对值
//然后相加,如果少于20,那就认为这两点颜色相同
//记录坐标
************************************************/
void draw_color(Mat &img)
{	
	int average = 0;
	int channels = img.channels();
	int nRows = img.rows;
	int nCols = img.cols* channels;
	int i = (int)nRows * 1 / 3;				//不用整个图像都扫描,高的1/3就行

											
	int b_color = 102;						//定义棋子蓝色通道值
	int g_color = 54;						//定义棋子绿色通道值
	int r_color = 53;						//定义棋子红色通道值

	int man_x = 0;
	int man_y = 0;
	int fla = 0;
	for (man_y = i + 350; man_y>i + 100; man_y--)
	{
		for (man_x = 99; man_x < nCols - 99; man_x += 3)
		{
			
			if ((abs((int)img.at<uchar>(man_y, man_x) - b_color) +
				abs((int)img.at<uchar>(man_y, man_x + 1) - g_color) +
				abs((int)img.at<uchar>(man_y, man_x + 2) - r_color)) < 20) //判断
			{
				ptm.x = (int)man_x / 3.0;	//记录坐标,因为opencv存储图像点是以三通道存储的,所以除以3
				ptm.y = man_y - 5;			//下移,防止找到背景点的bug
				fla = 1;
				break;
			}
		}
		if (fla)
			break;
	}
	draw_garry(img, ptm.x);
}
void draw_garry(Mat &thr,int man_xx)
{
	Mat img;
	cvtColor(thr, img, COLOR_RGB2GRAY);

	int sum = 0;
	int average = 0;
	int channels = img.channels();
	int nRows = img.rows;
	int nCols = img.cols* channels;

	int k = 0;
	int f = 0;
	Rect r1;
	Rect r2;
	Point pt1, pt2;
	int stat_y = (int)(nRows*(2.0 / 6.0));
	pt2.x = nCols;
	pt1.x = 0;
	pt1.y = 0;
	pt2.y = 0;


	int i;
	int j;
	//找出物体的第一行
	//扫描每一行,如果下一行与第一行的像素总和相差正负30个,那么就不是纯色,有物体了
	for (i = stat_y; i < nRows; ++i)
	{
		for (j = 0; j<nCols; j++)
		{
			sum += img.at<uchar>(i, j);//把一行的像素值加起来

		}
		if (sum != 0)
			if (k == 0)
			{
				f = sum;
				k = 1;
			}
		
		if (sum <f - 30|| sum>f + 30)//如果这行与参考行的像素值相差正负30,那么认为是有物体
		{
			average = 1;
			break;

		}
		if (average)
			break;
		sum = 0;
	}
	//找出有物体的第一行后
	//接着找出非背景的第一点
	int fl = i + 273;//物体最大不会高于273行
	int i_x = 0;
	int i_y = 0;
	int kk = 0;
	for (; i < fl; ++i)
	{
		for (j = 0; j<nCols-1; j++)
		{
			//如果前一点与后一点的像素值相差大于5,那么就是找到了
			kk = abs((int)img.at<uchar>(i, j) - (int)img.at<uchar>(i, j+1));
			//为了防止棋子的脑袋超过物体,所以先找出棋子的x轴方向的位置,
			//如果这时找出的第一点与棋子的坐标相接近,则继续找
			if (abs(j - man_xx) < 30)
				continue;
			if (kk>5)
			{
				i_x = j;
				i_y = i;
				pt1.x = i_x;
				pt1.y = i_y;
				break;
			}


		}
		if (i_x != 0)
			break;
	}


	int n;
	int n_x = 0;
	int n_y = 0;

	//找出物体的下界,以便求中点
	for (n = fl; n >= i; n--)
	{
			//用上面找到物体的那一点的像素值来判断,相差不大于10就是找到了
			if (abs((int)img.at<uchar>(i+1, j) - (int)img.at<uchar>(n, j)) < 10)
			{
				pt2.x = j;
				pt2.y = n;
				break;
			}

	}
	
	ptc.x = j;
	ptc.y = (int)((n - i) /2) + i;//计算物体中点

	//计算人到物体中心距离
	//两点距离公式,距离再乘以0.5再除以0.25就是按压时间
	int S = (int)sqrt((ptc.x - ptm.x)*(ptc.x - ptm.x) + (ptc.y - ptm.y)*(ptc.y - ptm.y))*0.5 / 0.25;
	char ch[50] = { " " };
	//把按压时间与命令放到一个字符串上
	sprintf(ch, "adb shell input swipe 320 410 310 410 %d", S);
	//用system命令输入
	system(ch);
	cout << "\n" << ch << endl;

}
头文件
#define _CRT_SECURE_NO_WARNINGS//关闭安全检查,须放到最前面
#include <opencv2/opencv.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <iostream>
#include <Windows.h>
#include <cmath>
using namespace cv;
using namespace std;
void draw_color(Mat &img);
void draw_garry(Mat &img, int x);//找出物体的中心位置
逐个添加进工程即可,至少分辨率的适配,调下里面那些数据就好, adb工具搜索下载即可。

希望期末考试不挂科不挂科






  • 3
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值