Windows下利用 Python OCR 识别电子发票(增值税专用发票)(使用 GhostScript 和 Tesseract )

news/2025/10/21 17:17:47/文章来源:https://www.cnblogs.com/geyee/p/19155914

早起Python公众号下,作者陈熹的解放双手|Python 批量自动提取、整理 PDF 发票!文章中,看到根据坐标识别图片的方法,觉得代码不是太详细。
试着在windows下重现,如下。
所需 requirements.txt 可以是

# Wand - ImageMagick 的 Python 绑定,用于 PDF 转图片
# 需要先安装 ImageMagick: https://imagemagick.org/script/download.php
Wand>=0.6.10# Pillow - Python 图像处理库,用于图片裁剪和处理
Pillow>=9.0.0# pyocr - Tesseract OCR 的 Python 包装器
# 需要先安装 Tesseract OCR: https://github.com/tesseract-ocr/tesseract
pyocr>=0.8.0# openpyxl - 用于读写 Excel 文件
openpyxl>=3.0.0# numpy - 数值计算库(图像处理可能需要)
numpy>=1.21.0# ========== 系统依赖(需要手动安装)==========
#
# 1. Ghostscript (必需 - 用于 PDF 处理)
#    Windows: https://www.ghostscript.com/download/gsdnld.html
#    推荐版本: 9.27 或更高
#    安装路径: C:\Program Files\gs\gs9.27\bin
#
# 2. ImageMagick (必需 - 用于 PDF 转图片)
#    Windows: https://imagemagick.org/script/download.php
#    推荐版本: 7.1.2-Q16-HDRI 或更高
#    安装路径: C:\Program Files\ImageMagick-7.1.2-Q16-HDRI
#
# 3. Tesseract OCR (必需 - 用于文字识别)
#    Windows: https://github.com/UB-Mannheim/tesseract/wiki
#    推荐版本: 5.0 或更高
#    安装路径: C:\Program Files\Tesseract-OCR
#
#    重要: 需要下载中文语言包 chi_sim.traineddata
#    语言包下载: https://github.com/tesseract-ocr/tessdata
#    语言包路径: C:\Program Files\Tesseract-OCR\tessdata\chi_sim.traineddata
#

image

一开始尝试发现图片背景变成了黑色,
而直接使用命令行,"C:\Program Files\gs\gs9.27\bin\gswin64c.exe" -dNOPAUSE -dBATCH -sDEVICE=png16m -r300 -sOutputFile=output_%d.png dzfp_1.pdf 则是正常的。
可以合并图像到白色背景色进行更正或者使用之后的代码。chat.z.ai 给的解释是 PDF 文件本身可能包含透明图层或未定义背景色的区域。

with Image(filename=path, resolution=300) as img:# 创建一个白色背景图层with Image(width=img.width, height=img.height, background=Color('white')) as bg:# 将原图像合并到白色背景上bg.composite(img, 0, 0)bg.convert('jpeg')bg.save(filename='output_white.jpg')

参考原文,借助 AI,得到代码如下

import os
import sys
import io
from PIL import ImageDraw# 设置 Ghostscript 和 Tesseract 路径(在导入 wand 之前)
os.environ['MAGICK_HOME'] = r'C:\Program Files\ImageMagick-7.1.2-Q16-HDRI'
os.environ['PATH'] = (r'C:\Program Files\gs\gs9.27\bin;' +r'C:\Program Files\Tesseract-OCR;' +os.environ.get('PATH', ''))from wand.image import Image
from wand.color import Color
from PIL import Image as PI
import pyocr
from openpyxl import Workbook# 获取桌面路径包装成一个函数
def GetDesktopPath():return os.path.join(os.path.expanduser("~"), 'Desktop')# 获取当前文件所在文件夹路径
def GetCurrentDirectoryPath():return os.path.dirname(os.path.abspath(__file__))# ========== 配置区域 ==========
# 选择要处理的 PDF(取消注释其中一个)
# path = GetCurrentDirectoryPath() + r"/dzfp_1.pdf"
path = GetCurrentDirectoryPath() + r"/pdf/dzfp_2.pdf"# ========== 坐标配置 ==========
# 完整页坐标 (2480 x 3508)
COORDS_FULL = {'total_amount': (1642, 3100, 2000, 3270),'tax_id': (640, 510, 1180, 570),'issuer': (370, 3380, 480, 3450),
}# 半页坐标 (2480 x 1654)
# 注意:这些坐标需要根据实际的 pdf2_grid.jpg 来调整
COORDS_HALF = {'total_amount': (1860, 1100, 2200, 1200),  # 需要根据实际位置调整'tax_id': (640, 510, 1180, 570),           # 'issuer': (360, 1500, 480, 1600),          # 需要根据实际位置调整
}# ========== 主程序 ==========
# 获取配置好的 tesseract
tools = pyocr.get_available_tools()
if len(tools) == 0:print("错误: 找不到 OCR 工具,请确认 Tesseract 已安装")sys.exit(1)tool = tools[0]
print(f"使用 OCR 工具: {tool.get_name()}")# 设置语言为简体中文
lang = 'chi_sim'
print(f"使用语言: {lang}")
print(f"处理文件: {os.path.basename(path)}\n")# 通过 wand 模块将 PDF 文件转化为分辨率为 300 的 jpeg 图片形式
with Image(filename=path, resolution=300) as image_pdf:# 设置白色背景,移除透明通道image_pdf.background_color = Color('white')image_pdf.alpha_channel = 'remove'# 转换为 JPEGimage_jpeg = image_pdf.convert('jpeg')# 如果是多个图片,则会保存多个,output-0.jpg, output-1.jpg, ...image_jpeg.save(filename="output.jpg")# 将图片解析为二进制矩阵image_lst = []for img in image_jpeg.sequence:img_page = Image(image=img)image_lst.append(img_page.make_blob('jpeg'))# 用 io 模块的 BytesIO 方法读取二进制内容列表的第一个为图片形式
new_img = PI.open(io.BytesIO(image_lst[0]))# 获取图片尺寸
width, height = new_img.size
print(f"PDF 转换成功,图片尺寸: {width} x {height}")
print(f"图片模式: {new_img.mode}\n")# ========== 新增功能:生成带网格的图片 ==========
# 创建一个带网格的图片,方便查看坐标
grid_img = new_img.copy()
draw = ImageDraw.Draw(grid_img)# 绘制网格线(每 50 像素一条)
grid_step = 50
for x in range(0, width, grid_step):draw.line([(x, 0), (x, height)], fill='red', width=2)# 标注 x 坐标draw.text((x + 5, 10), str(x), fill='red')for y in range(0, height, grid_step):draw.line([(0, y), (width, y)], fill='red', width=2)# 标注 y 坐标draw.text((10, y + 5), str(y), fill='red')# 保存网格图片
grid_img.save('coordinates_grid.jpg')
print(f"已生成网格图片: coordinates_grid.jpg\n")# ========== 自动检测页面类型 ==========
if height > 3000:page_type = 'FULL'coords = COORDS_FULLprint(f"检测到完整页发票 (高度 {height} > 3000)")
elif height > 1500:page_type = 'HALF'coords = COORDS_HALFprint(f"检测到半页发票 (高度 {height} 在 1500-3000 之间)")
else:print(f"未知的页面尺寸: {width} x {height}")sys.exit(1)print(f"使用 {page_type} 页坐标配置\n")# ========== 安全的 OCR 识别函数 ==========
def safe_ocr(img, coords, field_name):"""安全的 OCR 识别,检查坐标是否在范围内"""left, top, right, bottom = coords# 检查坐标是否在图片范围内if left < 0 or top < 0 or right > img.width or bottom > img.height:print(f"  {field_name}: 坐标超出范围 {coords},图片尺寸 {img.width} x {img.height}")return Nonetry:# 裁剪并识别cropped = img.crop(coords)# 保存裁剪图片用于调试debug_filename = f"debug_{field_name}.jpg"cropped.save(debug_filename)# OCR 识别text = tool.image_to_string(cropped, lang=lang)text = text.strip()if text:print(f"  {field_name}: {text}")return textelse:print(f"  {field_name}: (识别为空)")return Noneexcept Exception as e:print(f"  {field_name}: 识别失败 - {str(e)}")return None# ========== 识别各个字段 ==========
print("开始识别字段:")
print("="*60)# 总金额
txt1 = safe_ocr(new_img, coords['total_amount'], '总金额')# 纳税人识别号
txt2 = safe_ocr(new_img, coords['tax_id'], '纳税人识别号')# 开票人
txt3 = safe_ocr(new_img, coords['issuer'], '开票人')print("="*60)# ========== 写入 Excel ==========
workbook = Workbook()
sheet = workbook.active
header = ['字段', '值', '页面类型', '坐标']
sheet.append(header)sheet.append(['总金额', txt1 or '', page_type, str(coords['total_amount'])])
sheet.append(['纳税人识别号', txt2 or '', page_type, str(coords['tax_id'])])
sheet.append(['开票人', txt3 or '', page_type, str(coords['issuer'])])excel_path = GetDesktopPath() + r'\发票信息_自适应.xlsx'
workbook.save(excel_path)
print(f'\n数据已保存到: {excel_path}')# ========== 提示信息 ==========
if page_type == 'HALF':print("\n" + "="*60)print("注意:检测到半页发票")print("="*60)print("如果识别结果不正确,请:")print("1. 打开 coordinates_grid.jpg 查看网格")print("2. 找到字段的实际位置")print("3. 更新本文件中的 COORDS_HALF 配置")print("="*60)

对于电子发票信息识别提取,上面的方案有些过时,但也不失为一种编程练习。

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

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

相关文章

2025年臭氧检测仪厂家权威推荐榜:在线式/固定式/便携式/手持式/工业臭氧检测仪专业选购指南

2025年臭氧检测仪厂家权威推荐榜:在线式/固定式/便携式/手持式/工业臭氧检测仪专业选购指南 一、行业技术发展现状与趋势 随着工业安全与环境保护意识的不断提升,臭氧检测仪作为关键的气体监测设备,在半导体制造、水…

✨WPF编程基础【2.2】:布局面板实战 - 详解

✨WPF编程基础【2.2】:布局面板实战 - 详解pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", &quo…

2025年拖鞋机厂家权威推荐榜:酒店拖鞋生产线,全自动拖鞋机,一次性拖鞋机,酒店一次性拖鞋机器专业选购指南

2025年拖鞋机厂家权威推荐榜:酒店拖鞋生产线,全自动拖鞋机,一次性拖鞋机,酒店一次性拖鞋机器专业选购指南 行业背景与发展趋势 随着全球酒店业的快速发展和卫生标准的不断提升,一次性拖鞋作为酒店必备用品,其生产…

2025年不锈钢酸洗钝化液厂家推荐排行榜:环保型不锈钢清洗钝化液,不锈钢管酸洗钝化处理,不锈钢清洗剂专业选购指南

2025年不锈钢酸洗钝化液厂家推荐排行榜:环保型不锈钢清洗钝化液,不锈钢管酸洗钝化处理,不锈钢清洗剂专业选购指南 行业背景与发展趋势 随着制造业转型升级和环保政策趋严,不锈钢表面处理行业正经历深刻变革。不锈钢…

达梦8加密函数是什么怎么调用,达梦数据库加密算法

达梦数据库透明加密方法,可以分为全库加密、表空间加密、日志加密。 全库加密和日志加密,只有在初始化实例的时候配置生效。 配置了全库加密,则所有表空间也是加密的,不允许表空间再单独加密。 DB用户使用加密表空…

电话呼叫软件网页版实测报告:体验、稳定性与推荐名单

在呼叫中心、客服外呼、销售跟进等业务场景中,电话呼叫软件已成为企业的“标配工具”。但近年来,随着企业信息化转型的深入,越来越多的团队开始从传统的本地安装系统,转向部署更轻量、更灵活的网页版电话呼叫软件。…

【2025最新】ArcGIS for JS 实现地图卷帘效果,动态修改参数(进阶版) - 教程

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

基于Windows,Docker用法

1、安装Docker Desktop2、打开Docker Desktop,查找Images来Pull安装node:latestnginx:latest3、挂载本地目录 docker run -it --rm -v F:/demo:/app -w /app -p 5173:5173 node:latest bash # -v:与 /app 相互映射# …

厨房电子秤方案:厨房秤常规的功能有那些?

厨房秤的常规功能围绕精准称重和便捷操作两大核心设计,覆盖从基础称重到辅助烹饪的多个场景,满足日常家用和轻度专业需求。这些功能看似基础,却能大幅提升烹饪的精准度和效率,尤其适合烘焙、减脂餐制作等对食材重量…

Pandas -

Pandas - import pandas as pd import numpy as npdf_tables = pd.read_csv(tables.csv, names=[table_name], header=None)df_tables[split_list] = df_tables[table_name].str.split(_)df_tables[layer] = np.where(…

A-Beta 剪枝

模型介绍 Alpha-Beta 剪枝是极小化极大算法(Minimax)的优化版本,用于两人零和博弈的决策树搜索。它通过剪枝来减少需要评估的节点数量,同时保证找到与 Minimax 算法相同的最优解。 核心思想:Alpha:MAX 玩家能保证…

MySQL 死锁 怎么处理?

一、什么是死锁(Deadlock) 定义:死锁是指两个或多个事务在执行过程中,互相占用资源且等待对方释放,导致事务都无法继续执行的状态。简单例子:事务A事务BUPDATE t1 SET ... WHERE id=1; UPDATE t1 SET ... WHERE …

MyBatis 的 @SelectProvider 是一个强大的注解,用于动态生成 SQL 语句

MyBatis 的 @SelectProvider 是一个强大的注解,用于动态生成 SQL 语句。让我详细介绍一下它的用途和使用方法。 一、@SelectProvider 的作用 主要用途:动态 SQL 构建 - 根据条件动态生成复杂的 SQL 代码逻辑控制 - 使…

跨境客服系统如何保障国际数据传输安全?

在跨境业务不断扩张的今天,客户数据的流动已经不再局限于单一国家或地区。无论是欧洲客户的售后请求,还是东南亚市场的订单咨询,都意味着企业的客服系统需要跨越多国网络,实时响应用户需求。然而,在全球数据监管趋…

物联网短信收发速成:10分钟用SMS库上手实战

物联网设备与短信通信的结合,为众多场景带来便利。本篇文章是一场技术实战分享,聚焦于如何在短短10分钟内,利用SMS库实现物联网短信的收发,让你迅速上手,开启物联网短信功能开发的大门。 近期社群不少新朋友对短信…

250922

1.菜油 关注 9785-9799一线 支撑 可能波段升势开始点 2. 20号胶 之前提到看涨 目前暂看反弹 观察后续走势 至少要破掉12585重要阻力后才看涨势继续

https://vscode-elements.github.io/components/toolbar-button/

https://vscode-elements.github.io/components/toolbar-button/Toolbar Button | VSCode Elements 漫思

npx和npm exec有什么区别

npx 和 npm exec 在功能上非常相似,甚至可以说 npx 是 npm exec 的前身。它们的核心目的都是:在不全局安装包的情况下,临时运行一个 npm 包中的可执行命令。 简要总结区别:特性 npx npm exec引入时间 npm 5.2.0(2…

【深度学习计算机视觉】10:转置卷积 - 详解

【深度学习计算机视觉】10:转置卷积 - 详解2025-10-21 17:00 tlnshuju 阅读(0) 评论(0) 收藏 举报pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: bl…

2025年耳机插座厂家权威推荐榜:DC防水耳机插座,专业防水防尘设计,耐用稳定性能卓越之选

2025年耳机插座厂家权威推荐榜:DC防水耳机插座,专业防水防尘设计,耐用稳定性能卓越之选 随着智能音频设备的快速普及,耳机插座作为关键连接部件,其技术标准与性能要求日益提升。特别是在户外运动、工业应用及特殊…