跳一跳找中心算法

本人第一次写博客,欢迎各位大神前来批评指正。

话不多说,首先,分析一下从手机端获取到的图片

 

这张图是初始状态,各个色调比较分明。小人的颜色最容易辨认,且几乎不和其他任何板块或背景色冲突。其中小人的位置在屏幕的40%以下64%以上(图上是在左边,但是也有可能在右边,所以不能排除横坐标的一些点),它的颜色区间为RGB(40,40,70)-RGB(65,65,105)颜区间还是比较大的,为了包含所有的小人像素颜色,该区间被稍微放大过。

接下来的重点是找出小人的底部中心,我们放大小人的底部,如图:

 

因为小人底部是圆形,所以在放大后底部的像素呈直线排列。红色点是我们需要的位置,这个位置可以这样获得

 

通过底部的直线的左端点和右端点的横坐标算出中心中心来找出小人的x坐标位置。我们可以从64%屏幕往上(减小扫描区域,加快运算),从左往右遍历所有的像素点,那么第一个颜色在小人的颜色区间的像素点,必定是下面红色线的左端点,记录下该点(x1,y1)。为了找出右端点,继续向右扫描,当第一次扫描到的点为非小人颜色区间的点时,就说明小人最下面的像素点已经扫完了,那么右端点就是当前扫描的点(x,y1)的前一个像素点(x-1y1)同时记录下该点。通过左端点和右端点就能很快计算出小人的底部的横坐标(x1+x-1)/2;那么小人的中心横坐标如何获得?

python版本中找出的中点越在小人底部向上20个像素的位置,但是如果手机分辨率变化,可能就会引起较大的误差。从上面的图片可以看出,小人底座最宽的区域(红色长线的部分)就是小人底座的直径,小人底座中心的y坐标就是它的直径的左端点。

很显然,在底座半圆像素的图片上,直径左端点的x坐标是最小的。为了减少扫描的点的个数,我们已经找到了底部直线的左端点,只要顺着它向上想左找符合小人颜色的点,直到找到最左侧的点(x最小的点)就结束,那么,该点的y坐标yn就是小人的底部中心的y坐标。扫描区域如下图:

 

白色部分是找小人中点所扫描过的区域。最终中点坐标((x1+x-1)/2,yn),但是自我感觉2.5D视角需要稍微修正一下中心,所以修正后的坐标((x1+x-1)/2+1,yn+5)。

小人的位置已经找到了,剩下的就是下一个目标的中心坐标了。因为目标板块可能不是纯色(这是主要影响之前介绍的辅助的主要因素),所以需要换个思路,话不多说。直接分析图片。

 

这是第一幅图的部分,白色板块是个菱形,如何确定它的中心?

将板块放大后如图:

 

我只放大了右上部分,但是已经很能说明问题了。根据菱形的性质只要找到上顶点和右顶点就能确定它的中心了(如图标红的部分)。当然,左顶点也可以,为啥不用左顶点呢?这就需要根据小人的位置而定了,如果小人在左半部分,那么目标板块在离小人不远的情况下左顶点极有可能会被小人挡住,如下图情况:

 

同理,如果小人在右半部,这时候就是找左顶点了。这是菱形,如果遇到圆形怎么办呢?

还记得刚刚的小人底部怎么处理的吗?一个道理。只不过菱形的顶点是一个点,而椭圆的上班部分是一行颜色相近的像素点罢了,同样的算法菱形的左端点和右端点重合而已,结果就是菱形的顶点。剩下的找左右端点也是和找小人的直径一样。但是,有些特殊板块需要处理:

 

对于右边的板块,我们需要找到最右端的点作为右顶点,但是,这哥们的最右端的顶点有点尴尬它不在我们需要的目标平面上,那怎么办嘞。车到山前必有路吗,有路必有丰田车,丰田汽车,舒适,环保,一辆更比四两强。。。。。额,咳咳,扯远了。


这个竖线明显长啊,我们只要在一行一行扫描的时候添加个计数器变量,如果相同横坐标的值出现过的次数大于8次(出现了连续8个像素都是在同一个横坐标上),那么就不往下扫描了,就定找到的第一个相同横坐标的像素为右顶点。同理,左边扫描时由个影子的问题也可以这样解决。不过,即使这样殚精竭虑还是有漏网之鱼啊,比如非菱形的“CD机”,讨厌的“咖啡杯”,不过,运用该算法后,找到的中点偏差不会太大,不死就行了,哈哈!

好了,bb一大堆道理,上代码。

首先是找出角色中心的代码片段:

//获取角色中心点
	public static Point getRoleCenter(BufferedImage bufferedImage) throws IOException {
		int height = bufferedImage.getHeight();
		int width = bufferedImage.getWidth();
		int beginScanY = height * 760 / 1920;
		int endScanY = height * 1240 / 1920;
		Point topPoint = new Point(10000,0);//上部直径左端点
		Point bottomBPoint = null;//底部左端点
		Point bottomEPoint = null;//底部右端点
		boolean beginFlag = true;//是否开始找底部做端点
		boolean endFlag = false;//是否开始找底部右端点
		boolean topFlag = false;//是否开始找上部直径左端点
		boolean over = false;//是否结束扫描
		int x = 0;//x坐标,x轴循环因子
		//从下往上遍历,找出颜色区间内的x坐标
		for(int y=endScanY;y>=beginScanY;y--) {//由下往上扫描
			while(x<width) {
				if(x<1080&&x>=0)
					bufferedImage.setRGB(x, y+1, 255);//将已扫描的点的下面打上白色(做标记用并没有实际用途可以删除)
				//取色
				int rgb = bufferedImage.getRGB(x, y);
				int R = RGB.getR(rgb);
				int G = RGB.getG(rgb);
				int B = RGB.getB(rgb);
				//若当前点在小人的颜色区间内
				if(R>40&&R<65&&G>40&&G<65&&B>70&&B<105) {
					if(beginFlag) {//如果当前是在扫描底部左端点,那么说明当前扫描的点就是底部左端点
						beginFlag = false;
						endFlag =  true;//已经找到了底部左端点,开始找底部右端点
						bottomBPoint = new Point(x,y);//底部左端点为当前点
						
						bufferedImage.setRGB(x, y+1, 255);//做标记用,无实际效果,可以删除(以下所有的setRGB均为做标记,不再提示)
					}
					if(topFlag) {//如果当前在扫描上部直径左端点,说明当前点可能并不是最小的点
						x--;//横坐标左移
					}
				}else {//如果当前点不在小人颜色区间内
					if(endFlag) {//如果当前在找底部右端点,那么说明已经找到了底部右端点(因为只有当找到了左端点才会开始找右端点)
						endFlag = false;
						topFlag = true;//开始找上部直径左端点
						bottomEPoint = new Point(x-1,y);//右端点在当前点前一个点
						x = bottomBPoint.getX();//设置当前点为前一个点,准备开始进行扫描直径左端点
						
						bufferedImage.setRGB(x-2, y, 255);
						break;
					}
					if(topFlag) {
						if(x<topPoint.getX()) {//如果当前在找直径左端点且,当前的横坐标小于之前找到的横坐标
							if(x==topPoint.getX()-1) {//如果该点和上一点的横坐标相同则扫描上一行
								x=topPoint.getX();
								break;
							}
							//否则设置直径左端点为当前值+1
							topPoint.setX(x+1);
							topPoint.setY(y);
							
							bufferedImage.setRGB(x, y, 255);
							x++;
							break;
						}
						if(x==topPoint.getX()) {//如果当前扫描的非小人颜色点和找到的小人颜色直径左端点相同,那么结束循环,小人的直径左端点以及找到。
							over = true;
							break;
						}
					}
				}
				if(!topFlag) {//如果没有开始找小人直径左端点则向右遍历像素
					x++;
				}
				
			}
			if(over) {
				break;
			}
			if(!topFlag) {//如果没有开始找小人的直径左端点则重置横坐标为0
				x = 0;
			}
		}
		//计算小人中点的坐标(其中x坐标+1是补正横坐标,y坐标-5是补正y坐标,因为2.5d视角的原因,自我感觉需要稍微补正一下,视情况而定)
		Point center = new Point((bottomEPoint.getX()+bottomBPoint.getX())/2+1,topPoint.getY()-5);
		
		bufferedImage.setRGB(center.getX(), center.getY(), 255);
		
		return center;
	}

再来是找出目标板块的代码片段:

public static Point getTargetCenter(BufferedImage bufferedImage,Point roleCenter) {
		int height = bufferedImage.getHeight();
		int width = bufferedImage.getWidth();
		int beginScanY = height * 500 / 1920;
		int endScanY = height * 1120 / 1920;
		Point topBPoint = null;
		Point topEPoint = null;
		Point bottomPoint = new Point(width/2,0);
		int limitCountPixel = 0;//像素计数变量
		boolean topBFlag = true;//是否开始扫描上顶点起点
		boolean topEFlag = false;//是否开始扫描上顶点终点
		boolean bottomFlag = false;//是否开始扫描左(右)顶点
		boolean over = false;//是否结束循环
		boolean xScanDirector = true;//扫描方向,true从左向右找右顶点,false从右向左找左顶点
		int x = width/2+25;//让横坐标扫描区域避开角色,只扫描半屏减小扫描时间
		if(roleCenter.getX() > x ) {
			xScanDirector = false;
			x-=50;
		}
		//获取起点背景色
		int baseRgb = bufferedImage.getRGB(x, beginScanY);
		int baseBR = RGB.getR(baseRgb);
		int baseBG = RGB.getG(baseRgb);
		int baseBB = RGB.getB(baseRgb);
		//背景色区间最小值
		int baseER = baseBR-20;
		int baseEG = baseBG-20;
		int baseEB = baseBB-20;
		//开始扫描
		for(int y=beginScanY;y<=endScanY;y++) {
			while(xScanDirector?(x<width):(x>=0)) {//根据扫描方向更改判断条件
				if(x<1080&&x>=0)
					bufferedImage.setRGB(x, y-1, 255);//做标记用,可以删除
				//获取当前点颜色
				int rgb = bufferedImage.getRGB(x, y);
				int R = RGB.getR(rgb);
				int G = RGB.getG(rgb);
				int B = RGB.getB(rgb);
				//如果当前点不在背景色区间内,则说明已经扫描到了第一个顶点
				if(R<baseER||R>baseBR||G<baseEG||G>baseBG||B<baseEB||B>baseBB) {
					if(topBFlag) {
						topBFlag = false;
						topEFlag = true;
						topBPoint = new Point(x,y);//找到了上顶点起点
					}
					if(bottomFlag) {//如果正在找左(右)顶点,则根据扫描方向进行横坐标加减
						if(xScanDirector) {
							x++;
						}else {
							x--;
						}
					}
				}else {
					if(topEFlag) {//如果当前色在背景色区间内且真在找上顶点的终点,则该点的前一点为扫描终点
						topEFlag = false;
						bottomFlag = true;
						topEPoint = new Point(xScanDirector?(x-1):(x+1),y);
						break;
					}
					if(bottomFlag) {//如果正在找左(右)顶点,则根据扫描方向确定找左(右)顶点
						if(xScanDirector) {
							if(x>bottomPoint.getX()+1) {//若找右顶点且当前点比之前找到的右顶点还要大,则该顶点设置为右顶点
								bottomPoint.setX(x-1);
								bottomPoint.setY(y);
								x--;
								limitCountPixel = 0;//计数重置
							}else if(x==bottomPoint.getX()) {//如果当前点和右顶点相同(当前点的前一个点的位置比上一个右顶点小)则说明已经找到了右顶点(上次找到的点就是右顶点)
								bottomFlag = false;
								over = true;
							}else if(x==bottomPoint.getX() + 1) {//如果当前点的上一个点与之前找到的右顶点相同,则增加计数
								limitCountPixel++;
								if(limitCountPixel>=8) {//如果计数超过8个像素就没有必要继续查找了,结束循环
									bottomFlag = false;
									over = true;
								}
							}
						}else {//找左顶点
							if(x<bottomPoint.getX()-1) {
								bottomPoint.setX(x+1);
								bottomPoint.setY(y);
								x++;
								limitCountPixel=0;
							}else if(x==bottomPoint.getX()) {
								bottomFlag = false;
								over = true;
							}else if(x==bottomPoint.getX()-1) {
								limitCountPixel++;
								if(limitCountPixel>=5) {//避免影子问题(影子只有5个像素左右的距离)
									bottomFlag = false;
									over = true;
								}
							}
						}
						break;
					}
				}
				if(!bottomFlag) {//如果没有开始找左(右)顶点,则根据扫描方向扫描
					if(xScanDirector) {
						x++;
					}else {
						x--;
					}
				}
			}
			if(over) {
				break;
			}
			if(!bottomFlag) {//扫描位置重置
				if(xScanDirector)
					x=width/2+25;
				else
					x=width/2-25;
			}
		}
		//计算中点,添加修正值
		Point center = new Point((topBPoint.getX()+topEPoint.getX())/2+1,bottomPoint.getY()-5);
		bufferedImage.setRGB(center.getX(), center.getY(), 255);
		return center;
	}
}


主要的找中点算法有了,剩下的就是adb命令的发送了。对于adb命令,这里讲不在赘述,有兴趣可以百度一下哦。

最后附上源码,供各位参考指正。
点击打开链接

 

没有更多推荐了,返回首页