识别过程思路
1.获取百张以上的验证码图片,并手工编写结果,当作训练库
2.获取需要验证的图片
3.将训练库图片分割成单个字符并且二值化处理,根据之前手工编写结果把二值化数据存入对应的字库
以下是训练库图片其中两个未分割前的二值化数据,其实就是int[][] 的二维数组,0相当于代表白色像素点,7代表黑色像素点。
在部分地方是不可能出现黑色像素点的,有也是出现的污点,我们应该去除,比如两个数字之间,数字的上部和下部也不可能出现黑色像素点,所以直接吧这部分的值设置成0。
下面训练库其中的两个图片分别是2080和2260
我们将训练库图片分割成单个字符串的二值化数据,因为训练库的二值化数据结果是已知的,我们存入对应字库。
以下是我的存储方式。
以下是部分1字库的二值化数据
4.将需要验证的图片分割成单个字符并且二值化处理
5.将需要验证图片的二值化数据 与字库对比,相似度最大的,那就是这个字库对应的值
对比的方式:我们将需要验证的第一个字符二值化数据 和 10个字库(0字库,1字库,2字库·····)每个去对比。
先定义一下 “0字库”第一个二值化数据我称为 num0[0]
流程:首先待验证二值化数据先和num0[0]数据对比, 假如待验证二值化数据在x=0,y=0的位置假如等与7(x和y代表像素的位置,7代表是黑色像素点)且num0[0]数据的 x=0,y=0位置也是等于7, 就定义一个相似点加1(n++),以此类推可以算出n的值,然后在定义num0[0]等于7的像素点总数(sum),这样我们就得出了待验证二值化数据与num0[0]的相似点n的数据和sum的数据, 用n/sum 得出一个占比,然后我们与num0[1],num0[2]····依次计算n/sum中和得出平均值, 最后会得到 与10个字库的 n/sum数值,哪个数值最大就代表最接近该字库。这样我们就计算出单个字符的数值,然后一个字符一个字符的计算得出结果。
源码如下:
package com.yan.liulanqi;
import java.awt.image.BufferedImage;
import java.io.File;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
import javax.imageio.ImageIO;
public class Yzm {
//训练库地址
String imageXLPath="D:\\image\\";
List<int[][]> n0=new ArrayList(); //存储数字为0的二值化字库
List<int[][]> n1=new ArrayList(); //存储数字为1的二值化字库
List<int[][]> n2=new ArrayList(); //存储数字为2的二值化字库
List<int[][]> n3=new ArrayList(); //存储数字为3的二值化字库
List<int[][]> n4=new ArrayList(); //存储数字为4的二值化字库
List<int[][]> n5=new ArrayList(); //存储数字为5的二值化字库
List<int[][]> n6=new ArrayList(); //存储数字为6的二值化字库
List<int[][]> n7=new ArrayList(); //存储数字为7的二值化字库
List<int[][]> n8=new ArrayList(); //存储数字为8的二值化字库
List<int[][]> n9=new ArrayList(); //存储数字为9的二值化字库
/**
* 读取一张图片的RGB值,然后二值化处理,并且去除明显污点
* @param imagePath 图片路径
* @throws Exception
*/
public int[][] getImagePixel(String imagePath) throws Exception {
int[] rgb = new int[3];
File file = new File(imagePath);
BufferedImage bi = null;
try {
bi = ImageIO.read(file);
} catch (Exception e) {
e.printStackTrace();
}
int width = bi.getWidth();
int height = bi.getHeight();
int image_arr[][]=new int[width][height];
int minx = bi.getMinX();
int miny = bi.getMinY();
// System.out.println("width=" + width + ",height=" + height + ".");
// System.out.println("minx=" + minx + ",miniy=" + miny + ".");
for (int i = minx; i < width; i++) {
for (int j = miny; j < height; j++) {
int pixel = bi.getRGB(i, j); // 下面三行代码将一个数字转换为RGB数字
rgb[0] = (pixel & 0xff0000) >> 16;
rgb[1] = (pixel & 0xff00) >> 8;
rgb[2] = (pixel & 0xff);
// System.out.println("i=" + i + ",j=" + j + ":(" + rgb[0] + ","
// + rgb[1] + "," + rgb[2] + ")");
// image_arr[i][j]=((rgb[0]==255)?0:7);
//在部分地方是不可能出现黑色像素点的,有也是出现的污点,我们应该去除,比如两个数字之间,数字的上部和下部也不可能出现黑色像素点
if(j>7&&j<40&&((i>6&&i<34)||(i>46&&i<74)||(i>86&&i<114)||(i>125&&i<154))){
image_arr[i][j]=((rgb[0]==255)?0:7);//如果rgb是黑色,则定义这个像素点为7
}else{
image_arr[i][j]=0;//如果rgb是白色,则定义这个像素点为0
}
}
}
//去除污点x轴方向 起优化作用 可以不用
for (int i = minx; i < width; i++) {
for (int j = miny; j < height; j++) {
if(image_arr[i][j]==7){
int[] arr_f=new int[7];
for(int p=0;p<arr_f.length;p++){
arr_f[p]=image_arr[i-3+p][j];
}
if(isXTrue(arr_f,0,0)){
continue;
}else{
image_arr[i][j]=0;
}
}
}
}
//去除污点y轴方向 起优化作用 可以不用
for (int i = minx; i < width; i++) {
for (int j = miny; j < height; j++) {
if(image_arr[i][j]==7){
int[] arr_f2=new int[7];
for(int p=0;p<arr_f2.length;p++){
arr_f2[p]=image_arr[i][j-3+p];
}
if(isXTrue(arr_f2,0,0)){
continue;
}else{
image_arr[i][j]=0;
}
}
}
}
//第二次去除污点x轴方向 起优化作用 可以不用
for (int i = minx; i < width; i++) {
for (int j = miny; j < height; j++) {
if(image_arr[i][j]==7){
int[] arr_f=new int[7];
for(int p=0;p<arr_f.length;p++){
arr_f[p]=image_arr[i-3+p][j];
}
if(isXTrue(arr_f,0,0)){
continue;
}else{
image_arr[i][j]=0;
}
}
}
}
printlnSZ(image_arr,width,height);
return image_arr;//返回二值化数据
}
/**
* 判断数组中是否有连续的相同数字
* @param arr 数组
* @param index 记录位置 默认为0
* @param num 连续数量 默认为0
* @throws Exception
*/
public boolean isXTrue(int[] arr,int index,int num){//起到数据优化的作用,不要也行
if(arr[index]==7){
num++;
if(num==4){
return true;
}
if(arr.length==index+1){
return false;
}
index++;
return isXTrue(arr,index,num);
}else{
if(arr.length==index+1){
return false;
}
num=0;
index++;
return isXTrue(arr,index,num);
}
}
/**
* 获取路径下的所有文件
* @param directoryPath 需要遍历的文件夹路径
* @return
*/
public static List<File> getAllFile(String directoryPath) {
List<File> list = new ArrayList<File>();
File baseFile = new File(directoryPath);
if (baseFile.isFile() || !baseFile.exists()) {
return list;
}
File[] files = baseFile.listFiles();
list= Arrays.asList(files);
return list;//获得directoryPath下的所有图片
}
// /**
// * 返回屏幕色彩值
// *
// * @param x
// * @param y
// * @return
// * @throws AWTException
// */
// public int getScreenPixel(int x, int y) throws AWTException { // 函数返回值为颜色的RGB值。
// Robot rb = null; // java.awt.image包中的类,可以用来抓取屏幕,即截屏。
// rb = new Robot();
// Toolkit tk = Toolkit.getDefaultToolkit(); // 获取缺省工具包
// Dimension di = tk.getScreenSize(); // 屏幕尺寸规格
// System.out.println(di.width);
// System.out.println(di.height);
// Rectangle rec = new Rectangle(0, 0, di.width, di.height);
// BufferedImage bi = rb.createScreenCapture(rec);
// int pixelColor = bi.getRGB(x, y);
//
// return 16777216 + pixelColor; // pixelColor的值为负,经过实践得出:加上颜色最大值就是实际颜色值。
// }
/**
* 将训练库中的图片 进行单字拆分 然后存储在对应字库中
* @param name 训练库中的一张图片的名字
* @return
*/
public void putImage(String name) throws Exception {
int[][] sz4=getImagePixel(imageXLPath+name);//将训练库图片二值化处理
String n1=name.substring(0,1);//将训练库图片的第一个字符结果存入
String n2=name.substring(1,2);//将训练库图片的第二个字符结果存入
String n3=name.substring(2,3);//将训练库图片的第三个字符结果存入
String n4=name.substring(3,4);//将训练库图片的第四个字符结果存入
putList(n1,getImage1(sz4));//将第一个二值化数据存入字库
putList(n2,getImage2(sz4));//将第二个二值化数据存入字库
putList(n3,getImage3(sz4));//将第三个二值化数据存入字库
putList(n4,getImage4(sz4));//将第四个二值化数据存入字库
}
/**
* 根据参数将 二值化数据插入指定字库
* @param n
* @param sz 单字的二值化数据
* @return
*/
private void putList(String n,int[][] sz){
if("0".equals(n)){
n0.add(sz);//加入0字库
}else if("1".equals(n)){
n1.add(sz);//加入1字库
}else if("2".equals(n)){
n2.add(sz);//加入2字库
}else if("3".equals(n)){
n3.add(sz);//加入3字库
}else if("4".equals(n)){
n4.add(sz);//加入4字库
}else if("5".equals(n)){
n5.add(sz);//加入5字库
}else if("6".equals(n)){
n6.add(sz);//加入6字库
}else if("7".equals(n)){
n7.add(sz);//加入7字库
}else if("8".equals(n)){
n8.add(sz);//加入8字库
}else if("9".equals(n)){
n9.add(sz);//加入9字库
}else{
}
}
/**
* 从4个字的验证码二值化数据中取出第一个字的二值化数据
* @param sz4 4个字的验证码二值化数据
* @return
*/
private int[][] getImage1(int[][] sz4){
int widthMix=0;//截取的像素点位置
int widthMax=40;//截取的像素点位置
int heightMix=0;//截取的像素点位置
int heightMax=50;//截取的像素点位置
int[][] nsz=new int[40][50];
for(int i=widthMix,ni=0;i<widthMax;i++,ni++){
for (int j=heightMix,nj=0;j<heightMax;j++,nj++){
nsz[ni][nj]=sz4[i][j];
}
}
return nsz;//返回sz4中第一个位置的二值化数据
}
/**
* 从4个字的验证码二值化数据中取出第二个字的二值化数据
* @param sz4 4个字的验证码二值化数据
* @return
*/
private int[][] getImage2(int[][] sz4){
int widthMix=40;//截取的像素点位置
int widthMax=80;//截取的像素点位置
int heightMix=0;//截取的像素点位置
int heightMax=50;//截取的像素点位置
int[][] nsz=new int[40][50];
for(int i=widthMix,ni=0;i<widthMax;i++,ni++){
for (int j=heightMix,nj=0;j<heightMax;j++,nj++){
nsz[ni][nj]=sz4[i][j];
}
}
return nsz;//返回sz4中第二个位置的二值化数据
}
private int[][] getImage3(int[][] sz4){
int widthMix=80;//截取的像素点位置
int widthMax=120;//截取的像素点位置
int heightMix=0;//截取的像素点位置
int heightMax=50;//截取的像素点位置
int[][] nsz=new int[40][50];
for(int i=widthMix,ni=0;i<widthMax;i++,ni++){
for (int j=heightMix,nj=0;j<heightMax;j++,nj++){
nsz[ni][nj]=sz4[i][j];
}
}
return nsz;//返回sz4中第三个位置的二值化数据
}
private int[][] getImage4(int[][] sz4){
int widthMix=120;//截取的像素点位置
int widthMax=160;//截取的像素点位置
int heightMix=0;//截取的像素点位置
int heightMax=50;//截取的像素点位置
int[][] nsz=new int[40][50];
for(int i=widthMix,ni=0;i<widthMax;i++,ni++){
for (int j=heightMix,nj=0;j<heightMax;j++,nj++){
nsz[ni][nj]=sz4[i][j];
}
}
return nsz;//返回sz4中第四个位置的二值化数据
}
/**
* 打印二值化数据,让二值化数据可视化
* @param sz 二值化数据
* @param width 二值化数据平面化的宽
* @param height 二值化数据平面化的高
* @return
*/
private void printlnSZ(int[][] sz,int width,int height){//二值化数据可视化
for (int j = 0; j <height; j++) {
for (int i = 0; i <width; i++) {
if(i==width-1){
System.out.println(sz[i][j]);
}else{
System.out.print(sz[i][j]);
}
}
}
}
/**
* 返回指定字库的对比结果,计算方式: sz中与 nList中二维数组相同位置对比 如果相同 就cf++(相同像素点加1),并计算nList中二维数
* 组的 sum(黑点数量,即==7) 然后 cf/sum 得出覆盖值,也就是二值化数据与这个字库覆盖值数据,越等于1就越相似
* @param sz 需要对比的二值化数据
* @param nList 指定字库中的二值化数据集合
* @return
*/
private Double duiBi(int[][] sz,List<int[][]> nList){
List<Double> bilv=new ArrayList<Double>();
for(int[][] nc:nList){
int cf=0;//定义相同像素点
int sum= 0;//定义字库的黑色像素点
for(int i=0;i<sz.length;i++){
for(int j=0;j<sz[0].length;j++){
if(sz[i][j]==7&&sz[i][j]==nc[i][j]){
cf++;//当sz[i][j]==7且字库[i][j]==7 则cf++
}
if(nc[i][j]==7){
sum++;//字库[i][j]==7 sum++
}
}
}
bilv.add(BigDecimal.valueOf(cf).divide(BigDecimal.valueOf(sum),4, RoundingMode.HALF_UP).doubleValue());//求出与当前字库的 cf/sum数,并且存入集合。
}
return bilv.stream().mapToDouble(Number::doubleValue).summaryStatistics().getAverage();//求出 cf/sum的平均值用来减少误差
}
/**
* 挨个对比算出最接近数字
* @param sz 二值化数据
* @return
*/
private int daAnSZ(int[][] sz){
List<Double> d=new ArrayList<Double>();
d.add(duiBi(sz,this.n0));//得出与0字库的覆盖值数据
d.add(duiBi(sz,this.n1));//得出与1字库的覆盖值数据
d.add(duiBi(sz,this.n2));//得出与2字库的覆盖值数据
d.add(duiBi(sz,this.n3));//得出与3字库的覆盖值数据
d.add(duiBi(sz,this.n4));//得出与4字库的覆盖值数据
d.add(duiBi(sz,this.n5));//得出与5字库的覆盖值数据
d.add(duiBi(sz,this.n6));//得出与6字库的覆盖值数据
d.add(duiBi(sz,this.n7));//得出与7字库的覆盖值数据
d.add(duiBi(sz,this.n8));//得出与8字库的覆盖值数据
d.add(duiBi(sz,this.n9));//得出与9字库的覆盖值数据
return d.indexOf(Collections.max(d));//返回 二值化数据 最接近的数字
}
/**
* 给出答案,也是入口
* @param path 需要验证的图片路径
* @return
*/
public String daAn(String path) throws Exception {
List<File> list=getAllFile(imageXLPath);//获得训练库图片集合
if(list.size()==0){
throw new Exception("训练库的图片数量为0,请添加图片,当前训练库地址是:"+imageXLPath);
}
List<String> names = list.stream().map(file -> file.getName()).collect(Collectors.toList());//获得训练库图片名字集合
for(int i=0;i<list.size();i++){
this.putImage(names.get(i));//利用名字当路径将训练库图片导入
}
int[][] cc=this.getImagePixel(path);//获得需要验证图片的二值化数据
int[][] sz1=getImage1(cc);//截取需要验证图片二值化数据的第一个字符的二值化数据
int[][] sz2=getImage2(cc);//截取需要验证图片二值化数据的第二个字符的二值化数据
int[][] sz3=getImage3(cc);//截取需要验证图片二值化数据的第三个字符的二值化数据
int[][] sz4=getImage4(cc);//截取需要验证图片二值化数据的第四个字符的二值化数据
StringBuffer s=new StringBuffer();
s.append(daAnSZ(sz1));//第一个字符的二值化数据与字库对比,求出最接近的数字
s.append(daAnSZ(sz2));//第二个字符的二值化数据与字库对比,求出最接近的数字
s.append(daAnSZ(sz3));//第三个字符的二值化数据与字库对比,求出最接近的数字
s.append(daAnSZ(sz4));//第四个字符的二值化数据与字库对比,求出最接近的数字
return s.toString();//得出答案
}
/**
* @param args
*/
public static void main(String[] args) throws Exception {
Yzm c=new Yzm();
System.out.println(c.daAn("D:\\cccc.png"));
}
}
赋代码和训练库图片和可验证图片:https://gitee.com/YCH2018/yzm.git