文章目录
- 引言
- 系统要求
- [1. 环境准备:安装Miniconda](#1. 环境准备:安装Miniconda)
- [2. 配置pip源加速下载](#2. 配置pip源加速下载)
- [3. 配置学术加速(访问国外资源)](#3. 配置学术加速(访问国外资源))
- [4. 安装系统依赖](#4. 安装系统依赖)
- [5. 安装OLMOCR](#5. 安装OLMOCR)
- [6. 运行OLMOCR处理PDF文档](#6. 运行OLMOCR处理PDF文档)
- [7. 理解OLMOCR输出结果](#7. 理解OLMOCR输出结果)
- [9. 可视化UI界面](#9. 可视化UI界面)
-
- [9.1 安装界面依赖](#9.1 安装界面依赖)
- [9.2 创建界面应用](#9.2 创建界面应用)
- [9.3 启动界面](#9.3 启动界面)
- [10. 常见问题及解决方案](#10. 常见问题及解决方案)
- 字体渲染问题
- [11. 高级应用与优化](#11. 高级应用与优化)
- 总结
引言
OLMOCR是由Allen AI研究所(AI2)开发的一款强大的PDF文档处理工具,它结合了先进的光学字符识别(OCR)技术与大型语言模型能力,能够高效处理各类PDF文档,包括低质量扫描件、复杂格式的学术论文等。本文将详细介绍如何在高性能GPU环境下部署OLMOCR,帮助研究人员和开发者实现高效的文档内容提取与处理。
原图:
提取出来的文本:
系统要求
在开始部署前,请确保您的系统满足以下条件:
- GPU: 最新的NVIDIA GPU,如RTX 4090、L40S、A100或H100
- 显存: 至少20GB的GPU RAM
- 存储空间: 至少30GB可用磁盘空间
- 操作系统: Linux(推荐Ubuntu)
1. 环境准备:安装Miniconda
首先,我们需要安装Miniconda并创建一个独立的Python环境:
bash
# 下载并安装Miniconda
wget https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh -O ~/miniconda.sh
bash ~/miniconda.sh -b -p $HOME/miniconda
eval "$($HOME/miniconda/bin/conda shell.bash hook)"
echo 'export PATH="$HOME/miniconda/bin:$PATH"' >> ~/.bashrc
source ~/.bashrc
# 创建Python 3.11环境
conda create -n olmocr_ai python=3.11 -y
激活环境
bash
conda activate olmocr_ai
如果遇到CondaError: Run 'conda init' before 'conda activate'
错误,请执行:
bash
conda init bash
source ~/.bashrc
conda activate olmocr_ai
2. 配置pip源加速下载
对于国内用户,建议配置国内镜像源加速依赖包的下载:
bash
# 配置清华源
pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple
# 升级pip并清理缓存
python -m pip install --upgrade pip
pip cache purge
# 检查配置是否生效
pip config list
其他可选国内源:
- 阿里云:http://mirrors.aliyun.com/pypi/simple/
- 中国科技大学:https://pypi.mirrors.ustc.edu.cn/simple/
- 华中科技大学:http://pypi.hustunique.com/simple/
- 上海交通大学:https://mirror.sjtu.edu.cn/pypi/web/simple/
3. 配置学术加速(访问国外资源)
如需访问GitHub等国外资源,可以临时启用学术加速:
bash
# 启用学术加速
source /etc/network_turbo
# 配置Hugging Face镜像
export HF_ENDPOINT=https://hf-mirror.com
# 使用完毕后关闭学术加速
# unset http_proxy && unset https_proxy
4. 安装系统依赖
OLMOCR依赖一些系统库,需要提前安装:
bash
sudo apt-get update
sudo apt-get install poppler-utils ttf-mscorefonts-installer msttcorefonts fonts-crosextra-caladea fonts-crosextra-carlito gsfonts lcdf-typetools
注意:安装过程中如果出现MORE提示,按Enter键继续阅读,然后输入"yes"接受许可条款。
5. 安装OLMOCR
接下来,克隆OLMOCR代码库并安装:
bash
# 克隆代码库
git clone https://github.com/allenai/olmocr.git
cd olmocr
# 安装OLMOCR
pip install -e .
# 安装特定版本依赖
pip install sgl-kernel==0.0.3.post1 --force-reinstall --no-deps
pip install "sglang[all]==0.4.2" --find-links https://flashinfer.ai/whl/cu124/torch2.4/flashinfer/ -i https://pypi.tuna.tsinghua.edu.cn/simple --timeout 1800
6. 运行OLMOCR处理PDF文档
现在,我们可以使用OLMOCR来处理PDF文件:
bash
# 创建工作目录并处理PDF文件
python -m olmocr.pipeline ./localworkspace --pdfs tests/gnarly_pdfs/horribleocr.pdf
# 查看处理结果
cat localworkspace/results/output_*.jsonl
7. 理解OLMOCR输出结果
解析完成后的数据
OLMOCR处理完PDF后,会生成JSONL格式的输出文件。下面是输出字段的详细解释:
json
{
"id": "2d8a28b2aa386be36ff8d83135990cae1904257d",
"text": "Christians behaving themselves like Mahomedans...",
"source": "olmocr",
"added": "2025-03-19",
"created": "2025-03-19",
"metadata": {
"Source-File": "tests/gnarly_pdfs/horribleocr.pdf",
"olmocr-version": "0.1.60",
"pdf-total-pages": 1,
"total-input-tokens": 1809,
"total-output-tokens": 433,
"total-fallback-pages": 0
},
"attributes": {
"pdf_page_numbers": [[0, 1643, 1]]
}
}
- id: 处理任务的唯一标识符,通常是基于输入文件内容生成的哈希值
- text: 从PDF中提取的实际文本内容
- source: 标识数据来源为"olmocr"系统
- added/created: 记录添加和创建的时间戳
- metadata:
- Source-File: 原始PDF文件路径
- olmocr-version: 使用的OLMOCR版本
- pdf-total-pages: PDF文件的总页数
- total-input-tokens: 处理过程中输入的token数量
- total-output-tokens: 处理过程中输出的token数量
- total-fallback-pages: 使用备用处理方法的页面数量(当主要方法失败时)
- attributes:
- pdf_page_numbers: 包含页码信息的数组,格式为[页码索引,字符偏移量,页数]
9. 可视化UI界面
为了让OLMOCR更加易用,我开发了基于Gradio的直观可视化界面,可以通过Web浏览器轻松上传、处理和分析PDF文档。这个界面不仅简化了操作流程,还提供了多种方式查看和验证处理结果。
9.1 安装界面依赖
首先,确保安装必要的依赖包:
bash
pip install gradio pandas
9.2 创建界面应用
将以下代码保存为app.py
文件放到olmocr根目录下:
python
import os
import json
import gradio as gr
import subprocess
import pandas as pd
from pathlib import Path
import shutil
import time
import re
# 创建工作目录
WORKSPACE_DIR = "olmocr_workspace"
os.makedirs(WORKSPACE_DIR, exist_ok=True)
# 应用主题色
PRIMARY_COLOR = "#2563eb" # 蓝色主题
SECONDARY_COLOR = "#60a5fa"
BG_COLOR = "#f8fafc"
CARD_COLOR = "#ffffff"
def modify_html_for_better_display(html_content):
"""修改HTML以便在Gradio中更好地显示"""
if not html_content:
return html_content
# 增加容器宽度
html_content = html_content.replace('<div class="container">',
'<div class="container" style="max-width: 100%; width: 100%;">')
# 增加文本大小和字体
html_content = html_content.replace('<style>',
f'''<style>
body {{
font-family: "Source Sans Pro", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
font-size: 16px;
line-height: 1.6;
color: #333;
background-color: {BG_COLOR};
}}
.text-content {{
font-size: 16px;
line-height: 1.6;
color: #1e293b;
}}
''')
# 调整图像和文本部分的大小比例,添加阴影和圆角
html_content = html_content.replace('<div class="row">',
'<div class="row" style="display: flex; flex-wrap: wrap; margin: 0 -15px;">')
html_content = html_content.replace('<div class="col-md-6">',
'<div class="col-md-6" style="flex: 0 0 50%; max-width: 50%; padding: 15px;">')
# 增加页面之间的间距,添加卡片效果
html_content = html_content.replace('<div class="page">',
f'<div class="page" style="margin-bottom: 30px; border-radius: 8px; padding: 20px; background-color: {CARD_COLOR}; box-shadow: 0 4px 6px rgba(0,0,0,0.1);">')
# 增加图像样式
html_content = re.sub(r'<img([^>]*)style="([^"]*)"',
r'<img\1style="max-width: 100%; height: auto; border-radius: 4px; \2"',
html_content)
# 添加更美观的缩放控制
zoom_controls = f"""
<div style="position: fixed; bottom: 20px; right: 20px; background: {CARD_COLOR}; padding: 12px;
border-radius: 8px; box-shadow: 0 4px 15px rgba(0,0,0,0.15); z-index: 1000; display: flex; gap: 8px;">
<button onclick="document.body.style.zoom = parseFloat(document.body.style.zoom || 1) + 0.1;"
style="background: {PRIMARY_COLOR}; color: white; border: none; padding: 8px 12px; border-radius: 4px;
cursor: pointer; font-weight: 600; transition: all 0.2s;">
<span style="font-size: 16px;">+</span>
</button>
<button onclick="document.body.style.zoom = 1;"
style="background: #64748b; color: white; border: none; padding: 8px 12px; border-radius: 4px;
cursor: pointer; font-weight: 600; transition: all 0.2s;">
<span style="font-size: 14px;">100%</span>
</button>
<button onclick="document.body.style.zoom = parseFloat(document.body.style.zoom || 1) - 0.1;"
style="background: {PRIMARY_COLOR}; color: white; border: none; padding: 8px 12px; border-radius: 4px;
cursor: pointer; font-weight: 600; transition: all 0.2s;">
<span style="font-size: 16px;">-</span>
</button>
</div>
"""
html_content = html_content.replace('</body>', f'{zoom_controls}</body>')
return html_content
def process_pdf(pdf_file):
"""处理PDF文件并返回结果"""
if pdf_file is None:
return "请上传PDF文件", "", None, None
# 创建一个唯一的工作目录
timestamp = int(time.time())
work_dir = os.path.join(WORKSPACE_DIR, f"job_{timestamp}")
os.makedirs(work_dir, exist_ok=True)
# 复制PDF文件
pdf_path = os.path.join(work_dir, "input.pdf")
shutil.copy(pdf_file, pdf_path)
# 构建命令并执行
cmd = ["python", "-m", "olmocr.pipeline", work_dir, "--pdfs", pdf_path]
try:
# 执行命令,等待完成
process = subprocess.run(
cmd,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True,
check=True
)
# 命令输出
log_text = process.stdout
# 检查结果目录
results_dir = os.path.join(work_dir, "results")
if not os.path.exists(results_dir):
return f"处理完成,但未生成结果目录\n\n日志输出:\n{log_text}", "", None, None
# 查找输出文件
output_files = list(Path(results_dir).glob("output_*.jsonl"))
if not output_files:
return f"处理完成,但未找到输出文件\n\n日志输出:\n{log_text}", "", None, None
# 读取JSONL文件
output_file = output_files[0]
with open(output_file, "r") as f:
content = f.read().strip()
if not content:
return f"输出文件为空\n\n日志输出:\n{log_text}", "", None, None
# 解析JSON
result = json.loads(content)
extracted_text = result.get("text", "未找到文本内容")
# 生成HTML预览
try:
preview_cmd = ["python", "-m", "olmocr.viewer.dolmaviewer", str(output_file)]
subprocess.run(preview_cmd, check=True)
except Exception as e:
log_text += f"\n生成HTML预览失败: {str(e)}"
# 查找HTML文件
html_files = list(Path("dolma_previews").glob("*.html"))
html_content = ""
if html_files:
try:
with open(html_files[0], "r", encoding="utf-8") as hf:
html_content = hf.read()
# 修改HTML以更好地显示
html_content = modify_html_for_better_display(html_content)
except Exception as e:
log_text += f"\n读取HTML预览失败: {str(e)}"
# 创建元数据表格
metadata = result.get("metadata", {})
meta_rows = []
for key, value in metadata.items():
meta_rows.append([key, value])
df = pd.DataFrame(meta_rows, columns=["属性", "值"])
return log_text, extracted_text, html_content, df
except subprocess.CalledProcessError as e:
return f"命令执行失败: {e.stderr}", "", None, None
except Exception as e:
return f"处理过程中发生错误: {str(e)}", "", None, None
# 自定义CSS
custom_css = f"""
:root {{
--body-background-fill: {BG_COLOR};
--background-fill-primary: {CARD_COLOR};
--border-color-primary: #e2e8f0;
--color-text-input: #334155;
--block-background-fill: {CARD_COLOR};
--block-label-background-fill: #f1f5f9;
--block-label-text-color: #475569;
--block-title-text-color: #1e293b;
}}
.contain {{
max-width: 1200px;
margin: 0 auto;
padding: 2rem;
}}
/* 头部区域样式 */
.header-container {{
background: linear-gradient(135deg, {PRIMARY_COLOR} 0%, {SECONDARY_COLOR} 100%);
color: white;
border-radius: 10px;
padding: 1.5rem;
margin-bottom: 2rem;
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1);
}}
/* 上传卡片样式 */
.upload-container {{
background-color: {CARD_COLOR};
border-radius: 8px;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05);
padding: 1.5rem;
border: 1px solid #e2e8f0;
}}
/* 标签页样式 */
.tabs {{
background-color: {CARD_COLOR};
border-radius: 8px !important;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05);
overflow: hidden;
}}
/* 按钮样式 */
.primary-button {{
background-color: {PRIMARY_COLOR} !important;
border: none !important;
box-shadow: 0 4px 6px rgba(37, 99, 235, 0.2) !important;
}}
.primary-button:hover {{
background-color: #1d4ed8 !important;
box-shadow: 0 4px 12px rgba(37, 99, 235, 0.3) !important;
}}
/* 表格样式 */
table {{
border-collapse: collapse;
width: 100%;
border-radius: 8px;
overflow: hidden;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05);
}}
th, td {{
padding: 12px 15px;
text-align: left;
border-bottom: 1px solid #e2e8f0;
}}
th {{
background-color: #f8fafc;
font-weight: 600;
color: #475569;
}}
tr:nth-child(even) {{
background-color: #f8fafc;
}}
tr:hover {{
background-color: #f1f5f9;
}}
/* HTML预览容器 */
#html_preview_container {{
height: 800px;
width: 100%;
overflow: auto;
border: 1px solid #e2e8f0;
border-radius: 8px;
background-color: white;
}}
#html_preview_container iframe {{
width: 100%;
height: 100%;
border: none;
}}
/* 说明卡片样式 */
.instruction-card {{
background-color: {CARD_COLOR};
border-radius: 8px;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05);
padding: 1.5rem;
margin-top: 2rem;
border: 1px solid #e2e8f0;
}}
/* 特性卡片 */
.feature-card {{
background-color: {CARD_COLOR};
border-radius: 8px;
padding: 1.5rem;
margin: 1rem 0;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05);
border-left: 4px solid {PRIMARY_COLOR};
}}
/* 提示和警告样式 */
.alert-info {{
background-color: #eff6ff;
border-left: 4px solid {PRIMARY_COLOR};
padding: 1rem;
border-radius: 4px;
margin: 1rem 0;
}}
.alert-warning {{
background-color: #fff7ed;
border-left: 4px solid #f97316;
padding: 1rem;
border-radius: 4px;
margin: 1rem 0;
}}
"""
# 创建Gradio界面
with gr.Blocks(title="OLMOCR PDF提取工具", css=custom_css) as app:
# 头部区域
with gr.Row(elem_classes="header-container"):
with gr.Column(scale=3):
gr.Markdown("# 📄 OLMOCR PDF文本智能提取工具")
gr.Markdown("### 基于AI2最新OCR技术,高精度提取和分析PDF文档内容")
with gr.Column(scale=1):
gr.Markdown("")
with gr.Row():
# 左侧上传和控制区域
with gr.Column(scale=1, elem_classes="upload-container"):
gr.Markdown("## 📤 上传文档")
pdf_input = gr.File(label="选择PDF文件", file_types=[".pdf"], elem_id="pdf_upload")
with gr.Row():
process_btn = gr.Button("🚀 开始处理", variant="primary", elem_classes="primary-button")
# 添加功能说明
with gr.Accordion("🔍 功能说明", open=False):
gr.Markdown("""
* 支持扫描PDF和数字PDF文件
* 处理多语言文本内容
* 保留文档原始布局和结构
* 高精度识别表格和图表文字
* 支持低质量扫描件处理
""")
# 添加处理状态指示
gr.Markdown("### 📊 处理状态", elem_id="process_status")
status_html = gr.HTML("""
<div class="alert-info">
<p><b>就绪</b> - 请上传PDF文件并点击处理按钮</p>
</div>
""")
# 右侧结果显示区域
with gr.Column(scale=2):
# 使用更好看的标签页
tabs = gr.Tabs(elem_classes="tabs")
with tabs:
with gr.TabItem("📝 提取文本", elem_id="text_tab"):
with gr.Column(): # 使用Column替代Box
text_output = gr.Textbox(
label="提取的文本内容",
lines=20,
interactive=True,
# 移除不兼容的show_copy_button
)
with gr.TabItem("👁️ 视觉对比预览", elem_id="html_preview_tab"):
gr.Markdown("#### 原始PDF与提取文本对照预览")
# 使用更大的HTML组件
html_output = gr.HTML(elem_id="html_preview_container")
with gr.TabItem("📋 文档元数据", elem_id="metadata_tab"):
with gr.Column(): # 使用Column替代Box
gr.Markdown("#### 文档处理元数据")
meta_output = gr.DataFrame()
with gr.TabItem("🔧 处理日志", elem_id="log_tab"):
with gr.Column(): # 使用Column替代Box
log_output = gr.Textbox(
label="处理日志",
lines=15,
interactive=False,
# 移除不兼容的show_copy_button
)
# 底部说明区域
with gr.Row(elem_classes="instruction-card"):
gr.Markdown("""
## 📚 使用指南
<div class="feature-card">
<h3>🔷 基本步骤</h3>
<ol>
<li>点击"选择PDF文件"上传您需要处理的PDF文档</li>
<li>点击"开始处理"按钮启动OCR流程</li>
<li>等待处理完成(根据文件大小可能需要几分钟)</li>
<li>在标签页中查看提取的文本、视觉预览和元数据</li>
</ol>
</div>
<div class="feature-card">
<h3>🔷 关于视觉预览</h3>
<ul>
<li>视觉预览标签页展示原始PDF页面和提取的文本对照</li>
<li>可以清晰查看OCR过程的识别精度</li>
<li>使用右下角的缩放按钮调整预览大小</li>
</ul>
</div>
<div class="alert-warning">
<h4>⚠️ 注意事项</h4>
<ul>
<li>首次运行时会自动下载模型(约7GB),请确保网络畅通</li>
<li>处理大型PDF文件时可能需要较长时间,请耐心等待</li>
<li>对于复杂表格和特殊格式,可能需要额外调整提取的文本</li>
</ul>
</div>
""")
# 自定义JavaScript用于更新状态和界面交互
gr.HTML("""
<script>
// 处理按钮点击时的状态更新
document.addEventListener('DOMContentLoaded', function() {
const processBtn = document.querySelector('.primary-button');
const statusHTML = document.getElementById('process_status').nextElementSibling;
if (processBtn) {
processBtn.addEventListener('click', function() {
statusHTML.innerHTML = `
<div class="alert-info">
<p><b>处理中...</b> - 正在分析PDF文档,请耐心等待</p>
</div>
`;
});
}
});
</script>
""")
# 绑定按钮事件 - 使用阻塞模式
def update_status():
return """
<div class="alert-info">
<p><b>处理完成</b> - 请在右侧标签页查看结果</p>
</div>
"""
process_btn.click(
fn=process_pdf,
inputs=pdf_input,
outputs=[log_output, text_output, html_output, meta_output],
api_name="process"
).then(
fn=update_status,
outputs=[status_html]
)
# 启动应用
if __name__ == "__main__":
app.launch(
server_name="0.0.0.0", # 绑定到所有网络接口
server_port=19851, # 或其他端口
share=False # 禁用共享功能
# 移除不兼容的favicon_path参数
)
9.3 启动界面
保存代码后,在终端运行以下命令启动界面:
bash
python app.py
启动成功后,您会看到类似以下输出:
bash
Running on local URL: http://0.0.0.0:19851
现在您可以通过浏览器访问 http://ip:port
来使用OLMOCR的可视化界面。
10. 常见问题及解决方案
网络连接问题
如果在下载模型或依赖时遇到网络问题,建议:
- 确认已配置正确的镜像源
- 尝试使用学术加速
- 增加下载超时时间:
pip install --timeout 1800 [包名]
GPU内存不足
- 减小批处理大小
- 处理较小的PDF片段
- 考虑使用更大内存的GPU
字体渲染问题
如果PDF中的某些字体无法正确渲染,可能需要安装额外的字体包:
bash
sudo apt-get install fonts-noto fonts-noto-cjk
11. 高级应用与优化
针对不同GPU的优化
- RTX 4090: 适合处理中等规模的批量PDF文件,建议batch_size设置为2-4
- A100/H100: 可以处理更大规模的PDF批量,可将batch_size提高到8或更高
- 内存优化: 对于显存较小的GPU,可以使用--low_memory参数降低内存使用
总结
通过本教程,可以完成OLMOCR在高性能GPU环境下的部署和基本使用。OLMOCR作为一款结合OCR和大语言模型的工具,可以显著提高PDF文档处理的效率和准确性,特别适用于处理学术论文、技术文档等复杂格式的资料。
随着AI技术的不断发展,文档智能处理领域将会出现更多创新。OLMOCR提供了灵活的架构,允许研究者和开发者在此基础上进行扩展和定制,以满足特定领域的文档处理需求。
希望本文能帮助您顺利部署OLMOCR,提升文档处理效率!