【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 image
	if 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 = approx
		break

# 展示结果
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 rect

def 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 KK

WHOLE
FOODS
TM AR K CE T)

WHOLE FOODS MARKET ~ WESTPORT, CT 06880
399 POST RD WEST - (203) 227-6858

365 BACUN LS NP 499
$65 BACON LS NP 4.99

365 BACON LS NP 4.99

365 BACON LS NP 4.99
BROTH CHTC NP 2.19

FLOUR ALMUND NP 11.99

CHKN BRST BNLSS SK NP 18.80
HEAVY CREAM NP 3.39

BALSMC REDUCT NP 6.49

BEEF 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.33

TTA AATDA ABH HH oy

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

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

aardio 调用 python pickle load 数据

aardio 调用 python pickle load 词典数据 dump_pickle.py import os import sys import time import pickle from readmdict import MDX, MDDos.chdir("/mdict")mdxfile "your.mdx" if not os.path.exists(mdxfile):print("{mdxfile} not found!&…

2023年大数据与计算国际会议 (WBDC 2023)| EI、Scoups检索

会议简介 Brief Introduction 2023年大数据与计算国际会议(WBDC 2023) 会议时间:2023年11月17 -19日 召开地点:中国西安 大会官网:www.iwbdc.org 2023年大数据与计算国际会议(WBDC 2023)将围绕“…

docker下载和案例

文章目录 Docker安装一,根据官方文档安装二,根据我以下方式 Docker配置错误导致漏洞一,CRLF注入漏洞介绍在nginx中该漏洞例子解决方法 目录穿越漏洞介绍解决方法 Docker安装 一,根据官方文档安装 官方文档 二,根据我以下方式 docker安装要求: Docker要求Ce…

VTK学习笔记(二十一)

显示立方体已经做过了&#xff0c;现在想简单处理一下立方体&#xff0c;比如每个面上增加一些文字。效果如下。 #include <vtkAnnotatedCubeActor.h> #include <vtkCamera.h> #include <vtkNamedColors.h> #include <vtkNew.h> #include <vtkPrope…

SQL 数据科学:了解和利用联接

推荐&#xff1a;使用 NSDT场景编辑器助你快速搭建可编辑的3D应用场景 什么是 SQL 中的连接&#xff1f; SQL 联接允许您基于公共列合并来自多个数据库表的数据。这样&#xff0c;您就可以将信息合并在一起&#xff0c;并在相关数据集之间创建有意义的连接。 SQL 中的连接类型…

积木报表集成前端加载js文件404

项目场景&#xff1a; 在集成积木报表和shiro时候&#xff1a; 集成积木报表&#xff0c;shrio&#xff0c;shrio是定义在另一个模块下的&#xff0c;供另一个启动类使用&#xff0c;积木报表集成shrio的时候&#xff0c;需要依赖存放shrio的核心包&#xff0c;该核心包除了存…

Eleastisearch5.2.2利用镜像迁移构建实例后ES非健康状态

正常迁移完成后启动服务&#xff0c;查看ES非健康状态 此时观察ES集群状态&#xff1a;curl -XGET -u elastic:xxx localhost:9200/_cluster/health?pretty 注意到"active_shards_percent_as_number" : 88.8888 该项的值不产生变化;集群状态"status" : “…

常见的数据结构(顺序表、顺序表、链表、栈、队列、二叉树)

线性表&#xff08;Linear List&#xff09;  1.什么是线性表 2.线性表的特点 3.线性表的基本运算 顺序表 1.什么是顺序表 2.时间复杂度&#xff1a; 链表 1.什么是链表 2.单向链表 3. 双向链表 4.ArrayList和LinkedList的使用 栈Stack  1.什么是栈  2.栈的基本方法 队列…

【果树农药喷洒机器人】Part2:机器人变量喷药系统硬件选型

&#x1f4e2;&#xff1a;如果你也对机器人、人工智能感兴趣&#xff0c;看来我们志同道合✨ &#x1f4e2;&#xff1a;不妨浏览一下我的博客主页【https://blog.csdn.net/weixin_51244852】 &#x1f4e2;&#xff1a;文章若有幸对你有帮助&#xff0c;可点赞 &#x1f44d;…

【ChatGPT 指令大全】怎么使用ChatGPT辅助程式开发

目录 写程式 解读程式码 重构程式码 解 bug 写测试 写 Regex 总结 在当今快节奏的数字化世界中&#xff0c;程式开发变得越来越重要和普遍。无论是开发应用程序、网站还是其他软件&#xff0c;程式开发的需求都在不断增长。然而&#xff0c;有时候我们可能会遇到各种问题…

C语言数据类型

C语言具有多种数据类型&#xff0c;用于存储不同类型的数据。大体可以分为基本数据类型和派生数据类型两大类。 一、基本数据类型 整型&#xff08;Integer&#xff09;&#xff1a; 用于表示整数值&#xff0c;包括int、short、long和long long等类型&#xff0c;可以有不同…

AI:03-基于深度神经网络的低空无人机目标检测图像识别的研究

文章目录 数据集收集与预处理深度神经网络模型设计模型训练与优化目标检测与图像识别代码实现:实验结果与分析讨论与展望低空无人机的广泛应用为许多领域带来了巨大的潜力和机会。为了实现无人机的自主导航和任务执行,准确的目标检测和图像识别是至关重要的。本文旨在研究并提…

软件外包开发的VUE开发框架

Vue.js&#xff08;通常简称为Vue&#xff09;是一个流行的渐进式JavaScript框架&#xff0c;用于构建用户界面。它的核心库专注于视图层&#xff0c;但也可以结合其他库和工具来构建完整的单页面应用&#xff08;SPA&#xff09;。以下分享Vue.js的开发框架和特点&#xff0c;…

剑指offer-1.1C++

sizeof空类问题 问&#xff1a;定义一个空的类型&#xff0c;里面没有任何成员变量和成员函数。对该类型求sizeof&#xff0c;得到的结果是多少&#xff1f; 答&#xff1a;1 问&#xff1a;为什么不是0&#xff1f; 答&#xff1a;空类型的实例中不包含任何信息&#xff0…

小说推文怎么做详细教程。小说推文项目拆解及分享

科思创业汇 大家好&#xff0c;这里是科思创业汇&#xff0c;一个轻资产创业孵化平台。赚钱的方式有很多种&#xff0c;我希望在科思创业汇能够给你带来最快乐的那一种&#xff01; 如何制作小说推文的详细教程&#xff01; 我做自媒体已经五年了&#xff0c;在自媒体行业也…

STM32CubeMX之freeRTOS消息队列

创建一个消息队列&#xff0c;两个发送任务&#xff0c;一个接受任务 发送任务一&#xff1a;等待时间为0 发送任务二&#xff1a;等待时间为最大 接受为0 简单来说就是&#xff1a; 任务一&#xff1a;一个普写 一个死写 一个普读 任务二&#xff1a;创造队列 一个普写 …

什么是React?React与VU的优缺点有哪些?

什么是React&#xff1f;什么是VUE&#xff1f; 维基百科上的概念解释&#xff0c;Vue.js是一个用于创建用户界面的开源MVVM前端JavaScript框架&#xff0c;也是一个创建单页应用的Web应用框架。Vue.js由尤雨溪&#xff08;Evan You&#xff09;创建&#xff0c;由他和其他活跃…

Scractch3.0_Arduino_ESP32_学习随记_IO中断(六)

IO中断 目的器材程序联系我们 目的 ESP32 IO中断的使用。 中断&#xff1a; 当IO中断事件发生时&#xff0c;MCU将优先执行中断的程序。 打个比方&#xff1a; 你正在读一本书&#xff0c;突然手机收到一条紧急消息。你不想错过这个重要的消息&#xff0c;所以你立即停下手中的…

MySQL高阶知识点

MySQL 文章目录 MySQLchar和varchar的区别视图视图的特点 存储过程存储过程的特点 触发器触发器的特点 MySQL引擎索引为什么要有索引呢&#xff1f;什么是索引索引的优势索引的劣势索引类型索引种类组合索引最左前缀原则索引创建原则B树和B树的区别数据库为什么使用B树而不是B树…

伪类和伪元素有何区别?

聚沙成塔每天进步一点点 ⭐ 专栏简介⭐ 伪类&#xff08;Pseudo-class&#xff09;⭐ 伪元素&#xff08;Pseudo-element&#xff09;⭐ 区别总结⭐ 写在最后 ⭐ 专栏简介 前端入门之旅&#xff1a;探索Web开发的奇妙世界 记得点击上方或者右侧链接订阅本专栏哦 几何带你启航前…