【第二十七周】OCR学习02

文章目录

  • 摘要
  • Abstract
  • 一、发票关键字提取系统
    • [1. 系统核心功能](#1. 系统核心功能)
    • [2. 核心功能模块分析](#2. 核心功能模块分析)
      • [2.1 PDF Converter](#2.1 PDF Converter)
      • [2.2 Preprocessing](#2.2 Preprocessing)
      • [2.3 OCR Engine](#2.3 OCR Engine)
      • [2.4 Extractor](#2.4 Extractor)
      • [2.5 Visualizer](#2.5 Visualizer)
    • [3. main.py - 批量处理脚本](#3. main.py - 批量处理脚本)
      • [3.1 主要作用](#3.1 主要作用)
      • [3.2 准备工作](#3.2 准备工作)
      • [3.3 核心步骤](#3.3 核心步骤)
  • 总结

摘要

本周主要完成Github上发票关键字提取系统的学习,了解系统的核心功能,并对相应模块做出解读。


Abstract

This week, I mainly completed the learning of the invoice keyword extraction system on GitHub. I understood the core functions of the system and interpreted its corresponding modules in detail.


一、发票关键字提取系统

源项目地址:【invoice-ocr

1. 系统核心功能

系统主要功能:

从发票PDF中提取关键信息(发票号、日期、总金额等)

核心工作:

  • PDF Converter: 将pdf转为图片
  • Preprocessing:图像预处理 灰度化,阈值化(二值化实现分割背景)
  • OCR Engine: Tesseract OCR 提取文本和文本框信息
  • Extractor: 使用正则表达式和规则从原始文本中识别发票字段
  • Visualizer: 绘制文本框在文本边缘
  • Streamlit UI: 加载pdf以及转换后的结果

问题1:为什么要将pdf转为图片?

1,前提:原始数据pdf并不知"文本数据",对于计算机而言pdf为图像数据

2,OCR处理的对象就是"视觉信息"而非"文本信息"

问题2:为什么pdf作为图像数据,项目中还需要将pdf转为图片呢?

1,pdf存在两种类型:图像类和文本类

2,OCR引擎(Tesseract)只能处理图像

3,PDF不是像素矩阵,而是:矢量图形指令(画线、填充、文本渲染),压缩的图像数据(JPEG/PNG嵌入),字体和布局信息

问题2:为什么需要使用正则化?

正则化作用:防止过拟合,添加约束,数据增强,

OCR中批归一化也是一种正则化;有正则化的权重小而集中

问题3:Tesseract的具体任务?

2. 核心功能模块分析

2.1 PDF Converter

powershell 复制代码
import os
from pdf2image import convert_from_path

def convert_pdfs_to_images(pdf_dir, image_dir, dpi=300, poppler_path=None):
    os.makedirs(image_dir, exist_ok=True)
    #read the pdf file list from pdf directory
    pdf_files = [f for f in os.listdir(pdf_dir) if f.lower().endswith(".pdf")]

    all_image_paths = []


    for pdf_file in pdf_files:
        pdf_path = os.path.join(pdf_dir, pdf_file)
        images = convert_from_path(pdf_path, dpi=dpi, poppler_path=poppler_path)

        base_name = os.path.splitext(pdf_file)[0]
        for i, img in enumerate(images):
            output_filename = f"{base_name}_page{i+1}.jpg"
            output_path = os.path.join(image_dir, output_filename)
            img.save(output_path, "JPEG")
            all_image_paths.append(output_path)

    return all_image_paths

1,images = convert_from_path(pdf_path, dpi=dpi, poppler_path=poppler_path)

作用:将一个pdf文档转为一张张图片

说明:

convert_from_path():pdf2image库的核心函数

pdf_path:要转换的PDF文件路径

dpi:设置输出图像的分辨率(默认300)

poppler_path:指定poppler工具路径。Poppler是开源PDF渲染库,用于解析和渲染PDF文件。

返回值:images是一个PIL.Image对象的列表,每个元素对应PDF的一页

技术细节:实际调用了Poppler的pdftoppm工具将PDF渲染为图像

2, base_name = os.path.splitext(pdf_file)[0]

作用:取出pdf文档名称

说明:

os.path.splitext():分割文件名和扩展名,即"invoice.pdf" → ("invoice", ".pdf")

0\]:取第一个元素(基础名"invoice") 3,for循环遍历图片 作用:按照pdf文件名以及图片对应文档内的页面确定图片文件名,并在相应位置保存需要的格式的图片。 4,返回所有pdf文件生成的图片。 项目中存在raw文件夹下 #### 2.2 Preprocessing ```powershell import cv2 def preprocess_image(img): gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) blur = cv2.GaussianBlur(gray, (5, 5), 0) thresh = cv2.adaptiveThreshold( blur, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 11, 2 ) return thresh ``` 主要工作:首先进行灰度化处理,再使用5×5的高斯核进行高斯滤波,最后阈值化处理 1,thresh = cv2.adaptiveThreshold( blur, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 11, 2 ) 说明: 函数原型:cv2.adaptiveThreshold(src, maxValue, adaptiveMethod, thresholdType, blockSize, C) cv2.ADAPTIVE_THRESH_GAUSSIAN_C :自适应方法 使用邻域的高斯加权和作为阈值; 抗噪声更好,更平滑;适合光照不均的图像(如发票扫描件) cv2.THRESH_BINARY :阈值类型 二值化规则:如果 像素值 \> 阈值: 设为maxValue (255),否则: 设为0 11 :邻域大小。 2:常数。作用:从计算出的阈值中减去的常数# 用于微调阈值水平 问题1:自适应方法有什么用? 核心问题:光照不均。 传统阈值化方法:像素值\>阈值,变成白色,否则变成黑色。 传统阈值化方法结果: 1,亮的地方:全变白,文字消失 2,暗的地方:全变黑,文字被淹没 综合上述结果,需要使用自适应方法,为每个区域计算不同的阈值,使得每个区域都能正确区分文字和背景 问题2:邻域什么意思?为什么需要? 邻域:以当前像素点为中心的n×n矩阵。 作用:为了获取局部信息,而不是只看单个像素 ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/cde5e33eb5c8498fb015b2f727324371.png) 邻域大小的选择原则: 1,大小适中 太小(如3×3):对噪声敏感,容易把噪点当文字,文字可能断裂。适合:清晰文档,小字体。 中等(如11×11):平衡细节和平滑,抗噪声较好,保持文字完整。适合:大多数OCR场景。 太大(如31×31):过度平滑,小文字可能消失,边缘模糊。适合:大文字,强噪声。 2,奇数 要有明确的中心点。如果为偶数,中心点上下,左右难以均衡。 问题3:阈值化有什么用? 阈值化 = 把灰度世界变成黑白分明的世界。灰度化------\>二值化。 #### 2.3 OCR Engine ```powershell import pytesseract from pytesseract import Output def run_ocr(img): return pytesseract.image_to_data(img, output_type=Output.DICT) def get_full_text(ocr_dict): return " ".join(ocr_dict['text']) ``` 主要工作:使用Tesseract引擎从图像中提取文字信息,并以结构化格式返回。 1,from pytesseract import Output Output:是pytesseract定义的一个特殊类(本质是枚举) 作用:指定OCR输出的数据格式类型 ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/1cb96968fda54341bb3d6d3dc3a0b54f.png) 2,pytesseract.image_to_data() 作用:核心OCR函数,执行文字识别。不只是提取文本,而是获取所有识别数据。 ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/21e6607936d449aebf118440bee58c47.png) 问题1:为什么要用Output.DICT而不是直接获取文本? 直接获取文本会丢失位置、置信度等关键信息; 获取字典数据可以保留所有信息:过滤低置信度文字;按位置提取特定区域;分析文本布局;可视化边界框。 #### 2.4 Extractor 1,re模块:Python的正则表达式模块。作用:提供模式匹配功能,用于在文本中查找特定模式 2,为什么先replace('.', '')? 因为千分位点(.)和小数点(.)符号相同,需要区分处理,先去掉所有点,再把逗号换成点 由此可知方法的处理顺序由左至右(无嵌套情况) 3,try...except的作用: 尝试转换,如果转换失败(比如输入不是有效数字)时返回None,而不是程序崩溃 4,正则表达式提取发票信息:号码,日期,支付详情以及总金额,保存在字典变量results里面: ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/8fbcb6c63930446bae3cdbd2bb82d177.png) 金额的特殊提取:由于extract_field提取为字符串,因此先将金额的字符串提取,再转为数字。如果没有提取到金额的字符就返回none。 补充: 正则表达式: 捕获多个符号: * 捕获0-n个空格:\\s\* * 捕获0-n个:或 空格:\[:\\s\]\* 可选符号:符号? ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/4b6c4e8119a143809df596e3abccb1cf.png) 捕获数字: * 捕获\>=a个数字:(\\d{a,}) * 捕获b个数字:\[\\d\]{b}) * 捕获数字和点的组合:\[\\d.

捕获日期:([\d]{2}.[\d]{2}.[\d]{4})

5,文本匹配方法re.findall():

re.IGNORECASE:忽略大小写

pattern:正则表达式模式

疑问:通用文本匹配的返回值为什么取第一个匹配项match[0]?

假设文本中有多个发票号,只取第一个(通常是最相关的)

.strip()表示去掉首尾空白字符

2.5 Visualizer

powershell 复制代码
import cv2

def draw_boxes(img, ocr_result):
    img_copy = img.copy()
    n_boxes = len(ocr_result['level'])

    for i in range(n_boxes):
        (x, y, w, h) = (ocr_result['left'][i], ocr_result['top'][i], 
                        ocr_result['width'][i], ocr_result['height'][i])
        text = ocr_result['text'][i]
        #Only information is available, and the frames are drawn
        if len(text.strip()) > 1:
            cv2.rectangle(img_copy, (x, y), (x + w, y + h), (0, 255, 0), 2)

    return img_copy

可视化OCR结果,给pdf中所有信息添加边界框

1,为什么需要复制图片

目的:保留原始图像不被修改,便于对比

2,ocr_result为识别结果的字典

ocr_result['level']:Tesseract返回的层级列表

len():获取列表长度,即总边界框数量

补充:

ocr_result返回字典格式

ocr_result['level']:

具体举例如下:对于文本"Hello World"的识别

2,绘制矩形框

说明:

  • 需要注意Open CV默认颜色格式为BGR。根据BGR参数可知,绘制绿色线框。
  • 线框宽度:如果为负数或cv2.FILLED,则填充整个矩形

3. main.py - 批量处理脚本

powershell 复制代码
import os
import cv2
import pytesseract
import argparse
import logging
import pandas as pd
import matplotlib.pyplot as plt

from src.pdf_converter import convert_pdfs_to_images
from src.preprocess import preprocess_image
from src.ocr_engine import run_ocr, get_full_text
from src.extractor import extract_fields
from src.visualize import draw_boxes

# --- Set up logging ---
logging.basicConfig(
    level=logging.INFO,     # 日志级别:INFO及以上(INFO, WARNING, ERROR, CRITICAL)
    format='[%(asctime)s] %(levelname)s - %(message)s', # 日志格式
    datefmt='%Y-%m-%d %H:%M:%S'  # 时间格式
)

# --- CLI Args ---
parser = argparse.ArgumentParser(description="Invoice OCR pipeline")
parser.add_argument("--headless", action="store_true", help="Run without showing plots")
args = parser.parse_args()

# --- Paths ---
# 获取脚本所在目录的绝对路径
script_dir = os.path.dirname(os.path.abspath(__file__))
# save files to the paths
pdf_dir = os.path.join(script_dir, "data", "pdf")
image_dir = os.path.join(script_dir, "data", "raw")
visual_dir = os.path.join(script_dir, "data", "visuals")
output_dir = os.path.join(script_dir, "data", "processed")
poppler_bin_path = os.path.join(script_dir, "poppler-24.08.0", "Library","bin")
pytesseract.pytesseract.tesseract_cmd = r"D:\SOFT\Tesseract\tesseract.exe"

os.makedirs(image_dir, exist_ok=True)
os.makedirs(output_dir, exist_ok=True)
os.makedirs(visual_dir, exist_ok=True)

# --- Step 1: Convert all PDFs to images ---
logging.info("Converting PDFs to images...")
convert_pdfs_to_images(pdf_dir=pdf_dir, image_dir=image_dir, dpi=300, poppler_path=poppler_bin_path)

# --- Step 2: Process images ---
summary = []    # 用于存储所有文件的提取结果

for filename in os.listdir(image_dir):
    if not filename.lower().endswith((".jpg", ".jpeg", ".png")):
        continue

    img_path = os.path.join(image_dir, filename)
    logging.info(f"Processing: {filename}")

    img = cv2.imread(img_path)
    if img is None:
        logging.warning(f"Failed to load image: {filename}")
        continue

    # Preprocess + OCR
    preprocessed = preprocess_image(img)
    ocr_result = run_ocr(preprocessed)
    full_text = get_full_text(ocr_result)

    # Extract fields
    fields = extract_fields(full_text)
    fields["source_file"] = filename
    summary.append(fields)

    # Draw and save visualization
    boxed_img = draw_boxes(cv2.cvtColor(img, cv2.COLOR_BGR2RGB), ocr_result)
    visual_path = os.path.join(visual_dir, f"{os.path.splitext(filename)[0]}_boxed.jpg")
    plt.imsave(visual_path, boxed_img)
    
    # Show plot only if not headless
    if not args.headless:
        plt.imshow(boxed_img)
        plt.axis('off')
        plt.title(f"OCR: {filename}")
        plt.show()

# --- Step 3: Save output ---
df = pd.DataFrame(summary)
csv_path = os.path.join(output_dir, "invoice_summary.csv")
df.to_csv(csv_path, index=False)
logging.info(f"✅ Done. Output saved to: {csv_path}")

3.1 主要作用

  • 完全自动化,无需人工干预
  • 支持批量处理大量文件
  • 适合后台运行和定时任务
  • 日志记录详细的处理过程

3.2 准备工作

1,配置日志系统

powershell 复制代码
format='[%(asctime)s] %(levelname)s - %(message)s',  # 日志格式
powershell 复制代码
# 所有可用变量:
变量名          说明
────────────── ──────────────────────────────────────────
%(name)s         Logger的名称(默认是'root')
%(levelno)s     数字形式的日志级别(DEBUG=10, INFO=20等)
%(levelname)s   文本形式的日志级别('DEBUG', 'INFO'等)
%(pathname)s    调用日志记录函数的源文件的完整路径
%(filename)s    文件名部分
%(module)s      模块名(文件名去掉.py)
%(lineno)d      调用日志记录函数的源代码行号
%(funcName)s    调用日志记录函数的函数名
%(created)f     日志创建时间(time.time()返回值)
%(asctime)s     可读的时间字符串
%(msecs)d       毫秒部分
%(relativeCreated)d 相对于Logger创建时间的毫秒数
%(thread)d      线程ID
%(threadName)s  线程名
%(process)d     进程ID
%(message)s     日志消息

2,命令行参数解析

powershell 复制代码
parser = argparse.ArgumentParser(description="Invoice OCR pipeline")
parser.add_argument("--headless", action="store_true", help="Run without showing plots")
args = parser.parse_args()

3,路径配置

4,创建必要的目录:

os.makedirs(image_dir, exist_ok=True)

作用:确保输出目录存在,避免保存文件时出错

exist_ok=True:目录已存在时不报错

3.3 核心步骤

步骤一:pdf转图像

1,convert_pdfs_to_images(pdf_dir=pdf_dir, image_dir=image_dir, dpi=300, poppler_path=poppler_bin_path)

参数说明:

pdf_dir:源文件地址

image_dir:目的文件地址

dpi=300:高分辨率转换(OCR推荐)

poppler_path:指定poppler工具位置(Windows必需)

步骤二:处理每张图像

1,summary列表:收集所有发票的提取结果

2,if not filename.lower().endswith((".jpg", ".jpeg", ".png"))

文件过滤:只处理图像文件,跳过其他文件

3,预处理和OCR

preprocess_image(img):图像预处理(灰度化、去噪、二值化等)

run_ocr(preprocessed):执行OCR,返回结构化结果

get_full_text(ocr_result):从OCR结果中提取完整文本

4,字段提取

fields = extract_fields(full_text)

提取业务字段:从OCR文本中提取发票号、日期、金额等

fields["source_file"] = filename

添加源文件信息:记录当前处理的文件名

summary.append(fields)

收集结果:将字段字典添加到汇总列表

5,可视化

boxed_img = draw_boxes(cv2.cvtColor(img, cv2.COLOR_BGR2RGB), ocr_result)

转换颜色空间:OpenCV是BGR,matplotlib需要RGB

绘制识别框:在图像上绘制OCR识别的文字边界框

visual_path = os.path.join(visual_dir, f"{os.path.splitext(filename)[0]}_boxed.jpg")

保存可视化结果:保存为原始名_boxed.jpg

条件显示:根据--headless参数决定是否弹出显示窗口

步骤三:保存输出

df = pd.DataFrame(summary)

转换为DataFrame:将字典列表转换为pandas表格

csv_path = os.path.join(output_dir, "invoice_summary.csv")

df.to_csv(csv_path, index=False)

保存CSV:保存所有发票的提取结果


总结

对于pdf文件中内容的提取,首先是将文件转换为图片,对图片进行预处理后再进行文本的提取。主要受以下两个方面影响:pdf文件类型以及OCR处理的对象就是"视觉信息"。同时,需要对图片进行灰度化,去噪以及二值化处理以增加文字识别的准确率。

相关推荐
free-elcmacom2 小时前
机器学习高阶教程<11>当数据开始“折叠”:流形学习与深度神经网络如何发现世界的隐藏维度
人工智能·python·神经网络·学习·算法·机器学习·dnn
暗然而日章2 小时前
C++基础:Stanford CS106L学习笔记 12 运算符重载
c++·笔记·学习
红宝村村长2 小时前
【学习笔记】全解深度学习
笔记·深度学习·学习
wdfk_prog2 小时前
[Linux]学习笔记系列 -- [fs]exec
linux·笔记·学习
Master_oid2 小时前
机器学习26:增强式学习(Deep Reinforcement Learn)①
人工智能·学习·机器学习
龚子亦2 小时前
【GameFramework框架学习】事件系统的使用
学习·游戏开发·游戏框架
deng-c-f2 小时前
Linux C/C++ 学习日记(58):手写检测内存泄露的组件
学习
nnsix2 小时前
UnityXR中 重点对象 学习
学习
TL滕3 小时前
从0开始学算法——第二十一天(链表练习)
笔记·学习·算法