基于 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 列表
        ...

关键规则

  1. 继承 BaseReader
  2. 实现 load_data() 方法
  3. 返回 List[Document]
  4. 通过 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 引擎,避免重复加载模型
  • 关闭不必要的预处理模块(方向分类、图像矫正),提升速度
  • 支持 languse_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

关键收获

  1. 自定义 Reader 是 LlamaIndex 扩展数据源的标准方式——只需继承 BaseReader 并实现 load_data()
  2. PaddleOCR 中文识别精度高——对清晰图像的识别率接近完美
  3. 元数据设计很重要——avg_confidenceimage_path 等字段为后续过滤和溯源提供支撑
  4. OCR + RAG 的链路可行——图像中的文字经过 OCR → Document → 索引 → 问答,端到端效果良好

更多推荐