做书籍数字化,最怕的不是字认不出来,而是结构丢了

扫描版 PDF 往往是图片。传统的 OCR 引擎(如 Tesseract)是基于简单的启发式规则(Heuristic Rules)来判断行合并的。这就导致了著名的 “栏间隙失效” 问题:只要两栏中间的空白不够宽,OCR 就会无视它,直接横着读过去。

Surya 的核心优势在于,它不猜。它用 Segformer(一种基于 Transformer 的语义分割模型)直接去“看”页面的结构。它能输出每个文本块的 bbox 以及它们在逻辑上的 Reading Order(阅读顺序)

1. 核心机制:它是怎么分栏的?

Surya 的工作流和传统的 OCR 不同,它是两条线并行的:

  1. Text Detection:找出哪里有字。
  2. Layout Analysis:找出哪里是 Header,哪里是 Footer,哪里是 Image,最重要的是——哪里是分栏界线

在多列书籍中,Surya 会生成一个 Heatmap(热力图)。在这个热力图上,栏与栏之间的“沟”是非常明显的低响应区域。Surya 根据这个热力图切割文本块,然后根据垂直和水平位置,计算出一个 rank(排序),这就保证了它能先读完左栏,再读右栏。

2. 代码实战:提取分栏后的文本

安装就不废话了,pip install surya-ocr,记得配好 PyTorch 和 CUDA,因为这玩意儿吃显存。

下面这段代码展示了如何处理一张典型的双栏扫描页,并按正确的阅读顺序打印文本:

Python

from PIL import Image
from surya.ocr import run_ocr
from surya.model.detection import segformer
from surya.model.recognition.model import load_model
from surya.model.recognition.processor import load_processor
import numpy as np

def process_multicolumn_page(image_path):
    # 1. 初始化模型 (生产环境建议全局加载,不要放在函数里)
    # 检测模型负责找框和排序
    det_processor, det_model = segformer.load_processor(), segformer.load_model()
    # 识别模型负责读字
    rec_model, rec_processor = load_model(), load_processor()

    # 2. 加载图片
    image = Image.open(image_path)

    # 3. 运行 OCR + 布局分析
    # Surya 会自动处理 Reading Order
    predictions = run_ocr([image], [["en", "zh"]], det_model, det_processor, rec_model, rec_processor)
    
    result = predictions[0]
    
    # 4. 结构化输出
    # result.text_lines 已经按照 Reading Order 排好序了
    # 这一点至关重要!
    
    print(f"--- 处理页面: {image_path} ---")
    print(f"检测到 {len(result.text_lines)} 行文本")
    
    # 简单的可视化逻辑:判断左右栏
    img_width = image.size[0]
    mid_point = img_width / 2
    
    left_column = []
    right_column = []
    
    for line in result.text_lines:
        # line.bbox = [x1, y1, x2, y2]
        center_x = (line.bbox[0] + line.bbox[2]) / 2
        
        # 简单归类一下,方便调试
        if center_x < mid_point:
            left_column.append(line.text)
        else:
            right_column.append(line.text)

    print("\n=== 左栏内容 (Top 5) ===")
    print("\n".join(left_column[:5]))
    
    print("\n=== 右栏内容 (Top 5) ===")
    print("\n".join(right_column[:5]))

    # 实际存库时,直接存 result.text_lines 的顺序即可
    return result

if __name__ == "__main__":
    # 找一张双栏的论文或扫描书测试
    process_multicolumn_page("scanned_book_page.jpg")

3. 实测表现与“坑”

我在一批 1990 年代的计算机技术书籍扫描件(分辨率一般,有噪点,双栏排版)上跑了测试,结论如下:

表现好的地方(Pros):

  • 分栏极其准确:即使是两栏中间没有黑线分割(只有空白),Surya 也能精准切开,不会发生“文字粘连”。
  • 页眉页脚剔除:Layout 模型能识别出 Header/Footer。在做 RAG 清洗时,你可以根据 label 直接丢弃这些无关信息,这比写正则去匹配页码要稳得多。
  • 图文混排:如果书里插了一张图,文字绕着图排(Text Wrapping),Surya 通常能按照人类的阅读逻辑跳过图片继续读。

坑点(Cons):

  • 显存杀手:处理高清扫描图(比如 A3 幅面的 600DPI),显存占用很容易飙到 4G 以上。如果在 T4 (16G) 上跑,Batch Size 稍微大点就 OOM。
  • 对弯曲敏感:书脊处的文字往往是弯曲的(Page Curl)。Surya 的 Segformer 对这种几何形变的纠正能力不如 TextIn 或专门的 Dewarping 算法。如果字弯得太厉害,Detection 框可能会切歪。
  • 速度权衡:虽然号称快,但开启 Layout Analysis 后,它比单纯的 Detection 要慢一些。如果你的书全是单栏的,没必要上 Surya,PaddleOCR 足矣。

4. 优化建议

如果你要在生产环境(比如电子书加工流水线)上线 Surya,有几个工程化建议:

  1. 预处理裁剪:如果扫描件有黑边(扫描仪盖板造成的),一定要先切掉。黑边会严重干扰 Segformer 的热力图生成,导致整页布局判断失误。
  2. 按需降级:先用轻量级分类器判断页面是否为多栏。如果是单栏小说,直接切到 PaddleOCR(CPU 都能跑);如果是多栏期刊,再调起 Surya。
  3. Chunking 策略:在入库向量数据库时,直接利用 Surya 返回的 text_lines 顺序进行拼接。千万别自己按坐标 y 轴去排序,那是自找麻烦。

总结

Surya 不是为了替代 Tesseract 而生的,它是为了解决 “机器看不懂排版” 这个问题而生的。

在扫描版书籍、论文、报纸这种强结构化、多列布局的场景下,Surya 是目前 Python 生态里能拿到的最趁手的开源工具。它把“版面分析”这个曾经需要昂贵商业 SDK 才能做的事,变成了几行代码就能搞定的工程模块。