开源通用验证码识别OCR —— DdddOcr 源码赏析(一)

news2024/9/23 7:33:11

文章目录

    • @[toc]
  • 前言
  • DdddOcr
  • 环境准备
    • 安装DdddOcr
    • 使用示例
  • 源码分析
    • 实例化DdddOcr
      • 实例化过程
    • 分类识别
      • 分类识别过程
  • 未完待续

前言

DdddOcr 源码赏析
在这里插入图片描述

DdddOcr

DdddOcr是开源的通用验证码识别OCR
官方传送门

环境准备

安装DdddOcr

pip install ddddocr

使用示例

示例图片如下
在这里插入图片描述


import ddddocr

ocr = ddddocr.DdddOcr(show_ad=False)

image = open("example.png", "rb").read()
result = ocr.classification(image)
print(result)
# 识别结果 aFtf

源码分析

我们以实例代码为例,分析源码里面都做了什么

实例化DdddOcr

ocr = ddddocr.DdddOcr(show_ad=False)

对应源码如下

class DdddOcr(object):
    def __init__(self, ocr: bool = True, det: bool = False, old: bool = False, beta: bool = False,
                 use_gpu: bool = False,
                 device_id: int = 0, show_ad=True, import_onnx_path: str = "", charsets_path: str = ""):
        if show_ad:
            print("欢迎使用ddddocr,本项目专注带动行业内卷,个人博客:wenanzhe.com")
            print("训练数据支持来源于:http://146.56.204.113:19199/preview")
            print("爬虫框架feapder可快速一键接入,快速开启爬虫之旅:https://github.com/Boris-code/feapder")
            print(
                "谷歌reCaptcha验证码 / hCaptcha验证码 / funCaptcha验证码商业级识别接口:https://yescaptcha.com/i/NSwk7i")
        if not hasattr(Image, 'ANTIALIAS'):
            setattr(Image, 'ANTIALIAS', Image.LANCZOS)
        self.use_import_onnx = False
        self.__word = False
        self.__resize = []
        self.__charset_range = []
        self.__channel = 1
        if import_onnx_path != "":
            det = False
            ocr = False
            self.__graph_path = import_onnx_path
            with open(charsets_path, 'r', encoding="utf-8") as f:
                info = json.loads(f.read())
            self.__charset = info['charset']
            self.__word = info['word']
            self.__resize = info['image']
            self.__channel = info['channel']
            self.use_import_onnx = True

        if det:
            ocr = False
            self.__graph_path = os.path.join(os.path.dirname(__file__), 'common_det.onnx')
            self.__charset = []

实例化过程

1 show_ad
先来一波广告推广,开源不易,尤其是DdddOcr这么良心的开源Ocr,大家多多支持DdddOcr
2 ANTIALIAS 判断

if not hasattr(Image, 'ANTIALIAS'):
    setattr(Image, 'ANTIALIAS', Image.LANCZOS)

Image.LANCZOS,这是一种图像重采样过滤器,通常用于图像缩放时减少锯齿状边缘和模糊。
这段代码的作用主要是向后兼容或者为旧代码提供一种便捷的访问方式,使得即使PIL或Pillow库的官方API中没有直接提供ANTIALIAS这个属性,开发者也可以通过这种方式来使用LANCZOS过滤器进行图像缩放等操作。

3 然后初始化一些变量

self.use_import_onnx = False
        self.__word = False
        self.__resize = []
        self.__charset_range = []
        self.__channel = 1

4 判断是否使用自己的Ocr模型

if import_onnx_path != "":
     det = False
      ocr = False
      self.__graph_path = import_onnx_path
      with open(charsets_path, 'r', encoding="utf-8") as f:
          info = json.loads(f.read())
      self.__charset = info['charset']
      self.__word = info['word']
      self.__resize = info['image']
      self.__channel = info['channel']
      self.use_import_onnx = True

如果使用自己的Ocr模型,通过import_onnx_path指定模型路径,同时charsets_path指定字符集信息
5 是否启用目标检测

if det:
    ocr = False
    self.__graph_path = os.path.join(os.path.dirname(__file__), 'common_det.onnx')
    self.__charset = []
     

1.6 是否启用ocr
beta为True表示启用新的ocr模型, 为False启用老的ocr模型

if ocr:
    if not beta:
        self.__graph_path = os.path.join(os.path.dirname(__file__), 'common_old.onnx')
        self.__charset = [....]
    else:
        self.__graph_path = os.path.join(os.path.dirname(__file__), 'common.onnx')
        self.__charset = [...]

6 是否启用GPU

 if use_gpu:
    self.__providers = [
          ('CUDAExecutionProvider', {
              'device_id': device_id,
              'arena_extend_strategy': 'kNextPowerOfTwo',
              'cuda_mem_limit': 2 * 1024 * 1024 * 1024,
              'cudnn_conv_algo_search': 'EXHAUSTIVE',
              'do_copy_in_default_stream': True,
          }),
      ]
  else:
      self.__providers = [
          'CPUExecutionProvider',
      ]

这里根据use_gpu来决定是使用GPU还是CPU作为计算提供者(ExecutionProvider)

如果use_gpu为True,即决定使用GPU进行计算,那么会创建一个名为CUDAExecutionProvider的提供者配置列表,并设置了一系列与CUDA(GPU计算平台)相关的参数。这些参数包括:

  1. device_id:指定使用的GPU设备的ID,这允许在多GPU环境中选择特定的GPU进行计算。
  2. arena_extend_strategy:内存分配策略,这里设置为’kNextPowerOfTwo’,意味着内存分配时会向上取到最近的2的幂次方大小,这有助于减少内存碎片。
  3. cuda_mem_limit:限制CUDA设备(GPU)的内存使用量,这里设置为2GB(2 * 1024 * 1024 * 1024字节)。
  4. cudnn_conv_algo_search:指定卷积算法搜索策略,'EXHAUSTIVE’表示使用穷举搜索策略来找到最佳的卷积算法,这可能会增加预处理时间但可能提高执行效率。
  5. do_copy_in_default_stream:指定是否在默认流中执行数据复制操作,这里设置为True。

如果use_gpu为False,即决定使用CPU进行计算,那么会简单地设置计算提供者列表为仅包含一个’CPUExecutionProvider’的列表。

7 加载onnx模型

self.__ort_session = onnxruntime.InferenceSession(self.__graph_path, providers=self.__providers)

❓疑问❓
从代码来看只能加载一种模型,ocr模型(新/旧)、det模型、自己的onnx模型,三种模型三选一,这里self.__graph_path指定模型路径时,却使用了3个if, 而不是if-elif-else结构,个人感觉不太合理, 只能说瑕不掩瑜

源码结构如下

if import_onnx_path != "":
   	self.__graph_path = import_onnx_path
if det:
	self.__graph_path = os.path.join(os.path.dirname(__file__), 'common_det.onnx')
if ocr:
	if not beta:
		self.__graph_path = os.path.join(os.path.dirname(__file__), 'common_old.onnx')
	else:
        self.__graph_path = os.path.join(os.path.dirname(__file__), 'common.onnx')

分类识别

image = open("example.jpg", "rb").read()
result = ocr.classification(image)
print(result)

对应源码如下

def classification(self, img, png_fix: bool = False, probability=False):
        if self.det:
            raise TypeError("当前识别类型为目标检测")
        if not isinstance(img, (bytes, str, pathlib.PurePath, Image.Image)):
            raise TypeError("未知图片类型")
        if isinstance(img, bytes):
            image = Image.open(io.BytesIO(img))
        elif isinstance(img, Image.Image):
            image = img.copy()
        elif isinstance(img, str):
            image = base64_to_image(img)
        else:
            assert isinstance(img, pathlib.PurePath)
            image = Image.open(img)
        if not self.use_import_onnx:
            image = image.resize((int(image.size[0] * (64 / image.size[1])), 64), Image.ANTIALIAS).convert('L')
        else:
            if self.__resize[0] == -1:
                if self.__word:
                    image = image.resize((self.__resize[1], self.__resize[1]), Image.ANTIALIAS)
                else:
                    image = image.resize((int(image.size[0] * (self.__resize[1] / image.size[1])), self.__resize[1]),
                                         Image.ANTIALIAS)
            else:
                image = image.resize((self.__resize[0], self.__resize[1]), Image.ANTIALIAS)
            if self.__channel == 1:
                image = image.convert('L')
            else:
                if png_fix:
                    image = png_rgba_black_preprocess(image)
                else:
                    image = image.convert('RGB')
        image = np.array(image).astype(np.float32)
        image = np.expand_dims(image, axis=0) / 255.
        if not self.use_import_onnx:
            image = (image - 0.5) / 0.5
        else:
            if self.__channel == 1:
                image = (image - 0.456) / 0.224
            else:
                image = (image - np.array([0.485, 0.456, 0.406])) / np.array([0.229, 0.224, 0.225])
                image = image[0]
                image = image.transpose((2, 0, 1))

        ort_inputs = {'input1': np.array([image]).astype(np.float32)}
        ort_outs = self.__ort_session.run(None, ort_inputs)
        result = []

        last_item = 0

        if self.__word:
            for item in ort_outs[1]:
                result.append(self.__charset[item])
        else:
            if not self.use_import_onnx:
                # 概率输出仅限于使用官方模型
                if probability:
                    ort_outs = ort_outs[0]
                    ort_outs = np.exp(ort_outs) / np.sum(np.exp(ort_outs))
                    ort_outs_sum = np.sum(ort_outs, axis=2)
                    ort_outs_probability = np.empty_like(ort_outs)
                    for i in range(ort_outs.shape[0]):
                        ort_outs_probability[i] = ort_outs[i] / ort_outs_sum[i]
                    ort_outs_probability = np.squeeze(ort_outs_probability).tolist()
                    result = {}
                    if len(self.__charset_range) == 0:
                        # 返回全部
                        result['charsets'] = self.__charset
                        result['probability'] = ort_outs_probability
                    else:
                        result['charsets'] = self.__charset_range
                        probability_result_index = []
                        for item in self.__charset_range:
                            if item in self.__charset:
                                probability_result_index.append(self.__charset.index(item))
                            else:
                                # 未知字符
                                probability_result_index.append(-1)
                        probability_result = []
                        for item in ort_outs_probability:
                            probability_result.append([item[i] if i != -1 else -1 for i in probability_result_index ])
                        result['probability'] = probability_result
                    return result
                else:
                    last_item = 0
                    argmax_result = np.squeeze(np.argmax(ort_outs[0], axis=2))
                    for item in argmax_result:
                        if item == last_item:
                            continue
                        else:
                            last_item = item
                        if item != 0:
                            result.append(self.__charset[item])
                    return ''.join(result)

            else:
                last_item = 0
                for item in ort_outs[0][0]:
                    if item == last_item:
                        continue
                    else:
                        last_item = item
                    if item != 0:
                        result.append(self.__charset[item])
                return ''.join(result)

分类识别过程

1 目标检测任务不支持分类

if self.det:
	raise TypeError("当前识别类型为目标检测")

  1. 图片格式转换
 if not isinstance(img, (bytes, str, pathlib.PurePath, Image.Image)):
            raise TypeError("未知图片类型")
        if isinstance(img, bytes):
            image = Image.open(io.BytesIO(img))
        elif isinstance(img, Image.Image):
            image = img.copy()
        elif isinstance(img, str):
            image = base64_to_image(img)
        else:
            assert isinstance(img, pathlib.PurePath)
            image = Image.open(img)

未完待续

明天见

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2050772.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Datawhale X 魔搭 AI夏令营第四期 魔搭-AIGC方向全过程笔记

task1: 传送门 task2: 传送门 task3: 传送门 目录 Task1 赛题内容 可图Kolors-LoRA风格故事挑战赛 baseline要点讲解(请配合Datawhale速通教程食用) Step1 设置算例及比赛账号的报名和授权 Step2 进行赛事报名并创建PAI实例 Step3 执行baseline Step4…

软件架构:架构模式、特征及实践指南-读书笔记(1)

第二章 架构思维 2.1 架构与设计 为了使架构落地,必须打破阻碍在架构师和开发人员之间的所有障碍,从而使架构师和开发团队之间形成双向的强关联。如图2-3所示,架构师和开发人员必须在同一个虚拟团队中才能使架构落地。该模型不仅促进了架构师…

关于使用conda安装opencv-python失败的解决方法

当你想使用conda环境安装opencv-python时,会弹出: conda install opencv-python Collecting package metadata (current_repodata.json): done Solving environment: failed with initial frozen solve. Retrying with flexible solve. Collecting packa…

【网络】网络基础概念背景TCP/IP 五层模型跨网络传输详解

主页:醋溜马桶圈-CSDN博客 专栏:计算机网络原理_醋溜马桶圈的博客-CSDN博客 gitee:mnxcc (mnxcc) - Gitee.com 目录 1.计算机网络发展 1.1 独立模式 1.2 网络互联 1.3 局域网 LAN 1.4 广域网 WAN 2.协议 2.1 初识协议 2.2 协议分层 2…

应急响应:挖矿木马-实战 案例一.【Linux 系统-排查和删除】

什么是挖矿木马 挖矿木马是一种恶意软件,它在未经用户许可的情况下,利用用户的计算资源来挖掘加密货币,从而为攻击者带来非法收益。这类软件通常通过多种手段传播,例如利用系统漏洞、弱密码爆破、伪装正常软件等方法感染目标设备…

TI官网下载芯片原理图文件和封装文件导入Altium Designer 21.0.9

1、TI文件下载以 UCC27614为例 打开TI官网直接找到元器件资料界面 在设计与开发栏中找到封装模型文件下载 确认封装 选择封装导出的目标软件–AD 2、文件导入AD软件中 解压压缩包找到项目文件使用AD打开 导入AD输出芯片封装 打开UL_Import.pas然后点击Run 出现UL Import 选择…

20240818 每日AI必读资讯

3人干翻谷歌!免费学术搜索比谷歌学术相关性高5倍,已获YC投资 - 三人团队打造的学术搜索引擎:Lumina - 目前已处理了超30万次查询,支持24种语言 - 号称搜索结果相关性平均比谷歌学术高5倍,能搜索超1亿个研究对象&…

类和对象(下)(1)

类和对象(下) 再探构造函数 我们之前在实现构造函数的时候,初始化成员变量使用的方式都是在函数体内进行赋值,其实构造函数初始化成员变量还有一种方式:初始化列表。 初始化列表不只是为了写得方便,还能解…

信用贷款“并发”能做多少?“并发”前,得做好几件事

最近,咱们聊聊一个挺热门的话题——信用贷的“并发”现象,还有大家最关心的,信用贷款并发能做多少?“并发”操作过程中哪些是需要注意的。 信用贷款并发能做多少? 信用贷能贷多少,这事儿得从俩角度考虑&am…

问题: vue--elementUI 关于 Drawer 抽屉能打开而受蒙版影响不能正常关闭问题解决

对于一个后端为主的小白来说,刚接手Element 遇到的第一个大问题。 vue–elementUI 关于 Drawer 抽屉能打开而受蒙版影响不能正常关闭问题解决> 分享并且记录一下下 其实问题很简单。我们既然能打开。先定位问题。 当我们发现到问题的时候,我觉得应该 …

基于javaEE的校园二手书交易平台的设计与实现

TOC springboot287基于javaEE的校园二手书交易平台的设计与实现 第1章 绪论 1.1 研究背景 互联网概念的产生到如今的蓬勃发展,用了短短的几十年时间就风靡全球,使得全球各个行业都进行了互联网的改造升级,标志着互联网浪潮的来临。在这个…

NGINX 基础参数与功能

章节 1 NGINX 的源码安装 2 NGINX 核心配置详解 3 NGINX 之 location 匹配优先级 4 NGINX 基础参数与功能 目录 1 实现 Nginx 账户认证功能 1.1 创建htpasswd 认证文件 1.2 创建数据目录 1.3 指定认证文件路径 1.4 测试效果 2 定义重定向错误日志 2.1 指定错误日志访问路…

深度学习的量化和剪枝

一:背景 如果要将深度学习的AI模型部署到受限设备(FPGA)上,往往需要更小的存储需求和最低的计算复杂度。当然,还得保持一定的性能(下降在能够接受的范围)。受限设备资源的环境,一般是…

掌握ChatGPT写作艺术:从入门到精通的四个层次

这些周末我仔细研究了如何通过优化提示词提升ChatGPT输出内容的质量。 关于如何使用ChatGPT辅助我们的写作,我归纳了以下规律,希望能为你带来启发。 一、写作步骤 撰写一篇文章,思路上必须是从抽象到具体逐步深入。 首先我们需要明确写什么…

【数据结构与算法】冒泡排序

冒泡排序目录 一.冒泡排序原理二.图示三.冒泡排序具体实现四.冒泡排序升级版五.完整代码 一.冒泡排序原理 还是老样子,我们如何对这个进行排序呢? 冒泡排序的原理是,将两两进行比较,如果前面较大的我们就进行交换到后面. 然后再对交换后的这个和下一个进行比较,一轮过后,最大值…

spfa()算法(求最短路)

spfa算法是对bellman_ford算法的优化,大部分求最短路问题都可以用spaf算法来求。 注意: (1)如若图中有负权回路,不能用spfa算法,要用bellman_ford算法;若只有负权边,则可以用 spf…

Feign的基本使用

一、在项目中引入相关的依赖 创建两个微服务,分别为userservice、orderservice 现在需要在orderservie中查询用户相关的数据,所以需要使用feign进行远程调用userservice 1.1、在orderservice的pom.xml文件中添加下面的依赖 <dependency><groupId>org.springfram…

疫情居家办公系统--论文pf

TOC springboot394疫情居家办公系统--论文pf 第1章 绪论 1.1 课题背景 伴随着科技的进步&#xff0c;电子计算机已经成为人们日常生活不可或缺的办公工具。在这样的背景下&#xff0c;互联网技术被用于各个领域。为了能提高日常生活高效率&#xff0c;互联网信息技术性蓬勃…

Linux 网络套接字解析:实现网络通信

目录 一.网络基础1.协议2.OSI与TCP/IP模型3.网络通信流程4.IP与Mac地址 二.网络编程套接字1.端口号2.网络字节序3.tcp、udp协议4.socket编程5.sockaddr结构解析6.实现Udp_socket7.实现Windows与Linux通信8.Linux下远程执行指令9.实现tcp_socket10.守护进程 一.网络基础 1.协议…