美文网首页
完美验证码(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