由于近期工作的一些需要,研究了下验证码的自动识别方面的东西,同时参考了网上别人写的一些程序和思路,这里大概记一下,主要用于备忘。该方法只适用于字体统一规整的、没有扭曲拉伸的简单数字验证码的识别,形如
这样的图片验证码,可以考虑采用类似的法来进行自动识别。
算法思路如下:
1. 根据验证码图片的分析结果(主要是分析数字所在的像素位置),对其进行分割,分割成包含单个数字的图片。
2. 对分割后的图片先进行灰度化,然后二值化,生成单色位图。
3. 读取单色位图的像素点,转换为 0 , 1 数组。
4.把该数组和提前生成好的0-9的字模数组进行比对,取匹配率最大的那个字模所对应的数字。
package cn.xxx.util.image;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.InputStream;
import javax.imageio.ImageIO;
import javax.media.jai.JAI;
import javax.media.jai.RenderedOp;
/**
* 数字验证码识别器(用于识别xxx系统的图片验证码)
*
* 算法如下: 分析验证码图片结构,将其分隔成4个独立的数字图片,把四个独立的数字图片处理成单色位图。
* 把单色位图转换为0、1数组,然后分别和0-9的字模进行匹配,得到图片上的数字信息。
*
* @version 1.0 2009-7-7
* @author huangyuanmu
* @since JDK 1.5.0_8
*/
public class NumberVerificationCodeIdentifier {
static
{
System.setProperty("com.sun.media.jai.disableMediaLib", "true");
}
// 数字模板 0-9
static int[][] value = {
// num 0;
{ 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0,
0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1,
0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0,
0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0,
0, 1, 1, 1, 0, 0 },
// num 1
{ 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0,
0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0,
0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0,
0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0,
1, 1, 1, 1, 1, 0 },
// num2
{ 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0,
1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0,
0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0,
0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1,
1, 1, 1, 1, 1, 0 },
// num3
{ 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0,
1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0,
1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0,
1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0,
1, 1, 1, 1, 0, 0 },
// num4
{ 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 0,
1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0,
1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 1, 1,
1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0,
0, 0, 0, 0, 1, 0 },
// num5
{ 0, 1, 1, 1, 1, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0,
0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0,
0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0,
1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0,
1, 1, 1, 0, 0, 0 },
// num6
{ 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0,
0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 0, 0, 0, 1,
1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0,
0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0,
0, 1, 1, 1, 0, 0 },
// num7
{ 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0,
1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0,
0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0,
0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0,
1, 0, 0, 0, 0, 0 },
// num8
{ 0, 0, 1, 1, 1, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0,
0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0,
0, 1, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0,
0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0,
1, 1, 1, 1, 1, 0 },
// num9
{ 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0,
0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0,
1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0,
0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0,
1, 1, 1, 1, 0, 0 } };
/**
* 识别图像
*
* @author huangyuanmu 2009-7-14
* @param byteArray
* @return
* @throws Exception
*/
public static String recognize(byte[] byteArray) throws Exception {
InputStream is = new ByteArrayInputStream(byteArray);
BufferedImage image = ImageIO.read(is);
return recognize(image);
}
/**
* 识别图像
*
* @author huangyuanmu 2009-7-14
* @param image
* @return
* @throws Exception
*/
public static String recognize(BufferedImage image) throws Exception {
StringBuffer sb = new StringBuffer("");
BufferedImage newim[] = new BufferedImage[4];
if(null == image){
throw new RuntimeException("iamage为null");
}
// 将图像分成四块,因为要处理的文件有四个数字。
newim[0] = generateSingleColorBitMap(image.getSubimage(2, 1, 8, 11));
newim[1] = generateSingleColorBitMap(image.getSubimage(11, 1, 8, 11));
newim[2] = generateSingleColorBitMap(image.getSubimage(20, 1, 8, 11));
newim[3] = generateSingleColorBitMap(image.getSubimage(29, 1, 8, 11));
for (int k = 0; k < 4; k++) {
int iw = newim[k].getWidth(null);
int ih = newim[k].getHeight(null);
int[] pix = new int[iw * ih];
// 因为是二值图像,这里的方法将像素读取出来的同时,转换为0,1的图像数组。
for (int i = 0; i < ih; i++) {
for (int j = 0; j < iw; j++) {
pix[i * (iw) + j] = newim[k].getRGB(j, i);
if (pix[i * (iw) + j] == -1)
pix[i * (iw) + j] = 0;
else
pix[i * (iw) + j] = 1;
}
}
// 得到像匹配的数字。
int r = getMatchNum(pix);
sb.append(r);
}
return sb.toString();
}
/**
* 把单色位图转换成的0、1数组和字模数组进行比较,返回匹配的数字
*
* @author huangyuanmu 2009-7-7
* @param pix
* @return
*/
private static int getMatchNum(int[] pix) {
int result = -1;
int temp = 100;
int x;
for (int k = 0; k <= 9; k++) {
x = 0;
for (int i = 0; i < pix.length; i++) {
x = x + Math.abs(pix[i] - value[k][i]);
}
if(x == 0){
result = k;
break;
}else if (x < temp){
temp = x;
result = k;
}
}
return result;
}
/**
* 把彩色图像转换单色图像
*
* @author huangyuanmu 2009-7-7
* @param colorImage
* @return
*/
private static BufferedImage generateSingleColorBitMap(Image colorImage) {
BufferedImage image = new BufferedImage(8, 11,
BufferedImage.TYPE_BYTE_GRAY);
Graphics g = image.getGraphics();
g.drawImage(colorImage, 0, 0, null);
g.dispose();
RenderedOp ro = JAI.create("binarize", image, new Double(100));
BufferedImage bi = ro.getAsBufferedImage();
return bi;
}
/**
* 测试
*
* @author huangyuanmu 2009-7-7
* @param args
*/
public static void main(String args[]) throws Exception {
String s = recognize(ImageIO.read(new File("D:\\1.jpg")));
System.out.println("recognize result" + s);
}
}
复杂的验证码识别技术就相当复杂了,个人也没有精力去研究了,使用现成的OCR软件不失为一个便捷的方法。tesseract是一个不错的选择,它是惠普公司开发的一个OCR软件,后来提交给google进行了开源。经过个人试用,能力还是比较强大的,抗干扰能力挺不错的。