跳一跳图像寻找中心点

期末花了一天复习时间写了,调试了。觉得有点乱,今天重新写了一遍。

 

没学过图像处理,这里用的方法是纯的像素分析。比较low。

 

概述

  控制部分:连接安卓机,adb截图,保存到本地,然后算出点击时间,adb来执行触屏。 这个都是一样的。

  核心部分:找到人坐标和落点坐标。

  

核心(java实现)

  1.寻找紫人的坐标

  BufferedImage读取图片。getRGB获取每一个点的rgb值,紫人臀部我取的点是Color(227,108,76)。

   直接搜索全图没有必要,太费时,一般手机都是1080*1920的,可以在一个范围内搜索,我取得搜索范围是Range(200, 1000, 900, 1300)。

(可以用颜色距离,跳着搜索,优化搜索加快搜索速度)

  2.找到目标平台的最高点

    就是这个点:

    

    这个点这样找:

        取Point(540, 650)这个点作为背景颜色的点,竖下来的y轴从650开始搜索。

        如果紫人在540左边,即屏幕的左侧,则搜索范围就是Range(540,650,1080,1920)

        否则就搜索左边,Range(0,650,540,1920)

        在搜索范围内逐行搜索,如果遍历的点的颜色,和Point(540, 650)这个点颜色的距离(rgb差值的平方和的方根)小于30的就判断为背景。直到搜索到不是背景的点,就是要找的点。

        例如:

        这样搜索。

        

 

  3.找到目标平台的最低点

        从最高点开始,向下搜索相近的颜色。

        相近是指遍历的点的颜色和最高点颜色距离小于10。

        一行一行搜,直到刚才搜索的10行都没有相近的颜色时结束搜索。

        每行搜索的横向范围,是最近搜索到有相邻颜色的那一行的最小横坐标-35到最大横坐标+35。

        取最后搜索到的那一行的最左和最右点的中点,最为目标平台最低点。

        例如:

        搜索范围。

        

           这样可以让某些颜色断开的面也能被正确找到最低点。

            

        之前用的是广搜。这次重写还有一方面就是提升执行效率。

        

  4.对一些特殊平台做特判

        有些平台颜色很不规则。比如:

        

        所以做个特判,如果颜色是xxx,就说明是xxx,然后最低点直接根据最高点进行位移。这个唱片的位移是(0,245)。

        像这种奶瓶子。

        

        会把下面的乳白色也搜索到,搜索跨越范围减小,别的有些又搜索不好,所以特判。

        特判不能直接拿上面的白色作为特判点的颜色,因为还有别的平台最高点是白色的。

        所以特判算法是从最高点开始下来的一列搜索特判颜色值。

        所以这个奶瓶子的特判点的颜色我取的是Color(101, 135, 164)。下面的蓝色。

        每次搜到最高点,先搜索这一列颜色,查找有没有特判颜色,有的话就直接加上位移,作为最低点。否则按步骤3来找最低点。

 

  5.计算中心点
 

        取最高点和最低点的中点,作为平台的中心点。

 

   6.剩下的

       根据紫人臀部点和平台中点,计算距离,根据距离线性计算按压时间。

 

其他

    之前写的那个计算部分需要2,3秒,太慢了,我在旁边看的都等不及了。

        现在这个计算部分差不多要0.1-0.2s左右。

        真机调试,adb指令执行太慢,所以总的时间还是很长。一个截图就要2-3秒,pull发回手机又要1s左右。input swipe模拟点击也耗时。

       用x86的安卓模拟器快一些。那个截图就快很多,1s不到。

        

        

        这个游戏是有反外挂机制的。最初点击屏幕是固定点,直接分数无法上传,之后改成点击范围内随机点,不容易被检测为外挂。

        然后现在貌似分数到了800分,我的程序都会被检测出来。在点击坐标点随机上下功夫,没用。在相邻点击时间间隔上做处理,也没用。然后现在想让它点的快一点,算法上还可以了,那些控制的部分还得想想别的办法。

        这是模拟器跑的。

 

 

源码

 

Core.java

import java.awt.Color;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;

import javax.imageio.ImageIO;

public class Core {
	final int WIDTH = 1080, HEIGHT = 1920;
	final int HALF_WIDTH = 540, HALF_HEIGHT = 960;

	BufferedImage src;

	final int manRGB = new Color(54, 60, 102).getRGB();// 人臀部颜色
	final Range manSearRange = new Range(200, 1000, 900, 1300);// 人的搜索范围
	Point manPos;// 50ms
	boolean inLeft;

	Range topPosSearRange;
	Point bgBasePos = new Point(540, 650);
	Point topPos;// 目标平台最高点

	final int StageColorDis=5;//平台颜色容差
	final int leapDis = 10;// 颜色搜索最大跨越的距离
	final int sxext = 35;// 搜索横向延伸距离
	Point bottomPos;// 目标平台最低点

	// 木椅子,WZC瓶子,唱片,xx,xx
	final int[] scTopRGB = {
			new Color(214, 179, 146).getRGB(),
			new Color(101, 135, 164).getRGB(),
			new Color(220, 200, 161).getRGB(),
			new Color(136, 101, 99).getRGB(),
			new Color(225, 199, 141).getRGB(),
			new Color(247, 169, 85).getRGB(),
	};
	final Point[] scOffset = {
			new Point(10, 186),
			new Point(25, 50),
			new Point(0, 245),
			new Point(0, 175),
			new Point(0, 175),
			new Point(0, 212),
	};

	Point tarPos;// 目标落点

	int distance;// 距离

	void calc(String path) throws IOException {
		src = ImageIO.read(new File(path));
		
	//	rotate();//旋转图片
		searchMan();
		searchTopPos();
		searchBottomPos();
		calcTarPos();
		calcDis();

	}
	
	void rotate() throws IOException{
		System.out.println(src.getWidth()+" "+src.getHeight());
		BufferedImage o=new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_RGB);
		for(int i=0;i<HEIGHT;i++)
			for(int j=0;j<WIDTH;j++){
				int rgb=src.getRGB(i, j);
				o.setRGB(j,  HEIGHT-1-i, rgb);
			}
		src=o;
	//	ImageIO.write(src, "png", new File("1.png"));
	}

	void searchMan() {
		boolean break_flag = false;
		for (int x = manSearRange.x1; x < manSearRange.x2 && !break_flag; x++)
			for (int y = manSearRange.y1; y < manSearRange.y2; y++)
				if (src.getRGB(x, y) == manRGB) {
					manPos = new Point(x, y);
					break_flag = true;
					break;
				}
		if (manPos.x < HALF_WIDTH)
			inLeft = true;
		else
			inLeft = false;
	}

	void searchTopPos() {
		if (inLeft) {// 搜索右侧
			topPosSearRange = new Range(HALF_WIDTH, bgBasePos.y, WIDTH, HEIGHT);
		} else {// 搜索左侧
			topPosSearRange = new Range(0, bgBasePos.y, HALF_WIDTH, HEIGHT);
		}
		final int bgRGB = src.getRGB(bgBasePos.x, bgBasePos.y);

		for (int y = topPosSearRange.y1; y < topPosSearRange.y2; y++) {
			for (int x = topPosSearRange.x1; x < topPosSearRange.x2; x++) {
				if (!ColorUtil.similar(src.getRGB(x, y), bgRGB, 30)) {// 不是背景色
					topPos = new Point(x, y);
					// markPoint(x,y);
					return;
				}
			}
		}
	}

	void searchBottomPos() {
		if (specialCheck())
			return;// 特判

		int stageRGB = src.getRGB(topPos.x, topPos.y);
		Point bpl = topPos, bpr = topPos;

		for (int y = topPos.y; y < bpl.y + leapDis && y < HEIGHT; y++) {
			int xl = bpl.x - sxext, xr = bpr.x + sxext;
			if (xl < 0)
				xl = 0;
			if (xr > WIDTH)
				xr = WIDTH;
			for (int x = xl; x < xr; x++) {
				if (ColorUtil.similar(src.getRGB(x, y), stageRGB, StageColorDis)) {
					bpl = new Point(x, y);
					break;
				}
			}
			for (int x = xr - 1; x > xl; x--) {
				if (ColorUtil.similar(src.getRGB(x, y), stageRGB, StageColorDis)) {
					bpr = new Point(x, y);
					break;
				}
			}
		}
		bottomPos = new Point((bpl.x + bpr.x) / 2, bpl.y);
		
	//	 markPoint(topPos.x,topPos.y);
	//	 markPoint(bottomPos.x,bottomPos.y);
	}

	boolean specialCheck() {//特判
		for (int i = scTopRGB.length - 1; i >= 0; i--) {
			for(int y=topPos.y;y<HEIGHT;y++){
				if (src.getRGB(topPos.x, y) == scTopRGB[i]) {
					bottomPos = new Point(topPos.x + scOffset[i].x, topPos.y + scOffset[i].y);
					return true;
				}
			}
		}

		return false;
	}

	void calcTarPos() {
		tarPos = new Point((topPos.x + bottomPos.x) / 2, (topPos.y + bottomPos.y) / 2);

	//	markPoint(tarPos.x, tarPos.y);
	}

	void calcDis() {
		distance = manPos.distance(tarPos);
	}

	final int markSize = 3;

	void markPoint(int x, int y) {//标记某个点,然后保存成图片,便于分析
		for (int i = x - markSize; i <= x + markSize; i++) {
			for (int j = y - markSize; j <= y + markSize; j++) {
				src.setRGB(i, j, 1000);
			}
		}
	}

	void printImg(String fn) throws IOException {
		ImageIO.write(src, "png", new File(fn));
	}
}

class ColorUtil {
	static boolean similar(int s1, int s2, int SD) {//颜色相似 颜色1 颜色2 距离
		Color c1 = new Color(s1), c2 = new Color(s2);
		int r1 = c1.getRed(), g1 = c1.getGreen(), b1 = c1.getBlue();
		int r2 = c2.getRed(), g2 = c2.getGreen(), b2 = c2.getBlue();
		int dis = (int) Math.sqrt(Math.pow(r1 - r2, 2) + Math.pow(g1 - g2, 2) + Math.pow(b1 - b2, 2));

		// System.out.println("dis "+dis);
		return dis < SD;
	}
}

class Point {
	int x, y;

	public Point(int x, int y) {
		this.x = x;
		this.y = y;
	}

	public String toString() {
		return this.x + " " + this.y;
	}

	int distance(Point p) {
		return (int) Math.sqrt(Math.pow(x - p.x, 2) + Math.pow(y - p.y, 2));
	}
}

class Range {
	int x1, y1, x2, y2;

	public Range(int x1, int y1, int x2, int y2) {//左上角 右下角
		this.x1 = x1;
		this.y1 = y1;
		this.x2 = x2;
		this.y2 = y2;
	}
}

 

Other

Core c = new Core();
Runtime r = Runtime.getRuntime();
Process p;
p = r.exec("cmd /c adb shell /system/bin/screencap -p /sdcard/ss.png");//截图
p.waitFor();//等待命令执行完毕
p = r.exec("cmd /c adb pull /sdcard/ss.png " + path);//图片保存到本地
p.waitFor();
c.calc(path);//传入文件路径
int dis=c.distance;//获得距离
int pressTime = calcPressTime(c.distance);//计算点击时间
String pressPos = getPressPos();
p = r.exec("cmd /c adb shell input swipe " + pressPos + pressTime);//点击
p.waitFor();
static String getPressPos() {
    double sx = 300 + rand.nextInt(100) + rand.nextDouble();
    double sy = 1500 + rand.nextInt(100) + rand.nextDouble();

    double ex = sx + rand.nextInt(30) + rand.nextFloat();
    double ey = sy + rand.nextInt(30) + rand.nextFloat();

    return sx + " " + sy + " " + ex + " " + ey + " ";
}
Thread.sleep(2000);//等待人跳完还有白色光圈消失

注:

        我的点击时间是 1.32*距离+80 。有些时候不太好。

        不知道这个是不是线性的。如果是线性的,距离近跳的比较远说明截距b偏大;如果距离远跳的偏近,说明斜率k偏小。

        不知道这个距离是不是这样算的,能不能直接找某个紫色的点然后算距离。。

        特判可能还缺两个。

        等待2000ms可能会有点长,可以测试一下,缩短一点。

        有些模拟器截图出来是横着的,要先转过来,不转就得改程序的核心部分了。

 

 

 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值