【验证码识别】OpenCV挑战百度拖动旋转图片角度验证码_selenium 百度旋转图片验证-程序员宅基地

技术标签: 验证码识别  python  java  百度验证码  百度  oauth  图像识别  

前言

戳这里→康康你手机号在过多少网站注册过!!!
友情推荐:新一代安全短信

谷歌图形验证码在AI 面前已经形同虚设,所以谷歌宣布退出验证码服务, 那么当所有的图形验证码都被破解时
《腾讯防水墙滑动拼图验证码》
《百度旋转图片验证码》
《网易易盾滑动拼图验证码》
《顶象区域面积点选验证码》
《顶象滑动拼图验证码》
《极验滑动拼图验证码》
《使用深度学习来破解 captcha 验证码》
《验证码终结者-基于CNN+BLSTM+CTC的训练部署套件》

百度的验证码又双叒更新了。
当然出于好奇,猫又拿起了键盘开始挑战。

在这里插入图片描述

正文来了。

先来看看继上次破解百度旋转验证码后,百度的大佬又做出了哪些改变。

1.抓取图片时加上了马赛克
2.增加了图片库

抓取图片时加上了马赛克
  • 截图是这个亚子的

在这里插入图片描述

  • 后台拿到的却是这个亚子的

在这里插入图片描述
哦呦,这个马赛克有点东西的呀~

图片抓下来都不一样还咋识别,百度这里也是煞费苦心,给您点个赞。

不过话说回来,就算这样也难不住我们的呀,这里我思考了一下还有几种方式来获取这个图片:

  • 1 .通过系统级鼠标来获取
  • 2 .通过网页截图来获取

1.通过系统级鼠标来获取

首先,试了下第一种方式

在这里插入图片描述
定位到图片路径位置拿到图片途径,然后再通过模拟器打来另一个页面

在这里插入图片描述
然后通过下面这段代码实现保存图片的操作(这里用到了Robot系统级鼠标控制类 )

public byte[] sivePic(String url, WebDriver driver, String window_one) {
    
		((JavascriptExecutor) driver).executeScript("window.open('" + url + "')"); // 用js打开新的窗口
		sleep(2000);
		Set<String> allWindow = driver.getWindowHandles(); // 获取所有的窗口句柄
		sleep(1 * 500);
		for (String i : allWindow) {
    
			if (i != window_one) {
    
				driver.switchTo().window(i);
			}
		}
		WebElement img = driver.findElement(By.tagName("img"));
		Actions actions = new Actions(driver);
		Robot robot;
		byte[] picBytes = null;
		File imgFile = null;
		// 声明一个StingSelection 对象,并使用String的参数完成实例化;
		String imgName = "baidu_" + System.currentTimeMillis()+".jpg";
		// 使用Toolkit对象的setContents将字符串放到粘贴板中 ;
		Toolkit.getDefaultToolkit().getSystemClipboard().setContents( new StringSelection(imgName), null);
		try {
    
			robot = new Robot();
			robot.setAutoDelay(100);
			actions.moveToElement(img).contextClick().perform();
			sleep(100);
			robot.keyPress(KeyEvent.VK_DOWN);
			sleep(100);
			robot.keyRelease(KeyEvent.VK_DOWN);
			sleep(100);
			robot.keyPress(KeyEvent.VK_DOWN);
			sleep(100);
			robot.keyRelease(KeyEvent.VK_DOWN);
			sleep(100);
			// 确认
			robot.keyPress(KeyEvent.VK_ENTER);
			robot.keyRelease(KeyEvent.VK_ENTER);
			sleep(1000);
			// 删除
			robot.keyPress(KeyEvent.VK_DELETE);
			robot.keyRelease(KeyEvent.VK_DELETE);
			sleep(500);
			// 按下crtl v键 ;
			robot.keyPress(KeyEvent.VK_CONTROL);
			robot.keyPress(KeyEvent.VK_V);
			sleep(500);
			// 释放crtl v 键
			robot.keyRelease(KeyEvent.VK_V);
			robot.keyRelease(KeyEvent.VK_CONTROL);
			sleep(500);
			// 文件名字后确认
			robot.keyPress(KeyEvent.VK_ENTER);
			robot.keyRelease(KeyEvent.VK_ENTER);
			sleep(5000);
			String name = System.getenv().get("USERNAME");
			imgFile = new File("C:/Users/" + name + "/Downloads/"+imgName);
			picBytes = FileUtils.readFileToByteArray(imgFile);
			System.out.println("save ok");
		} catch (Exception e) {
    
			e.printStackTrace();
		} finally {
    
			imgFile.delete();
		}
		return picBytes;
	}

啊哈,拿到了。
在这里插入图片描述
本以为就这样结束了。

万万没想到,抓了几张之后马赛克又出现了。。。。。。。
在这里插入图片描述
到此,第一种方法宣告西败。

2.通过网页截图来获取

这个方法就比较靠谱了,百度总不能让用户看到马赛克的图片吧,哈哈(手动狗头)

// 获取ID的随机数
WebElement vcodesElemet = driver.findElement(By.className("mod-vcodes"));
String num = vcodesElemet.getAttribute("id");
num = num.split("mod-vcodes")[num.split("mod-vcodes").length - 1];
WebElement imgElemet = driver.findElement(By.id("vcode-spin-img" + num));
File img = getImgFile(driver, imgElemet.getLocation().getX() - 8,imgElemet.getLocation().getY());
/**
* 截图(验证码) 这里的 152 是页面显示图片的实际宽高
 */
private File getImgFile(WebDriver driver, int i, int j) {
    
	BufferedImage imgbuf = null;
	File srcFile, imgFile = null;
	try {
    
		srcFile = ((TakesScreenshot) driver).getScreenshotAs(OutputType.FILE);
		imgbuf = ImageIO.read(srcFile).getSubimage(i, j, 152, 152);
		imgFile = new File("C:\\daidu_" + System.currentTimeMillis() + ".png");
		ImageIO.write(imgbuf, "png", imgFile);
	} catch (IOException e) {
    
		e.printStackTrace();
	}
	return imgFile;
}

那么到这里我们就拿到了验证图,截来的图片肯定没有原图清晰度高,所以识别率就会稍微降低一些。不过影响不是太大。
在这里插入图片描述
到这里抓取图片的问题就解决了。

增加了图片库

说道增加图片库这里倒不是什么大问题,只要抓到一张图片,就可以通过旋转生成对应的图片,只需要找到不同的图片就可以了。

之前百度大概有50-60张不同的图片,也就是说不同的角度(360°)全部加入图片库的话最多21600张图(估计值)。

现在通过抓取了几百张图片观察,不同的图片大概有120多张,之前的50-60张也包括在内,也就是说百度又新加了一倍的图库。大概在43000张图左右。

这里不存在什么大问题,只是我们的模型库需要更新一下而已。


正儿八经的分割线


好了上面的问题既然都解决了,那么来说下具体的破解思路及步骤。

  1. 抓取到大量图片并筛选
  2. 根据筛选的图片生成模型库
  3. 将模型库接入到自动化模拟程序

说起来也不是很复杂嘛。开搞~

一、抓取到大量图片并筛选

上面有提到抓取图片我们采用截图的方式,自动化程序这里就先不放了,文章后面有完整代码。我们先看下结果。

在这里插入图片描述
可以看到有很多相似的图片,我们抓到300-500张左右基本就可以找到全部的不同的图片,然后把相似的只留下一张就好了。

二、根据筛选的图片生成模型库

  1. 这里我们拿到从步骤一筛选出来的图片,将每一张片旋转生成360度各个角度的图片,并通过比例计算出原图到对应角度应该滑动的距离。

在这里插入图片描述

在这里插入图片描述

  1. 然后在每个模型库中找到正的那张图,将之前标记好的距离值标记到原图上。

在这里插入图片描述
在这里插入图片描述

  1. 再通过计算得出模型库中每个图应该滑动的距离,并标记。

为了提升效率,我们将模型库以map对象的形式存入.obj文件中。
启动程序时只需要读一个文件,然后将其存入map中,大大提升识别效率。

模型库:baidu_mod.obj

三、将模型库接入到自动化模拟程序

  1. 自动化模拟程序
public class Baidu {
    
	private final static Logger logger = LoggerFactory.getLogger(BaiduTrain.class);
	private final String INDEX_URL = "http://passport.baidu.com/?getpassindex&tt=1597054938536&gid=00C700C-A457-4CCF-8588-F118FFF70829&tpl=mn&u=https%3A%2F%2Fwww.baidu.com%2Fs%3Fie%3DUTF-8";
	private static Map<String, Map<String, PicFinger>> Map = new HashMap<String, Map<String, PicFinger>>();
	/**
	 * 截图(验证码)
	 */
	private File getImgFile(WebDriver driver, int i, int j) {
    
		BufferedImage imgbuf = null;
		File srcFile, imgFile = null;
		try {
    
			srcFile = ((TakesScreenshot) driver).getScreenshotAs(OutputType.FILE);
			imgbuf = ImageIO.read(srcFile).getSubimage(i, j, 152, 152);
			imgFile = new File("C:\\daidu_" + System.currentTimeMillis() + ".png");
			ImageIO.write(imgbuf, "png", imgFile);
		} catch (IOException e) {
    
			e.printStackTrace();
		}
		return imgFile;
	}
	//将图片变为可比较的对象
	public static PicFinger getPicFinger(File file) {
    
		try {
    
			BufferedImage libBuf = ImageIO.read(file);
			PicFinger picFinger = new PicFinger(libBuf);
			byte[] img = picFinger.getBinaryzationMatrix();
			PicFinger fp = new PicFinger(img);
			return fp;
		} catch (IOException e) {
    
			e.printStackTrace();
			return null;
		}
	}
	//匹配模型库获取滑动距离
	public String getDistance(File file) {
    
		double succRate = 80;
		float ret, maxRet = -1;
		PicFinger picFinger = getPicFinger(file);
		PicFinger fpSrc = null;
		String distance = "";
		for (String mapkey : Map.keySet()) {
    
			for (String key : Map.get(mapkey).keySet()) {
    
				fpSrc = Map.get(mapkey).get(key);
				ret = picFinger.compare(fpSrc) * 100;
				if (ret >= succRate) {
    
					if (ret > maxRet) {
    
						maxRet = ret;
						distance = key.substring(key.indexOf('-') + 1, key.length());
					}
				}
			}
		}
		System.out.println("相似度最高为:" + maxRet + "|distance=" + distance);
		return distance;
	}
	//自动化程序
	public void seleniumTest() {
    
		String phone = "13888888888";
		ChromeDriverManager manager = ChromeDriverManager.getInstance();
		String gtText = null, num;
		Integer distance = 30;
		int status = -1;
		By moveBy, gtTextBy, submit, phoneBy, securemobil, close, tipInfo;
		WebElement moveElemet, submitElemet, gtTextElement, phoneElemet, securemobilElemet, closeElemet, tipInfoElemet, vcodesElemet;
		WebDriver driver = null;
		try {
    
			driver = manager.getDriver();
			driver.get(INDEX_URL);
			sleep(1 * 500);
			driver.navigate().refresh();
			sleep(1 * 500);
			// 输入手机号
			phoneBy = By.id("account");
			phoneElemet = waitWebElement(driver, phoneBy, 400);
			if (phoneElemet == null)
				return null;
			phoneElemet.clear();
			for (int i = 0; i < phone.length(); i++) {
    
				char c = phone.charAt(i);
				phoneElemet.sendKeys(c + "");
				phoneElemet.click();
			}
			// 下一步
			submit = By.id("submit");
			submitElemet = waitWebElement(driver, submit, 400);
			if (submitElemet == null)
				return null;
			submitElemet.click();
			// 点击关闭
			close = By.xpath("//div[@class='vcode-close']");
			closeElemet = waitWebElement(driver, close, 200);
			if (closeElemet == null)
				return null;
			if (closeElemet != null)
				closeElemet.click();
			// 点击我选中的是手机号
			securemobil = By.xpath("//p[@s='securemobil']");
			securemobilElemet = waitWebElement(driver, securemobil, 200);
			if (securemobilElemet == null)
				return null;
			if (securemobilElemet != null && securemobilElemet.isDisplayed())
				securemobilElemet.click();
			// 下一步
			submit = By.id("submit");
			submitElemet = waitWebElement(driver, submit, 200);
			if (submitElemet == null)
				return null;
			submitElemet.click();
			// 获取ID的随机数
			vcodesElemet = waitWebElement(driver, By.className("mod-vcodes"), 200);
			if (vcodesElemet == null)
				return null;
			num = vcodesElemet.getAttribute("id");
			num = num.split("mod-vcodes")[num.split("mod-vcodes").length - 1];

			tipInfo = By.id("pass-slide-tipInfo2" + num);
			tipInfoElemet = waitWebElement(driver, tipInfo, 200);
			if (tipInfoElemet == null)
				return null;
			if (tipInfoElemet.getText().contains("最右")) {
    
				// 点击关闭
				close = By.className("vcode-close");
				closeElemet = waitWebElement(driver, close, 200);
				if (closeElemet == null)
					return null;
				closeElemet.click();
				// 下一步
				submit = By.id("submit");
				submitElemet = waitWebElement(driver, submit, 200);
				if (submitElemet == null)
					return null;
				submitElemet.click();
				// 获取ID的随机数
				vcodesElemet = waitWebElement(driver, By.className("mod-vcodes"), 200);
				if (vcodesElemet == null)
					return null;
				num = vcodesElemet.getAttribute("id");
				num = num.split("mod-vcodes")[num.split("mod-vcodes").length - 1];
			}
			for (int i = 0; i < 100; i++) {
    
				// 获取滑动按钮
				moveBy = By.id("vcode-spin-button" + num);
				moveElemet = waitWebElement(driver, moveBy, 400);
				if (moveElemet != null) {
    
					moveElemet.click();
				} else {
    
					System.out.println("error get moveElemet=" + moveElemet);
					break;
				}
				sleep(100);
				WebElement imgElemet = driver.findElement(By.id("vcode-spin-img" + num));
				File img = getImgFile(driver, imgElemet.getLocation().getX() - 8, imgElemet.getLocation().getY());
				distance = Integer.parseInt(getDistance(img));
				// 滑动
				move(driver, moveElemet, distance);
				sleep(100);
				// 获取滑动结果 10s
				boolean isWaitLoad = false;
				for (int k = 0; k < 100; k++) {
    
					gtTextBy = By.id("vcode-spin-icon" + num);
					gtTextElement = waitWebElement(driver, gtTextBy, 400);
					if (gtTextElement == null)
						return null;
					gtText = gtTextElement.getAttribute("class");
					if (gtText.contains("loading") || gtText.contains("hide")) {
    
						if (!isWaitLoad) {
    
							System.out.print("loading(" + gtTextBy.toString() + ")");
							isWaitLoad = true;
						}
						System.out.print(".");
						sleep(100);
						continue;
					} else
						break;
				}
				if (gtText.contains("success")) {
    
					sleep(100);
					break;
				}
				sleep(2000);
			}
		} catch (Exception e) {
    
			e.printStackTrace();
		} finally {
    
			sleep(1000);
			manager.closeDriver(status);
		}
	}
	//线程睡眠
	private static void sleep(long millis) {
    
		try {
    
			Thread.sleep(millis);
		} catch (InterruptedException e) {
    
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	/**
	 * 模拟人工移动
	 * 
	 * @param driver
	 * @param element页面滑块
	 * @param distance需要移动距离
	 */
	private static void move(WebDriver driver, WebElement element, int distance) throws InterruptedException {
    
		int randomTime = 0;
		if (distance > 90)
			randomTime = 250;
		else if (distance > 80 && distance <= 90)
			randomTime = 150;
		List<Integer> track = GeetCanvasApi.getMoveTrack(distance);
		int moveY = 1;
		try {
    
			Actions actions = new Actions(driver);
			actions.clickAndHold(element).perform();
			Thread.sleep(200);
			for (int i = 0; i < track.size(); i++) {
    
				actions.moveByOffset(track.get(i), moveY).perform();
				Thread.sleep(new Random().nextInt(300) + randomTime);
			}
			Thread.sleep(200);
			actions.release(element).perform();
		} catch (Exception e) {
    
			logger.error("move:err = " + e.toString());
		}
	}

	// 延时加载
	private static WebElement waitWebElement(WebDriver driver, By by, int count) {
    
		WebElement webElement = null;
		boolean isWait = false;
		for (int k = 0; k < count; k++) {
    
			try {
    
				webElement = driver.findElement(by);
				if (isWait)
					System.out.println(" ok!");
				return webElement;
			} catch (org.openqa.selenium.NoSuchElementException | org.openqa.selenium.ElementNotVisibleException ex) {
    
				isWait = true;
				if (k == 0)
					System.out.print("waitWebElement(" + by.toString() + ")");
				else
					System.out.print(".");
				sleep(50);
			}
		}
		if (isWait) {
    
			System.out.println(" outTime!");
			logger.error("WebElement = null");
		}
		return null;
	}
	//读文件,用于读取模型库
	public static Object load(String file) throws Exception {
    
		FileInputStream freader = null;
		ObjectInputStream objectInputStream = null;
		try {
    
			freader = new FileInputStream(file);
			objectInputStream = new ObjectInputStream(freader);
			Object o = objectInputStream.readObject();
			return o;
		} catch (Exception e) {
    
			return null;
		} finally {
    
			if (freader != null)
				freader.close();
			if (objectInputStream != null)
				objectInputStream.close();
		}
	}

	@SuppressWarnings("unchecked")
	public static void main(String[] args) throws Exception {
    
		Map = (Map<String, Map<String, PicFinger>>) load("C:\\baidu_mod.obj");
		OCRUtil.chromePath = "C://chrome";
		Baidu baidu = new Baidu();
		baidu.seleniumTest();

	}

}

  1. 将图片信息转变为可比较信息
public final class PicFinger implements Serializable {
    
	private static final long serialVersionUID = 431106089062884937L;
	/**
	 * 图像指纹的尺寸,将图像resize到指定的尺寸,来计算哈希数组
	 */
	private static final int HASH_SIZE = 16;
	/**
	 * 保存图像指纹的二值化矩阵
	 */
	private final byte[] binaryzationMatrix;

	public PicFinger(byte[] hashValue) {
    
		if (hashValue.length != HASH_SIZE * HASH_SIZE) {
    
			throw new IllegalArgumentException(String.format("length of hashValue must be %d", HASH_SIZE * HASH_SIZE));
		}
		this.binaryzationMatrix = hashValue;
	}

	public PicFinger(String hashValue) {
    
		this(toBytes(hashValue));
	}

	public PicFinger(BufferedImage src) {
    
		this(hashValue(src));
	}

	public byte[] getBinaryzationMatrix() {
    
		return binaryzationMatrix;
	}

	private static byte[] hashValue(BufferedImage src) {
    
		BufferedImage hashImage = resize(src, HASH_SIZE, HASH_SIZE);
		byte[] matrixGray = (byte[]) toGray(hashImage).getData().getDataElements(0, 0, HASH_SIZE, HASH_SIZE, null);
		return binaryzation(matrixGray);
	}

	/**
	 * 从压缩格式指纹创建{@link PicFinger}对象
	 *
	 * @param compactValue
	 * @return
	 */
	public static PicFinger createFromCompact(byte[] compactValue) {
    
		return new PicFinger(uncompact(compactValue));
	}

	public static boolean validHashValue(byte[] hashValue) {
    
		if (hashValue.length != HASH_SIZE) {
    
			return false;
		}
		for (byte b : hashValue) {
    
			{
    
				if (0 != b && 1 != b) {
    
					return false;
				}
			}
		}
		return true;
	}

	public static boolean validHashValue(String hashValue) {
    
		if (hashValue.length() != HASH_SIZE) {
    
			return false;
		}
		for (int i = 0; i < hashValue.length(); ++i) {
    
			if ('0' != hashValue.charAt(i) && '1' != hashValue.charAt(i)) {
    
				return false;
			}
		}
		return true;
	}

	public byte[] compact() {
    
		return compact(binaryzationMatrix);
	}

	/**
	 * 指纹数据按位压缩
	 *
	 * @param hashValue
	 * @return
	 */
	private static byte[] compact(byte[] hashValue) {
    
		byte[] result = new byte[(hashValue.length + 7) >> 3];
		byte b = 0;
		for (int i = 0; i < hashValue.length; ++i) {
    
			if (0 == (i & 7)) {
    
				b = 0;
			}
			if (1 == hashValue[i]) {
    
				b |= 1 << (i & 7);
			} else if (hashValue[i] != 0) {
    
				throw new IllegalArgumentException("invalid hashValue,every element must be 0 or 1");
			}
			if (7 == (i & 7) || i == hashValue.length - 1) {
    
				result[i >> 3] = b;
			}
		}
		return result;
	}

	/**
	 * 压缩格式的指纹解压缩
	 *
	 * @param compactValue
	 * @return
	 */
	private static byte[] uncompact(byte[] compactValue) {
    
		byte[] result = new byte[compactValue.length << 3];
		for (int i = 0; i < result.length; ++i) {
    
			if ((compactValue[i >> 3] & (1 << (i & 7))) == 0) {
    
				result[i] = 0;
			} else {
    
				result[i] = 1;
			}
		}
		return result;
	}

	/**
	 * 字符串类型的指纹数据转为字节数组
	 *
	 * @param hashValue
	 * @return
	 */
	private static byte[] toBytes(String hashValue) {
    
		hashValue = hashValue.replaceAll("\\s", "");
		byte[] result = new byte[hashValue.length()];
		for (int i = 0; i < result.length; ++i) {
    
			char c = hashValue.charAt(i);
			if ('0' == c) {
    
				result[i] = 0;
			} else if ('1' == c) {
    
				result[i] = 1;
			} else {
    
				throw new IllegalArgumentException("invalid hashValue String");
			}
		}
		return result;
	}

	/**
	 * 缩放图像到指定尺寸
	 *
	 * @param src
	 * @param width
	 * @param height
	 * @return
	 */
	private static BufferedImage resize(Image src, int width, int height) {
    
		BufferedImage result = new BufferedImage(width, height, BufferedImage.TYPE_3BYTE_BGR);
		Graphics g = result.getGraphics();
		try {
    
			g.drawImage(src.getScaledInstance(width, height, Image.SCALE_SMOOTH), 0, 0, null);
		} finally {
    
			g.dispose();
		}
		return result;
	}

	/**
	 * 计算均值
	 *
	 * @param src
	 * @return
	 */
	private static int mean(byte[] src) {
    
		long sum = 0;
		// 将数组元素转为无符号整数
		for (byte b : src) {
    
			sum += (long) b & 0xff;
		}
		return (int) (Math.round((float) sum / src.length));
	}

	/**
	 * 二值化处理
	 *
	 * @param src
	 * @return
	 */
	private static byte[] binaryzation(byte[] src) {
    
		byte[] dst = src.clone();
		int mean = mean(src);
		for (int i = 0; i < dst.length; ++i) {
    
			// 将数组元素转为无符号整数再比较
			dst[i] = (byte) (((int) dst[i] & 0xff) >= mean ? 1 : 0);
		}
		return dst;

	}

	/**
	 * 转灰度图像
	 *
	 * @param src
	 * @return
	 */
	private static BufferedImage toGray(BufferedImage src) {
    
		if (src.getType() == BufferedImage.TYPE_BYTE_GRAY) {
    
			return src;
		} else {
    
			// 图像转灰
			BufferedImage grayImage = new BufferedImage(src.getWidth(), src.getHeight(), BufferedImage.TYPE_BYTE_GRAY);
			new ColorConvertOp(ColorSpace.getInstance(ColorSpace.CS_GRAY), null).filter(src, grayImage);
			return grayImage;
		}
	}

	@Override
	public String toString() {
    
		return toString(true);
	}

	/**
	 * @param multiLine
	 *            是否分行
	 * @return
	 */
	public String toString(boolean multiLine) {
    
		StringBuffer buffer = new StringBuffer();
		int count = 0;
		for (byte b : this.binaryzationMatrix) {
    
			buffer.append(0 == b ? '0' : '1');
			if (multiLine && ++count % HASH_SIZE == 0) {
    
				buffer.append('\n');
			}
		}
		return buffer.toString();
	}

	@Override
	public boolean equals(Object obj) {
    
		if (obj instanceof PicFinger) {
    
			return Arrays.equals(this.binaryzationMatrix, ((PicFinger) obj).binaryzationMatrix);
		} else {
    
			return super.equals(obj);
		}
	}

	/**
	 * 与指定的压缩格式指纹比较相似度
	 *
	 * @param compactValue
	 * @return
	 * @see #compare(PicFinger)
	 */
	public float compareCompact(byte[] compactValue) {
    
		return compare(createFromCompact(compactValue));
	}

	/**
	 * @param hashValue
	 * @return
	 * @see #compare(PicFinger)
	 */
	public float compare(String hashValue) {
    
		return compare(new PicFinger(hashValue));
	}

	/**
	 * 与指定的指纹比较相似度
	 *
	 * @param hashValue
	 * @return
	 * @see #compare(PicFinger)
	 */
	public float compare(byte[] hashValue) {
    
		return compare(new PicFinger(hashValue));
	}

	/**
	 * 与指定图像比较相似度
	 *
	 * @param image2
	 * @return
	 * @see #compare(PicFinger)
	 */
	public float compare(BufferedImage image2) {
    
		return compare(new PicFinger(image2));
	}

	/**
	 * 比较指纹相似度
	 *
	 * @param src
	 * @return
	 * @see #compare(byte[], byte[])
	 */
	public float compare(PicFinger src) {
    
		if (src.binaryzationMatrix.length != this.binaryzationMatrix.length) {
    
			throw new IllegalArgumentException("length of hashValue is mismatch");
		}
		return compare(binaryzationMatrix, src.binaryzationMatrix);
	}

	/**
	 * 判断两个数组相似度,数组长度必须一致否则抛出异常
	 *
	 * @param f1
	 * @param f2
	 * @return 返回相似度(0.0 ~ 1.0)
	 */
	private static float compare(byte[] f1, byte[] f2) {
    
		if (f1.length != f2.length) {
    
			throw new IllegalArgumentException("mismatch FingerPrint length");
		}
		int sameCount = 0;
		for (int i = 0; i < f1.length; ++i) {
    
			{
    
				if (f1[i] == f2[i]) {
    
					++sameCount;
				}
			}
		}
		return (float) sameCount / f1.length;
	}

	public static float compareCompact(byte[] f1, byte[] f2) {
    
		return compare(uncompact(f1), uncompact(f2));
	}

	public static float compare(BufferedImage image1, BufferedImage image2) {
    
		return new PicFinger(image1).compare(new PicFinger(image2));
	}

	public static Map<String, byte[]> getLibMatrix(List<File> imgListLib) {
    
		Map<String, byte[]> binMap = new ConcurrentHashMap<String, byte[]>();
		BufferedImage libBuf = null;
		PicFinger fpLib = null;
		// 初始化Lib库
		String fileName = null;
		System.out.print("getLibMatrix() imgListLib size=" + imgListLib.size());
		int c = 0;
		for (File imgfileLib : imgListLib) {
    
			if (imgfileLib.exists()) {
    
				fileName = imgfileLib.getName();
				fileName = fileName.substring(0, fileName.indexOf("."));
				try {
    
					libBuf = ImageIO.read(imgfileLib);
				} catch (IOException e) {
    
					e.printStackTrace();
				}
				if (libBuf != null) {
    
					fpLib = new PicFinger(libBuf);
					binMap.put(fileName, fpLib.getBinaryzationMatrix());
					if (c % 100 == 0) {
    
						System.out.print("\ngetLib() list c=" + c + " ");
					}
					System.out.print(fileName + ",");
					c++;
				} else {
    
					System.out.println("libBuf=" + libBuf + "|imgfileLib=" + imgfileLib.getName());
				}
			} else {
    
				continue;
			}
		}
		System.out.println("\ngetLibMatrix() size=" + binMap.size());
		return binMap;
	}
}
  1. 把图片旋转360°
public static Color bgColor = new Color(255, 255, 255);

	/**
	 * 创建任意角度的旋转图像
	 * 
	 * @param image
	 * @param theta
	 * @param backgroundColor
	 * @return
	 */
	public BufferedImage rotateImage(BufferedImage image, double theta, Color backgroundColor) {
    
		int width = image.getWidth();
		int height = image.getHeight();
		double angle = theta * Math.PI / 180; // 度转弧度
		double[] xCoords = getX(width / 2, height / 2, angle);
		double[] yCoords = getY(width / 2, height / 2, angle);
		int WIDTH = (int) (xCoords[3] - xCoords[0]);
		int HEIGHT = (int) (yCoords[3] - yCoords[0]);
		BufferedImage resultImage = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_RGB);
		for (int i = 0; i < WIDTH; i++) {
    
			for (int j = 0; j < HEIGHT; j++) {
    
				int x = i - WIDTH / 2;
				int y = HEIGHT / 2 - j;
				double radius = Math.sqrt(x * x + y * y);
				double angle1;
				if (y > 0) {
    
					angle1 = Math.acos(x / radius);
				} else {
    
					angle1 = 2 * Math.PI - Math.acos(x / radius);
				}
				x = (int) Math.round(radius * Math.cos(angle1 - angle));
				y = (int) Math.round(radius * Math.sin(angle1 - angle));
				if (x < (width / 2) & x > -(width / 2) & y < (height / 2) & y > -(height / 2)) {
    
					int rgb = image.getRGB((int) Math.round(x + width / 2), (int) Math.round(height / 2 - y));
					resultImage.setRGB(i, j, rgb);
				} else {
    
					resultImage.setRGB(i, j, -1);
				}
			}
		}
		return resultImage;
	}

	// 获取四个角点旋转后Y方向坐标
	private double[] getY(int i, int j, double angle) {
    
		double results[] = new double[4];
		double radius = Math.sqrt(i * i + j * j);
		double angle1 = Math.asin(j / radius);
		results[0] = radius * Math.sin(angle1 + angle);
		results[1] = radius * Math.sin(Math.PI - angle1 + angle);
		results[2] = -results[0];
		results[3] = -results[1];
		Arrays.sort(results);
		return results;
	}

	// 获取四个角点旋转后X方向坐标
	private double[] getX(int i, int j, double angle) {
    
		double results[] = new double[4];
		double radius = Math.sqrt(i * i + j * j);
		double angle1 = Math.acos(i / radius);
		results[0] = radius * Math.cos(angle1 + angle);
		results[1] = radius * Math.cos(Math.PI - angle1 + angle);
		results[2] = -results[0];
		results[3] = -results[1];
		Arrays.sort(results);
		return results;
	}

	public BufferedImage writeCyclePic(BufferedImage image) {
    
		BufferedImage newImage = new BufferedImage(152, 152, BufferedImage.TYPE_INT_BGR);
		try {
    
			int width = image.getWidth();
			int heigth = image.getHeight();
			double x0 = width / 2;
			double y0 = heigth / 2;
			int woffset = (width - 152) / 2;
			int hoffset = (heigth - 152) / 2;
			for (int i = woffset; i < 152 + woffset; i++) {
    
				for (int j = hoffset; j < 152 + hoffset; j++) {
    
					double r = Math.sqrt(Math.pow(Math.abs(i - x0), 2.0) + Math.pow(Math.abs(j - y0), 2.0));
					if (r > (x0 - woffset)) {
    
						newImage.setRGB(i - woffset, j - hoffset, -1);
					} else {
    
						newImage.setRGB(i - woffset, j - hoffset, image.getRGB(i, j));
					}
				}
			}
			return newImage;
		} catch (Exception e) {
    
			e.printStackTrace();
			return null;
		}
	}

	// 旋转生成图片
	//start 原图转正需要滑动的距离
	//total 最长距离
	//input 原图距离
	//outPath 新图目录
	public void rotate360(Integer start, Integer total, File input, String outPath) {
    
		try {
    
			BufferedImage image = ImageIO.read(input);
			int distance;
			BufferedImage mid, result;
			File output;
			for (int i = 0; i < 360; i++) {
    
				distance = start + Math.round(i * total / 360);
				distance = distance > total ? distance - total : distance;
				mid = rotateImage(image, i, bgColor);
				result = writeCyclePic(mid);
				if (outPath != null && !"".equals(outPath)) {
    
					output = new File(outPath+i+"-"+distance+".png");
					ImageIO.write(result, "png", output);
				}
			}
		} catch (Exception e) {
    
			e.printStackTrace();
		}
	}
  1. 把图片模型转为可比较对象信息存入模型库
//Map<String, Map<String, PicFinger>> 中的第一个String唯一就好了可以用自然数依次记录,
//第二个String记录需要滑动的距离,PicFinger为可比较的图片信息
public static Map<String, Map<String, PicFinger>> allMap = new HashMap<String, Map<String, PicFinger>>();

	public static PicFinger getPicFinger(File file) {
    
		byte[] img;
		try {
    
			BufferedImage libBuf = ImageIO.read(file);
			PicFinger picFinger = new PicFinger(libBuf);
			img = picFinger.getBinaryzationMatrix();
			PicFinger fp = new PicFinger(img);
			return fp;
		} catch (IOException e) {
    
			e.printStackTrace();
			return null;
		}
	}

	public static List<File> getFiles(String path) {
    
		File root = new File(path);
		List<File> files = new ArrayList<File>();
		if (!root.isDirectory()) {
    
			files.add(root);
		} else {
    
			File[] subFiles = root.listFiles();
			for (File f : subFiles) {
    
				files.addAll(getFiles(f.getAbsolutePath()));
			}
		}
		return files;
	}
	
	public static void save(String file, Object o) throws Exception {
    
		FileOutputStream outStream = null;
		ObjectOutputStream objectOutputStream = null;
		try {
    
			outStream = new FileOutputStream(file);
			objectOutputStream = new ObjectOutputStream(outStream);
			objectOutputStream.writeObject(o);
			objectOutputStream.close();
		} catch (Exception e) {
    
		} finally {
    
			if (outStream != null)
				outStream.close();
			if (objectOutputStream != null)
				objectOutputStream.close();
		}
	}
	
	public static Object load(String file) throws Exception {
    
		FileInputStream freader = null;
		ObjectInputStream objectInputStream = null;
		try {
    
			freader = new FileInputStream(file);
			objectInputStream = new ObjectInputStream(freader);
			Object o = objectInputStream.readObject();
			return o;
		} catch (Exception e) {
    
			return null;
		} finally {
    
			if (freader != null)
				freader.close();
			if (objectInputStream != null)
				objectInputStream.close();
		}
	}

四、结果展示

所有的流程都走完了,不妨做个测试。

在这里插入图片描述
粗略观察了下,效果还不错。难免其中也存在识别错误的情况,接下来做下结果分析。

五、结果分析

目标:

识别图片角度,推算出对应滑动距离,模拟滑动。

实现思路:

抓取图片,筛选
生成各个角度图片模型,标记正向图
将推算距离整合模型数据,建造模型库
抓到图片后通过图片相似度比较算法匹配模型库
根据匹配出的距离模拟滑动

检测耗时:

15 - 100毫秒

通过率:

95%(低样本)

最终测试结果为300条样本结果,这个样本数还是偏少了,不确定在更多的测试条数时还会不会达到这样的效果,应该不会差太远哈。

六、结语

这篇文章到这里就结束了,感谢大佬们驻足观看,大佬们点个关注、点个赞呗~

谢谢大佬~
在这里插入图片描述
戳这里→康康你手机号在过多少网站注册过!!!
友情推荐:新一代安全短信

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/weixin_44549063/article/details/112193334

智能推荐

oracle 12c 集群安装后的检查_12c查看crs状态-程序员宅基地

文章浏览阅读1.6k次。安装配置gi、安装数据库软件、dbca建库见下:http://blog.csdn.net/kadwf123/article/details/784299611、检查集群节点及状态:[root@rac2 ~]# olsnodes -srac1 Activerac2 Activerac3 Activerac4 Active[root@rac2 ~]_12c查看crs状态

解决jupyter notebook无法找到虚拟环境的问题_jupyter没有pytorch环境-程序员宅基地

文章浏览阅读1.3w次,点赞45次,收藏99次。我个人用的是anaconda3的一个python集成环境,自带jupyter notebook,但在我打开jupyter notebook界面后,却找不到对应的虚拟环境,原来是jupyter notebook只是通用于下载anaconda时自带的环境,其他环境要想使用必须手动下载一些库:1.首先进入到自己创建的虚拟环境(pytorch是虚拟环境的名字)activate pytorch2.在该环境下下载这个库conda install ipykernelconda install nb__jupyter没有pytorch环境

国内安装scoop的保姆教程_scoop-cn-程序员宅基地

文章浏览阅读5.2k次,点赞19次,收藏28次。选择scoop纯属意外,也是无奈,因为电脑用户被锁了管理员权限,所有exe安装程序都无法安装,只可以用绿色软件,最后被我发现scoop,省去了到处下载XXX绿色版的烦恼,当然scoop里需要管理员权限的软件也跟我无缘了(譬如everything)。推荐添加dorado这个bucket镜像,里面很多中文软件,但是部分国外的软件下载地址在github,可能无法下载。以上两个是官方bucket的国内镜像,所有软件建议优先从这里下载。上面可以看到很多bucket以及软件数。如果官网登陆不了可以试一下以下方式。_scoop-cn

Element ui colorpicker在Vue中的使用_vue el-color-picker-程序员宅基地

文章浏览阅读4.5k次,点赞2次,收藏3次。首先要有一个color-picker组件 <el-color-picker v-model="headcolor"></el-color-picker>在data里面data() { return {headcolor: ’ #278add ’ //这里可以选择一个默认的颜色} }然后在你想要改变颜色的地方用v-bind绑定就好了,例如:这里的:sty..._vue el-color-picker

迅为iTOP-4412精英版之烧写内核移植后的镜像_exynos 4412 刷机-程序员宅基地

文章浏览阅读640次。基于芯片日益增长的问题,所以内核开发者们引入了新的方法,就是在内核中只保留函数,而数据则不包含,由用户(应用程序员)自己把数据按照规定的格式编写,并放在约定的地方,为了不占用过多的内存,还要求数据以根精简的方式编写。boot启动时,传参给内核,告诉内核设备树文件和kernel的位置,内核启动时根据地址去找到设备树文件,再利用专用的编译器去反编译dtb文件,将dtb还原成数据结构,以供驱动的函数去调用。firmware是三星的一个固件的设备信息,因为找不到固件,所以内核启动不成功。_exynos 4412 刷机

Linux系统配置jdk_linux配置jdk-程序员宅基地

文章浏览阅读2w次,点赞24次,收藏42次。Linux系统配置jdkLinux学习教程,Linux入门教程(超详细)_linux配置jdk

随便推点

matlab(4):特殊符号的输入_matlab微米怎么输入-程序员宅基地

文章浏览阅读3.3k次,点赞5次,收藏19次。xlabel('\delta');ylabel('AUC');具体符号的对照表参照下图:_matlab微米怎么输入

C语言程序设计-文件(打开与关闭、顺序、二进制读写)-程序员宅基地

文章浏览阅读119次。顺序读写指的是按照文件中数据的顺序进行读取或写入。对于文本文件,可以使用fgets、fputs、fscanf、fprintf等函数进行顺序读写。在C语言中,对文件的操作通常涉及文件的打开、读写以及关闭。文件的打开使用fopen函数,而关闭则使用fclose函数。在C语言中,可以使用fread和fwrite函数进行二进制读写。‍ Biaoge 于2024-03-09 23:51发布 阅读量:7 ️文章类型:【 C语言程序设计 】在C语言中,用于打开文件的函数是____,用于关闭文件的函数是____。

Touchdesigner自学笔记之三_touchdesigner怎么让一个模型跟着鼠标移动-程序员宅基地

文章浏览阅读3.4k次,点赞2次,收藏13次。跟随鼠标移动的粒子以grid(SOP)为partical(SOP)的资源模板,调整后连接【Geo组合+point spirit(MAT)】,在连接【feedback组合】适当调整。影响粒子动态的节点【metaball(SOP)+force(SOP)】添加mouse in(CHOP)鼠标位置到metaball的坐标,实现鼠标影响。..._touchdesigner怎么让一个模型跟着鼠标移动

【附源码】基于java的校园停车场管理系统的设计与实现61m0e9计算机毕设SSM_基于java技术的停车场管理系统实现与设计-程序员宅基地

文章浏览阅读178次。项目运行环境配置:Jdk1.8 + Tomcat7.0 + Mysql + HBuilderX(Webstorm也行)+ Eclispe(IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持)。项目技术:Springboot + mybatis + Maven +mysql5.7或8.0+html+css+js等等组成,B/S模式 + Maven管理等等。环境需要1.运行环境:最好是java jdk 1.8,我们在这个平台上运行的。其他版本理论上也可以。_基于java技术的停车场管理系统实现与设计

Android系统播放器MediaPlayer源码分析_android多媒体播放源码分析 时序图-程序员宅基地

文章浏览阅读3.5k次。前言对于MediaPlayer播放器的源码分析内容相对来说比较多,会从Java-&amp;amp;gt;Jni-&amp;amp;gt;C/C++慢慢分析,后面会慢慢更新。另外,博客只作为自己学习记录的一种方式,对于其他的不过多的评论。MediaPlayerDemopublic class MainActivity extends AppCompatActivity implements SurfaceHolder.Cal..._android多媒体播放源码分析 时序图

java 数据结构与算法 ——快速排序法-程序员宅基地

文章浏览阅读2.4k次,点赞41次,收藏13次。java 数据结构与算法 ——快速排序法_快速排序法