Tesseract OCR 完整教程 / 第 7 章:PDF 处理
第 7 章:PDF 处理
将扫描 PDF 转换为可搜索、可复制的文档。
7.1 PDF OCR 概述
PDF OCR 三种场景
├── 1. 扫描件 PDF → 添加文本层 → 可搜索 PDF
├── 2. 图片 PDF → OCR → 可搜索 PDF
└── 3. 已有文本 PDF → 增强 → 更好文本层
| 输入类型 | 处理方式 | 输出 |
|---|
| 纯图片 PDF | 全页 OCR | 可搜索 PDF |
| 混合 PDF | 仅 OCR 无文本层页面 | 可搜索 PDF |
| 已有文本 | 跳过或增强 | 可搜索 PDF |
| 多页扫描 | 逐页 OCR | 可搜索 PDF |
7.2 基本 PDF OCR
7.2.1 Tesseract 直接输出 PDF
# 单图片 → 可搜索 PDF
tesseract image.png output pdf
# 多图片 → 多页 PDF
tesseract page1.png page2.png page3.png output pdf
# 指定语言
tesseract scan.png output pdf -l chi_sim+eng
# TIFF 多页 → PDF
tesseract multipage.tiff output pdf
7.2.2 Python 实现
import pytesseract
from PIL import Image
def image_to_searchable_pdf(image_path, output_path, lang='chi_sim+eng'):
"""将图片转换为可搜索 PDF"""
img = Image.open(image_path)
# 生成 PDF(含隐藏文本层)
pdf_bytes = pytesseract.image_to_pdf_or_hocr(img, lang=lang, extension='pdf')
with open(output_path, 'wb') as f:
f.write(pdf_bytes)
print(f"已生成: {output_path}")
# 使用
image_to_searchable_pdf('scan.png', 'output.pdf')
7.2.3 多页 PDF 生成
import pytesseract
from PIL import Image
import os
def images_to_pdf(image_dir, output_path, lang='chi_sim+eng'):
"""多张图片合并为一个可搜索 PDF"""
images = []
for filename in sorted(os.listdir(image_dir)):
if filename.endswith(('.png', '.jpg', '.tif', '.tiff')):
img = Image.open(os.path.join(image_dir, filename))
if img.mode == 'RGBA':
img = img.convert('RGB')
images.append(img)
if not images:
print("未找到图片")
return
# 第一张图作为基础
first_image = images[0]
other_images = images[1:]
# 逐页 OCR 并合并
pdf_pages = []
for img in images:
pdf_bytes = pytesseract.image_to_pdf_or_hocr(img, lang=lang, extension='pdf')
pdf_pages.append(pdf_bytes)
# 使用 PyPDF2 合并(如果安装了的话)
# 或者直接用第一张图生成多页 PDF
first_image.save(
output_path, 'PDF',
save_all=True,
append_images=other_images,
resolution=300
)
print(f"已生成多页 PDF: {output_path} ({len(images)} 页)")
images_to_pdf('./scans', 'output.pdf')
7.3 OCRmyPDF(推荐方案)
7.3.1 安装
# pip 安装
pip install ocrmypdf
# Ubuntu 安装
sudo apt install ocrmypdf
# 安装中文语言支持
sudo apt install tesseract-ocr-chi-sim tesseract-ocr-chi-tra
7.3.2 基本使用
# 基本 OCR
ocrmypdf input.pdf output.pdf
# 指定语言
ocrmypdf -l chi_sim+eng input.pdf output.pdf
# 跳过已有文本的页面
ocrmypdf --skip-text input.pdf output.pdf
# 强制重新 OCR(即使已有文本)
ocrmypdf --force-ocr input.pdf output.pdf
# 优化 PDF 大小
ocrmypdf --optimize 3 --output-type pdf input.pdf output.pdf
# 添加书签
ocrmypdf --title "文档标题" --author "作者" input.pdf output.pdf
7.3.3 OCRmyPDF 选项详解
| 选项 | 说明 | 示例 |
|---|
-l | 语言 | -l chi_sim+eng |
--skip-text | 跳过有文本的页面 | --skip-text |
--force-ocr | 强制重新 OCR | --force-ocr |
--optimize | 优化级别 0-3 | --optimize 2 |
--output-type | 输出类型 | pdf / pdfa |
--deskew | 自动校正倾斜 | --deskew |
--rotate-pages | 自动旋转页面 | --rotate-pages |
--clean | 清理扫描件 | --clean |
--title | PDF 标题 | --title "标题" |
--jobs | 并行进程数 | --jobs 4 |
7.3.4 批量 PDF OCR
#!/bin/bash
# batch_pdf_ocr.sh - 批量 PDF OCR
INPUT_DIR="./input_pdfs"
OUTPUT_DIR="./output_pdfs"
LANG="chi_sim+eng"
mkdir -p "$OUTPUT_DIR"
for pdf in "$INPUT_DIR"/*.pdf; do
if [ -f "$pdf" ]; then
filename=$(basename "$pdf")
echo "Processing: $filename"
ocrmypdf -l "$LANG" --deskew --clean --optimize 2 \
"$pdf" "$OUTPUT_DIR/$filename"
fi
done
echo "Done!"
import ocrmypdf
import os
from concurrent.futures import ProcessPoolExecutor
def ocr_single_pdf(args):
"""处理单个 PDF"""
input_path, output_path, lang = args
try:
ocrmypdf.ocr(
input_path, output_path,
language=lang,
deskew=True,
clean=True,
optimize=2,
skip_text=True
)
return f"成功: {input_path}"
except ocrmypdf.PriorOcrFoundError:
return f"跳过(已有文本): {input_path}"
except Exception as e:
return f"失败: {input_path} - {e}"
def batch_pdf_ocr(input_dir, output_dir, lang='chi_sim+eng', workers=4):
"""批量 PDF OCR(并行)"""
os.makedirs(output_dir, exist_ok=True)
tasks = []
for filename in os.listdir(input_dir):
if filename.endswith('.pdf'):
input_path = os.path.join(input_dir, filename)
output_path = os.path.join(output_dir, filename)
tasks.append((input_path, output_path, lang))
print(f"待处理: {len(tasks)} 个文件")
with ProcessPoolExecutor(max_workers=workers) as executor:
results = list(executor.map(ocr_single_pdf, tasks))
for result in results:
print(result)
batch_pdf_ocr('./input_pdfs', './output_pdfs')
7.4 PDF 文本提取
7.4.1 从可搜索 PDF 提取文本
import fitz # PyMuPDF
def extract_text_from_pdf(pdf_path):
"""从可搜索 PDF 提取文本"""
doc = fitz.open(pdf_path)
for page_num in range(len(doc)):
page = doc[page_num]
text = page.get_text()
print(f"=== 第 {page_num + 1} 页 ===")
print(text[:500])
print()
doc.close()
# pip install PyMuPDF
extract_text_from_pdf('output.pdf')
7.4.2 检查 PDF 是否已 OCR
import fitz
def is_pdf_searchable(pdf_path):
"""检查 PDF 是否包含文本层"""
doc = fitz.open(pdf_path)
for page_num in range(min(3, len(doc))): # 检查前3页
page = doc[page_num]
text = page.get_text().strip()
if len(text) > 10: # 有足够文本
doc.close()
return True
doc.close()
return False
# 使用
if is_pdf_searchable('scan.pdf'):
print("PDF 已包含文本层")
else:
print("PDF 需要 OCR")
7.5 PDF 页面处理
7.5.1 PDF 页面提取为图片
import fitz
def pdf_to_images(pdf_path, output_dir, dpi=300):
"""将 PDF 页面转换为图片"""
os.makedirs(output_dir, exist_ok=True)
doc = fitz.open(pdf_path)
zoom = dpi / 72 # 72 是 PDF 默认 DPI
matrix = fitz.Matrix(zoom, zoom)
for page_num in range(len(doc)):
page = doc[page_num]
pix = page.get_pixmap(matrix=matrix)
output_path = os.path.join(output_dir, f'page_{page_num+1:03d}.png')
pix.save(output_path)
print(f"保存: {output_path}")
doc.close()
7.5.2 选择性页面 OCR
import fitz
import pytesseract
from PIL import Image
import io
def selective_pdf_ocr(pdf_path, output_path, pages=None, lang='chi_sim+eng'):
"""选择性对指定页面进行 OCR"""
doc = fitz.open(pdf_path)
if pages is None:
pages = range(len(doc))
for page_num in pages:
page = doc[page_num]
text = page.get_text().strip()
if len(text) < 10: # 页面无文本,需要 OCR
print(f"OCR 第 {page_num + 1} 页")
# 提取页面图片
pix = page.get_pixmap(dpi=300)
img = Image.open(io.BytesIO(pix.tobytes('png')))
# OCR
ocr_text = pytesseract.image_to_string(img, lang=lang)
# 可以将 OCR 文本保存或插入 PDF 文本层
print(f" 识别文本: {ocr_text[:100]}...")
else:
print(f"跳过第 {page_num + 1} 页(已有文本)")
doc.close()
selective_pdf_ocr('mixed.pdf', 'output.pdf', pages=[0, 2, 5])
7.6 PDF 后处理
7.6.1 PDF 压缩优化
# 使用 Ghostscript 压缩
gs -sDEVICE=pdfwrite -dCompatibilityLevel=1.4 -dPDFSETTINGS=/ebook \
-dNOPAUSE -dQUIET -dBATCH \
-sOutputFile=output_compressed.pdf input.pdf
# 使用 OCRmyPDF 优化
ocrmypdf --optimize 2 --output-type pdf input.pdf output.pdf
7.6.2 PDF 元数据添加
import fitz
def add_pdf_metadata(pdf_path, output_path, metadata):
"""添加 PDF 元数据"""
doc = fitz.open(pdf_path)
doc.set_metadata({
'title': metadata.get('title', ''),
'author': metadata.get('author', ''),
'subject': metadata.get('subject', ''),
'keywords': metadata.get('keywords', ''),
'producer': 'Tesseract OCR'
})
doc.save(output_path)
doc.close()
add_pdf_metadata('input.pdf', 'output.pdf', {
'title': 'OCR 处理文档',
'author': 'Tesseract',
'keywords': 'OCR, 文档, 扫描'
})
7.7 业务场景
| 场景 | 推荐方案 | 配置 |
|---|
| 法律文档存档 | OCRmyPDF + 搜索 | --deskew --clean --optimize 2 |
| 合同数字化 | OCRmyPDF + 元数据 | --title --author |
| 发票批量处理 | Python + 并行 | ProcessPoolExecutor |
| 古籍扫描存档 | Tesseract + best 模型 | tessdata_best |
| 学术论文归档 | OCRmyPDF + PDF/A | --output-type pdfa |
7.8 常见问题
| 问题 | 原因 | 解决方案 |
|---|
| PDF 文件过大 | 未压缩 | --optimize 2 |
| OCR 速度慢 | 单线程 | --jobs 4 |
| 中文乱码 | 缺少语言包 | apt install tesseract-ocr-chi-sim |
| 文本层不准确 | 图片质量差 | 先预处理图片 |
| PDF/A 不兼容 | 格式问题 | --output-type pdfa |
7.9 本章小结
| 要点 | 说明 |
|---|
| 推荐工具 | OCRmyPDF(功能全面、稳定) |
| 基本命令 | ocrmypdf -l chi_sim+eng input.pdf output.pdf |
| 批量处理 | Shell 脚本或 Python 并行 |
| 优化 | --optimize 2 --deskew --clean |
| 文本提取 | PyMuPDF (fitz) |
7.10 扩展阅读
上一章: 模型训练 | 下一章: Python 集成