跳至主要内容

使用 Pytesseract 的 Python 光学字符识别(OCR)全指南

使用 PyTesseract 和 OpenCV 掌握 OCR 光学字符识别的基础知识。
· 11分钟 阅读

使用 AI 探索

为什么要学习光学字符识别(OCR)?

手动从图像和文档中提取文本既繁琐又耗时。幸运的是,OCR(光学字符识别)可以自动化这一过程,帮助您将这些图像转换为可编辑、可搜索的文本文件。

您即将学习的技术可用于许多场景:

  • 将纸质文档数字化:将扫描件、PDF 或包含文本的照片转换为可编辑、可搜索的文件。
  • 自动化数据录入:从表单、发票、收据等文档中提取信息,自动填充数据库或电子表格。
  • 提升无障碍体验:为视障人群创建书籍、菜单或标牌的数字文本版本,随后可配合文本转语音工具朗读。
  • 自动驾驶:识别交通标志和车牌以实现安全导航。

本教程将聚焦 Tesseract OCR 引擎及其 Python API——PyTesseract。在开始写代码之前,让我们简要回顾一些常见的 OCR 库。

Python 中顶级开源 OCR 库

由于 OCR 长期以来都是热门课题,许多开源库都在尝试解决它。本节将介绍那些因性能和准确性而最受欢迎的库。

Tesseract

Tesseract OCR 是最受开发者欢迎的开源光学字符识别引擎。与本列表中的其他工具类似,Tesseract 可以将文本图像转换为可编辑文本。

优点

  • 应用广泛、成熟稳定,拥有庞大社区
  • 支持 100 多种语言
  • 免费且开源

缺点

  • 与部分基于深度学习的方案相比,准确率可能略低
  • 可配置选项有限

Easy OCR

EasyOCR 是一个为“轻松”进行光学字符识别而设计的 Python 库。名副其实,提供了对图像文本提取的友好上手方式。

优点

  • 用户友好,易于上手
  • 基于深度学习模型,准确率较高
  • 开箱即用地支持多种语言

缺点

  • 依赖预训练模型,体积可能较大
  • 对于简单任务,速度可能不如 Tesseract

Keras OCR

Keras-OCR 是基于 Keras(流行的深度学习框架)构建的 Python 库,提供现成的 OCR 模型和端到端的训练流程,便于构建新的 OCR 模型。

优点

  • 基于深度学习,对多种文本类型具有较高准确率
  • 可自定义模型选择与训练
  • 在复杂版式或手写文本上可能更准确

缺点

  • 为获得最佳性能需要 GPU
  • 由于采用深度学习,学习曲线更陡峭
  • 自定义训练模型耗时

下面的表格总结了它们的差异、优缺点:

软件包名称

优点

缺点

Tesseract (pytesseract)

成熟、广泛使用、支持完善

较慢,在复杂版式上的准确率较低

EasyOCR

易用、提供多种模型

准确率较低,自定义受限

Keras-OCR

准确率更高、可定制

需要 GPU,学习曲线更陡

在本教程中,我们将专注于 PyTesseract,也就是 Tesseract 的 Python API。我们将学习如何从简单图像中提取文本、如何为文本绘制边界框,并通过一个扫描文档的案例研究来实践。

使用 PyTesseract 和 OpenCV 进行 OCR 的分步指南

安装

PyTesseract 运行在官方 Tesseract 引擎之上,它是一个单独的命令行软件。在安装 pytesseract 之前,您必须先安装该引擎。以下是不同平台的安装说明。

适用于 Ubuntu 或 WSL2(我选择的环境):

$ sudo apt update && sudo apt upgrade
$ sudo apt install tesseract-ocr
$ sudo apt install libtesseract-dev

在 Mac 上使用 Homebrew:

$ brew install tesseract

在 Windows 上,请按照 此 GitHub 页面的说明进行操作。

接下来,创建一个新的虚拟环境。我将使用 Conda:

$ conda create -n ocr python==3.9 -y
$ conda activate ocr

然后,安装用于 OCR 的 pytesseract 和用于图像处理的 opencv:

$ pip install pytesseract
$ pip install opencv-python

如果您在 Jupyter 中跟随本教程,请在同一终端会话中运行以下命令,以便将新虚拟环境添加为内核:

$ pip install ipykernel
$ ipython kernel install --user --name=ocr

现在,我们可以开始写代码了。

基础用法

首先导入必要的库:

import cv2
import pytesseract

我们的任务是读取下图中的文本:

使用 PyTesseract 和 OpenCV 执行 OCR 的示例图像

首先,定义图像路径并传给 cv2.imread 函数:

# Read image
easy_text_path = "images/easy_text.png"
easy_img = cv2.imread(easy_text_path)

然后,将加载的图像传给 pytesseract 的 image_to_string 函数提取文本:

# Convert to text
text = pytesseract.image_to_string(easy_img)
print(text)
This text is
easy to extract.

就是这么简单!让我们把刚才的步骤封装成一个函数:

def image_to_text(input_path):
   """
   A function to read text from images.
   """
   img = cv2.imread(input_path)
   text = pytesseract.image_to_string(img)

   return text.strip()

将该函数用于一张更难的图像:

使用 PyTesseract 和 OpenCV 执行 OCR 的另一张示例图像

这张图更具挑战性,因为包含更多标点和不同字体的文本。

# Define image path
medium_text_path = "images/medium_text.png"

# Extract text
extracted_text = image_to_text(medium_text_path)
print(extracted_text)
Home > Tutorials » Data Engineering

Snowflake Tutorial For Beginners:
From Architecture to Running
Databases

Learn the fundamentals of cloud data warehouse management using
Snowflake. Snowflake is a cloud-based platform that offers significant
benefits for companies wanting to extract as much insight from their data as
quickly and efficiently as possible.

Jan 2024 - 12 min read

我们的函数几乎完美。它把一个点和“>”符号弄混了,但总体结果可以接受。

为文本绘制边界框

在 OCR 中,一个常见操作是为文本绘制边界框。PyTesseract 支持该操作。

首先,将加载的图像传给 image_to_data 函数:

from pytesseract import Output

# Extract recognized data from easy text
data = pytesseract.image_to_data(easy_img, output_type=Output.DICT)

Output.DICT 确保以字典形式返回图像详情。来看一下:

data
{'level': [1, 2, 3, 4, 5, 5, 5, 4, 5, 5, 5],
'page_num': [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
'block_num': [0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
'par_num': [0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1],
'line_num': [0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2],
'word_num': [0, 0, 0, 0, 1, 2, 3, 0, 1, 2, 3],
'left': [0, 41, 41, 236, 236, 734, 1242, 41, 41, 534, 841],
'top': [0, 68, 68, 68, 68, 80, 68, 284, 309, 284, 284],
'width': [1658, 1550, 1550, 1179, 380, 383, 173, 1550, 381, 184, 750],
'height': [469, 371, 371, 128, 128, 116, 128, 155, 130, 117, 117],
'conf': [-1, -1, -1, -1, 96, 95, 95, -1, 96, 96, 96],
'text': ['', '', '', '', 'This', 'text', 'is', '', 'easy', 'to', 'extract.']}

该字典包含了大量图像信息。首先注意 conf 和 text 键。它们的长度都是 11:

len(data["text"])
11

这意味着 pytesseract 绘制了 11 个框。conf 表示置信度。如果等于 -1,则对应的框是围绕文本块而非单词绘制的。

例如,如果您查看前四个 width 和 height 值,会发现它们相比其他值更大,因为这些框分别围绕中间的整段文本、文本的每一行以及整个图像绘制。

此外:

  • left 表示边界框左上角到图像左边界的距离。
  • top 表示边界框左上角到图像上边界的距离。
  • width 和 height 表示边界框的宽度和高度。

利用这些信息,让我们在 OpenCV 中把边框画在图像上。

首先,重新提取数据并获取其长度:

from pytesseract import Output

# Extract recognized data
data = pytesseract.image_to_data(easy_img, output_type=Output.DICT)
n_boxes = len(data["text"])

然后,按找到的框数量创建循环:

for i in range(n_boxes):
   if data["conf"][i] == -1:
       continue

在循环中,如果 conf 等于 -1 则跳过当前迭代。跳过较大的边界框能使图像更清爽。

接着,定义当前框的坐标,即左上角和右下角的位置:

for i in range(n_boxes):
   if data["conf"][i] == -1:
       continue
   # Coordinates
   x, y = data["left"][i], data["top"][i]
   w, h = data["width"][i], data["height"][i]

   # Corners
   top_left = (x, y)
   bottom_right = (x + w, y + h)

在定义了一些参数(如颜色和像素厚度)后,将所有信息传给 cv2.rectangle 函数:

for i in range(n_boxes):
   if data["conf"][i] == -1:
       continue
   # Coordinates
   x, y = data["left"][i], data["top"][i]
   w, h = data["width"][i], data["height"][i]

   # Corners
   top_left = (x, y)
   bottom_right = (x + w, y + h)

   # Box params
   green = (0, 255, 0)
   thickness = 3  # pixels

   cv2.rectangle(
       img=easy_img, pt1=top_left, pt2=bottom_right, color=green, thickness=thickness
   )

该函数会在原图上绘制边框。让我们保存并查看图像:

# Save the image
output_image_path = "images/text_with_boxes.jpg"
cv2.imwrite(output_image_path, easy_img)
True

为每个单词绘制边界框的示例图像。使用 PyTesseract 和 OpenCV 实现。

结果正合我们预期!

现在,再次把上述步骤封装成函数:

def draw_bounding_boxes(input_img_path, output_path):
   img = cv2.imread(input_img_path)

   # Extract data
   data = pytesseract.image_to_data(img, output_type=Output.DICT)
   n_boxes = len(data["text"])

   for i in range(n_boxes):
       if data["conf"][i] == -1:
           continue
       # Coordinates
       x, y = data["left"][i], data["top"][i]
       w, h = data["width"][i], data["height"][i]

       # Corners
       top_left = (x, y)
       bottom_right = (x + w, y + h)

       # Box params
       green = (0, 255, 0)
       thickness = 1  # The function-version uses thinner lines

       cv2.rectangle(img, top_left, bottom_right, green, thickness)

   # Save the image with boxes
   cv2.imwrite(output_path, img)

将该函数用于中等难度文本:

output_path = "images/medium_text_with_boxes.png"

draw_bounding_boxes(medium_text_path, output_path)

为每个单词绘制边界框的另一张示例图像。使用 PyTesseract 和 OpenCV 实现。

即便是更难的图像,效果也非常好!

案例研究:使用 Python 对 PDF 文件进行 OCR

让我们对一个示例扫描版 PDF 文件做个案例研究。在实际应用中,您很可能会处理扫描版 PDF 而不是图像,比如下面这个:

将使用 PyTesseract 执行 OCR 的扫描文档示例。

您可以从 我的 GitHub 这个页面下载该 PDF。

下一步是安装 pdf2image 库,它依赖名为 Poppler 的 PDF 处理软件。以下是不同平台的安装说明:

Mac:

$ brew install poppler
$ pip install pdf2image

Linux 和 WSL2:

$ sudo apt-get install -y poppler-utils
$ pip install pdf2image

Windows 上,您可以按照 PDF2Image 文档中的说明进行安装。

安装完成后,导入相关模块:

import pathlib
from pathlib import Path

from pdf2image import convert_from_path

convert_from_path 函数可以将给定 PDF 转换为一系列图像。下面的函数会将 PDF 文件的每一页保存为图像至指定目录:

def pdf_to_image(pdf_path, output_folder: str = "."):
   """
   A function to convert PDF files to images
   """
   # Create the output folder if it doesn't exist
   if not Path(output_folder).exists():
       Path(output_folder).mkdir()

   pages = convert_from_path(pdf_path, output_folder=output_folder, fmt="png")

   return pages

在我们的文档上运行它:

pdf_path = "scanned_document.pdf"

pdf_to_image(pdf_path, output_folder="documents")
[<PIL.PngImagePlugin.PngImageFile image mode=RGB size=1662x2341>]

输出是包含单个图像 PngImageFile 对象的列表。来看一下 documents 目录:

$ ls documents
2d8f6922-99c4-4ef4-a475-ef81effe65a3-1.png

图像已生成,所以将其传给我们一开始编写的 image_to_text 函数,并打印提取文本的前几百个字符:

scanned_img_path = "documents/2d8f6922-99c4-4ef4-a475-ef81effe65a3-1.png"

print(image_to_text(scanned_img_path)[:377])
PEU Business report

New customer's development
and increasing the sale of product

My country economy at this season keeps escaping from Odoba of business though holds a crude oil
high so on unstable element that continues still, and recovering gradually and well.
In the IT industry, there is an influence such as competing intensification in narrowing investment field.

与原文件对比可见,一切运行良好——格式与空格得以保留,文本也准确。那么,如何分享提取的文本呢?

分享从 PDF 提取的文本,最佳格式还是另一个 PDF!PyTesseract 提供 image_to_pdf_or_hocr 函数,可将含文本的任意图像转换为原始的、可全文检索的 PDF 文件。让我们对扫描图像使用它:

raw_pdf = pytesseract.image_to_pdf_or_hocr(scanned_img_path)

with open("searchable_pdf.pdf", "w+b") as f:
   f.write(bytearray(raw_pdf))

下面是 searchable_pdf 的效果:

展示对扫描 PDF 文档执行 OCR 后效果的 GIF。

如您所见,我可以在文件中高亮并复制文本,而且原 PDF 的各个元素都被保留。

OpenCV 中的 OCR 图像预处理技术

OCR 并不存在一招通吃的方法。我们今天介绍的技术,未必适用于其他类型的图像。建议您针对具体图像,尝试不同的图像预处理技术和 Tesseract 配置,以找到最佳设置。

图像质量是影响 OCR 的最重要因素。正确扫描、垂直对齐、以及高对比度(黑白)图像通常在任何 OCR 软件中表现最佳。请记住:您能看懂的文字,计算机未必能。

如果您的图像未达到 Tesseract 的高质量要求、输出出现乱码,可以尝试以下预处理步骤。

1. 灰度化

首先,将彩色图像转换为灰度图。这样可以去除可能干扰识别过程的色彩差异,从而提升准确率。在 OpenCV 中,代码如下:

def grayscale(image):
   """Converts an image to grayscale.

   Args:
       image: The input image in BGR format.

   Returns:
       The grayscale image.
   """
   return cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)

2. 降噪

并非所有图像(尤其是扫描文档)都具备干净、统一的背景。有些图像还可能来自纸张老化的旧文档。如下示例:

用于执行 OCR 的噪声示例图像。

可以应用去噪滤波(如中值模糊)等技术,以降低图像中的噪声伪影,避免 OCR 过程中的误判。在 OpenCV 中,您可以使用 medianBlur 函数:

def denoise(image):
   """Reduces noise in the image using a median blur filter.

   Args:
       image: The input grayscale image.

   Returns:
       The denoised image.
   """
   return cv2.medianBlur(image, 5)  # Adjust kernel size as needed

3. 锐化

在某些情况下,图像锐化可以增强边缘,从而提升字符识别效果,尤其适用于模糊或低分辨率图像。在 OpenCV 中可通过应用拉普拉斯滤波实现锐化:

def sharpen(image):
   """Sharpens the image using a Laplacian filter.

   Args:
       image: The input grayscale image.

   Returns:
       The sharpened image (be cautious with sharpening).
   """
   kernel = np.array([[0, -1, 0], [-1, 5, -1], [0, -1, 0]])
   return cv2.filter2D(image, -1, kernel)

4. 二值化

对于某些图像,二值化(将图像转为黑白)可能更有利。请尝试不同的阈值分割方法,以找到前景(文本)与背景的最佳分离。

不过,二值化对光照变化较为敏感,也不一定总是必要。下面是二值化图像的示例:

示例图像:左为原图,右为在 OpenCV 中的二值化版本。

在 OpenCV 中进行二值化,可以使用 adaptiveThreshold 函数:

def binarize(image):
   """Binarizes the image using adaptive thresholding.

   Args:
       image: The input grayscale image.

   Returns:
       The binary image.
   """
   thresh = cv2.adaptiveThreshold(
       image, 255, cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY, 11, 2
   )

   return thresh

5. 其他技术

还有许多其他预处理技术,例如:

  • 膨胀(Dilation):使较小、较细的字体更粗,以提升识别效果
  • 腐蚀(Erosion):对较粗的文本进行腐蚀以提升准确率。常见于字体较厚的历史文档。
  • Canny 边缘检测
  • 倾斜校正:校正文本行的倾斜(偏斜)。常见于扫描不当的文档

您可以在 Tesseract 文档的此页面了解更多图像质量改进方法。

结语

通过本文,您已迈出了了解 OCR 这一动态问题的第一步。我们先介绍了如何从简单图像中提取文本,然后转向格式更复杂的图像。

我们还学习了一个端到端流程,用于从扫描 PDF 中提取文本,并将提取的文本再次保存为可搜索的 PDF。最后给出了一些在将图像交给 Tesseract 之前,使用 OpenCV 提升图像质量的建议。

如果您想进一步学习图像相关问题的解决方案,以下是一些计算机视觉资源,供您参考:

Bexruz (Bex) Tuychiev

作者

Bexruz (Bex) Tuychiev

我是一名数据科学内容创作者,拥有超过 2 年的经验,并在 Medium 上拥有较大的读者群。我喜欢用略带讽刺的笔调撰写有关 AI 和机器学习的深度文章——毕竟总得想点办法让这些内容不那么枯燥。我已发表 130 多篇文章,并制作了一门 DataCamp 课程,另有一门正在筹备中。我的内容已被超过 500 万人次阅读,其中有 2 万人在 Medium 和 LinkedIn 上成为关注者。 

Python OCR 常见问题

我为什么这么高?

遗传因素。

主题