从具有不同布局的 PDF 文件中提取文本信息 - 机器学习
Posted
技术标签:
【中文标题】从具有不同布局的 PDF 文件中提取文本信息 - 机器学习【英文标题】:Extract text information from PDF files with different layouts - machine learning 【发布时间】:2020-05-21 06:26:09 【问题描述】:我目前正在尝试创建的 ML 项目需要帮助。
我收到了来自许多不同供应商的大量发票 - 所有这些发票都采用了自己独特的布局。我需要从发票中提取 3 个关键元素。这些 3 元素都位于所有发票的表格/行项目中。
3 元素是:
1:关税编号(数字) 2:数量(始终为数字) 3:总行金额(货币价值)请参阅下面的屏幕截图,我已在示例发票上标记了这些字段。
我使用基于正则表达式的模板方法开始了这个项目。然而,这根本无法扩展,我最终得到了大量不同的规则。
我希望机器学习可以在这里帮助我 - 或者也许是一种混合解决方案?
共同点
在我的所有发票中,尽管布局不同,但每个行项目将始终包含一个关税编号。此关税编号始终为 8 位数字,并且始终采用以下一种格式:
xxxxxxxx xxxx.xxxx xx.xx.xx.xx(其中“x”是从 0 到 9 的数字)。
进一步,正如您在发票上看到的,每行既有单价又有总金额。我需要的量总是每行最高。
输出
对于上述每张发票,我需要每一行的输出。例如,这可能是这样的:
"line":"0",
"tariff":"85444290",
"quantity":"3",
"amount":"258.93"
,
"line":"1",
"tariff":"85444290",
"quantity":"4",
"amount":"548.32"
,
"line":"2",
"tariff":"76109090",
"quantity":"5",
"amount":"412.30"
从这里到哪里去?
我不确定我要做什么属于机器学习,如果是,属于哪个类别。是计算机视觉吗?自然语言处理?命名实体识别?
我最初的想法是:
-
将发票转换为文本。 (发票都是文本格式的 PDF,所以我可以使用
pdftotext
之类的东西来获取确切的文本值)
为quantity
、tariff
和amount
创建自定义命名实体
导出找到的实体。
但是,我觉得我可能遗漏了一些东西。
谁能帮助我朝正确的方向前进?
编辑:
请参阅下面的更多示例,了解发票表格部分的外观:
示例发票 #2
示例发票 #3
编辑 2:
请参阅下面的三个示例图片,没有边框/边界框:
图 1:
图 2:
图 3:
【问题讨论】:
您能否再展示一些输入 PDF 的示例,看看实际有多少变化? (=解决方案的灵活性) @sjaustirni 刚刚又添加了两个!我认为供应商发票之间的最大差异是表格布局(以及随后的行项目,以及特定文本的格式) 完美!鉴于这些示例,我可能会将 pdf 转换为文本并尝试将其中的值与前面的标签(例如Tariff No.:
或 $
)或它所属的列配对(在这里它可以帮助您保存字母的空间信息,如果有任何 OCR 工具这样做的话)。我相信你不需要带着这个问题进入机器学习(除了预制的 OCR),也不需要 NLP(它不是自然语言)。但是,在没有看到这些工具如何处理您的数据的情况下,我们只能推测下一步是什么以及什么是必要的:D
@sjaustirni 不会以我已经在做的事情结束,这是不可扩展的吗? (基于模板/正则表达式的方法)。
您不能将表格本身从 pdf 提取到数据结构中,然后处理列吗?也许你可以使用 tabula-py 来做这件事,然后直接得到数量和总数,加上一些正则表达式,关税
【参考方案1】:
这里尝试使用OpenCV,思路是:
获取二值图像。我们加载图像,使用放大
imutils.resize
帮助获得更好的OCR结果(见Tesseract improve quality),转换为灰度,然后Otsu's threshold获得二值图像(1通道)。
删除表格网格线。我们创建一个horizontal and vertical kernels,然后执行morphological operations 将相邻的文本轮廓组合成一个轮廓。这个想法是将 ROI 行作为一个整体提取到 OCR。
提取行 ROI。 我们 find contours 然后使用 imutils.contours.sort_contours
从上到下排序。这确保我们以正确的顺序遍历每一行。从这里我们遍历轮廓,使用 Numpy 切片提取行 ROI,使用 Pytesseract 进行 OCR,然后解析数据。
这是每个步骤的可视化:
输入图像
二值图像
变形关闭
遍历每一行的可视化
提取的行 ROI
输出发票数据结果:
'line': '0', 'tariff': '85444290', 'quantity': '3', 'amount': '258.93'
'line': '1', 'tariff': '85444290', 'quantity': '4', 'amount': '548.32'
'line': '2', 'tariff': '76109090', 'quantity': '5', 'amount': '412.30'
不幸的是,在尝试第二张和第三张图片时,我得到了不同的结果。这种方法在其他图像上不会产生很好的效果,因为发票的布局都不同。但是,这种方法表明,假设您有固定的发票布局,可以使用传统的图像处理技术来提取发票信息。
代码
import cv2
import numpy as np
import pytesseract
from imutils import contours
import imutils
pytesseract.pytesseract.tesseract_cmd = r"C:\Program Files\Tesseract-OCR\tesseract.exe"
# Load image, enlarge, convert to grayscale, Otsu's threshold
image = cv2.imread('1.png')
image = imutils.resize(image, width=1000)
height, width = image.shape[:2]
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)[1]
# Remove horizontal lines
horizontal_kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (50,1))
detect_horizontal = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, horizontal_kernel, iterations=2)
cnts = cv2.findContours(detect_horizontal, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
for c in cnts:
cv2.drawContours(thresh, [c], -1, 0, -1)
# Remove vertical lines
vertical_kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (1,50))
detect_vertical = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, vertical_kernel, iterations=2)
cnts = cv2.findContours(detect_vertical, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
for c in cnts:
cv2.drawContours(thresh, [c], -1, 0, -1)
# Morph close to combine adjacent contours into a single contour
invoice_data = []
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (85,5))
close = cv2.morphologyEx(thresh, cv2.MORPH_CLOSE, kernel, iterations=3)
# Find contours, sort from top-to-bottom
# Iterate through contours, extract row ROI, OCR, and parse data
cnts = cv2.findContours(close, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
(cnts, _) = contours.sort_contours(cnts, method="top-to-bottom")
row = 0
for c in cnts:
x,y,w,h = cv2.boundingRect(c)
ROI = image[y:y+h, 0:width]
ROI = cv2.GaussianBlur(ROI, (3,3), 0)
data = pytesseract.image_to_string(ROI, lang='eng', config='--psm 6')
parsed = [word.lower() for word in data.split()]
if 'tariff' in parsed or 'number' in parsed:
row_data =
row_data['line'] = str(row)
row_data['tariff'] = parsed[-1]
row_data['quantity'] = parsed[2]
row_data['amount'] = str(max(parsed[10], parsed[11]))
row += 1
print(row_data)
invoice_data.append(row_data)
# Visualize row extraction
'''
mask = np.zeros(image.shape, dtype=np.uint8)
cv2.rectangle(mask, (0, y), (width, y + h), (255,255,255), -1)
display_row = cv2.bitwise_and(image, mask)
cv2.imshow('ROI', ROI)
cv2.imshow('display_row', display_row)
cv2.waitKey(1000)
'''
print(invoice_data)
cv2.imshow('thresh', thresh)
cv2.imshow('close', close)
cv2.waitKey()
【讨论】:
谢谢@nathancy!虽然这个答案不是我所有已发布发票的通用解决方案,但我仍然认为这是我见过的最接近的解决方案 - 这只是使用 OpenCV。非常酷,你的代码示例教会了我很多!再次感谢您抽出宝贵时间发布此内容。【参考方案2】:我正在处理物流行业的一个类似问题,相信我说这些文档表格有多种布局。许多公司已经在一定程度上解决了这个问题并正在改进这个问题,如下所示
领导者:ABBYY、AntWorks、Kofax 和 WorkFusion 主要竞争者:Automation Anywhere、Celaton、Datamatics、EdgeVerve、Extract Systems、Hyland、Hyperscience、Infrrd 和 Parascript 入围者:Ikarus、Rossum、Shipmnts(Alex)、Amazon(Textract)、Docsumo、Docparser、Aidock我想将这个问题归为多模态学习,因为文本和图像模态在这个问题上都有很大的贡献。尽管 OCR 标记在属性值分类中起着至关重要的作用,但它们的在页面上的位置、间距和字符间距离在检测表、行和列边界时仍然是非常重要的特征。当行跨页或某些列带有非空值时,问题变得更加有趣。
虽然学术界和会议使用术语智能文档处理,但通常用于提取单一字段和表格数据。在研究文献中,前者以属性值分类而闻名,后者以表提取或重复结构提取而闻名。
在我们 3 年来处理这些半结构化文档的过程中,我觉得同时实现准确性和可扩展性是一个漫长而艰巨的旅程。提供可扩展性/“无模板”方法的解决方案确实具有数万甚至数百万数量级的半结构化业务文档的注释语料库。虽然这种方法是一种可扩展的解决方案,但它与它所训练的文档一样好。如果您的文件来自物流或保险行业,这些行业以其复杂的布局而闻名,并且由于合规程序而需要超级准确,那么“基于模板”的解决方案将是解决您问题的灵丹妙药。保证提供更高的准确性。
如果您需要现有研究的链接,请在下面的 cmets 中提及,我很乐意分享。
另外,我建议使用 pdfparser1 而不是 pdf2text 或 pdfminer,因为前者在数字文件中提供字符级信息的性能要好得多。
很乐意接受任何反馈,因为这是我在这里的第一个答案。
【讨论】:
如果您正在寻找开源存储库,github.com/invoice-x/invoice2data 可能是一个很好的起点 SAHANI - Gone Through the Github repository,想知道在hole文件夹中提到了输入文件路径的位置。请指导我完成这个。 @manish 在Usage github.com/invoice-x/invoice2data#usage from invoice2data import extract_data result = extract_data('path/to/my/file.pdf')部分的末尾 已按照建议创建了一个单独的文件 - 使用 pip 安装了库 - 导入库 - 尝试按照建议提取数据,但它似乎不起作用,错误 - ImportError: cannot import name 'extract_data ' @Manish 我假设你写了这个----> from invoice2data import extract_data 而不仅仅是导入extract_data。它在我的情况下工作以上是关于从具有不同布局的 PDF 文件中提取文本信息 - 机器学习的主要内容,如果未能解决你的问题,请参考以下文章