分类目录归档:Java

如何用 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

从数据结构的角度看ArrayList与LinkedList

数组应该是大部分开发者最常用的数据结构,而链表使用频率则相对小一些。对于Java开发者而言,数组和链表这两种数据结构分别对应Java集合类中ArrayList和LinkedList。对我而言,至少在Java开发中,我99%以上都是使用ArrayList。
为了避免上述的情况,就从数据结构的角度来分析一下这两者的优缺点以及使用场景。

数组

数组很简单,就是一段相同类型的元素的集合。用ArrayList简单的创建一个集合:

ArrayList<Integer> arrays = new ArrayList<Integer>();
arrays.add(2);
arrays.add(4);
arrays.add(6);
arrays.add(8);

它在内部的数据结构即为数组,类似下面:

int[] arrays = {2,4,6,8};

此时,如果我们想要在索引为2的位置插入一个元素5,使数组变成{2,4,5,6,8}。如下图:

arraylist

Java中对应操作为:

arrays.add(2, 5);

这时ArrayList内部其实做了下面这些事情:

  1. 动态扩大数组;
  2. 将原来数组索引为2及其后面所有的元素向后移动;
  3. 插入新元素5到索引为2的位置。

对于删除某个元素也是类似的操作,所以现在应该可以看出数组的随机访问速度应该是不错的,而随机存储效率比较低。

链表

链表有单向链表、双向链表和循环链表,LinkedList使用的是非循环双向链表。双向链表的每一个元素为一个节点,每个节点都有一个直接前驱,数据域,直接后继。同样用LinkedList简单创建一个集合:

LinkedList<Integer> linkedList = new LinkedList<Integer>();
linkedList.add(2);
linkedList.add(4);
linkedList.add(6);
linkedList.add(8);

它在内部的数据结构即为链表,同样如果想要在索引为2的位置添加一个5,对应的图如下:

linkedlist

 

Java中对应操作为:

linkedList.add(2, 5);

这时LinkedList内部其实做了下面这些事情:

  1. 同样先检查是否超出边界;
  2. 对半查找出原来索引为2的节点将其前驱改为新增节点5;
  3. 将新增节点5的前驱改为4,后继改为6;
  4. 节点4的后继改为5。

同样对于链表中节点的删除也是如此,只要修改指针指向即可,而不必像数组那样大费周章的移动。由此可见链表的优点是随机存储效率高,而缺点就是随机访问效率较低。

总结

比较完数组与链表的优缺点,现在对于它们各自的使用场景应该更清楚了。
数组适合在大量随机访问集合的情况下使用,而链表更适合在大量随机存储,即大量的添加删除时使用。

THE END