实现识别验证码过程思路-赋源码,无任何代码依赖

1 篇文章 0 订阅
1 篇文章 0 订阅

识别过程思路

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

 

 

 

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值