扫描的PDF是提取信息时常见的难题。与原生数字版的PDF不同,扫描的文档通常将文本作为图像呈现,这使得直接复制粘贴变得困难。这篇文章将详细介绍两种有效的方法,尤其是面对复杂布局和表格时。我们将介绍如何利用视觉语言模型(VLMs)的优势,并结合文档分割和OCR技术实现准确的文本提取。
PDF 扫描的难题。扫描的PDF没有底层文本信息。这意味着简单的复制粘贴无法实现。实际上,你是在处理文本图片而不是真正的文本信息。复杂的布局、表格和不同大小的文字进一步加大了处理难度。我们需要能识别视觉布局并将图像准确转换为有意义、结构化文本的解决方案。
方法一:使用视觉-语言模型(VLMs)如果你的页面数量不多,并且想要得到最好的结果,使用视觉语言模型(VLM)会是最好的选择。这些强大的模型能够准确地分析图像、理解和处理文本与表格。
我们将用谷歌的Gemini 1.5 Pro来做这件事,因为它在图像理解上特别出色,还能很好地提取文本。
代码实现部分:
</TRANSLATION>
import PIL.Image
import os
import google.generativeai as genai
from pdf2image import convert_from_path
# YOUR_API_KEY 替换为你的 API 密钥
GOOGLE_API_KEY = "YOUR_API_KEY"
genai.configure(api_key=GOOGLE_API_KEY)
pdf_path = "test.pdf" # 请将此路径更改为你的 PDF 路径
pdf_name = os.path.splitext(os.path.basename(pdf_path))[0]
# 创建输出目录(如果它不存在)
output_dir = "GeminiResult"
os.makedirs(output_dir, exist_ok=True)
# 选择一个 Gemini 模型。
model = genai.GenerativeModel(model_name="gemini-1.5-pro")
prompt = """
提取图像中的所有文本内容和表格数据,严格遵循图像中的阅读顺序处理内容,不得重新排列或重新排序块或表格。
1. **阅读顺序:** 根据图像中的阅读顺序处理内容,不得重新排列或重新排序块或表格。
2. **文本块:** 提取图像中的独立文本块,并将每个块表示为一个独立实体,使用双换行符 ("\\n\\n") 分隔。
3. **表格:** 识别图像中的任何表格。对于每个表格,以结构化的逗号分隔格式(.csv)输出。每行表格应放置在新行上,用逗号分隔列值。
- 包括(如有)表头行。
- 确保每一行的所有列都用逗号分隔。
4. **输出格式:**
- 按照页面上的阅读顺序输出文本块和表格。当阅读页面时遇到表格,则在输出中的相应位置输出为 CSV 格式。
5. 如果没有文本或表格,则返回空字符串。
如果表格仅含一行,则返回该行内容,用逗号分隔。
"""
try:
# 将 PDF 的所有页面转换为 PIL 图像对象
images = convert_from_path(pdf_path)
if not images:
raise FileNotFoundError(f"无法将 PDF 转换为图像对象")
for i, img in enumerate(images):
page_number = i + 1
output_file_path = os.path.join(output_dir, f"{pdf_name}_{page_number}.txt")
try:
response = model.generate_content([prompt, img], generation_config={"max_output_tokens": 4096})
response.resolve()
with open(output_file_path, "w", encoding="utf-8") as f:
f.write(response.text)
print(f"处理了第 {page_number} 页并保存到 {output_file_path}")
except Exception as page_err:
print(f"在处理第 {page_number} 页时遇到错误: {page_err}")
with open(output_file_path, "w", encoding="utf-8") as f:
f.write(f"错误: 在处理第 {page_number} 页时发生错误:{page_err}")
except FileNotFoundError as e:
print(f"错误: 无法找到文件: {e}")
except Exception as e:
print(f"错误: 处理过程中发生错误: {e}")
它是怎么工作的:
- 设置步骤: 首先,我们导入所需的库并配置 Google Gemini API 的设置。
- PDF 转换: 然后,我们使用 pdf2image 将 PDF 的每一页转换成图像。
- 提示工程: 接着,提示引导模型按正确的顺序提取文本内容,将文本块分离并结构化表格。
- 图像处理: 接着,Gemini 模型分析图像并根据提示提取文本内容。
- 输出步骤: 最后,提取的文本和表格会被保存为每个 PDF 页面的独立文本文件。
优点:
- 高准确性: VLMs 在处理少量页面时非常准确,能够很好地理解上下文和结构信息。
- 简单性: 实现起来非常简单,特别是如果你熟悉 API 调用。
- 表格处理: Gemini 可以提取结构化的 CSV 格式表格。
缺点:,
- API费用: 例如Gemini这样的VLMs有一个相关的成本。
- 扩展性: 处理非常大的PDF文件可能会非常慢并且成本很高。
这个链接指向了一个比特币发现基金的网站: 查看有关比特币发现基金的详情:https://www.energyfunders.com/bitcoindiscoveryfund
输出:
方法2:文档切分+OCR(光学字符识别)以实现更好的扩展性对于更复杂的PDF文件,结合文档拆分和OCR技术是一种有效的策略。这种方法可以识别页面上的不同文本区域,然后对每个区域进行OCR处理。
我们将使用 YOLO 进行文档切分,使用 DocTr 进行 OCR。
Yolo模型链接:https://huggingface.co/DILHTWD/documentlayoutsegmentation_YOLOv8_ondoclaynet
代码实现:
from ultralytics import YOLO
import fitz
import os
import pathlib
from PIL import Image, ImageEnhance
import numpy as np
from doctr.io import DocumentFile
from doctr.models import ocr_predictor
# 要处理的示例PDF文件列表
pdf_list = ['test.pdf']
# 加载文档分割模型
docseg_model = YOLO('yolov8x-doclaynet-epoch64-imgsz640-initiallr1e-4-finallr1e-5.pt')
# 初始化一个字典来存储结果
mydict = {}
def enhance_image(img):
"""应用图像增强以提高质量."""
# 增强清晰度
enhancer = ImageEnhance.Sharpness(img)
img = enhancer.enhance(1.5)
# 增强对比度
enhancer = ImageEnhance.Contrast(img)
img = enhancer.enhance(1.2)
# 增强颜色
enhancer = ImageEnhance.Color(img)
img = enhancer.enhance(1.1)
return img
def process_pdf_page(pdf_path, page_num, docseg_model, output_dir):
"""处理PDF中的单页,采用最高质量设置."""
pdf_doc = fitz.open(pdf_path)
page = pdf_doc[page_num]
# 提高分辨率矩阵以获得最高质量
zoom = 4 # 用于增加分辨率的放大因子
matrix = fitz.Matrix(zoom, zoom)
# 使用高质量渲染选项
pix = page.get_pixmap(
matrix=matrix,
alpha=False, # 禁用alpha通道以获得更清晰的图像
colorspace=fitz.csRGB # 强制使用RGB颜色空间
)
img = Image.frombytes("RGB", [pix.width, pix.height], pix.samples)
# 应用图像增强
img = enhance_image(img)
# 以高质量设置进行缩放
if zoom != 1:
original_size = (int(page.rect.width), int(page.rect.height))
img = img.resize(original_size, Image.Resampling.LANCZOS)
# 为页面图像生成一个临时文件名
temp_img_filename = os.path.join(output_dir, f"temp_page_{page_num}.png")
# 以最高质量设置保存图像
img.save(
temp_img_filename,
"PNG",
quality=100,
optimize=False,
dpi=(300, 300) # 设置高DPI
)
# 对图像运行模型
results = docseg_model(source=temp_img_filename, save=True, show_labels=True, show_conf=True, boxes=True)
# 提取结果
page_width = page.rect.width
one_third_width = page_width / 3
all_coords = []
for entry in results:
thepath = pathlib.Path(entry.path)
thecoords = entry.boxes.xyxy.numpy()
all_coords.extend(thecoords)
# 将坐标分为两组,然后按y1排序每组
left_group = []
right_group = []
for bbox in all_coords:
x1 = bbox[0]
if x1 < one_third_width:
left_group.append(bbox)
else:
right_group.append(bbox)
left_group = sorted(left_group, key=lambda bbox: bbox[1])
right_group = sorted(right_group, key=lambda bbox: bbox[1])
sorted_coords = left_group + right_group
mydict[f"{pdf_path} Page {page_num}"] = sorted_coords
# 清理临时图像
os.remove(temp_img_filename)
pdf_doc.close()
# 处理列表中的每个PDF
for pdf_path in pdf_list:
try:
pdf_doc = fitz.open(pdf_path)
num_pages = pdf_doc.page_count
pdf_doc.close()
output_dir = os.path.splitext(pdf_path)[0] + "_output"
os.makedirs(output_dir, exist_ok=True)
for page_num in range(num_pages):
process_pdf_page(pdf_path, page_num, docseg_model, output_dir)
except Exception as e:
print(f"处理 {pdf_path} 时出错: {e}")
# 如果不存在,创建'tmp'目录
tmp_dir = 'tmp'
os.makedirs(tmp_dir, exist_ok=True)
# 遍历结果并保存裁剪的图像,采用最高质量
for key, coords in mydict.items():
pdf_name, page_info = key.split(" Page ")
page_number = int(page_info)
pdf_doc = fitz.open(pdf_name)
page = pdf_doc[page_number]
zoom = 4
matrix = fitz.Matrix(zoom,zoom)
for i, bbox in enumerate(coords):
# 适当缩放边界框坐标
xmin, ymin, xmax, ymax = map(lambda x: x , bbox)
# 使用边界框创建一个矩形
rect = fitz.Rect(xmin, ymin, xmax, ymax)
# 使用最大分辨率矩阵裁剪
cropped_pix = page.get_pixmap(
clip=rect,
matrix=matrix,
alpha=False,
colorspace=fitz.csRGB
)
cropped_img = Image.frombytes("RGB", [cropped_pix.width, cropped_pix.height], cropped_pix.samples)
cropped_img = enhance_image(cropped_img)
output_filename = os.path.join(tmp_dir, f"{os.path.splitext(os.path.basename(pdf_name))[0]}_page{page_number}_{i}.png")
# 保存裁剪的图像
cropped_img.save(output_filename, "PNG", quality=100, optimize=False, dpi=(300, 300))
pdf_doc.close()
def extract_text_from_image(image_path, model):
"""使用DocTr从单个图像中提取文本."""
doc = DocumentFile.from_images(image_path)
result = model(doc)
text_content = ""
for page in result.pages:
for block in page.blocks:
for line in block.lines:
for word in line.words:
text_content += word.value + " "
text_content += "\n"
return text_content.strip()
def process_cropped_images(tmp_dir, pdf_list):
"""遍历裁剪的图像,使用DocTr提取文本并将其存储在文本文件中."""
doctr_model = ocr_predictor(pretrained=True)
for pdf_path in pdf_list:
pdf_name = os.path.splitext(os.path.basename(pdf_path))[0]
output_txt_path = f"{pdf_name}_extracted_text.txt"
with open(output_txt_path, 'w', encoding='utf-8') as outfile:
pdf_doc = fitz.open(pdf_path)
num_pages = pdf_doc.page_count
pdf_doc.close()
for page_num in range(num_pages):
outfile.write(f"Page: {page_num}\n")
# 按块顺序对裁剪的图像文件名进行排序
cropped_images_for_page = sorted([
f for f in os.listdir(tmp_dir)
if f.startswith(f"{pdf_name}_page{page_num}_") and f.endswith(".png")
], key=lambda f: int(f.split("_")[-1].split(".")[0]))
for i, image_filename in enumerate(cropped_images_for_page):
image_path = os.path.join(tmp_dir, image_filename)
text = extract_text_from_image(image_path, doctr_model)
outfile.write(f"块 {i}: {text}\n")
print(f"从{pdf_name}提取的文本已保存到{output_txt_path}")
# 示例用法:
tmp_dir = 'tmp' # 确保你的tmp目录存在
pdf_list = ['test.pdf'] # 你的PDF列表
process_cropped_images(tmp_dir, pdf_list)
它是怎么工作的:
- 设置: 导入库并加载用于文档分割的YOLO模型和用于OCR的DocTr模型。
- 图像预处理: 使用fitz(PyMuPDF)加载PDF,提高分辨率并增强图像,从而提升OCR的识别质量。保存临时图像。
- 文档分割: YOLO模型识别并定位页面上的文本块及其他元素。
- 裁剪: 根据边界框裁剪每个部分,应用图像增强技术,并将裁剪后的图像保存到临时目录中。
- OCR应用: 使用DocTr模型从每个裁剪部分提取文本内容。
- 输出: 将提取的文本按每页的节/块编号保存到文本文件中,每个部分都有对应的编号。
优点:
- 可扩展性: 可高效处理大量页面的大量页面。
- 在复杂布局中准确性: 文档分割技术通过将OCR聚焦于特定区域上来提高准确性。
- 免费/开源: 使用的模型是开源的,降低了成本。
- 灵活性和定制化: 提供根据准确性和速度需求在不同OCR模型间切换的功能。此外,用户可以集成专门用于提取复杂表格数据的VLM(视觉语言模型),同时使用传统OCR处理普通文本,从而实现最佳效果并提供定制解决方案。
不足:
- 更复杂的实现: 需要使用更多的库和步骤来完成。
- 准确性权衡: 此方法可能无法达到视觉语言模型那样的完美精度,特别是在处理复杂布局或不常见字体的情况下。可能会有文字或表格结构的细微差异被忽略或误解。
https://www.energyfunders.com/bitcoindiscoveryfund
输出:
- 使用其他OCR模型进行测试以找到最适合的模型。
- 建议: DocTr,PaddleOCR 或者甚至可以使用VLM处理表格,OCR处理文本框。
从复杂的扫描PDF中提取文本可能会很棘手,但并非不可能。视觉语言模型在识别较小文件时更为精确,而将文档分割与OCR技术结合使用则更适合处理较大的文件。选择最佳方法将取决于您的需求、文档页数以及可用资源。希望这次深入探讨能帮助您更好地利用扫描的PDF。
联系我 : https://www.linkedin.com/in/yash-bhaskar/,
更多精彩文章 : https://medium.com/@yash9439