• 利用modelarts和物体检测方式识别验证码


    近来有朋友让老山帮忙识别验证码。在github上查看了下,目前开源社区中主要流行以下几种验证码识别方式:

    1. tesseract-ocr模块:
      这是HP实验室开发由Google 维护的开源 OCR引擎,内置传统模式识别方法和现代深度神经网络算法
    2. 采用深度学习网络
      通常是基于CNN网络,通过captcha等验证码生产器自动生产训练集,通常对生成器内置的验证码类型有极高的识别度。

    需求中需要识别的验证码来自特定网站 http://fota.redstone.net.cn/,使用通用的验证码识别模块识别准确率比较低,因此很可能需要自己创建数据集进行训练。

    查看数据

    无论如何,都必须看到数据才能处理数据。因此我们通过对网站进行请求生成验证码集:

    import os, requests
    from time import sleep
    # 文件保存路径
    path = 'images'
    # 验证码网站
    url = 'http://fota.redstone.net.cn/index.php/home/index/get_verify.html'
    # 图片数目
    number = 100
    
    os.makedirs(path, exist_ok=True)
    
    # 请求并保存图片
    for i in range(number):
       sleep(0.1)
       r = requests.get(url)
       with open(f'{path}/{i}.jpg', 'wb') as f:
           f.write(r.content)

    观察数据:

    通过观察数据,发现这些验证码基本有这几个特点:

    1. 需要识别的是4个数字;
    2. 数字字体有多种风格,数字有一定旋转,数字之间有可能交叉;
    3. 背景中有许多小字;
    4. 与数字相同的颜色的间断粗线穿过数字,对数字识别造成影响。

    标注

    按通常的图像处理方式,可以通过形态学去除小字,然后转换成灰度图进行后期训练处理。但老山正好最近涉及些图像识别的深度学习方法,于是在想,是否能直接通过yolov3,faster-rcnn等物体检测模型来识别验证码呢?

    要按物体检测方式识别验证码,首先要有标注数据集,这里采用modelarts内置的标注进行标注。

    1. 先将图像传到obs中

    2. 在modelarts上点击创建数据集

    3. 按要求填写列表,选择物体检测,(标签可以在此处添加,也可以在标注的时候添加),然后创建数据集

    4. 点击进入已创建的数据集,然后就可以进行图片标注了

    5. 这里不得不提的是智能标注,这也成为后面老山深深的怨念;在标注图片超过20的时候,就可以启用智能标注,可以自动识别物体,减少标注时间

    运行10多分钟后,标注就完成了,可以看到,标注的效果是真好

    在已标注集仅为20的情况下,虽然还有不少需要更改的地方,但效果还是可用的;但标注集达到100时,效果就可以达到95%以上的准确率;由于智能标注使用的算法无非是常见的物体检测方法的一种,如此好的效果让我一度产生“标注数据集100就可以得到很好的训练效果“的幻觉。

    训练

    1. 标注完成后,点击数据集右侧的发布按钮,然后你在数据集输出路径里很深的路径里找到生成的标注的xml文件

    2. 如果需要追加待标注数据集,可以对输入位置对应的obs路径中添加图像,然后在标注页面里点击”同步数据源“

    3. 第一期标注了200张已标注图片,于是老山自信满满的打开训练作业,创建了faster-rcnn的训练作业

    话说训练作业不用写代码还是很方便的,尤其适合前期尝试模型,让大家对模型能达到的准确率做个初步的判断,避免在一个不适合的模型中花费太多的时间。

    在目前物体检测中,faster-rcnn、yolov3还有retinanet都是比较主流的模型。先尝试了faster-rcnn,mAP值只有0.2,然后是yolov3,mAP是0.29,用了retinanet,mAP值到了0.58。

    老山于是便有了怨念,智能标注用啥模型啊,不还得是主流目标检测模型吗,为什么标注20张图就能有如此高的准确率,老山200张图训练的模型效果都不如他。尤其是前面faster-rcnn和yolov3的mAP值都如此的低,让老山一度很灰心,还好retinanet的效果还不错,可以继续调×教。

    虽然老山调不出智能标注使用的模型,但模型复制嘛,不只是复制模型一种方法而已,把模型的结果集作为老山模型的训练集,只要数量足够大,也可以训练出相近的模型。于是就一口气又生成了800张图片,然后用已标注的200张图片做智能标注。由于标注结果十分准确,大部分标注结果就直接确认了,标注了800张图片,只花了1个多小时,效率可以说是杠杠的!

    然后使用这1000个样本的数据集杀了回来,mAP值达到了0.96!看来数据集的大小非常重要,这还只是用了默认参数。

    在观察日志的时候,发现右上角有个修改,点进去发现可以基于训练模型基础继续训练,同时发现预制算法旁边有个算法详情,里面描述了算法的运行参数。考虑到上次运行早停了,看了一通参数的含义,最后只改了decay_patience这个参数。

    又跑了一遍,这次mAP又小小的提高到了0.97!但这些都只是个摸不着的结果,老山决定部署下看下结果

    先在训练作业结果中点击创建模型,把名称改成有意义的,其他参数保持不变就好,

    部署

    等待模型状态正常后,点击右边的部署

    等到部署完成后,点击部署任务中的预测,并上传图片,查看结果

    许多结果看起来还可以

    但也有不少结果没有预测正确

    为此统计下这1000张图片的预测结果(老山这里比较懒,并没有单独准备测试集),我们需要对这些图片进行预测。由于不可能人工上传预测,为此老山祭出了大杀器,python。

    首先,我们要获取X-Auth-Token认证

    import requests
    import json
    url = "https://iam.cn-north-1.myhuaweicloud.com/v3/auth/tokens"
    headers = {"Content-Type":"application/json"}
    data = {
     "auth": {
       "identity": {
         "methods": ["password"],
         "password": {
           "user": {
             "name": "your-username", 
             "password": "your-password",
             "domain": {
               "name": "your-domainname:normally equal to your-username"
             }
           }
         }
       },
       "scope": {
         "project": {
           "name": "cn-north-1" 
         }
       }
     }
    }
    
    data = json.dumps(data)
    
    r = requests.post(url, data = data, headers = headers)
    print(r.headers['X-Subject-Token'])

    data具体参数填写可网址详见https://support.huaweicloud.com/api-modelarts/modelarts_03_0004.html

    程序最后获得的便是X-Auth-Token认证码。

    接下来我们开始预测

    config.py

    X_Auth_Token = "MIIZpAYJKoZIhvcNAQcCoIIZlTCC..." # 前面获取的X-Auth-Token值
    url = "https://39ae62200d7f439eaae44c7cabccf5de.apigw.cn-north-1.huaweicloud.com/v1/infers/55d5..." #在调用指南页面获取的url值

    predict.py

    import os
    import json
    from lxml import etree as ET
    import requests
    from config import url, X_Auth_Token
    import logging
    logging.basicConfig(level=logging.INFO,
                       format="%(asctime)s %(name)s %(levelname)s %(message)s",
                       handlers = [
                           logging.FileHandler(f"log.txt"), #生成日志文件
                           logging.StreamHandler()
                       ])
    
    # 图片(jpg文件)和标注文件(xml文件)所在位置,注意jpg和xml文件一一对应
    path = 'images1000'
    
    def requestImage(filename):
       '''根据图片文件利用在线服务预测结果, jsonStr'''
       files = {'images':open(filename,'rb')}
       headers2 = {'X-Auth-Token': X_Auth_Token}
       response = requests.request("POST", url, files=files, headers=headers2)
       return response.text
    
    def getImageFile(xml_file):
       '''根据xml文件返回对应的图片文件'''
       return os.path.splitext(xml_file)[0]+'.jpg'
    
    def getFileList(folder):
       '''folder下的同名xml和jpg组成tuple,并以list返回 [(xml_file, jpeg_file), ...]'''
       return [(os.path.join(folder, filename), os.path.join(folder, getImageFile(filename))) for filename in os.listdir(folder) iffilename.endswith('.xml')]
    
    def json2text(jsonStr):
       '''根据预测结果jsonStr返回预测的数字'''
       obj = json.loads(jsonStr)
       boxes = []
       for className, box in zip(obj["detection_classes"], obj["detection_boxes"]):
           boxes.append([className]+[float(pos) for pos in box]) 
       # 注意预测的box以[top, left, bottom, right]进行排序,与xml文件有点不同
       boxes.sort(key = lambda x: x[2])
       return ''.join([className for className, *_ in boxes])
    
    def xml2text(xml_file):
       '''根据xml_file里所有box生成list [(class_name, left, top, right, bottom), ...]'''
       tree = ET.parse(xml_file)
       root = tree.getroot()
       boxes = []
       for obj in root.findall('object'):
           name = obj.find('name').text
           xmlbox = obj.find('bndbox')
           b = (round(float(xmlbox.find('xmin').text)), round(float(xmlbox.find('ymin').text)), 
           round(float(xmlbox.find('xmax').text)), round(float(xmlbox.find('ymax').text)))
           boxes.append((name, *b))
       boxes.sort(key = lambda x: x[1])
       return ''.join([className for className, *_ in boxes])        
    
    if __name__ == "__main__":
       fileList = getFileList(path)
       count = 0 # 预测数目
       sameCount = 0 # 预测正确数目
       for xml_file, jpg_file in fileList:
           # 根据图片文件在线预测结果
           jsonStr = requestImage(jpg_file)
           text_image = json2text(jsonStr)
           # 根据标注文件获得正确结果
           text_xml = xml2text(xml_file)
           # 对比输出
           count += 1
           if text_image==text_xml:
               sameCount+=1
           logging.info(f"count:{count}, sameCount:{sameCount}, 
               text_image:{text_image}, text_xml:{text_xml}, percent:{sameCount/count}")

    运行程序结果如下

    可以看到,由于在线服务使用的是CPU,预测的结果比较慢,大概5~6秒出一个结果,准确率一直不高,最后定格在75.6%。对于这个结果,老山只能说,还好是用于预测验证码,可以反复预测,预测正确的期望次数是1.3次,算是可堪一用把。

    总结

    当然,本文算是使用modelarts对使用物体检测算法来识别验证码做了个试探,总体结果说明这种识别验证码的方法完全是可以用的,如果后续需要继续进展的话,完全可以在以下方面进行展开:

    1. 数据集生成:
      数据集仍可以继续扩大。此时就不需要在手动标注了,老山想到一个很好的办法。生成数据集后,用智能标注的方式生成标注结果,将标注结果利用网站去验证正确与否,这样就可以无需手动,生成很好的数据集,现在智能标注免费,完全是白嫖,数据集够大时,至少能达到智能标注的水平(无限怨念)。
    2. 模型参数调节

    预置模型很省心,但却看不到细节,如果想自己把握模型,可以使用开源模型来运行,老老实实的写代码调参数。如果没有GPU环境,可以使用开发环境的notebook进行开发;

    3. 在本地开启服务

    用在线服务功能当然省心,但如果只是为了识别验证码,这性价比就不是太高了。我们可以在预置模型的输出路径找到模型生成文件,这样就可以把模型布置在本地了。

    作者:山找海味

  • 相关阅读:
    前端的UI设计与交互之布局篇
    前端的UI设计与交互之文案篇
    前端的UI设计与交互之字体篇
    前端的UI设计与交互之图标篇
    前端的UI设计与交互之色彩篇
    Linux学习(一)
    PHP正则表达式基本语法
    PHP正则表达式函数学习
    PHP发送HTTP请求的几种方式
    PHP实现队列
  • 原文地址:https://www.cnblogs.com/huaweicloud/p/11861477.html
Copyright © 2020-2023  润新知