淘宝小游戏玩个球

淘宝小游戏"玩个球"自动执行

本文记录了为实现本游戏的自动执行而做的探索过程

第一阶段: 通过截屏进行判断

1.1 基本步骤

1) 通过adb shell截屏
2) 判断特定行的蓝色和红色像素数量
3) 通过adb shell发送指令

1.1.1 截屏

首先获得用su获得root权限(后面的步骤需要) 然后用screencap命令截屏, 图片放到放到手机SD卡里, 然后通过pull命令将图片复制到电脑上(d:\ss.png)

su
adb shell screencap sdcard/#swap/ss.png
adb pull /sdcard/#swap/ss.png d:\ss.png

1.1.2 加载图片, 判断颜色

最开始使用C语言编写, 使用altimage.h提供的库.

CImage类是ATL和MFC共用的一个类,其头文件为atlimage.h,主要用于图片文件的打开,显示与保存。这里需要注意的是,在VS2010和VS2012的MFC编程中,不需要将头文件包含进来。MFC中要使用CImage类,必须先将头文件包含进来,可以包含在当前代码的CPP文件中,也可以包含在所属类的头文件中,不过最好还是包含在工程的stdafx.h文件中。CImage总共有39个成员函数。

(百度百科)

首先执行上面的命令, 然后进行图片的判断

system("D:\\input.bat");
image.Load(_T("D:\\ss.png"));
bool result = check(895, image);

其中check函数定义如下, 判断第row行红色像素和蓝色像素哪个多一些.

// 返回 false代表蓝色, true代表红色
bool check(int row, CImage& image) {
	int blue = 0;
	int red = 0;
	for (int i = 0; i < 1080; ++i) {
		COLORREF color = image.GetPixel(i, row);
		BYTE r = GetRValue(color);
		BYTE g = GetGValue(color);
		BYTE b = GetBValue(color);
                // 当时考虑到方块表面可以有一些轻微的渐变效果 所以设置了RGB的范围 后来发现是纯色
		if (r >= 250 && g >= 94 && g <= 103 && b >= 97 && b <= 103) {
			red++;
		}
		if (r >= 50 && r <= 56 && g >= 250 && b >= 250) {
			blue++;
		}
	}
	return red > blue;
}

1.1.3 命令发送

使用adb提供的input命令可以模拟触摸操作(需要root权限)

没用root权限直接使用input tap只会显示一个killed, 手机上没有任何反应. 获得root权限之后手机就有反应了, 电脑上没有任何报错.

代码如下, 首先打开一个文件 向里面写入root授权命令和input命令, 然后将adb shell命令的输入定向到该文件

ofstream f("D:\\.input");
bool result = check(895, image);
f << "su" << endl;
if (result[i])
    f << "input tap " << 284 << " " << 1606 << endl;
else
    f << "input tap " << 797 << " " << 1608 << endl;
system("adb shell < D:\\.input");

1.2 出现的问题及优化

1.2.1 出现的问题

程序根本无法使用! 因为太慢了. root授权需要1s左右, 截屏需要1s左右, tap命令从发出到执行也至少需要1秒左右~

所以我从针对上面的问题进行了如下优化

1.2.2 改用java语言

C++似乎无法获取到adb命令的输入流,所以只能讲命令写到文件里,adb执行完这几条命令就退出了;要执行新的命令必须重启adb,重启就意味着要重新进行root授权,极其浪费时间。

Java语言的优势是不仅可以执行外部程序,还能获得输入流输出流,可以在其它程序执行时向其动态写入命令(代码的参考资料)

try {
    Process mainProcess = Runtime.getRuntime().exec("adb shell");
    DataOutputStream os = new DataOutputStream(mainProcess.getOutputStream());
    os.writeBytes("su" + "\n");
    os.flush();

    //处理错误输出流
    final BufferedReader brError = new BufferedReader(new InputStreamReader(mainProcess.getErrorStream()));
    ReaderThread t2 = new ReaderThread(brError, "error");
    t2.start();

    //处理标准输出流
    final BufferedReader br = new BufferedReader(new InputStreamReader(mainProcess.getInputStream()));
    ReaderThread t1 = new ReaderThread(br, "std");
    t1.start();

    os.writeBytes("input tap " + (797 + random.nextInt(30) - 15) + " " + (1608 + random.nextInt(30) - 15) + "\n");
    os.flush();
} catch (IOException e) {
    e.printStackTrace();
}

改用Java语言之后, 原来的图像处理库就不能用了。经过搜索发现java提供图片读取的处理的功能。

import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;

Process captureProcess = Runtime.getRuntime().exec(captureCommand);
// TRY
    captureProcess.waitFor();    // 等待截图完成
    File f = new File("D:\\ss.png");
    BufferedImage image = ImageIO.read(f);
    result = handle(image, 815);
// CATCH
// 省略

 判断函数如下, 读取一行像素缓存到数组中, 然后判断这一行有多少个红色, 多少个蓝色

    static int[] colors = new int[1080];

    // 处理图片 返回true代表红色
    public static boolean handle(BufferedImage image, int row) throws Exception {
        int blue = 0;
        int red = 0;
        image.getRGB(0, row, 1080, 1, colors, 0, image.getWidth());    // 获得第row行像素
        for (int i = 0; i < 1080; ++i) {
            Color color = new Color(colors[i]);
            int r = color.getRed();
            int g = color.getGreen();
            int b = color.getBlue();
            if (r >= 250 && g >= 94 && g <= 103 && b >= 97 && b <= 103) red++;
            if (r >= 50 && r <= 56 && g >= 250 && b >= 250) blue++;
        }
        if (red < 10 && blue < 10)
            throw new Exception("异常状况! blue=" + blue + " red=" + red);
        return red > blue;
    }

1.2.3 一次判断多行

从每一张截图都可以得到4个方块的颜色, 所以首先想到的是一次输出4个命令.

bool result[4];

result[0] = check(895, image);
result[1] = check(815, image);
result[2] = check(737, image);
result[3] = check(658, image);

while (i < 4) {
    if (result[i])
        // f << "input swipe 615 1600 615 500" << endl;
        f << "input tap " << 284 + rand() % 30 - 15 << " " << 1606 + rand() % 30 - 15 << endl;
    else
        f << "input tap " << 797 + rand() % 30 - 15 << " " << 1608 + rand() % 30 - 15 << endl;
    i++;
}

这样做的结果还是失败. 设4个方块为一组, 组内的问题解决了,组之间仍然需要root授权、截屏等漫长的操作。

解决方案是3个方块为一组. 在刚跳到方块2, 还没开始到方块3的起跳时马上进行截图, 并发出命令(要过一会才会真正执行)

if (firstTime)
    result[0] = handle(image, 895);    // 判断第一行
result[1] = handle(image, 815);        // 判断第二行
result[2] = handle(image, 737);        // 判断第三行
result[3] = handle(image, 658);        // 判断第四行

1.3 本阶段总结

步数越多,小球下落的速度就越快。受限于截图速度和发送命令的速度,做到这里程序可以实现跳140步。

第二阶段: 经过拍照进行判断

2.1 基本步骤

由于截屏速度太慢, 所以我想对手机屏幕拍照, 然后用照片来判断, 这样获得照片的延迟就很小了.

基本步骤如下

1) 拍照
2) 判断颜色
3) 发送命令

2.1.1 拍照

JavaCV是一款开源的视觉处理库,基于GPLv2协议,对各种常用计算机视觉库封装后的一组jar包,封装了OpenCV、libdc1394、OpenKinect、videoInput和ARToolKitPlus等计算机视觉编程人员常用库的接口。

(百度百科)

OpenCVFrameGrabber grabber = new OpenCVFrameGrabber(0);
grabber.start();   //开始获取摄像头数据
CanvasFrame canvas = new CanvasFrame("摄像头");//新建一个窗口
canvas.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
canvas.setAlwaysOnTop(true);

Frame f = grabber.grab();        // 获得一帧图像
canvas.showImage(f);             // 显示到窗口中

2.1.2 判断颜色

经过摄像头拍照, 方块的颜色已经不是纯色, 外加摄像头有自动调节色温和亮度和功能, 游戏背景的变化让摄像头不断进行调节, 导致直接判断某一块像素的颜色是否在某个区间已经很不准确了.

我的方案是将两个红色矩形圈住的像素颜色的平均值作为参数(共6个,R1 G1 B1 R2 G2 B2),进行线性分类。

从摄像头采集大量数据(共8000帧)进行训练,4种情况 (左蓝 右蓝 左红 右红)各2000帧。使用某人写的一个fisher线性判别法的分类器(链接)求出线性分类所需的参数(6个系数+1个常数), 然后6个系数分别与6个参数相乘减常数得到结果, 根据结果的正负就是颜色的红蓝.

经过这样处理, 本反应速度可以满足跳到140步.

2.2 并发处理

上面只能140步的瓶颈是拍照+命令执行串行执行的速度仍然不够快.

所以我的方案是将拍照识别和命令执行并发执行(在代码上仍然是串行的),即在第i步起跳之前抓紧判断出第i+1步的颜色。

这种方法的重点就在判断过程。上面的Fisher判断法只能判断两类颜色,这里好像还是只需要判断两类颜色。

但是有个因素需要考虑。在2.1.2节中矩形2圈住的部分是偏右的,为什么不能偏左呢?因为下一层的颜色会对结果产生影响。在下图中,红色圈住的部分是深蓝色,但我们希望这一块是背景色,这样才能让分类结果更准确,所以把方块向右挪了一些。

由于这个原因,第三层中间的方块的采样位置要随着第二层的位置的变化而变化。如下图所示。所以需要用线性分类器求出来第二层的方块所在位置,然后再用线性分类器求出第三层方块的颜色。

在实际操作时发现,用上面的方法好像无法判断出第二层的方块在左边还是在右边。所以就先判断了一下第二层的颜色,再判断第二层的方块位置。代码如下:

    // 处理图片 返回true代表红色
    public static boolean handle(opencv_core.Mat mat) {
        int area = 2 * radius + 1;
        area *= area;
        int[] sumr = new int[]{0, 0};
        int[] sumg = new int[]{0, 0};
        int[] sumb = new int[]{0, 0};
        // 第一列
        for (int location = 0; location < 2; location++)
            for (int i = xs[location] - radius; i <= xs[location] + radius; ++i) {
                for (int j = ys[location] - radius; j <= ys[location] + radius; ++j) {
                    int r = mat.ptr(i, j).get(2);
                    if (r < 0) r += 256;
                    int g = mat.ptr(i, j).get(1);
                    if (g < 0) g += 256;
                    int b = mat.ptr(i, j).get(0);
                    if (b < 0) b += 256;
                    mat.ptr(i, j).put(2, (byte) 0);
                    mat.ptr(i, j).put(1, (byte) 0);
                    mat.ptr(i, j).put(0, (byte) 0);
                    sumr[location] += r;
                    sumg[location] += g;
                    sumb[location] += b;
                }
            }
        boolean top;
        double v1 = sumr[0] / area, v2 = sumg[0] / area, v3 = sumb[0] / area;
        double v4 = sumr[1] / area, v5 = sumg[1] / area, v6 = sumb[1] / area;
        if (-3.071493634915069e-08 * v1 + -8.965083557914888e-09 * v2 + 5.008942301758934e-08 * v3 + -4.538183589991674e-08 * v4 + 6.484576383927891e-09 * v5 + 1.338740636500615e-08 * v6 - -7.921691543220516e-07 > 0.0) {
            top = -3.496725019873052e-07 * v1 + -3.051296334266514e-07 * v2 + 9.296019536868188e-07 * v3 + 4.172851262673971e-07 * v4 + 3.456453947975248e-08 * v5 + -5.255057496652702e-07 * v6 - 3.837558916574416e-05 > 0.0;
        } else {
            // 判断是红色左还是红色右
            top = 1.504201326901337e-07 * v1 + 2.349572163952280e-07 * v2 + -1.274891509349603e-06 * v3 + -9.486338303509874e-08 * v4 + -1.371281460535185e-07 * v5 + 1.015599357102451e-06 * v6 - -1.401218220219478e-05 > 0.0;
        }
        System.out.print(top ? "左 " : "右 ");
        sumr[0] = 0;        // 重置颜色累计器
        sumr[1] = 0;
        sumg[0] = 0;
        sumg[1] = 0;
        sumb[0] = 0;
        sumb[1] = 0;
        if (top) {
            // 第二列 左中
            for (int location = 0; location < 2; location++)
                for (int i = xss[0][location] - radius; i <= xss[0][location] + radius; ++i) {
                    for (int j = yss[0][location] - radius; j <= yss[0][location] + radius; ++j) {
                        int r = mat.ptr(i, j).get(2);
                        if (r < 0) r += 256;
                        int g = mat.ptr(i, j).get(1);
                        if (g < 0) g += 256;
                        int b = mat.ptr(i, j).get(0);
                        if (b < 0) b += 256;
                        mat.ptr(i, j).put(2, (byte) -128);
                        mat.ptr(i, j).put(1, (byte) 0);
                        mat.ptr(i, j).put(0, (byte) 0);
                        sumr[location] += r;
                        sumg[location] += g;
                        sumb[location] += b;
                    }
                }
            v1 = sumr[0] / area;
            v2 = sumg[0] / area;
            v3 = sumb[0] / area;
            v4 = sumr[1] / area;
            v5 = sumg[1] / area;
            v6 = sumb[1] / area;
            return 5.418415219721577e-08 * v1 + 2.153937329720428e-08 * v2 + -5.16423473866430e-08 * v3 + 6.26519705171625e-08 * v4 + 6.40218352025572e-09 * v5 + -2.00462022639291e-08 * v6 - 6.79348987631247e-06 > 0;
        } else {
            // 第二列 中右
            for (int location = 0; location < 2; location++)
                for (int i = xss[1][location] - radius; i <= xss[1][location] + radius; ++i) {
                    for (int j = yss[1][location] - radius; j <= yss[1][location] + radius; ++j) {
                        int r = mat.ptr(i, j).get(2);
                        if (r < 0) r += 256;
                        int g = mat.ptr(i, j).get(1);
                        if (g < 0) g += 256;
                        int b = mat.ptr(i, j).get(0);
                        if (b < 0) b += 256;
                        mat.ptr(i, j).put(2, (byte) 0);
                        mat.ptr(i, j).put(1, (byte) -128);
                        mat.ptr(i, j).put(0, (byte) 0);
                        sumr[location] += r;
                        sumg[location] += g;
                        sumb[location] += b;
                    }
                }
            v1 = sumr[0] / area;
            v2 = sumg[0] / area;
            v3 = sumb[0] / area;
            v4 = sumr[1] / area;
            v5 = sumg[1] / area;
            v6 = sumb[1] / area;
            return 3.07920338699231e-08 * v1 + -5.73074515948778e-09 * v2 + -2.83755827904865e-08 * v3 + 3.80993052348830e-08 * v4 + -1.78177491190312e-08 * v5 + -9.56819783128036e-09 * v6 - -1.649897238658026e-07 > 0;
        }
    }
    public static void main(String[] args) {
        Scanner input = new Scanner(System.in);
        int i = 0;
        try {
            // 打开adb shell
            System.out.println("打开adb shell");
            Process mainProcess = Runtime.getRuntime().exec("adb shell");

            DataOutputStream os = new DataOutputStream(mainProcess.getOutputStream());
            os.writeBytes("su" + "\n");
            os.flush();

            //处理错误输出流
            final BufferedReader brError = new BufferedReader(new InputStreamReader(mainProcess.getErrorStream()));
            ReaderThread t2 = new ReaderThread(brError, "error");
            t2.start();

            //处理标准输出流
            final BufferedReader br = new BufferedReader(new InputStreamReader(mainProcess.getInputStream()));
            ReaderThread t1 = new ReaderThread(br, "std");
            t1.start();

            // 摄像头预备
            OpenCVFrameGrabber grabber = new OpenCVFrameGrabber(0);
            grabber.start();   //开始获取摄像头数据
            CanvasFrame canvas = new CanvasFrame("摄像头");//新建一个窗口
            canvas.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            canvas.setAlwaysOnTop(true);
//            首次检测
            Frame f;
            if (!canvas.isDisplayable()) {//窗口是否关闭
                grabber.stop();//停止抓取
                System.exit(2);//退出
            }
            f = grabber.grab();
            canvas.showImage(f);

            String cmd = input.nextLine();
            if (cmd.charAt(0) == 'b') {
                os.writeBytes("input tap " + (797) + " " + (1558) + "\n");
            } else if (cmd.charAt(0) == 'r') {
                os.writeBytes("input tap " + (284) + " " + (1556) + "\n");
            }
            os.flush();
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("GO!");
            // 循环检测
            while (true) {
                // 拍照 获得图像
                if (!canvas.isDisplayable()) {//窗口是否关闭
                    grabber.stop();//停止抓取
                    System.exit(2);//退出
                }
                f = grabber.grab();
                opencv_core.Mat mat = converter.convertToMat(f);
                try {
                    boolean r = handle(mat);
                    if (i == 1) {
                        if (r)
                            os.writeBytes("input tap " + (284) + " " + (1556) + "\n");
                        else
                            os.writeBytes("input tap " + (797) + " " + (1558) + "\n");
                        os.flush();
                        System.out.println("\n发出命令:" + (r ? "红" : "蓝"));
                        i++;
                        i %= 19;
                    } else {
                        i++;
                        i %= 19;
                        System.out.print(r ? "红" : "蓝");
                    }
                } catch (Exception e) {
                }
                canvas.showImage(converter.convert(mat));
              try {
                  Thread.sleep(10);
              } catch (InterruptedException e) {
                  e.printStackTrace();
              }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

2.3 总结

一切完成之后,看着小球逐渐超越了140步,万分高兴,然而最后等来的是大大的失望(T▽T)只能跳到200步!

速度首先的原因是通过adb执行命令的速度太慢了。

我好想没有优化办法了,靠摄像头采集图像的方法就告一段落了。

第三阶段: 使用杀手锏——按键精灵

3.1 前言

以前用过按键精灵电脑版,而且了解过按键精灵手机版,知道它一定能解决这个问题。

但是我不想用,我觉得这个方法太简单了,跟作弊似的!

但是我没有办法

3.2 步骤

手机上安装按键精灵,电脑上安装按键精灵手机助手。

然后新建脚本,复制粘贴以下代码就OK了。代码很简单,判断特定像素点的颜色是蓝的还是红的,然后按某个键

Dim c1,c2

While True
	c1 = GetPixelColor(377, 897)
	c2 = GetPixelColor(699, 896)

	If c1 = "6563FF" or c2 = "6563FF" Then 
		Tap 285 + Rnd() * 30, 1610 + Rnd() * 30
	ElseIf c1 = "FFFF35" or c2 = "FFFF35" Then
		Tap 796 + Rnd() * 30, 1593 + Rnd() * 30
	End If
	Delay 175
Wend

3.3 结论

只要手机不卡,刷几万分不成问题。速度贼快。

猜你喜欢

转载自blog.csdn.net/u010099177/article/details/81841085
今日推荐