如何用 Java + OpenCV 实现自动玩跳一跳?



前言

自从微信更新到 6.6.1 版本之后,微信小游戏也随之发布。跳一跳成了第一批小游戏的代表作,到处刷屏。而在程序员的朋友圈里,则是各种刷分,各种版本的算法实现自动玩刷分。

秉着学习 OpenCV 的心态,我也用 Java + OpenCV 实现了一个版本。

目标分解

跳一跳这个小游戏规则很简单,就是根据触摸屏幕的时间长短决定跳跃的距离。
所以整个程序可以分解为以下 6 步,循环执行:

  1. 获取跳一跳游戏画面
  2. 找到起点,即跳一跳中的紫色小人
  3. 找到终点,即小人要跳跃的下一个目标的中点
  4. 根据欧式距离公式计算起点与终点的距离
  5. 根据得出的距离计算触摸时间
  6. 用 adb 工具模拟触摸屏幕

准备工作

1.熟悉 ADB 命令

adb shell screencap -p xxx //截屏
adb pull xxx //从设备复制资源到电脑上
adb shell input swipe x1 y1 x2 y2 time //模拟从(x1,y1)滑动到(x2,y2),滑动时间为time(ms)

2.开发环境

本文基于 Java 1.8.0 与 OpenCV 3.4.0 开发。

3.欧式距离公式

编程实现

1.找起点

//载入跳一跳小人图片
Mat template = Imgcodecs.imread("i.jpg");
//载入屏幕截图
Mat source = Imgcodecs.imread("1.png");
//新建 result 用于保存模板匹配结果
Mat result = Mat.zeros(source.rows(),source.cols(),CvType.CV_32FC1);
//在模板图像和输入图像之间寻找匹配,获得匹配结果图像 result
Imgproc.matchTemplate(source, template, result, Imgproc.TM_CCOEFF_NORMED);
//在给定的矩阵中寻找最大和最小值(包括它们的位置)
MinMaxLocResult minMaxLocResult = Core.minMaxLoc(result);
//minMaxLocResult 返回的 maxLoc 为匹配的模板在截图中的左上位置,所以可以得到起点的位置
Point startPoint = new Point(minMaxLocResult.maxLoc.x + template.width() / 2, minMaxLocResult.maxLoc.y + template.height() - 20);

如图,minMaxLoc 方法返回的 maxLoc 点为匹配到目标的左上点。

2.找终点

先判断下一个目标是否有小白点,有的话按照找起点的方法模板匹配白点的坐标,即可得到终点。

如果找不到白点,则先利用高斯模糊消除噪声,再运行边缘检测算法,然后从上到下遍历得到上边缘的顶点与下边缘的顶点,从而可以得出终点。

//载入小白点图片
Mat template_white_dot = Imgcodecs.imread("white_dot.jpg");
//dot_result 用于保存白点模板匹配结果
Mat dot_result = Mat.zeros(source.rows(),source.cols(),CvType.CV_32FC1);
Imgproc.matchTemplate(source, template_white_dot, dot_result, Imgproc.TM_CCOEFF_NORMED);
MinMaxLocResult minMaxLocDotResult = Core.minMaxLoc(dot_result);
//判断模板匹配值,如果大于 0.7 就相信存在白点,即可直接获取目标点
if (minMaxLocDotResult.maxVal > 0.7) {
    System.out.println("found white dot.");
	targetPoint.x = minMaxLocDotResult.maxLoc.x + template_white_dot.width() / 2;
	targetPoint.y = minMaxLocDotResult.maxLoc.y + template_white_dot.height() / 2;
} else {
	System.out.println("white dot not found.");
	//result2 用于存放高斯模糊结果
	Mat result2 = Mat.zeros(source.rows(),source.cols(),CvType.CV_32FC1);
	//result2 用于存放边缘检测结果
	Mat result3 = Mat.zeros(source.rows(),source.cols(),CvType.CV_32FC1);
	//使用高斯平滑滤波器降噪
	Imgproc.GaussianBlur(source, result2, new Size(5, 5), 0);
	//运行 Canny 算子
	Imgproc.Canny(result2, result3, 1, 10);
	//把小人在图中抹掉,防止高度过高对结果产生影响
	clearTemplateRect(result3, new Rect(minMaxLocResult.maxLoc, p));
	//遍历边缘像素,计算终点
	targetPoint = findTargetPoint(result3);
}

3.计算距离

//传入起点和终点,根据欧式距离公式计算距离
public static double getDistance(Point s, Point e){
	return Math.sqrt(Math.pow(s.x-e.x, 2) + Math.pow(s.y-e.y, 2));
}

4.计算时间

//1.35 这个参数适合1080*1920分辨率,其他分辨率需要调整。
int time = getDistance(startPoint, targetPoint) * 1.35;

5.模拟触屏

int x1 = source.width() / 3 * 2; //取图片宽度的 2/3
int y1 = source.height() / 3 * 2; //取图片高度的 2/3
int x2 = x1 + random.nextInt(10); //随机模拟移动10像素内
int y2 = y1 + random.nextInt(10); //随机模拟移动10像素内
cmd("adb shell input swipe "+ x1 + " " + y1 + " "+ x2 +" " + y2 + " " + time);

最后

在与微信防作弊的不断博弈中,算法实现也需要不断更新,需要更加真实地模拟人为操作,否则刷到很高的分也会被微信拒绝。截止目前,这个版本已经可以跑得很高分了。

当然,这篇文章的目的并不是为了宣传刷分作弊,只是作为我这次学习实践的一次记录。而通过这次的实践,对 OpenCV 有了初步了解,相信以后有机会用在更有趣的场景。

如需完整项目及代码,关注公众号「现代晓说」回复【跳一跳】获取。

THE END

发表评论

电子邮件地址不会被公开。 必填项已用*标注