在 PaddleOCR 称霸的这几年里,DBNet 是绝对的主流。它的逻辑很简单:用 ResNet 提取特征,预测文本的“收缩核”,然后放大回去得到文本框。它的优势是快,劣势是感受野(Receptive Field)有限

DBNet 很难理解“整页布局”。它不知道左边的文本行和右边的文本行是两列不同的内容,因为它只盯着局部看。

Vik Paruchuri(Surya 作者)的解法非常暴力且现代:用 SegFormer(一种高效的 Transformer)把 OCR 检测变成一个像素级的分割任务。

1. 核心架构:SegFormer 的降维打击

Surya 的检测模型(Detection Model)本质上是一个 SegFormer

SegFormer 是 NVIDIA 在 2021 年提出的,它结合了 Transformer 的全局感知能力和 CNN 的高效性。在 Surya 中,它的作用不是识别猫和狗,而是对文档图像的每一个像素进行分类。

  • Encoder (MiT): 使用 Mix Transformer 提取多尺度的特征。不同于 ViT 的定长 Patch,MiT 可以生成高分辨率的特征图,这对检测小字至关重要。
  • Decoder (MLP): 一个轻量级的 MLP 解码器,直接把特征图聚合成我们需要的 Mask。

为什么要用 Transformer? 因为 Self-Attention(自注意力机制)。 当模型在处理页面左上角的像素时,它能通过 Attention 机制“看到”页面右下角的像素。这意味着模型能理解 “分栏”“段落间距”“阅读顺序” 这种全局的几何关系。这是传统 CNN 很难做到的。

2. 输出层设计:不仅仅是二值化

Surya 的输出不是简单的 0/1(是字/不是字),它的 Head 输出非常丰富。

如果你去扒 surya.model.detection.segformer 的源码,你会发现它的输出通道(Logits)包含了多个维度的信息:

  1. Text Mask:文本区域的概率(类似于 DBNet 的 Probability Map)。
  2. Layout Mask:布局区域(标题、图片、表格)的概率。
  3. Reading Order Heatmap这是 Surya 的黑科技

阅读顺序是如何预测的? 传统方法是拿到框之后,按 y 坐标排序。这在多列布局中 100% 会失败。 Surya 训练了一个 Heatmap,像素的亮度代表了它在阅读顺序中的 Rank。

  • 第一段的像素值可能是 0.1。
  • 第二段(哪怕在第一段右边)的像素值可能是 0.2。
  • 下一行(左边)的像素值可能是 0.3。

后处理时,只需要根据这个 Heatmap 的梯度,就能把检测到的文本框串联成正确的顺序。

3. 代码深潜:扒开 SegFormer 的输出

让我们写一段 Python 代码,绕过 run_ocr 的高层封装,直接调用底层的检测模型,看看它吐出来的 Logits 长什么样。

Python

import torch
import numpy as np
from PIL import Image
from surya.model.detection import segformer
from surya.settings import settings
import cv2

def inspect_surya_architecture(image_path):
    # 1. 加载 SegFormer 检测模型
    # model 是 HuggingFace 的 SegformerForSemanticSegmentation 变体
    model = segformer.load_model()
    processor = segformer.load_processor()
    
    # 2. 预处理
    image = Image.open(image_path).convert("RGB")
    # processor 会把图片 Resize 并 Normalize (通常是 1024x1024 或更大)
    inputs = processor(images=image, return_tensors="pt")
    
    # 移至 GPU
    device = "cuda" if torch.cuda.is_available() else "cpu"
    model.to(device)
    inputs = {k: v.to(device) for k, v in inputs.items()}

    # 3. 前向传播 (Forward Pass)
    with torch.no_grad():
        outputs = model(**inputs)
    
    # 4. 分析 Logits
    # outputs.logits 的形状通常是 [Batch, Num_Classes, Height/4, Width/4]
    logits = outputs.logits
    print(f"Logits Shape: {logits.shape}") 
    # 示例输出: torch.Size([1, 5, 256, 256]) 
    # 这里的 5 可能对应: [Background, Text, Header, Table, Image] 等类别

    # 5. 可视化文本类的热力图 (假设索引 1 是文本)
    # SegFormer 输出的分辨率通常是原图的 1/4,需要上采样回去
    upsampled_logits = torch.nn.functional.interpolate(
        logits,
        size=image.size[::-1], # (H, W)
        mode="bilinear",
        align_corners=False,
    )
    
    # 提取文本通道 (Text Channel)
    text_heatmap = upsampled_logits[0, 1, :, :].cpu().numpy()
    
    # 简单的归一化保存查看
    text_heatmap = (text_heatmap - text_heatmap.min()) / (text_heatmap.max() - text_heatmap.min())
    heatmap_img = (text_heatmap * 255).astype(np.uint8)
    cv2.imwrite("debug_heatmap.png", heatmap_img)
    print("热力图已保存为 debug_heatmap.png")

if __name__ == "__main__":
    inspect_surya_architecture("complex_doc.jpg")

4. 架构的代价:VRAM 与 延迟

虽然 Transformer 架构在精度和逻辑理解上完爆 CNN,但在工程落地时,代价是显而易见的。

  1. 显存占用 (Quadratic Complexity): 虽然 SegFormer 用了 Efficient Attention 降低了复杂度,但它依然比 ResNet-18 (Paddle 的 Backbone) 吃显存。处理一张 A4 高清扫描图,Surya 可能需要 2GB 显存,而 Paddle 可能只需要 500MB。
    • 优化建议:在生产环境中,严格限制 batch_size,或者对超大图进行切片(Tiling)。
  2. 输入分辨率敏感: SegFormer 依赖 Positional Embedding(位置编码)。如果推理时的分辨率和训练时差异过大(比如输入一个 4000×4000 的长条图),效果会下降。
    • 架构差异:CNN 是平移不变的,滑窗过去就行;ViT 是位置敏感的,Resize 策略至关重要。

5. 总结:下一代 OCR 的雏形

Surya 的架构代表了 OCR 的未来趋势:从“检测+识别”向“端到端文档理解”进化。

  • 传统架构 (Paddle):只负责把字框出来,至于这些字是标题还是正文,是左栏还是右栏,它不管。
  • Surya 架构:通过 SegFormer 的全局注意力,把“版面分析”和“文本检测”合二为一。

对于技术人员来说,理解 Surya 的架构,其实就是理解如何利用 Semantic Segmentation 来解决 Object Detection 无法解决的结构化难题。如果你正在做 RAG 数据清洗,这种带有“结构感知”能力的架构,绝对值得你深入研究。