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

news2024/9/22 9:35:59

文章目录

  • 前言
  • DdddOcr
  • 分类识别
    • 调用识别功能
    • classification 函数源码
    • classification 函数源码解读
      • 1. 分类功能不支持目标检测
      • 2. 转换为Image对象
      • 3. 根据模型配置调整图片尺寸和色彩模式
      • 4. 图像数据转换为浮点数据并归一化
      • 5. 图像数据预处理
      • 6. 运行模型,返回预测结果
  • 总结


前言

DdddOcr 源码赏析
上文我们读到了分类识别部分的源码,这里我们继续往下进行
在这里插入图片描述

DdddOcr

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

分类识别

调用识别功能

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

classification 函数源码

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)

classification 函数源码解读

1. 分类功能不支持目标检测

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

2. 转换为Image对象

 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)

3. 根据模型配置调整图片尺寸和色彩模式

 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')
  • 如果使用dddocr的模型,则将图像调整为高度为64,同时保持原来的宽高比,同时将图片转为灰度图
  • 如果使用自己传入的模型,则根据从charsets_path读取的charset info调整图片尺寸,之后根据charset 需要调整为灰度图片或RGB模式的图片,这里png_rgba_black_preprocess也是将图片转为RGB模式
def png_rgba_black_preprocess(img: Image):
    width = img.width
    height = img.height
    image = Image.new('RGB', size=(width, height), color=(255, 255, 255))
    image.paste(img, (0, 0), mask=img)
    return image

4. 图像数据转换为浮点数据并归一化

image = np.array(image).astype(np.float32)
image = np.expand_dims(image, axis=0) / 255.
  • image = np.array(image).astype(np.float32):首先,将图像从PIL图像或其他格式转换为NumPy数组,并确保数据类型为float32。这是为了后续的数学运算,特别是归一化和标准化。
  • image = np.expand_dims(image, axis=0) / 255.:然后,通过np.expand_dims在第一个维度(axis=0)上增加一个维度,这通常是为了符合某些模型输入的形状要求(例如,批处理大小)。之后,将图像数据除以255,将其归一化到[0, 1]区间内。

5. 图像数据预处理

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))

这段代码主要进行了图像数据的预处理,具体地,根据是否使用私人的onnx模型(self.use_import_onnx)以及图像的通道数(self.__channel),对图像数据image进行了不同的归一化处理。这种处理在机器学习和深度学习模型中是常见的,特别是当使用预训练的模型进行推理时,需要确保输入数据与模型训练时使用的数据具有相同的分布。

  • 如果不使用私人的ONNX模型 (self.use_import_onnx 为 False, 也就是使用官方的模型)

图像数据image会先减去0.5,然后除以0.5,实现了一个简单的归一化,将图像的像素值从[0, 255]范围缩放到[-1, 1]范围。这种归一化方式可能适用于某些特定训练的模型。

  • 如果使用私人的ONNX模型 (self.use_import_onnx 为 True)
  • 首先,根据图像的通道数self.__channel进行不同的处理。
    如果图像是单通道(self.__channel == 1),则图像数据image会先减去0.456,然后除以0.224,实现另一种归一化。这种归一化参数(0.456和0.224)是针对单通道图像(如灰度图)预训练的模型所使用的。
  • 如果图像是多通道(通常是RGB三通道),则图像数据image会先减去一个包含三个值的数组[0.485, 0.456, 0.406](这些值分别是RGB三通道的均值),然后除以另一个包含三个值的数组[0.229, 0.224, 0.225](这些值分别是RGB三通道的标准差或缩放因子)。这种归一化方式是为了将图像数据标准化到常见的分布,与许多预训练的深度学习模型(如ResNet, VGG等)训练时使用的数据分布相匹配。
  • 接着,对于多通道图像,还执行了两个额外的步骤:
  • image = image[0]:由于之前通过np.expand_dims增加了一个维度,这里通过索引[0]将其移除,恢复到原始的三维形状(高度、宽度、通道数)。
  • image = image.transpose((2, 0, 1)):最后,将图像的维度从(高度、宽度、通道数)转换为(通道数、高度、宽度)。这是因为某些模型(特别是使用PyTorch等框架训练的模型)期望输入数据的维度顺序为(通道数、高度、宽度)。

6. 运行模型,返回预测结果

ort_inputs = {'input1': np.array([image]).astype(np.float32)}
ort_outs = self.__ort_session.run(None, ort_inputs)
result = []
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)
  • 使用模型预测字符并拼接字符串,官方模型可以输出概率信息

argmax_result = np.squeeze(np.argmax(ort_outs[0], axis=2))这行代码在ort_outs[0]的第三个维度(axis=2)上应用np.argmax函数,以找到序列中每个元素最可能的字符索引。np.squeeze用于去除结果中维度为1的轴


总结

本文介绍了DdddOcr的分类识别任务的源码实现过程,主要是调整图片尺寸和色彩模式,以及图像数据的预处理,最后运行模型预测得到结果,下一篇文章中我们将继续阅读DdddOcr目标检测任务的源码实现过程,天命人,明天见!
在这里插入图片描述

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

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

相关文章

如何在Windows和Mac上免费提取RAR文件?这里有方法

序言 你有没有下载过一个文件,却发现它有一个奇怪的.rar文件扩展名?RAR是一种压缩文件格式,与ZIP文件非常相似,在本文中,我们将向你展示如何在Windows或macOS上打开RAR文件。 如何在Windows 11上打开RAR文件 Windows 11在2023年增加了对RAR文件的原生支持。从那时起,你…

前端框架vue3中的条件渲染(v-show,v-if,v-else-if,v-else)

目录 v-show: 需求&#xff1a; v-if 区别与v-show&#xff1a; v-if和v-show的选择&#xff1a; v-else-if和v-else 联合使用&#xff1a; v-show: 部分代码如图&#xff1a; <body><div id"root"><div ><h1>n的值为{{n}}</h1>…

【计算机网络】浏览器输入访问某网址时,后台流程是什么

在访问网址时&#xff0c;后台的具体流程可以因不同的网站、服务器和应用架构而异。 实际过程中可能还涉及更多的细节和步骤&#xff0c;如缓存处理、重定向、负载均衡等。 此外&#xff0c;不同的网站和应用架构可能会有不同的实现方式和优化策略。 部分特定网站或应用&#x…

数据仓库系列19:数据血缘分析在数据仓库中有什么应用?

你是否曾经在复杂的数据仓库中迷失方向&#xff0c;不知道某个数据是从哪里来的&#xff0c;又会流向何方&#xff1f;或者在处理数据质量问题时&#xff0c;无法快速定位根源&#xff1f;如果是这样&#xff0c;那么数据血缘分析将会成为你的得力助手&#xff0c;帮助你在数据…

协议转换桥+高速协议传输终端

多路协议传输终端&#xff08;正在更新&#xff09; 整体框图&#xff08;正在更新&#xff09; 万兆UDP协议栈 整体框图 10G 8b10b phy层设计 整体框图 报文格式

从pdf复制的表格内容粘贴到word或excel表格保持表格格式

对于it工作&#xff0c;硬件和软件&#xff0c;经常需要从pdf复制表格内容到word或excel&#xff0c;但是windows的ctrlc和ctrlv只能复制内容而不能保留表格的格式。 粘贴进word或excel的表格后&#xff0c;不能保持原来表格的排列&#xff0c;特别是word&#xff0c;复制的pdf…

[Leetcode] 接雨水(相向双指针)

可以直接移步大神的解题思路&#xff0c;非常详细 -> 盛最多水的容器 接雨水_哔哩哔哩_bilibili 11. 盛最多水的容器 https://leetcode.cn/problems/container-with-most-water/description/ 42. 接雨水 https://leetcode.cn/problems/trapping-rain-water/description/ 11…

并发编程之LockSupport的 park 方法及线程中断响应

并发编程之LockSupport的 park 方法及线程中断响应-CSDN博客

STM32CubeIDE

文章目录 Stm32CubeIDE开发环境介绍获取路径 新建工程 Stm32CubeIDE 开发环境介绍 也就是说IDE是集合了CubeMX 和MDK5的。 区别&#xff1a; 获取路径 官网&#xff1a;https://www.st.com/en/development-tools/stm32cubeide.html A盘路径&#xff1a;A盘\6&#xff0c;软…

Signed distance fields (SDFs) and Truncated Signed Distance Field(TSDF)

1. Signed distance fields (SDFs) 笔记来源&#xff1a; [1] Signed distance fields (SDFs) [2] Signed Distance Function (SDF): Implicit curves or surfaces [3] Ray Marching and Signed Distance Functions [4] Truncated Signed Distance Function [5] Wiki/Signed d…

个人旅游网(4)——功能详解——收藏功能

文章目录 一、收藏排行榜功能1.1、接口详解1.1.1、findRouteList 二、收藏功能2.1、接口详解2.1.1、find&#xff08;用于判断当前旅游路线是否已被收藏&#xff09;2.1.2、add-favorite&#xff08;用于实现收藏功能&#xff09;2.1.3、remove-favorite&#xff08;用于实现取…

ubuntu20.04搭建kubernetes1.28.13集群配置calico网络插件

写在前面 这里是我在搭建过程中从某站找到的教学视频,搭载的都是最新的,大家可以参考一下 搭建kubernetes集群学习视频: 视频链接。最后面会有我遇见报错信息的所有连接和解决方案,自行查看 不说废话,直接开搭 搭建集群大纲 一、三台虚拟机的初始化 二、三台虚拟机连接…

内存管理篇-19 TLB和Table wake unit

TLB这几节&#xff0c;停下来感觉怪怪的。没有从TLB的引入&#xff0c;工作原理&#xff0c;实际源码应用来深入分析。 TLB 是一种高速缓存&#xff0c;用于存储最近使用的页表项&#xff08;Page Table Entries, PTEs&#xff09;。它的主要目的是加速虚拟地址到物理地址的转换…

卷积公式的几何学理解

1、Required Knowledge 1.1、概率密度函数 用于描述连续型随机变量在不同取值上的概率密度&#xff0c;记作 f ( x ) f(x) f(x)。 如随机变量 X X X的分布为正态分布&#xff0c;则其概率密度函数为&#xff1a; f ( x ) 1 σ 2 π e − ( x − μ ) 2 2 σ 2 f(x)\frac{1}…

容器化你的应用:使用 Docker 入门指南

Docker 是一个流行的平台&#xff0c;它允许开发者将应用程序及其依赖项打包在一起&#xff0c;形成一个轻量级、可移植的容器。这种做法极大地简化了开发、测试和部署流程&#xff0c;因为无论是在本地还是在云端&#xff0c;容器都能确保应用的一致性。本指南将带你从头开始学…

粗心的懒洋洋做Python二级真题(错一大堆,分享错题)

以下内容&#xff0c;皆为原创&#xff0c;制作不易。感谢大家的点赞和关注。 一.数据流图 数据流图&#xff08;Data Flow Diagram&#xff0c;简称DFD&#xff09;是一种图形化表示法&#xff0c;用于展示信息系统中数据的流动和处理过程。 考点&#xff1a;数据流图是系统逻…

【我要成为配环境高手】Visual Studio中Qt安装与配置(无伤速通)

1.下载安装Qt和VSIX插件 2.本地环境变量配置 添加如下&#xff1a; D:\ProgramData\Qt\Qt5.14.2\5.14.2\msvc2017_64\libD:\ProgramData\Qt\Qt5.14.2\5.14.2\msvc2017_64\bin3.VS配置 ⭐项目右键->属性->调试->环境&#xff0c;添加如下&#xff1a;(很重要&#x…

TCP的连接与断开

三次握手 主动发起连接建立的应用进程叫做客户端(client)。被动等待连接建立的应用进程叫做服务器(server)。 第一次握手&#xff1a;Client将同步比特SYN置为1&#xff08;表示这是一个连接请求或连接接受报文&#xff09;&#xff0c;并发送初始报文段序号seq x&#xff0…

kali——nikto的使用

目录 前言 使用方法 查看帮助&#xff08;--help&#xff09; 常规扫描&#xff08;-h&#xff09; 指定端口扫描&#xff08;-h -p&#xff09; 目录猜解&#xff08;-h -C&#xff09; 扫描敏感目录&#xff08;-h&#xff09; 保存扫描信息 前言 linux自带的nikto工…

【Motion Forecasting】SIMPL:简单且高效的自动驾驶运动预测Baseline

SIMPL: A Simple and Efficient Multi-agent Motion Prediction Baseline for Autonomous Driving 这项工作发布于2024年&#xff0c;前一段时间我已经对这篇文章的摘要和结论进行了学习和总结&#xff0c;这一部分详见https://blog.csdn.net/Coffeemaker88/article/details/1…