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

最近在弄验证码识别,由于本人对那些深度学习、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 :整数型,选项数值。

*

* 参数详解:

* OptionIndex OptionValue

* 1. 返回方式 取值范围:0~1 默认为0,直接返回验证码,为1返回验证码字符和矩形范围形如:S,10,11,12,13|A,1,2,3,4 表示识别到文本 S 左边横坐标10,左边纵坐标11,右边横坐标,右边纵坐标12

*

* 2. 识别方式 取值范围:0~4 默认为0,0整体识别,1连通分割识别,2纵分割识别,3横分割识别,4横纵分割识别。可以进行分割的验证码,建议优先使用分割识别,因为分割后不仅能提高识别率,而且还能提高识别速度

*

* 3. 识别模式 取值范围:0~1 默认为0,0识图模式,1为识字模式。识图模式指的是背景白色视为透明不进行对比,识字模式指的是白色不视为透明,也加入对比。绝大多数我们都是使用识图模式,但是有少数部分验证码,使用识字模式更佳。

*

* 4. 识别加速 取值范围:0~1 默认为0,0为不加速,1为使用加速。一般我们建议开启加速功能,开启后对识别率几乎不影响。而且能提高3-5倍识别速度。

*

* 5. 加速返回 取值范围:0~1 默认为0,0为不加速返回,1为使用加速返回。使用加速返回一般用在粗体字识别的时候,可以大大提高识别速度,但是使用后,会稍微影响识别率。识别率有所下降。一般不是粗体字比较耗时的验证码,一般不用开启

*

* 6. 最小相似度 取值范围:0~100 默认为90

*

* 7. 字符间隙 取值范围:-10~0 默认为0,如果字符重叠,根据实际情况填写,如-3允许重叠3像素,如果不重叠的话,直接写0,注意:重叠和粘连概念不一样,粘连的话,其实字符间隙为0.

*

* @param OptionIndex

* @param OptionValue

* @return

*/

boolean SetWmOption(int OptionIndex, int OptionValue);

/**

* 设置传入传出dll的各个文本类型参数是否使用unicode格式,一次设置在程序运行期间有效。设置成功返回真,失败返回假

*

* 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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值