【第二十七周】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处理的对象就是"视觉信息"。同时,需要对图片进行灰度化,去噪以及二值化处理以增加文字识别的准确率。

相关推荐
西岸行者4 天前
学习笔记:SKILLS 能帮助更好的vibe coding
笔记·学习
悠哉悠哉愿意5 天前
【单片机学习笔记】串口、超声波、NE555的同时使用
笔记·单片机·学习
别催小唐敲代码5 天前
嵌入式学习路线
学习
毛小茛5 天前
计算机系统概论——校验码
学习
babe小鑫5 天前
大专经济信息管理专业学习数据分析的必要性
学习·数据挖掘·数据分析
winfreedoms5 天前
ROS2知识大白话
笔记·学习·ros2
在这habit之下5 天前
Linux Virtual Server(LVS)学习总结
linux·学习·lvs
我想我不够好。5 天前
2026.2.25监控学习
学习
im_AMBER5 天前
Leetcode 127 删除有序数组中的重复项 | 删除有序数组中的重复项 II
数据结构·学习·算法·leetcode
CodeJourney_J5 天前
从“Hello World“ 开始 C++
c语言·c++·学习