title: “RAGFlow-04-DeepDoc模块” categories: [“RAGFlow”]

RAGFlow-04-DeepDoc模块

模块概览

1.1 职责与定位

DeepDoc 模块是 RAGFlow 的文档解析引擎,负责将非结构化文档转换为结构化文本。主要职责包括:

  1. 多格式文档解析:支持 PDF、Docx、Excel、PPT、图片、HTML、Markdown 等
  2. 布局识别:使用深度学习模型(YOLO)识别文档布局(标题、段落、表格、图片)
  3. OCR 文字识别:使用 PaddleOCR 识别图片中的文字
  4. 表格识别与结构化:使用 Table Transformer 模型识别表格结构,转换为 HTML/Markdown
  5. 图片理解:使用多模态 LLM(如 GPT-4V)描述图片内容
  6. 版面分析:识别阅读顺序、合并段落、过滤页眉页脚

1.2 核心能力

文档类型支持

文档类型 解析器 支持格式 核心能力
PDF PdfParser .pdf 布局识别、OCR、表格识别、阅读顺序
Word DocxParser .docx 样式保留、表格提取、图片描述
Excel ExcelParser .xlsx, .xls, .csv 工作表解析、公式提取
PPT PptParser .pptx 幻灯片文本与图片提取
图片 ImageParser .jpg, .png, .bmp OCR + 多模态 LLM 描述
HTML HtmlParser .html DOM 解析、样式保留
Markdown MarkdownParser .md 结构保留、代码块识别

1.3 技术栈

深度学习模型

  • 布局识别:YOLO v8(物体检测)
  • OCR:PaddleOCR(文字识别)
  • 表格识别:Table Transformer(结构化提取)
  • 多模态理解:GPT-4V / Gemini(图片描述)

依赖库

  • PDF 处理:PyMuPDF(fitz)、pdfplumber
  • 图像处理:OpenCV、PIL
  • 文档处理:python-docx、openpyxl、python-pptx
  • HTML 处理:BeautifulSoup、lxml

1. 模块架构图

flowchart TB
    subgraph "解析器层"
        PdfParser[PDF 解析器<br/>PdfParser]
        DocxParser[Docx 解析器<br/>DocxParser]
        ExcelParser[Excel 解析器<br/>ExcelParser]
        PptParser[PPT 解析器<br/>PptParser]
        ImageParser[图片解析器<br/>ImageParser]
        HtmlParser[HTML 解析器<br/>HtmlParser]
        MdParser[Markdown 解析器<br/>MarkdownParser]
    end

    subgraph "AI 能力层"
        LayoutModel[布局识别模型<br/>YOLO v8]
        OCR[OCR 引擎<br/>PaddleOCR]
        TableModel[表格识别模型<br/>Table Transformer]
        VisionLLM[多模态 LLM<br/>GPT-4V/Gemini]
    end

    subgraph "工具层"
        ImageProcessor[图像处理<br/>OpenCV]
        TextMerger[文本合并<br/>NLP算法]
        LayoutAnalyzer[版面分析<br/>阅读顺序]
        FormatConverter[格式转换<br/>HTML/Markdown]
    end

    subgraph "输入"
        File[原始文档<br/>PDF/Docx/Excel等]
    end

    subgraph "输出"
        Sections[文本段落列表<br/>sections]
        Tables[表格列表<br/>tables]
        Images[图片列表<br/>images]
    end

    File --> PdfParser
    File --> DocxParser
    File --> ExcelParser
    File --> ImageParser

    PdfParser --> LayoutModel
    PdfParser --> OCR
    PdfParser --> TableModel
    DocxParser --> VisionLLM
    ImageParser --> OCR
    ImageParser --> VisionLLM

    LayoutModel --> LayoutAnalyzer
    OCR --> TextMerger
    TableModel --> FormatConverter

    LayoutAnalyzer --> Sections
    TextMerger --> Sections
    FormatConverter --> Tables
    VisionLLM --> Images

    Sections --> Output[(输出到 RAG Pipeline)]
    Tables --> Output
    Images --> Output

2. 核心功能详细剖析

2.1 PDF 解析器

2.1.1 解析流程

sequenceDiagram
    autonumber
    participant Task as TaskExecutor
    participant Parser as PdfParser
    participant Layout as 布局识别模型
    participant OCR as OCR 引擎
    participant Table as 表格识别模型

    Task->>Parser: __call__(filename, binary, from_page, to_page)
    
    Parser->>Parser: __images__() - 转换为图片
    note right of Parser: PDF → PIL Image (zoomin=3倍)
    
    Parser->>Layout: _layouts_rec(zoomin)
    Layout->>Layout: YOLO 模型推理
    Layout-->>Parser: 返回边界框 [x0, y0, x1, y1, class]
    note right of Layout: class: title/text/table/figure
    
    Parser->>OCR: OCR 识别各个 bbox
    OCR-->>Parser: 返回文本与置信度
    
    Parser->>Table: _table_transformer_job(zoomin)
    Table->>Table: 识别表格结构(行列)
    Table-->>Parser: 返回 HTML 表格
    
    Parser->>Parser: _text_merge() - 合并段落
    Parser->>Parser: _extract_table_figure() - 提取表格与图片
    Parser->>Parser: _concat_downward() - 垂直合并
    Parser->>Parser: _final_reading_order_merge() - 阅读顺序
    
    Parser-->>Task: 返回 sections + tables

2.1.2 核心代码

# deepdoc/parser/pdf_parser.py
class PdfParser:
    def __call__(self, filename, binary=None, from_page=0, to_page=100000, zoomin=3, callback=None):
        """解析 PDF"""
        # 1. 转换为图片
        self.__images__(filename if not binary else binary, zoomin, from_page, to_page, callback)
        
        # 2. 布局识别(YOLO)
        self._layouts_rec(zoomin)
        callback(0.63, "Layout analysis finished")
        
        # 3. 表格识别(Table Transformer)
        self._table_transformer_job(zoomin)
        callback(0.65, "Table analysis finished")
        
        # 4. 文本合并(基于位置)
        self._text_merge(zoomin=zoomin)
        callback(0.67, "Text merged")
        
        # 5. 提取表格与图片
        tbls = self._extract_table_figure(True, zoomin, True, True)
        
        # 6. 垂直合并(段落连接)
        self._naive_vertical_merge()
        
        # 7. 向下合并(跨页段落)
        self._concat_downward()
        
        # 8. 最终阅读顺序合并
        self._final_reading_order_merge()
        
        # 9. 返回结果
        return [(b["text"], self._line_tag(b, zoomin)) for b in self.boxes], tbls
    
    def __images__(self, fnm, zoomin, from_page, to_page, callback):
        """PDF 转图片"""
        doc = fitz.open(fnm)
        for i, page in enumerate(doc):
            if i < from_page or i >= to_page:
                continue
            # 放大 zoomin 倍(提高 OCR 精度)
            mat = fitz.Matrix(zoomin, zoomin)
            pix = page.get_pixmap(matrix=mat)
            img = Image.frombytes("RGB", [pix.width, pix.height], pix.samples)
            self.images.append(np.array(img))
            callback(0.6 * (i - from_page) / (to_page - from_page), f"Page {i}")
    
    def _layouts_rec(self, zoomin):
        """布局识别"""
        from deepdoc.vision.layout_recognizer import LayoutRecognizer
        layout_model = LayoutRecognizer()
        
        for i, img in enumerate(self.images):
            bboxes = layout_model(img)  # YOLO 推理
            # bboxes: [[x0, y0, x1, y1, confidence, class_id], ...]
            
            for box in bboxes:
                x0, y0, x1, y1, conf, cls = box
                # OCR 该区域
                text = self._ocr_bbox(img, x0, y0, x1, y1)
                self.boxes.append({
                    "text": text,
                    "x0": x0 / zoomin,
                    "x1": x1 / zoomin,
                    "top": y0 / zoomin,
                    "bottom": y1 / zoomin,
                    "page_number": i,
                    "layoutno": cls  # 0=title, 1=text, 2=table, 3=figure
                })
    
    def _table_transformer_job(self, zoomin):
        """表格识别"""
        from deepdoc.vision.table_recognizer import TableRecognizer
        table_model = TableRecognizer()
        
        for i, img in enumerate(self.images):
            # 找到标记为 table 的 bbox
            table_boxes = [b for b in self.boxes if b["layoutno"] == 2 and b["page_number"] == i]
            
            for box in table_boxes:
                # 裁剪表格区域
                table_img = img[int(box["top"]):int(box["bottom"]), int(box["x0"]):int(box["x1"])]
                
                # 识别表格结构
                html = table_model(table_img)
                
                # 存储表格
                self.tables.append({
                    "html": html,
                    "page": i,
                    "bbox": (box["x0"], box["top"], box["x1"], box["bottom"])
                })
    
    def _text_merge(self, zoomin):
        """文本合并"""
        # 按页码、Y 坐标排序
        self.boxes.sort(key=lambda x: (x["page_number"], x["top"], x["x0"]))
        
        # 合并同一行的 boxes
        merged = []
        for box in self.boxes:
            if not merged or abs(merged[-1]["top"] - box["top"]) > 5:
                merged.append(box)
            else:
                # 同一行,横向合并
                merged[-1]["text"] += " " + box["text"]
                merged[-1]["x1"] = max(merged[-1]["x1"], box["x1"])
        
        self.boxes = merged

关键点

  1. Zoomin 参数:PDF 转图片时放大 3 倍,提高 OCR 精度,最后坐标除以 3 还原
  2. 布局识别:YOLO 模型输出 [x0, y0, x1, y1, conf, class],class 表示类型(标题/正文/表格/图片)
  3. 表格识别:Table Transformer 输出 HTML 格式,保留行列结构
  4. 文本合并:基于位置关系(Y 坐标相近)合并同一行的文本块

2.2 Docx 解析器

2.2.1 核心代码

# deepdoc/parser/docx_parser.py
class DocxParser:
    def __call__(self, filename, binary=None):
        """解析 Docx"""
        if binary:
            doc = docx.Document(io.BytesIO(binary))
        else:
            doc = docx.Document(filename)
        
        sections = []
        tables = []
        
        # 1. 遍历段落
        for para in doc.paragraphs:
            text = para.text.strip()
            if not text:
                continue
            
            # 判断样式(标题/正文)
            style = para.style.name
            layoutno = 0 if "Heading" in style else 1
            
            sections.append((text, {"layoutno": layoutno}))
        
        # 2. 遍历表格
        for table in doc.tables:
            html = self._table_to_html(table)
            tables.append((None, (0, 0, 0, 0, 0), html))
        
        # 3. 处理图片(多模态 LLM 描述)
        for rel in doc.part.rels.values():
            if "image" in rel.target_ref:
                image_data = rel.target_part.blob
                img = Image.open(io.BytesIO(image_data))
                
                # 调用多模态 LLM
                vision_llm = LLMBundle(tenant_id, LLMType.IMAGE2TEXT)
                description = vision_llm.describe(image_data)
                sections.append((f"[图片描述] {description}", {"layoutno": 3}))
        
        return sections, tables
    
    def _table_to_html(self, table):
        """表格转 HTML"""
        html = "<table>"
        for row in table.rows:
            html += "<tr>"
            for cell in row.cells:
                html += f"<td>{cell.text}</td>"
            html += "</tr>"
        html += "</table>"
        return html

2.3 Excel 解析器

2.3.1 核心代码

# deepdoc/parser/excel_parser.py
class ExcelParser:
    def __call__(self, filename, binary=None):
        """解析 Excel"""
        if binary:
            wb = openpyxl.load_workbook(io.BytesIO(binary))
        else:
            wb = openpyxl.load_workbook(filename)
        
        sections = []
        
        # 遍历工作表
        for sheet in wb.worksheets:
            # 转换为 HTML 表格
            html = "<table>"
            for row in sheet.iter_rows():
                html += "<tr>"
                for cell in row:
                    value = str(cell.value) if cell.value else ""
                    html += f"<td>{value}</td>"
                html += "</tr>"
            html += "</table>"
            
            # 转换为 Markdown(可选)
            markdown = self._html_to_markdown(html)
            sections.append((markdown, {"layoutno": 2}))
        
        return sections
    
    def _html_to_markdown(self, html):
        """HTML 表格转 Markdown"""
        from html_to_markdown import convert
        return convert(html)

3. 关键数据结构

3.1 解析结果结构

# sections: List[Tuple[str, Dict]]
sections = [
    ("第一章 引言", {"layoutno": 0, "page_number": 1, "top": 100}),
    ("本文介绍了...", {"layoutno": 1, "page_number": 1, "top": 150}),
    ("[图片描述] 架构图显示了...", {"layoutno": 3, "page_number": 2})
]

# tables: List[Tuple[Any, Tuple, str]]
tables = [
    (None, (1, 100, 500, 200, 400), "<table><tr><td>...</td></tr></table>"),
    (None, (2, 50, 600, 100, 300), "<table>...</table>")
]

# layoutno 含义:
# 0 = 标题 (title)
# 1 = 正文 (text)
# 2 = 表格 (table)
# 3 = 图片 (figure)

4. 性能优化建议

1. 布局识别优化

  • 使用 GPU 加速 YOLO 推理(速度提升 10 倍)
  • 批量处理图片(batch_size=4)

2. OCR 优化

  • 使用 PaddleOCR GPU 版本
  • 跳过置信度低的区域(< 0.5)

3. 表格识别优化

  • 仅对标记为 table 的区域执行识别
  • 使用 ONNX 加速推理

4. 内存优化

  • 逐页处理,避免一次性加载所有页面
  • 及时释放图片内存

5. 最佳实践

1. Zoomin 参数选择

  • 普通 PDF:zoomin=3
  • 扫描件/低分辨率:zoomin=5
  • 高分辨率:zoomin=2

2. 多模态 LLM 使用

  • 图片内容复杂时使用(如图表、架构图)
  • 纯文字图片优先使用 OCR(成本低)

3. 表格处理

  • Excel 保留公式(可选)
  • 合并单元格需特殊处理