美文网首页
完美验证码(Windows) — Java版调用

完美验证码(Windows) — Java版调用

作者: Artfox丶艺狸 | 来源:发表于2020-10-24 14:06 被阅读0次

最近在弄验证码识别,由于本人对那些深度学习、AI 之类的都不了解,无意中接触到【完美验证码】这款软件(我只找到在win下使用的dll,其他系统下不知道怎么弄)。

关于完美验证码的训练和使用,网上教程很多,这里只说java如何对接,以及使用可能出现的问题,下面直接放 java 代码:

一、 外部依赖

  • jna (net.java.dev.jna:jna:5.6.0)可自行下载 github-jna

1. java代码

(1) WmOcr.java

package cn.ido2.ocr;

import com.sun.jna.Library;

public interface WmOcr extends Library {

    boolean LoadWmFromFile(String path, String pwd);

    /**
     * 函数功能说明:从内存中载入识别库文件,成功返回True,否则返回False。
     * 函数参数说明:
     * FileBuffer :整数型,一个记录了识别库文件的二进制数据的字节数组,或一块同样功能的内存区域。这里请提供数组第一个成员的地址,或内存区域的地址。
     * FileBufLen :整数型,上述字节数组的数组成员数,或内存区域大小。
     * Password  :文本型,识别库调用密码
     *
     * @param FileBuffer
     * @param FileBufLen
     * @param Password
     * @return
     */
    boolean LoadWmFromBuffer(byte[] FileBuffer, int FileBufLen, String Password);

    boolean GetImageFromFile(String path, char[] result);

    boolean GetImageFromBuffer(byte[] buffer, int length, byte[] result);

    /**
     * Private Declare Function SetWmOption Lib "WmCode.dll" (ByVal OptionIndex As Long,ByVal OptionValue As Long) As Boolean
     * 函数功能说明:设定识别库选项。设定成功返回真,否则返回假。
     * 函数参数说明:
     * OptionIndex :整数型,选项索引,取值范围1~7
     * OptionValue :整数型,选项数值。
     * <p>
     * 参数详解:
     * OptionIndex  OptionValue
     * 1.   返回方式    取值范围:0~1     默认为0,直接返回验证码,为1返回验证码字符和矩形范围形如:S,10,11,12,13|A,1,2,3,4 表示识别到文本 S 左边横坐标10,左边纵坐标11,右边横坐标,右边纵坐标12
     * <p>
     * 2.      识别方式        取值范围:0~4     默认为0,0整体识别,1连通分割识别,2纵分割识别,3横分割识别,4横纵分割识别。可以进行分割的验证码,建议优先使用分割识别,因为分割后不仅能提高识别率,而且还能提高识别速度
     * <p>
     * 3.   识别模式    取值范围:0~1     默认为0,0识图模式,1为识字模式。识图模式指的是背景白色视为透明不进行对比,识字模式指的是白色不视为透明,也加入对比。绝大多数我们都是使用识图模式,但是有少数部分验证码,使用识字模式更佳。
     * <p>
     * 4.   识别加速    取值范围:0~1     默认为0,0为不加速,1为使用加速。一般我们建议开启加速功能,开启后对识别率几乎不影响。而且能提高3-5倍识别速度。
     * <p>
     * 5.   加速返回    取值范围:0~1     默认为0,0为不加速返回,1为使用加速返回。使用加速返回一般用在粗体字识别的时候,可以大大提高识别速度,但是使用后,会稍微影响识别率。识别率有所下降。一般不是粗体字比较耗时的验证码,一般不用开启
     * <p>
     * 6.   最小相似度   取值范围:0~100   默认为90
     * <p>
     * 7.      字符间隙        取值范围:-10~0   默认为0,如果字符重叠,根据实际情况填写,如-3允许重叠3像素,如果不重叠的话,直接写0,注意:重叠和粘连概念不一样,粘连的话,其实字符间隙为0.
     *
     * @param OptionIndex
     * @param OptionValue
     * @return
     */
    boolean SetWmOption(int OptionIndex, int OptionValue);

    /**
     * 设置传入传出dll的各个文本类型参数是否使用unicode格式,一次设置在程序运行期间有效。设置成功返回真,失败返回假
     * <p>
     * 1.     传入是否使用unicode格式    取值范围:0~1   默认为0使用ansi格式,为1使用unicode文本
     * 2.     传出是否使用unicode格式    取值范围:0~1   默认为0使用ansi格式,为1使用unicode文本
     *
     * @return
     */
    boolean UseUnicodeString(int OptionIndex, int OptionValue);

    /**
     * Private Declare Function Calculator Lib "WmCode.dll" (ByVal Expression As String,ByVal CalcResult As String) As Boolean
     * 函数功能说明:计算数学表达式。失败返回空文本,成功返回计算结果文本。功能简单,只是用来计算那些需要填写计算结果的验证码。计算完成返回真,否则返回假。
     * 函数参数说明:
     * @param Expression :文本型,数学表达式,只能计算加,减,乘,除,次方运算,支持小括号,中括号,大括号运算,支持负数运算。
     * @param result :文本型,计算结果,使用需要将一个足够长的空白字符串赋值给它。
     */
    boolean Calculator(byte[] Expression, byte[] result);

}

主要是要构造一个与dll对应的java对象,注释都来自于wmcode里面提供的

(2) OcrPredictWmCode.java

package cn.ido2.ocr.impl;

import com.sun.jna.Native;

import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;

public final class OcrPredictWmCode {

    private static final String USER_HOME = System.getProperty("user.home");

    /**
     * 完美验证码中的 WmCode.dll 的路径,可以指定到其他路径,放在jar包中其实也没问题,但是使用spring-boot时加载不到,原因未知
     */
    private static final String OCR_DLL = new File(USER_HOME, "WmCode3.2.1.dll").getAbsolutePath();

    /**
     * 使用完美验证码中训练出的文件
     */
    private static final String OCR_DAT = new File(USER_HOME, "OCR2.dat").getAbsolutePath();

    /**
     * 训练文件的密码
     */
    private static final String OCR_DAT_PWD = "123456";

    public final static WmOcr ocr;

    static {
        ocr = Native.load(OCR_DLL, WmOcr.class);
        loadFromAssets();
    }

    private static void loadFromAssets() {
        try (
                 // 将文件放在 jar包内部的加载方式,使用spring-boot无法读取到文件
                // InputStream stream = OcrPredict.class.getClassLoader().getResourceAsStream(OCR_DAT);
                InputStream stream = new FileInputStream(OCR_DAT);
                ByteArrayOutputStream output = new ByteArrayOutputStream();
        ) {

            byte[] buffer = new byte[4096];
            int n;
            while (-1 != (n = stream.read(buffer))) {
                output.write(buffer, 0, n);
            }

            byte[] fileBuffer = output.toByteArray();
            boolean loadResult = ocr.LoadWmFromBuffer(fileBuffer, fileBuffer.length, OCR_DAT_PWD);
            // 加载成功
            if (loadResult) {
                // 设置识别方式, 2纵分割识别
                ocr.SetWmOption(2, 3);
                // 设置字符间隙,负数是有连接部分,给3px,根据实际情况设置
                ocr.SetWmOption(7, -3);
                System.out.println("OcrPredict load Success.");
            }

        } catch (Exception e) {
            e.printStackTrace();
        }

    }

    public String predict(InputStream stream) throws Exception {
        // 图片处理
        BufferedImage image = OcrProcess.imageProcessing(stream);
        byte[] buffer = OcrProcess.imageToBytes(image, "jpg");
        // 存放结果的,根据实际情况设置长度
        byte[] result = new byte[8];
        boolean b = ocr.GetImageFromBuffer(buffer, buffer.length, result);
        // 识别成功
        if (b) {
            // 截取 buffer中有数据的部分
            int length = result.length;
            for (int i = 0; i < result.length; i++) {
                if (result[i] == 0) {
                    length = i;
                    break;
                }
            }

            return new String(result, 0, length);
        }

        return null;
    }


}


(2) OcrProcess.java

这个是图片处理类,自行根据情况处理

package cn.ido2.ocr.impl;

import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;

public class OcrProcess {

    /**
     * 转换BufferedImage 数据为byte数组
     *
     * @param image  Image对象
     * @param format image格式字符串.如"gif","png"
     * @return byte数组
     */
    public static byte[] imageToBytes(BufferedImage image, String format) {
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        try {
            ImageIO.write(image, format, out);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return out.toByteArray();
    }

    public static BufferedImage imageProcessing(InputStream stream) throws IOException {

        BufferedImage bufferedImage = ImageIO.read(stream);
        if (bufferedImage == null) {
            System.out.println("error");
            return null;
        }

        int h = bufferedImage.getHeight();
        int w = bufferedImage.getWidth();

        // 灰度化
        int[][] gray = new int[w][h];
        for (int x = 0; x < w; x++) {
            for (int y = 0; y < h; y++) {
                int argb = bufferedImage.getRGB(x, y);
                // 图像加亮(调整亮度识别率非常高)
                int r = (int) (((argb >> 16) & 0xFF) * 1.1 + 30);
                int g = (int) (((argb >> 8) & 0xFF) * 1.1 + 30);
                int b = (int) (((argb) & 0xFF) * 1.1 + 30);
                if (r >= 255) {
                    r = 255;
                }
                if (g >= 255) {
                    g = 255;
                }
                if (b >= 255) {
                    b = 255;
                }

                //此处根据实际需要进行设定阈值
                gray[x][y] = (int) Math.pow((
                        Math.pow(r, 2.2) * 0.2973
                                + Math.pow(g, 2.2) * 0.6274
                                + Math.pow(b, 2.2) * 0.0753), 1 / 2.2);
            }
        }

        // 二值化
        int threshold = ostu(gray, w, h);
        BufferedImage binaryBufferedImage = new BufferedImage(w, h, BufferedImage.TYPE_BYTE_BINARY);
        for (int x = 0; x < w; x++) {
            for (int y = 0; y < h; y++) {
                if (gray[x][y] > threshold) {
                    gray[x][y] |= 0x00FFFF;
                } else {
                    gray[x][y] &= 0xFF0000;
                }
                binaryBufferedImage.setRGB(x, y, gray[x][y]);
            }
        }

        //去除干扰点 或 干扰线(运用八领域,即像素周围八个点判定,根据实际需要判定)
        for (int y = 1; y < h - 1; y++) {
            for (int x = 1; x < w - 1; x++) {

                boolean lineFlag = false;//去除线判定
                int pointflagNum = 0;//去除点判定

                if (isBlack(binaryBufferedImage.getRGB(x, y))) {
                    //左右像素点为"白"即空时,去掉此点
                    if (isWhite(binaryBufferedImage.getRGB(x - 1, y)) && isWhite(binaryBufferedImage.getRGB(x + 1, y))) {
                        lineFlag = true;
                        pointflagNum += 2;
                    }
                    //上下像素点为"白"即空时,去掉此点
                    if (isWhite(binaryBufferedImage.getRGB(x, y + 1)) && isWhite(binaryBufferedImage.getRGB(x, y - 1))) {
                        lineFlag = true;
                        pointflagNum += 2;
                    }
                    //斜上像素点为"白"即空时,去掉此点
                    if (isWhite(binaryBufferedImage.getRGB(x - 1, y + 1)) && isWhite(binaryBufferedImage.getRGB(x + 1, y - 1))) {
                        lineFlag = true;
                        pointflagNum += 2;
                    }
                    if (isWhite(binaryBufferedImage.getRGB(x + 1, y + 1)) && isWhite(binaryBufferedImage.getRGB(x - 1, y - 1))) {
                        lineFlag = true;
                        pointflagNum += 2;
                    }
                    //去除干扰线
                    if (lineFlag) {
                        binaryBufferedImage.setRGB(x, y, -1);
                    }
                    //去除干扰点
                    if (pointflagNum > 3) {
                        binaryBufferedImage.setRGB(x, y, -1);
                    }
                }
            }
        }

        return binaryBufferedImage;
    }

    public static boolean isBlack(int colorInt) {
        Color color = new Color(colorInt);
        return color.getRed() + color.getGreen() + color.getBlue() <= 300;
    }

    public static boolean isWhite(int colorInt) {
        Color color = new Color(colorInt);
        return color.getRed() + color.getGreen() + color.getBlue() > 300;
    }

    public static int isBlackOrWhite(int colorInt) {
        if (getColorBright(colorInt) < 30 || getColorBright(colorInt) > 730) {
            return 1;
        }
        return 0;
    }

    public static int getColorBright(int colorInt) {
        Color color = new Color(colorInt);
        return color.getRed() + color.getGreen() + color.getBlue();
    }

    public static int ostu(int[][] gray, int w, int h) {
        int[] histData = new int[w * h];
        // Calculate histogram
        for (int x = 0; x < w; x++) {
            for (int y = 0; y < h; y++) {
                int red = 0xFF & gray[x][y];
                histData[red]++;
            }
        }

        // Total number of pixels
        int total = w * h;

        float sum = 0;
        for (int t = 0; t < 256; t++)
            sum += t * histData[t];

        float sumB = 0;
        int wB = 0;
        int wF = 0;

        float varMax = 0;
        int threshold = 0;

        for (int t = 0; t < 256; t++) {
            wB += histData[t]; // Weight Background
            if (wB == 0)
                continue;

            wF = total - wB; // Weight Foreground
            if (wF == 0)
                break;

            sumB += (float) (t * histData[t]);

            float mB = sumB / wB; // Mean Background
            float mF = (sum - sumB) / wF; // Mean Foreground

            // Calculate Between Class Variance
            float varBetween = (float) wB * (float) wF * (mB - mF) * (mB - mF);

            // Check if new maximum found
            if (varBetween > varMax) {
                varMax = varBetween;
                threshold = t;
            }
        }

        return threshold;
    }

}

注意 只能使用32位的jdk,我使用jdk版本是jdk8

相关文章

网友评论

      本文标题:完美验证码(Windows) — Java版调用

      本文链接:https://www.haomeiwen.com/subject/ydiimktx.html