【Opencv入门到项目实战】(十):项目实战|文档扫描|OCR识别

所有订阅专栏的同学可以私信博主获取源码文件

文章目录

  • 1.引言
    • 1.1 什么是光学字符识别 (OCR)
    • 1.2 应用领域
  • 2.项目背景介绍
  • 3.边缘检测
    • 3.1 原始图像读取
    • 3.2 预处理
    • 3.3 结果展示
  • 3.轮廓检测
  • 4.透视变换
  • 5.OCR识别
    • 5.1 tesseract安装
    • 5.2 字符识别

1.引言

今天我们来看一个OCR相关的文档扫描项目。首先我们先来介绍一些相关理论

1.1 什么是光学字符识别 (OCR)

OCR(即光学字符识别)是识别图像中的文本并将其转换为电子形式的过程。这些图像可以是手写文本、打印文本(如文档、收据、名片等),甚至是自然场景照片。

简单来说,OCR 有两个部分。第一部分是文本检测,确定图像内的文本部分。第二部分文本识别,从图像中提取文本。 结合使用这些技术可以从任何图像中提取文本。具体的流程如下图所示

image-20230809111956152

OCR 在各个行业都有广泛的应用(主要目的是减少人工操作)。它已经融入我们的日常生活,并且有很多的应用。

1.2 应用领域

OCR 越来越多地被各行业用于数字化,以减少人工工作量。这使得从商业文档、收据、发票、护照等中提取和存储信息变得非常容易和高效,几十年前,OCR 系统的构建非常昂贵且繁琐。但计算机视觉和深度学习领域的进步使得我们现在自己就可以构建一个OCR 系统。但构建 OCR 系统需要利用到我们之前介绍的一系列方法。

2.项目背景介绍

背景:我们有一张随手拍的发票照片如下,我们希望识别出文档信息并扫描
在这里插入图片描述

思考:我们如何实现上述需求呢?

首先,我们的算法应该能够正确的对齐文档,检测图像的边界,获得目标文本图像

其次,我们能对目标文本图像的文档进行扫描

下面我们来看一下具体如何在Opencv中处理

这里一共需要四大步

第一步,边缘检测,

第二步,提取轮廓。

第三步,透视变换,使得图像对齐,从上图可以看出,我们的图片是一个倾斜的,我们需要通过各种转换方法将其放平。

第四步,OCR识别

3.边缘检测

3.1 原始图像读取

首先,我们读取要扫描的图像。

下述代码我们计算了一个ratio比例,这是因为我们后续要对图像进行resize操作,里面每一个点的坐标也会有相同的一个变化,因此,我们先算出来这样一个比例,可以推导出resize完之后图像的坐标变化,然后方便我们后续在原图上进行修改。

# 读取输入
image = cv2.imread('images/receipt.jpg')
#坐标也会相同变化
ratio = image.shape[0] / 500.0 #这里我们首先得到一个比例,方便后续操作
orig = image.copy()

3.2 预处理

下面我们对图形进行一些基本的预处理工作,包含resize、灰度处理、二值处理。

第一,我们定义了一个resize()函数,它的基本逻辑是根据输入的高度或宽度,自动的计算出宽度或高度。

第二,我们将图形进行灰度处理

第三,我们使用gaussian滤波器去除噪音点

def resize(image, width=None, height=None, inter=cv2.INTER_AREA):dim = None(h, w) = image.shape[:2]if width is None and height is None:return imageif width is None:r = height / float(h)dim = (int(w * r), height)else:r = width / float(w)dim = (width, int(h * r))resized = cv2.resize(image, dim, interpolation=inter)return resized
image = resize(orig, height = 500) # # 预处理
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) # 灰度处理
gray = cv2.GaussianBlur(gray, (5, 5), 0) # 高斯滤波器

3.3 结果展示

经过上述得到预处理后的图片,经过canny边缘检测。

# 展示预处理结果
edged = cv2.Canny(gray, 75, 200) # canny边缘检测,得到边缘print("STEP 1: 边缘检测")
cv2.imshow("Image", image) #原始图像
cv2.imshow("Edged", edged) #边缘结果
cv2.waitKey(0)
cv2.destroyAllWindows()

image-20230809151054227

image-20230809150545141

现在我们得到边缘检测的结果,可以看到有很多个边缘,我们做文档扫描,需要的是最外面的结果,接下来我们来具体如何实现。

3.轮廓检测

我们先来思考一下最外面这个轮廓它有什么特点。

首先,它是最大的,因此,我们可以根据它的面积或者周长进行排序,这里我们对面积进行排序。然后我们要去找轮廓,这里我们遍历每一个轮廓,然后去计算轮廓的一个近似,因为直接算轮廓的时候不太好算,往往是一个不规则形状,我们做一个矩形近似,然后此时就只需要确定四个点就行。具体代码如下:

 # 轮廓检测
cnts = cv2.findContours(edged.copy(), cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)[1]
cnts = sorted(cnts, key = cv2.contourArea, reverse = True)[:5] #按照面积排序# 遍历轮廓
for c in cnts:# 计算轮廓近似peri = cv2.arcLength(c, True)# C表示输入的点集# epsilon表示从原始轮廓到近似轮廓的最大距离,它是一个准确度参数# True表示封闭的approx = cv2.approxPolyDP(c, 0.02 * peri, True) #轮廓近似# 4个点的时候就拿出来if len(approx) == 4:screenCnt = approxbreak# 展示结果
print("STEP 2: 获取轮廓")
cv2.drawContours(image, [screenCnt], -1, (0, 255, 0), 2)
cv2.imshow("Outline", image)
cv2.waitKey(0)
cv2.destroyAllWindows()

image-20230809152343021

4.透视变换

透视变换(Perspective Transformation),也称为投影变换,它可以用于纠正图像畸变、实现视角变换和图像合成等应用。借助透视变换,我们可以从不同视角获得准确的图像数据,并进行更精确的分析、处理和识别。

它的基本原理是基于相机的投影模型,通过处理图像中的四个控制点,将原始图像上的任意四边形区域映射到新的位置和形状上,我们需要得到四个输入坐标和四个输出坐标。通常情况下,透视变换会改变图像中的视角、缩放和旋转等属性。它有几个关键步骤如下:

  • 控制点选择:为了进行透视变换,我们需要选择原始图像中的四个控制点(例如四个角点),以定义目标区域的形状和位置。这些控制点应该在原始图像和目标图像之间有明确的对应关系,然后通过高度和宽度信息,我们计算出目标图像的四个控制点

  • 透视变换矩阵:通过使用控制点的坐标,可以计算出透视变换矩阵,透视变换矩阵是一个3x3的矩阵。它包含了图像变换所需的所有信息。这里需要输入坐标和输出坐标,然后利用cv2.getPerspectiveTransform函数获取变换矩阵。通过将透视变换矩阵应用于原始图像上的点,可以得到它们在目标图像中的对应位置。

接下来我们来看一下具体是如何实现的,首先我们定义了一个ordr_points()函数来获取坐标点,然后我们定义four_point_transform函数来实现透视变换。具体代码如下,

# 获取坐标点
def order_points(pts):# 一共4个坐标点rect = np.zeros((4, 2), dtype = "float32")# 按顺序找到对应坐标0123分别是 左上,右上,右下,左下# 计算左上,右下s = pts.sum(axis = 1)rect[0] = pts[np.argmin(s)]rect[2] = pts[np.argmax(s)]# 计算右上和左下diff = np.diff(pts, axis = 1)rect[1] = pts[np.argmin(diff)]rect[3] = pts[np.argmax(diff)]return rectdef four_point_transform(image, pts):# 获取输入坐标点rect = order_points(pts)(tl, tr, br, bl) = rect# 计算输入的w和h值widthA = np.sqrt(((br[0] - bl[0]) ** 2) + ((br[1] - bl[1]) ** 2))widthB = np.sqrt(((tr[0] - tl[0]) ** 2) + ((tr[1] - tl[1]) ** 2))maxWidth = max(int(widthA), int(widthB))heightA = np.sqrt(((tr[0] - br[0]) ** 2) + ((tr[1] - br[1]) ** 2))heightB = np.sqrt(((tl[0] - bl[0]) ** 2) + ((tl[1] - bl[1]) ** 2))maxHeight = max(int(heightA), int(heightB))# 变换后对应坐标位置dst = np.array([[0, 0],[maxWidth - 1, 0],[maxWidth - 1, maxHeight - 1],[0, maxHeight - 1]], dtype = "float32")# 计算变换矩阵M = cv2.getPerspectiveTransform(rect, dst) #通过输入和输出坐标,可以计算出M矩阵warped = cv2.warpPerspective(image, M, (maxWidth, maxHeight))# 返回变换后结果return warped

定义好上述函数之后,接下来看一下经过同时变换之后的结果,为了方便展示,我们再进行二值化处理

# 透视变换
warped = four_point_transform(orig, screenCnt.reshape(4, 2) * ratio) #这里乘ratio是为了恢复我们原始图像坐标# 二值处理
warped = cv2.cvtColor(warped, cv2.COLOR_BGR2GRAY)
ref = cv2.threshold(warped, 100, 255, cv2.THRESH_BINARY)[1]
cv2.imwrite('scan.jpg', ref)
# 展示结果
print("STEP 3: 变换")
cv2.imshow("Scanned", resize(ref, height = 650))
cv2.waitKey(0)
cv2.destroyAllWindows()

image-20230809155008797

可以看到我们现在就得到了扫描之后得到的结果,并且我们保存为scan.jpg操作

5.OCR识别

得到扫描后的文档之后,我们需要对其中的字符进行识别,这里我们要用到tesseract工具包,我们先来看一下如何安装相关环境。

5.1 tesseract安装

安装地址:https://digi.bib.uni-mannheim.de/tesseract/

image-20230809165235000

首先选择一个合适的版本进行安装就行,我这里选择最新的w64版本,如何安装时一直点击下一步就行,但是我们要记住安装的路径。

注意:我们需要进行环境变量配置

把刚刚安装的路径添加到环境变量中即可

image-20230809165515720

接下来我们希望在python中使用它,因此要下载对应的python工具包。

安装命令如下:pip install pytesseract

5.2 字符识别

我们刚刚已经得到了扫描后的图像,并保存为scan.jpg如下所示

image-20230809165828358

接下来我们希望把其中的文本字符全部提取出来,我们来看一下具体代码吧

from PIL import Image
import pytesseract
import cv2
import os# 读取图片
image = cv2.imread('scan.jpg')# 灰度处理
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)# 二值处理
gray = cv2.threshold(gray, 0, 255,cv2.THRESH_BINARY | cv2.THRESH_OTSU)[1]    filename = "{}.png".format(os.getpid())
cv2.imwrite(filename, gray)
# OCR识别,提取字符    
text = pytesseract.image_to_string(Image.open(filename))
print(text)
x * KK KK K KR KH KR RK KKWHOLE
FOODS
TM AR K CE T)WHOLE FOODS MARKET ~ WESTPORT, CT 06880
399 POST RD WEST - (203) 227-6858365 BACUN LS NP 499
$65 BACON LS NP 4.99365 BACON LS NP 4.99365 BACON LS NP 4.99
BROTH CHTC NP 2.19FLOUR ALMUND NP 11.99CHKN BRST BNLSS SK NP 18.80
HEAVY CREAM NP 3.39BALSMC REDUCT NP 6.49BEEF GRND 85/15 NP 5.04
JUICE COF CASHEW L NP = 8.99
DOCS PINT ORGANIC NF 14.49
HNY ALMOND BUTTER NP 9.99
xeee TAX = 00 9 BAL 101.33TTA AATDA ABH HH oy

对比一下可以看到识别的字符都比较准确。

🔎本章的介绍到此介绍,如果文章对你有帮助,请多多点赞、收藏、评论、订阅支持!!《Opencv入门到项目实战》

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

桂林小程序https证书

现在很多APP都相继推出了小程序,比如微信小程序、百度小程序等,这些小程序的功能也越来越复杂,不可避免的和网站一样会传输数据,因此小程序想要上线就要保证信息传输的安全性,也就是说各种类型的小程序也需要部署https…

怎么用PS的魔术棒抠图?PS魔术棒抠图的操作方法

使用PS的魔术棒抠图教程: 1、首先,在ps界面上方点击“文件”选项,再在其弹出的选项栏中选择“打开”选项。然后,打开你所需要的图片。 2、然后,单击左侧的“魔术棒”工具。 3、然后,用鼠标点击图片的背景&…

【C++11】类的新功能 | 可变参数模板

文章目录 一.类的新功能1.默认成员函数2.类成员变量初始化3.强制生成默认函数的关键字default4.禁止生成默认函数的关键字delete5.继承和多态中final与override关键字 二.可变参数模板1.可变参数模板的概念2.可变参数模板的定义方式3.参数包的展开方式①递归展开参数包②逗号表…

Vue过滤器(时间戳转时间)

目录 过滤器 HTML写法: 定义过滤器: 定义全局过滤器: 过滤器串联: 带参数过滤器: 时间戳转时间 过滤器 官方地址:过滤器 — Vue.js (vuejs.org) 过滤器是指Vue.js支持在{{}}插值的尾部添加一个管道符“&#xff0…

vue3 + vite + ts 封装 SvgIcon组件

环境 vite vue3 ts "vue": "^3.3.4", "vite": "^4.4.0", "typescript": "^5.0.2",# 需要下载的依赖 "vite-plugin-svg-icons": "^2.0.1",不同版本可能存在一定差异, 这篇文章不可能对应所…

通达OA SQL注入漏洞【CVE-2023-4166】

通达OA SQL注入漏洞【CVE-2023-4166】 一、产品简介二、漏洞概述三、影响范围四、复现环境POC小龙POC检测工具: 五、修复建议 免责声明:请勿利用文章内的相关技术从事非法测试,由于传播、利用此文所提供的信息或者工具而造成的任何直接或者间接的后果及损…

宋浩线性代数笔记(五)矩阵的对角化

本章的知识点难度和重要程度都是线代中当之无愧的T0级,对于各种杂碎的知识点,多做题复盘才能良好的掌握,良好掌握的关键点在于:所谓的性质A与性质B,是谁推导得谁~

ApiPost的使用

1. 设计接口 请求参数的介绍 Query:相当于get请求,写的参数在地址栏中可以看到 Body: 相当于 post请求,请求参数不在地址栏中显示。 请求表单类型,用form-data json文件类型,用row 2. 预期响应期望 设置完每一项点一下生成响应…

设计实现数据库表扩展的7种方式

设计实现数据库表扩展的7种方式 在软件开发过程中,数据库是一项关键技术,用于存储、管理和检索数据。数据库表设计是构建健壮数据库系统的核心环节之一。然而,随着业务需求的不断演变和扩展,数据库表中的字段扩展变得至关重要。 …

neo4j的CQL命令实例演示

天行健,君子以自强不息;地势坤,君子以厚德载物。 每个人都有惰性,但不断学习是好好生活的根本,共勉! 文章均为学习整理笔记,分享记录为主,如有错误请指正,共同学习进步。…

92. 反转链表 II

92. 反转链表 II 题目-中等难度示例1. 获取头 反转中间 获取尾 -> 拼接2. 链表转换列表 -> 计算 -> 转换回链表 题目-中等难度 给你单链表的头指针 head 和两个整数 left 和 right &#xff0c;其中 left < right 。请你反转从位置 left 到位置 right 的链表节点…

elk开启组件监控

elk开启组件监控 效果&#xff1a; logstash配置 /etc/logstash/logstash.yml rootnode1:~# grep -Ev "^#|^$" /etc/logstash/logstash.yml path.data: /var/lib/logstash path.logs: /var/log/logstash xpack.monitoring.enabled: true xpack.monitoring.elasti…

深兰科技熊猫汽车牵手首恒出行,人工智能技术提升商用车运营服务

8月8日&#xff0c;深兰科技集团旗下熊猫新能源汽车(上海)有限公司(下称熊猫新能源汽车)与河南首恒出行服务有限公司(下称首恒出行)在深兰科技总部举行签约仪式&#xff0c;首恒出行将向熊猫新能源汽车年定向采购10000台商用车&#xff0c;双方将在汽车后市场领域进行技术合作。…

【ElasticSearch入门】

目录 1.ElasticSearch的简介 2.用数据库实现搜素的功能 3.ES的核心概念 3.1 NRT(Near Realtime)近实时 3.2 cluster集群&#xff0c;ES是一个分布式的系统 3.3 Node节点&#xff0c;就是集群中的一台服务器 3.4 index 索引&#xff08;索引库&#xff09; 3.5 type类型 3.6 doc…

Linux学习————redis服务

目录 一、redis主从服务 一、redis主从服务概念 二、redis主从服务作用 三、缺点 四、主从复制流程 五、搭建主从服务 配置基础环境 下载epel源&#xff0c;下载redis​编辑 二、哨兵模式 一、概念 二、作用 三、缺点 四、结构 五、搭建 修改哨兵配置文件 启动服务…

IntelliJ中文乱码问题

1、控制台乱码 运行时控制台输出的中文为乱码&#xff0c;解决方法&#xff1a;帮助 > 编辑自定义虚拟机选项… > 此时会自动创建出一个新文件&#xff0c;输入&#xff1a;-Dfile.encodingUTF-8&#xff0c;然后重启IDE即可&#xff0c;操作截图如下&#xff1a; 2、…

SAP从入门到放弃系列之BOM组-Part1

目录 BOM组两种模式&#xff1a; 创建BOM的方式 方式一&#xff1a;直接在每个工厂分别创建BOM。 方式二&#xff1a;创建BOM组&#xff0c;然后每个工厂参考创建 方式三&#xff1a;创建BOM组&#xff0c;每个工厂参考创建&#xff0c;针对有特殊的工厂复制BOM组后进行调…

c++中的继承

文章目录 1.继承的概念及定义1.1继承的概念1.2继承的定义1.2.1定义格式1.2.2继承关系和访问限定符1.2.3继承基类成员访问方式的变化 2.基类和派生类对象赋值转换3.继承中的作用域4.派生类的默认成员函数5.继承与友元6.继承与静态成员7.复杂的菱形继承及菱形虚拟继承 1.继承的概…

AI Deep Reinforcement Learning Autonomous Driving(深度强化学习自动驾驶)

AI Deep Reinforcement Learning Autonomous Driving&#xff08;深度强化学习自动驾驶&#xff09; 背景介绍研究背景研究目的及意义项目设计内容算法介绍马尔可夫链及马尔可夫决策过程强化学习神经网络 仿真平台OpenAI gymTorcs配置GTA5 参数选择行动空间奖励函数 环境及软件…

途乐证券-最准确的KDJ改良指标?

KDJ目标是技术剖析的一种重要目标之一&#xff0c;它是利用随机目标&#xff08;%R&#xff09;发展而来的&#xff0c;是一种反映商场超买和超卖状况的买卖目标。KDJ目标由快线&#xff08;K线&#xff09;、慢线&#xff08;D线&#xff09;和随机值&#xff08;J线&#xff…