小白花一个小时轻松破解百度拖动旋转验证码?

前言

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

timg (3).jpg

正文来了。

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

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

抓取图片时加上了马赛克

  • 截图是这个亚子的
217-54.png
  • 后台拿到的却是这个亚子的
baidu.png

哦呦,这个马赛克有点东西的呀~

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

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

  • 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;
    }

啊哈,拿到了。


u=1987194893,3253744034&fm=15&gp=0.jpg

本以为就这样结束了。

万万没想到,抓了几张之后马赛克又出现了。。。。。。。


timg (2).jpg

到此,第一种方法宣告西败。

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;
}

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


56.png

到这里抓取图片的问题就解决了。

增加了图片库

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

之前百度大概有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();
        }
    }

四、结果展示

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

20201218_111322 (1).gif

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

五、结果分析

目标:

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

实现思路:

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

检测耗时:

15 - 100毫秒

通过率:

95%(低样本)

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

六、结语

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

谢谢大佬~


u=2318861387,4200595386&fm=26&gp=0.jpg

作者:香芋味的猫丶

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容