验证码识别之旅(一)

当我们拿到一份验证码时,我们首先应该对它进行观察.

可以看到,它的长和高分别为250和60.不过由于它内容区域的周边含有许多白色的"无效内容"区域,会对我们进行识别造成干扰,所以第一步,就是应该进行噪音数据的过滤.

即将内容区域提取出来.

这个验证码的无效区域为白色,而且是纯白,没有噪音数据,所以rgb会为>=(240,240,240).处理起来就比较容易了.我们只需要分别找出:

  • 最左的<(240,240,240)像素点leftX
  • 最右的<(240,240,240)像素点rightX
  • 最上的<(240,240,240)像素点topY
  • 最下的<(240,240,240)像素点bottomY

那么即可以得到边框的(x,y,width,height)数值为(leftX,topY,rightX-leftX+1,bottomY-topY+1).(注:(x,y)为边框左上角坐标)

代码如下:

/**
     * @param rgb
     * @return false非白色,true白色
     */
    public boolean isWhite(int rgb){
        int r = (rgb & 16711680) >> 16;
        int g = (rgb & 65280) >> 8;
        int b = (rgb & 255);
        if(r >= 240 && g>= 240 && b>=240){
            return true;
        }
        return false;
    }

    /**
     *
     * @return 获取图片的像素边缘
     */
    public int[] getBorder(){
        int[] border = new int[4];
        try{
            int leftX = -1;
            int rightX = -1;
            int topY = -1;
            int bottomY = -1;

            //获取最左侧的非白色像素位置
            for(int i=0;i<width&&leftX==-1;i++){
                for(int j=0;j<height;j++){
                    int rgb = bimg.getRGB(i,j);
                    if(!isWhite(rgb)){
                        leftX = i;
                        break;
                    }
                }
            }
            //获取最右侧的非白色像素位置
            for(int i=width-1;i>=0&&rightX==-1;i--){
                for(int j=0;j<height;j++){
                    int rgb = bimg.getRGB(i,j);
                    if(!isWhite(rgb)){
                        rightX = i;
                        break;
                    }
                }
            }
            //获取最上的非白色像素位置
            for(int i=0;i<height&&topY==-1;i++){
                for(int j=0;j<width;j++){
                    int rgb = bimg.getRGB(j,i);
                    if(!isWhite(rgb)){
                        topY = i;
                        break;
                    }
                }
            }
            //获取最下方的非白色像素的位置
            for(int i=height-1;i>=0&&bottomY==-1;i--){
                for(int j=0;j<width;j++){
                    int rgb = bimg.getRGB(j,i);
                    if(!isWhite(rgb)){
                        bottomY = i;
                        break;
                    }
                }
            }

            int x = leftX;
            int y = topY;
            int width = rightX - leftX + 1;
            int height = bottomY - topY + 1;
            border = new int[]{x,y,width,height};
        }catch (Exception ex){
            ex.printStackTrace();
        }
        return border;
    }

为了方便我们查看边框是否正确的框住了内容区域,我们可以绘制一下识别图片,将边框识别的区域用蓝色矩形框住.

public static void main(String[] args){
        String[] filePaths = new String[]{
                "/home/pijing/comp/validcode/train2/type2_train_1.jpg",
                "/home/pijing/comp/validcode/train2/type2_train_2.jpg",
                "/home/pijing/comp/validcode/train2/type2_train_3.jpg",
                "/home/pijing/comp/validcode/train2/type2_train_4.jpg",
                "/home/pijing/comp/validcode/train2/type2_train_5.jpg",
                "/home/pijing/comp/validcode/train2/type2_train_6.jpg",
                "/home/pijing/comp/validcode/train2/type2_train_7.jpg",
                "/home/pijing/comp/validcode/train2/type2_train_8.jpg",
                "/home/pijing/comp/validcode/train2/type2_train_9.jpg",
                "/home/pijing/comp/validcode/train2/type2_train_10.jpg"
        };
        int index = -1;
        for(String filePath:filePaths){
            index++;
            Type2ImageModel type2ImageModel = new Type2ImageModel(filePath);
            int[] border = type2ImageModel.getBorder();
            for(int i=0;i<border.length;i++){
                System.out.print(border[i]+" ");
            }
            System.out.println();
            //绘制边框以供验证
            Graphics2D g2 = (Graphics2D) type2ImageModel.getBimg().createGraphics();
            g2.setColor(Color.BLUE);
            g2.setStroke(new BasicStroke(3.0f));
            g2.drawRect(border[0], border[1], border[2], border[3]);
            g2.dispose();
            try {
                ImageIO.write(type2ImageModel.getBimg(),"jpg",
                        new File("/home/pijing/comp/validcode/train2/reco_"+index+".jpg"));
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        System.out.println("done!");
    }

可以看到,输出的reco图片中蓝色框是比较准确的框住了内容区域的:


但是,这时我们就发现了一个问题,由于内容区域大小不是绝对相等,所以边框大小时不相等的.这时我们可以打印训练集中的10份验证码(训练集有10000份图片,所以只能抽样)的边框大小:

可以看到,width处于[160,168],height处于[40,48].取最大的with和height作为边框长宽(168,48),另外可以看到验证码含有5个字符,而168不能被5整除,所以定width=170.

最终确定的标准的边框长宽为(170,48)

绘制边框当然不是我们的主要目的,我们的主要目的是获取内容区域的图片.所以按照边框对于图片进行截取,然后再缩放为统一的长宽(170,48):

/**
     *
     * @return 返回按边框截取的图片,并按照统一的长宽(170,480)进行缩放
     */
    public BufferedImage cutImage(){
        int[] border = getBorder();
        Image scaleImage = bimg.getSubimage(border[0],border[1],border[2],border[3]).
                getScaledInstance(borderWidth,borderHeight,Image.SCALE_SMOOTH);
        BufferedImage newImg = new BufferedImage(borderWidth,borderHeight,BufferedImage.TYPE_INT_RGB);
        Graphics2D g = (Graphics2D) newImg.getGraphics();
        g.drawImage(scaleImage,0,0,borderWidth,borderHeight,null);
        g.dispose();
        return newImg;
    }

这样我们就可以得到内容区域的图片,而且图片大小是大小一致的.

接着,我们需要将它按照5等分进行切割,代码如下:

/**
     * @return 由于待识别的验证码的图片的数字就为5个,所以可以按照5对于图片进行切割
     */
    public BufferedImage[] divideImage(){
        BufferedImage curImg = cutImage();
        BufferedImage[] imgArr = new BufferedImage[5];
        try{
            int divideWidth = borderWidth/5;
            int divideHeight = borderHeight;
            for(int i=0;i<5;i++){
                int x = divideWidth*i;
                int y = 0;
                imgArr[i] = curImg.getSubimage(x,y,divideWidth,divideHeight);
            }
        }catch(Exception ex){
            ex.printStackTrace();
        }
        return imgArr;
    }

切割后的图片如下图所示:

所以可以得到每一个被切割图片的灰度值数组:

/**
     * @param rgb
     * @return 根据rgb计算灰度值
     */
    public double getGrayValue(int rgb){
        int r = (rgb & 16711680) >> 16;
        int g = (rgb & 65280) >> 8;
        int b = (rgb & 255);
        return ( r*38 +  g * 75 +  b * 15 )>>7;
    }

    /**
     * 得到5张图片的灰度值数组
     */
    public void calGrayArray(){
        BufferedImage[] arr = divideImage();
        for(int i=0;i<arr.length;i++){
            grayValue[i] = new double[arr[i].getWidth()*arr[i].getHeight()];
            for(int m=0;m<arr[i].getHeight();m++){
                for(int n=0;n<arr[i].getWidth();n++){
                    grayValue[i][m*arr[i].getWidth()+n] = getGrayValue(arr[i].getRGB(n,m));
                }
            }
        }
    }

可以知道,每个验证码对象的灰度数组都长度为5,包含对象为34*48=1632长度的double数组的数组.

完整代码如下:

package com.fetching.validcode.model;
import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.Image;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.io.Serializable;
/**
 * Created by pijing on 17-4-29.
 * 处理类型为2的验证码图片模型类
 */
public class Type2ImageModel implements Serializable{
    private String filePath = null;
    private BufferedImage bimg = null;
    private int width = 0;
    private int height = 0;
    //根据经验取得边框大小
    private int borderWidth = 170;
    private int borderHeight = 48;
    //灰度值的数组
    private double[][] grayValue = new double[5][];

    public Type2ImageModel(String filePath){
        this.filePath = filePath;
        try{
            bimg = ImageIO.read(new File(filePath));
            this.width = bimg.getWidth();
            this.height = bimg.getHeight();
        }catch(Exception ex){
            ex.printStackTrace();
        }
    }

    public BufferedImage getBimg(){
        return this.bimg;
    }

    /**
     * @param rgb
     * @return false非白色,true白色
     */
    public boolean isWhite(int rgb){
        int r = (rgb & 16711680) >> 16;
        int g = (rgb & 65280) >> 8;
        int b = (rgb & 255);
        if(r >= 240 && g>=240  && b>= 240){
            return true;
        }
        return false;
    }

    /**
     *
     * @return 获取图片的像素边缘
     */
    public int[] getBorder(){
        int[] border = new int[4];
        try{
            int leftX = -1;
            int rightX = -1;
            int topY = -1;
            int bottomY = -1;

            //获取最左侧的非白色像素位置
            for(int i=0;i<width&&leftX==-1;i++){
                for(int j=0;j<height;j++){
                    int rgb = bimg.getRGB(i,j);
                    if(!isWhite(rgb)){
                        leftX = i;
                        break;
                    }
                }
            }
            //获取最右侧的非白色像素位置
            for(int i=width-1;i>=0&&rightX==-1;i--){
                for(int j=0;j<height;j++){
                    int rgb = bimg.getRGB(i,j);
                    if(!isWhite(rgb)){
                        rightX = i;
                        break;
                    }
                }
            }
            //获取最上的非白色像素位置
            for(int i=0;i<height&&topY==-1;i++){
                for(int j=0;j<width;j++){
                    int rgb = bimg.getRGB(j,i);
                    if(!isWhite(rgb)){
                        topY = i;
                        break;
                    }
                }
            }
            //获取最下方的非白色像素的位置
            for(int i=height-1;i>=0&&bottomY==-1;i--){
                for(int j=0;j<width;j++){
                    int rgb = bimg.getRGB(j,i);
                    if(!isWhite(rgb)){
                        bottomY = i;
                        break;
                    }
                }
            }

            int x = leftX;
            int y = topY;
            int width = rightX - leftX + 1;
            int height = bottomY - topY + 1;
            border = new int[]{x,y,width,height};
        }catch (Exception ex){
            ex.printStackTrace();
        }
        return border;
    }

    /**
     *
     * @return 返回按边框截取的图片,并按照统一的长宽(170,480)进行缩放
     */
    public BufferedImage cutImage(){
        int[] border = getBorder();
        Image scaleImage = bimg.getSubimage(border[0],border[1],border[2],border[3]).
                getScaledInstance(borderWidth,borderHeight,Image.SCALE_SMOOTH);
        BufferedImage newImg = new BufferedImage(borderWidth,borderHeight,BufferedImage.TYPE_INT_RGB);
        Graphics2D g = (Graphics2D) newImg.getGraphics();
        g.drawImage(scaleImage,0,0,borderWidth,borderHeight,null);
        g.dispose();
        return newImg;
    }

    /**
     * @return 由于待识别的验证码的图片的数字就为5个,所以可以按照5对于图片进行切割
     */
    public BufferedImage[] divideImage(){
        BufferedImage curImg = cutImage();
        BufferedImage[] imgArr = new BufferedImage[5];
        try{
            int divideWidth = borderWidth/5;
            int divideHeight = borderHeight;
            for(int i=0;i<5;i++){
                int x = divideWidth*i;
                int y = 0;
                imgArr[i] = curImg.getSubimage(x,y,divideWidth,divideHeight);
            }
        }catch(Exception ex){
            ex.printStackTrace();
        }
        return imgArr;
    }

    /**
     * @param rgb
     * @return 根据rgb计算灰度值
     */
    public double getGrayValue(int rgb){
        int r = (rgb & 16711680) >> 16;
        int g = (rgb & 65280) >> 8;
        int b = (rgb & 255);
        return ( r*38 +  g * 75 +  b * 15 )>>7;
    }

    /**
     * 得到5张图片的灰度值数组
     */
    public void calGrayArray(){
        BufferedImage[] arr = divideImage();
        for(int i=0;i<arr.length;i++){
            grayValue[i] = new double[arr[i].getWidth()*arr[i].getHeight()];
            for(int m=0;m<arr[i].getHeight();m++){
                for(int n=0;n<arr[i].getWidth();n++){
                    grayValue[i][m*arr[i].getWidth()+n] = getGrayValue(arr[i].getRGB(n,m));
                }
            }
        }
    }

    public double[][] getGrayValue(){
        return this.grayValue;
    }

    public static void main(String[] args){
        String[] filePaths = new String[]{
                "/home/pijing/comp/validcode/train2/type2_train_1.jpg",
                "/home/pijing/comp/validcode/train2/type2_train_2.jpg",
                "/home/pijing/comp/validcode/train2/type2_train_3.jpg",
                "/home/pijing/comp/validcode/train2/type2_train_4.jpg",
                "/home/pijing/comp/validcode/train2/type2_train_5.jpg",
                "/home/pijing/comp/validcode/train2/type2_train_6.jpg",
                "/home/pijing/comp/validcode/train2/type2_train_7.jpg",
                "/home/pijing/comp/validcode/train2/type2_train_8.jpg",
                "/home/pijing/comp/validcode/train2/type2_train_9.jpg",
                "/home/pijing/comp/validcode/train2/type2_train_10.jpg"
        };
        int index = -1;
        for(String filePath:filePaths){
            index++;
            Type2ImageModel type2ImageModel = new Type2ImageModel(filePath);
            /*
            int[] border = type2ImageModel.getBorder();
            for(int i=0;i<border.length;i++){
                System.out.print(border[i]+" ");
            }
            System.out.println();*/
            //绘制边框以供验证
            /*
            Graphics2D g2 = (Graphics2D) type2ImageModel.getBimg().createGraphics();
            g2.setColor(Color.BLUE);
            g2.setStroke(new BasicStroke(3.0f));
            g2.drawRect(border[0], border[1], border[2], border[3]);
            g2.dispose();
            try {
                ImageIO.write(type2ImageModel.getBimg(),"jpg",
                        new File("/home/pijing/comp/validcode/train2/reco_"+index+".jpg"));
            } catch (IOException e) {
                e.printStackTrace();
            }*/
            /*
            BufferedImage cutImage = type2ImageModel.cutImage();
            try {
                ImageIO.write(cutImage,"jpg",
                        new File("/home/pijing/comp/validcode/train2/cut_"+index+".jpg"));
            } catch (IOException e) {
                e.printStackTrace();
            }*/
            /*
            BufferedImage[] tempArr = type2ImageModel.divideImage();
            for(int m=0;m<tempArr.length;m++){
                try {
                    ImageIO.write(tempArr[m],"jpg",
                            new File("/home/pijing/comp/validcode/train2/divide_"+index+"_"+m+".jpg"));
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }*/
            type2ImageModel.calGrayArray();
            double[][] grayArr = type2ImageModel.getGrayValue();
            for(int i=0;i<grayArr.length;i++){
                for(int j=0;j<grayArr[i].length;j++){
                    System.out.print(grayArr[i][j]+" ");
                }
                System.out.println();
            }
            System.out.println();
        }
        System.out.println("done!");
    }
}


唉,自从废寝忘食的让验证码识别的准确率到了99.1%,就丧失了完成文章的动力。周末补上所有文章。


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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值