在 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)包含了多个维度的信息:
- Text Mask:文本区域的概率(类似于 DBNet 的 Probability Map)。
- Layout Mask:布局区域(标题、图片、表格)的概率。
- 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,但在工程落地时,代价是显而易见的。
- 显存占用 (Quadratic Complexity): 虽然 SegFormer 用了 Efficient Attention 降低了复杂度,但它依然比 ResNet-18 (Paddle 的 Backbone) 吃显存。处理一张 A4 高清扫描图,Surya 可能需要 2GB 显存,而 Paddle 可能只需要 500MB。
- 优化建议:在生产环境中,严格限制
batch_size,或者对超大图进行切片(Tiling)。
- 优化建议:在生产环境中,严格限制
- 输入分辨率敏感: SegFormer 依赖 Positional Embedding(位置编码)。如果推理时的分辨率和训练时差异过大(比如输入一个 4000×4000 的长条图),效果会下降。
- 架构差异:CNN 是平移不变的,滑窗过去就行;ViT 是位置敏感的,Resize 策略至关重要。
5. 总结:下一代 OCR 的雏形
Surya 的架构代表了 OCR 的未来趋势:从“检测+识别”向“端到端文档理解”进化。
- 传统架构 (Paddle):只负责把字框出来,至于这些字是标题还是正文,是左栏还是右栏,它不管。
- Surya 架构:通过 SegFormer 的全局注意力,把“版面分析”和“文本检测”合二为一。
对于技术人员来说,理解 Surya 的架构,其实就是理解如何利用 Semantic Segmentation 来解决 Object Detection 无法解决的结构化难题。如果你正在做 RAG 数据清洗,这种带有“结构感知”能力的架构,绝对值得你深入研究。