zoukankan      html  css  js  c++  java
  • 验证码识别

    一、前言

    工作关系,在做自动化测试的时候,不可避免要碰到验证码,如果中途暂停手动输入的话,未免太繁琐,所以我在这里总结了自己搜索到的资料,结合实践经验,与各位分享。


    二、解决的问题

    本次我解决的问题主要是比较传统的图片验证码识别,类似下图这样的:

          

    滑块验证和顺序点击图片那种逆天的验证码本次不涉及。


    三、方法

    我这里有java和python的不同实现,背后的思路大体一致:

    ① 图片二值化

    ② 去噪点

    ③ 识别

    下面通过代码给大家讲解,相关代码已上传至github,可在文末查看。


    四、java实现

    首先列出工程目录:

     Entrance是程序入口,DT是一些配置信息,PictureOcr是识别用到的一些方法。

    ① 去噪点 

     1     /**
     2      * 图片去噪点
     3      * @param picPath
     4      * @return
     5      * @throws IOException
     6      */
     7     public static void removeBackground(String picPath) throws IOException {
     8         BufferedImage bufferedImage = ImageIO.read(new File(picPath));
     9         int width = bufferedImage.getWidth();
    10         int height = bufferedImage.getHeight();
    11         for (int x = 0; x < width; ++x) {
    12             for (int y = 0; y < height; ++y) {
    13                 if (isWrite(bufferedImage.getRGB(x, y)) == 1) {
    14                     bufferedImage.setRGB(x, y, Color.white.getRGB());
    15                 } else {
    16                     bufferedImage.setRGB(x, y, Color.black.getRGB());
    17                 }
    18             }
    19         }
    20         ImageIO.write(bufferedImage, picType, new File(picPath));
    21     }
     1     /**
     2      * 如果某个像素的三原色值大于所设定的阈值,就将此像素设为白色,即为背景
     3      * @param colorInt
     4      * @return
     5      */
     6     public static int isWrite(int colorInt) {
     7 
     8         Color color = new Color(colorInt);
     9         if (color.getRed() + color.getGreen() + color.getBlue() > DT.DictOfOcr.threshold) {
    10             return 1;
    11         }
    12         return 0;
    13     }

     先取得图片的分辨率(长 * 宽),然后设定一个阈值,阈值就是某个像素的R,G,B三原色值的和,大家可以使用截图工具来分析要识别图像的验证码阈值是多少,以微信为例,验证码待识别区域的RGB值即可设定为阈值,大于此阈值的像素均设为白色,否则即设为黑色,这样便可以有效去除噪点。

    ② 裁剪边框

    裁剪边框是为了尽可能大的保留图片特征,提高识别率

     1     /**
     2      * 裁剪边角
     3      * @param picPath
     4      * @throws IOException
     5      */
     6     public static void cutPic(String picPath) throws IOException {
     7 
     8         BufferedImage bufferedimage=ImageIO.read(new File(picPath));
     9         int width = bufferedimage.getWidth();
    10         int height = bufferedimage.getHeight();
    11 
    12 
    13         bufferedimage = cropPic(bufferedimage, (cutWidth / 2),0, (width - cutWidth / 2), height);
    14         bufferedimage = cropPic(bufferedimage,0, (cutHeight / 2),(width - cutWidth), (height - cutHeight / 2));
    15         ImageIO.write(bufferedimage, picType, new File(picPath));
    16     }
    17 
    18     /**
    19      * 根据参数裁剪图片
    20      * @param bufferedImage
    21      * @param startX
    22      * @param startY
    23      * @param endX
    24      * @param endY
    25      * @return
    26      */
    27     public static BufferedImage cropPic(BufferedImage bufferedImage, int startX, int startY, int endX, int endY) {
    28         int width = bufferedImage.getWidth();
    29         int height = bufferedImage.getHeight();
    30         if (startX == -1) {
    31             startX = 0;
    32         }
    33         if (startY == -1) {
    34             startY = 0;
    35         }
    36         if (endX == -1) {
    37             endX = width - 1;
    38         }
    39         if (endY == -1) {
    40             endY = height - 1;
    41         }
    42         BufferedImage result = new BufferedImage(endX - startX, endY - startY, 4);
    43         for (int x = startX; x < endX; ++x) {
    44             for (int y = startY; y < endY; ++y) {
    45                 int rgb = bufferedImage.getRGB(x, y);
    46                 result.setRGB(x - startX, y - startY, rgb);
    47             }
    48         }
    49         return result;
    50     }

    ③ 执行OCR识别

     1     /**
     2      * 执行Ocr识别
     3      * @param picPath
     4      * @return
     5      * @throws TesseractException
     6      */
     7     public static String executeOcr(String picPath) throws TesseractException {
     8 
     9         ITesseract iTesseract = new Tesseract();
    10         iTesseract.setDatapath(tessdataPath);
    11         //iTesseract.setLanguage("eng");
    12         //可根据需要引入相关的训练集
    13         String ocrResult = iTesseract.doOCR(new File(picPath));
    14         return ocrResult;
    15     }

    用到了tessdata数据包

    ④ 效果

    对于规范的验证码来说,识别率还是很不错的,80%左右。我在工程resources路径下建立了一个image文件夹,里面有些图片,大家可以自行尝试。


    五、python实现

    思路如下:

    构建一定数量的数据集(被打上标签的验证码图片),然后进行模型的训练:

    1、二值化图片

    2、分割并保存每一张图片中的字符

    3、“提取分割出的中的特征值”

    4、生成训练集

    5、定义分类模型

    6、测试分类效果

     1 def capt_process(capt):
     2     """
     3     图像预处理,将验证码图片转为二值型图片,按字符切割
     4     :param capt: image
     5     :return: 一个数组包含四个元素,每个元素是一张包含单个字符的二值型图片
     6     """
     7     # 转为灰度图
     8     capt_gray = capt.convert("L")
     9     # 取得图片阈值
    10     threshold = get_threshold(capt_gray)
    11     # 二值化图片
    12     table = get_bin_table(threshold)
    13     capt_bw = capt_gray.point(table, "1")
    14     capt_per_char_list = []
    15     for i in range(4):
    16         x = 5 + i * 15
    17         y = 2
    18         capt_per_char = capt_bw.crop((x, y, x + 13, y + 24))
    19         capt_per_char_list.append(capt_per_char)
    20 
    21     return capt_per_char_list
    View Code
     1 def get_threshold(capt):
     2     """
     3     获取一张图片中,像素出现次数最多的像素,作为阈值
     4     :param capt:
     5     :return:
     6     """
     7     pixel_dict = defaultdict(int)
     8     # 取得图片长、宽
     9     rows, cols = capt.size
    10     for i in range(rows):
    11         for j in range(cols):
    12             # 取得这一点的(r,g,b)
    13             pixel = capt.getpixel((i, j))
    14             # 以像素做key,出现的次数做value
    15             pixel_dict[pixel] += 1
    16     # 取得字典中像素出现最多的次数
    17     count_max = max(pixel_dict.values())
    18     # 反转字典,改为以出现次数做key,方便后面取得像素
    19     pixel_dict_reverse = {v: k for k, v in pixel_dict.items()}
    20     # 取得出现次数最多的像素
    21     threshold = pixel_dict_reverse[count_max]
    22     return threshold
    View Code
     1 def get_bin_table(threshold):
     2     """
     3     按照阈值进行二值化处理
     4     :param threshold:
     5     :return:
     6     """
     7     table = []
     8     rate = 0.1
     9     for i in range(256):
    10         if threshold * (1 - rate) <= i <= threshold * (1 + rate):
    11             table.append(1)
    12         else:
    13             table.append(0)
    14     return table
    View Code

    代码里都有注释,就不详细解释了。

    本人python实现大量参考了这篇博文:

    https://blog.csdn.net/weixin_38641983/article/details/80899354

    具体每一步怎么做的,为什么这么做,都有清楚地解释,我在这里不再赘述,感谢这位博主。

    我要说明的是,训练集可能每个人都不一样,图片切割尺寸也可能都不一样,这些需要在使用时随机应变。


    六、结语

    以上提供的方法只能识别简单的验证码,但是它为我们提供了一种问题解决范式,今后遇到类似的问题,不至于手忙脚乱。

    相关代码还参考了以下博文:

    https://segmentfault.com/a/1190000015240294?utm_source=tag-newest

    https://blog.csdn.net/problc/article/details/5794460

    再次感谢以上博主。

    本文相关代码已上传至github,有问题欢迎与我交流。

    https://github.com/Thinker-Mars/Demo/tree/master/picture-ocr

  • 相关阅读:
    加法问题,编程入门课
    三.Python变量,常量,注释
    二、python介绍
    一. python 安装
    Syntax behind sorted(key=lambda :)
    Java动态数组
    Package name does not correspond to the file path (IntelliJ IDEA)
    理解Java构造器中的"this"
    The Constructor with No Arguments
    160229-01、web页面常用功能js实现
  • 原文地址:https://www.cnblogs.com/cone/p/11767355.html
Copyright © 2011-2022 走看看