基于 PaddleOCR 的 LlamaIndex 图像文本加载器设计与实现
·
基于 PaddleOCR 的 LlamaIndex 图像文本加载器设计与实现
前言
LlamaIndex 的 RAG 流程从 Reader 加载数据开始。内置的 SimpleDirectoryReader 能处理 txt、PDF、HTML 等常见格式,但面对图像中的文字却无能为力。现实中大量信息以图像形式存在——扫描文档、屏幕截图、海报路牌、PDF 扫描件。如何让 LlamaIndex "读懂"图像?
本文基于 PaddleOCR 实现了一个自定义 ImageOCRReader,打通 图像 → OCR 文本 → Document → LlamaIndex 索引 → RAG 问答 的完整链路。

1. 技术选型
1.1 为什么选 PaddleOCR
| OCR 方案 | 中文支持 | 部署难度 | 模型体积 | 精度 |
|---|---|---|---|---|
| PaddleOCR | 原生支持 | 低(pip install) | 轻量(~10MB) | 高 |
| Tesseract | 一般(需训练) | 中(系统依赖) | 中等 | 中 |
| EasyOCR | 支持 | 低 | 较大 | 中高 |
| 云 OCR API | 支持 | 需联网 | 无 | 最高 |
PaddleOCR 的优势:
- 百度飞桨生态,中文识别效果业界领先
- PP-OCRv5 模型轻量且精度高
- 纯 Python 部署,无系统级依赖
- 支持检测 + 识别 + 方向分类一体化
1.2 环境安装
# CPU 环境
pip install "paddlepaddle<=2.6"
# 或直接安装最新版
pip install paddlepaddle paddleocr
验证安装:
from paddleocr import PaddleOCR
ocr = PaddleOCR(use_doc_orientation_classify=False, use_doc_unwarping=False, use_textline_orientation=False)
result = ocr.predict("test.png")
2. LlamaIndex Reader 设计模式
2.1 BaseReader 接口规范
LlamaIndex 的所有数据加载器都继承自 BaseReader,核心只需实现一个方法:
from llama_index.core.readers.base import BaseReader
from llama_index.core.schema import Document
from typing import List
class MyReader(BaseReader):
def load_data(self, file) -> List[Document]:
# 解析数据源 → 返回 Document 列表
...
关键规则:
- 继承
BaseReader - 实现
load_data()方法 - 返回
List[Document] - 通过
metadata传递元数据
2.2 Document 对象
doc = Document(
text="识别出的文本内容",
metadata={
"image_path": "/path/to/image.png", # 来源溯源
"ocr_model": "PP-OCRv5", # 模型版本
"avg_confidence": 0.98, # 质量指标
}
)
| 属性 | 类型 | 说明 |
|---|---|---|
text |
str | 文档文本内容 |
metadata |
Dict | 元数据(来源、置信度等) |
id_ |
str | 自动生成的唯一 ID |
3. ImageOCRReader 实现
3.1 整体架构

3.2 核心代码
import os
from pathlib import Path
from typing import List, Union
from llama_index.core.readers.base import BaseReader
from llama_index.core.schema import Document
class ImageOCRReader(BaseReader):
"""使用 PaddleOCR 从图像中提取文本并返回 LlamaIndex Document"""
def __init__(self, lang: str = "ch", use_gpu: bool = False, **kwargs):
super().__init__()
from paddleocr import PaddleOCR
ocr_kwargs = {
"use_doc_orientation_classify": False,
"use_doc_unwarping": False,
"use_textline_orientation": False,
}
if lang != "ch":
ocr_kwargs["lang"] = lang
if use_gpu:
ocr_kwargs["device"] = gpu
ocr_kwargs.update(kwargs)
self._ocr = PaddleOCR(**ocr_kwargs)
self._lang = lang
设计要点:
- 在
__init__中初始化 PaddleOCR 引擎,避免重复加载模型 - 关闭不必要的预处理模块(方向分类、图像矫正),提升速度
- 支持
lang、use_gpu等参数灵活配置
3.3 load_data 方法
def load_data(self, file: Union[str, List[str]]) -> List[Document]:
if isinstance(file, (str, Path)):
file = [str(file)]
documents = []
for img_path in file:
if not os.path.isfile(img_path):
print(f"[警告] 文件不存在,跳过: {img_path}")
continue
doc = self._process_image(img_path)
if doc is not None:
documents.append(doc)
return documents
支持单张图像或批量处理,自动跳过不存在的文件。
3.4 _process_image 方法
def _process_image(self, img_path: str) -> Document:
result = self._ocr.predict(img_path)
all_texts = []
all_scores = []
for res in result:
rec_texts = res.get("rec_texts", [])
rec_scores = res.get("rec_scores", [])
for text, score in zip(rec_texts, rec_scores):
text = text.strip()
if text:
all_texts.append(text)
all_scores.append(float(score))
# 拼接文本
full_text = "\n".join(all_texts)
avg_confidence = sum(all_scores) / len(all_scores)
# 构造 Document
metadata = {
"image_path": os.path.abspath(img_path),
"ocr_model": "PP-OCRv5",
"language": self._lang,
"num_text_blocks": len(all_texts),
"avg_confidence": round(avg_confidence, 4),
}
return Document(text=full_text, metadata=metadata)
PaddleOCR 返回结构:
| 字段 | 类型 | 说明 |
|---|---|---|
rec_texts |
List[str] | 识别出的文本块列表 |
rec_scores |
List[float] | 每个文本块的置信度 |
rec_polys |
List[ndarray] | 文本区域的多边形坐标 |
rec_boxes |
ndarray | 检测框的边界框 |
3.5 元数据设计
| 字段 | 说明 | 用途 |
|---|---|---|
image_path |
原始图像绝对路径 | 溯源、可视化调试 |
ocr_model |
OCR 模型版本 | 版本管理、对比实验 |
language |
识别语言 | 多语言场景过滤 |
num_text_blocks |
检测到的文本块数量 | 质量评估 |
avg_confidence |
平均识别置信度 | 低质量图像过滤 |
4. 测试图像与 OCR 效果
4.1 测试图像设计
为验证不同场景下的 OCR 识别能力,准备了 3 类测试图像:
| 类型 | 文件名 | 特征 | 难度 |
|---|---|---|---|
| 扫描文档 | 01_scanned_document.png | 白底黑字、段落排版 | 低 |
| 屏幕截图 | 02_screenshot.png | 深色背景、彩色文字、卡片布局 | 中 |
| 自然场景 | 03_natural_scene.png | 渐变背景、半透明遮罩、大字标题 | 中高 |
4.2 OCR 识别结果
======================================================================
【第一步】ImageOCRReader 加载图像 → OCR 识别
======================================================================
──────────────────────────────────────────────────────────────────────
图像: 01_scanned_document.png
识别文本块数: 11
平均置信度: 0.9965
识别内容预览:
[1] 量子计算发展报告
[2] 量子计算是一种利用量子力学原理进行信息处理的计算范式。
[3] 与经典计算机使用比特(bit)不同,量子计算机使用量子比特
[4] (qubit),它能够同时处于0和1的叠加态。
──────────────────────────────────────────────────────────────────────
图像: 02_screenshot.png
识别文本块数: 14
平均置信度: 0.9861
识别内容预览:
[1] 系统监控面板一AI训练集群
[2] GPU 使用率
[3] 显存占用
[4] 训练进度
[5] 87.3%
──────────────────────────────────────────────────────────────────────
图像: 03_natural_scene.png
识别文本块数: 7
平均置信度: 0.9935
识别内容预览:
[1] 人工智能技术大会
[2] 2025年8月20日-22日 北京国家会议中心
[3] 主题演讲:大语言模型、多模态AI、具身智能
[4] 展览面积:50000平方米 参展企业:200+
[5] 报名热线:400-888-9999
分析:
- 三类图像平均置信度均 > 0.98,PaddleOCR 对程序生成的清晰图像识别效果优秀
- 扫描文档识别最完整(11 个文本块),段落结构保留良好
- 屏幕截图的 UI 元素(卡片、日志)被逐块识别(14 个文本块)
- 自然场景海报在渐变背景 + 遮罩下仍可准确识别
5. LlamaIndex 集成与 RAG 问答
5.1 完整流程
from llama_index.core import Settings, VectorStoreIndex
from llama_index.llms.openai_like import OpenAILike
from llama_index.embeddings.dashscope import DashScopeEmbedding, DashScopeTextEmbeddingModels
from image_ocr_reader import ImageOCRReader
# 配置模型
Settings.llm = OpenAILike(
model="qwen-plus",
api_base="https://dashscope.aliyuncs.com/compatible-mode/v1",
api_key=os.getenv("DASHSCOPE_API_KEY"),
is_chat_model=True,
)
Settings.embed_model = DashScopeEmbedding(
model_name=DashScopeTextEmbeddingModels.TEXT_EMBEDDING_V3,
embed_batch_size=6,
embed_input_length=8192,
)
# 第一步:OCR 加载图像
reader = ImageOCRReader(lang="ch")
documents = reader.load_data(["img1.png", "img2.jpg", "img3.png"])
# 第二步:构建向量索引
index = VectorStoreIndex.from_documents(documents)
# 第三步:查询
query_engine = index.as_query_engine(similarity_top_k=3)
response = query_engine.query("GPU使用率是多少?")
print(response) # 输出:87.3%
5.2 数据流
图像文件
↓ ImageOCRReader.load_data()
↓ PaddleOCR.predict()
Document(text="识别文本", metadata={image_path, avg_confidence, ...})
↓ VectorStoreIndex.from_documents()
↓ DashScope Embedding 向量化
向量索引
↓ query_engine.query()
↓ Retriever 检索 Top-3 相关文档
↓ qwen-plus LLM 生成回答
"87.3%"
5.3 RAG 问答验证结果
针对每张图像设计了 2 个问题,共 6 个查询:
| 问题 | 目标图像 | LLM 回答 | 命中 |
|---|---|---|---|
| 量子计算的肖尔算法是什么? | 扫描文档 | 肖尔算法是1994年由彼得·肖尔提出的著名量子算法… | ✓ |
| 报告中提到的报告日期是哪天? | 扫描文档 | 2025年6月15日 | ✓ |
| GPU使用率是多少? | 截图 | 87.3% | ✓ |
| 训练日志中验证集准确率是多少? | 截图 | 97.8% | ✓ |
| 人工智能技术大会在哪里举办? | 海报 | 北京国家会议中心 | ✓ |
| 大会的报名热线是什么? | 海报 | 400-888-9999 | ✓ |
命中率:6/6(100%),所有查询均正确命中目标图像并返回准确答案。
6. 错误分析与讨论
6.1 OCR 识别错误
| 错误类型 | 原文 | 识别结果 | 原因 |
|---|---|---|---|
| 标点混淆 | 彼得·肖尔 | 彼得:肖尔 | 间隔号被误识为冒号 |
| 符号误识 | — (破折号) | 一 (汉字) | 长横线与汉字笔画混淆 |
| 全角/半角 | – (半角) | - (全角) | 连字符宽度识别偏差 |
影响评估:这些错误对 RAG 问答影响极小——embedding 模型对轻微字符差异具有鲁棒性,语义检索仍能正确匹配。
6.2 文本拼接方式
当前方案将每个识别块用 \n 分隔拼接。
优点:实现简单,保留识别顺序,LlamaIndex 的 SentenceSplitter 可进一步按语义切分。
改进空间:
- 引入坐标信息,按从上到下、从左到右的阅读顺序排序
- 多栏布局需根据 x 坐标分栏处理
6.3 元数据的价值
avg_confidence 字段可用于:
- 过滤低质量图像(如置信度 < 0.7 的图像不参与索引)
- 检索结果排序加权
- 质量审计与监控
7. 局限性与改进方向
| 方向 | 当前状态 | 改进方案 |
|---|---|---|
| 版面分析 | 无(线性拼接) | 集成 PP-StructureV2 保留表格结构 |
| 批量处理 | 逐张处理 | 实现 load_data_from_dir() 支持目录遍历 |
| PDF 支持 | 仅静态图像 | 结合 PyMuPDF 将 PDF 页转为图像后 OCR |
| 空间结构 | 丢失 | 利用 rec_polys 坐标还原布局 |
| 可视化 | 无 | OpenCV 绘制检测框,直观调试 |
8. 核心 API 速查
8.1 PaddleOCR
from paddleocr import PaddleOCR
# 初始化
ocr = PaddleOCR(
use_doc_orientation_classify=False,
use_doc_unwarping=False,
use_textline_orientation=False,
)
# 识别
result = ocr.predict("image.png")
# 提取结果
for res in result:
texts = res["rec_texts"] # 文本列表
scores = res["rec_scores"] # 置信度列表
8.2 ImageOCRReader
# 初始化
reader = ImageOCRReader(lang="ch")
# 单张图像
docs = reader.load_data("image.png")
# 批量处理
docs = reader.load_data(["img1.png", "img2.jpg"])
# 访问结果
for doc in docs:
print(doc.text) # 识别文本
print(doc.metadata["avg_confidence"]) # 平均置信度
print(doc.metadata["image_path"]) # 图像路径
9. 总结
核心成果
| 组件 | 说明 |
|---|---|
ImageOCRReader |
继承 BaseReader,封装 PaddleOCR 为 LlamaIndex Reader |
| 3 类测试图像 | 扫描文档、屏幕截图、自然场景海报 |
| RAG 问答验证 | 6/6 查询命中目标图像,回答准确 |
| OCR 精度 | 三类图像平均置信度均 > 0.98 |
关键收获
- 自定义 Reader 是 LlamaIndex 扩展数据源的标准方式——只需继承
BaseReader并实现load_data() - PaddleOCR 中文识别精度高——对清晰图像的识别率接近完美
- 元数据设计很重要——
avg_confidence、image_path等字段为后续过滤和溯源提供支撑 - OCR + RAG 的链路可行——图像中的文字经过 OCR → Document → 索引 → 问答,端到端效果良好
更多推荐
所有评论(0)