space ocr
指南文章价格文档
developer

从扫描件里取出人会读的那个顺序的文本

朴素 OCR 按检测顺序返回页面上的所有词,而那并不是任何人阅读的顺序。这是一份关于还原阅读顺序的纯文本抽取指南——附带逐区块坐标,以及“文字有没有被改写”的校验。

6 分钟阅读· 2026-07-28

“把文字给我就行” —— 听上去是最简单的诉求,却是多数 OCR 集成悄悄做错的一环。

视觉 OCR 引擎会把找到的词按段落归组,以 检测顺序 返回:大致自上而下、自左而右扫过整页。单栏便签恰好与阅读顺序一致;双栏文章、带侧栏的发票、略有倾斜的扫描件就不是了。你会拿到左栏第一段、然后右栏第一段、再回到左栏。词都没错,文档却读不通。

如果这些文字进入检索索引、向量或差分比对,损害是安静的:不报错,只是结果慢慢变差,而没人会追溯到 OCR 这一步。

即使不保留结构,纯文本仍需要三件事

就算没有结构要保留,可用的纯文本抽取也必须回答三个问题:

  • 什么顺序? 区块要按阅读顺序出来,被换行截断的句子要重新接上,而不是留成两截。
  • 来自哪里? 如果你索引了一个段落,之后需要向人解释这份文档为什么命中,你需要那个段落的坐标——页码不够。
  • 是不是页面上写的? 转写页面的模型也会顺手“规范化”。悄悄的改良比错误更糟,因为它读起来是对的。

一次调用

POST /ocr/text 接收一张图像并返回文档文本。加上 includeBlocks,还会拿到每个区块及其坐标。

1
2
3
4
5
6
7
8
curl -X POST https://api.space-ocr.com/ocr/text \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "image": "https://example.com/page.jpg",
    "imageType": "url",
    "includeBlocks": true
  }'
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
{
  "status": "success",
  "data": {
    "text": "樱花商事株式会社\n发票\n合计 1,451",
    "source": "llm",
    "image_size": { "width": 1654, "height": 2339 },
    "review_summary": {
      "unit": "block",
      "total": 12,
      "boxed": 12,
      "text_verified": 11,
      "text_mismatch": 1,
      "needs_review": 1,
      "flagged": [{ "path": "blocks[7]", "reason": "text_mismatch" }],
      "recovered_blocks": 0,
      "token_coverage": 1.0
    },
    "blocks": [
      {
        "text": "樱花商事株式会社",
        "bbox": { "xmin": 60, "ymin": 48, "xmax": 470, "ymax": 92 },
        "vertices": [{ "x": 60, "y": 48 }, "…"],
        "bbox_source": "token_id",
        "text_verified": true,
        "needs_review": false
      }
    ]
  }
}

text 是整份文档拼成的一个字符串(区块以换行连接)。blocks[] 是同样的内容,按实际定位到的单位切分,每个区块带 0–1000 归一化的 bboxvertices

分工值得写清楚:字符与坐标以视觉 OCR 为准,模型只被问及顺序与归组。 由它决定下一个区块是哪个、哪些片段属于同一块——它无法凭空造出坐标,对字符也没有最终决定权。

text_verified,以及它为 false 的时候

每个区块都带 text_verified。引擎取出该区块所主张的词元,查出视觉 OCR 在这些坐标上读到的内容,归一化后比对。true 表示两次判读一致;false 表示转写与页面不同——文字照样返回,被标记出来而不是悄悄出错。

这不是准确率百分比。它回答的是更窄也更有用的问题:这条有没有和像素对过,并且对上了? 对索引流水线来说,一个自然的策略是全部入库但保留标记,等到最终要展示给人时,不确定的区块被标出来,而不是当作事实呈现。

✓ Verified

两种失败被显式处理,而不是藏起来。 如果模型这一路彻底失败(无密钥、配额用尽、重试耗尽),端点不会报错,而是降级为视觉转写并如实告知:source: "vision" 加一条 warning。另外,任何没有被任何区块主张的页面词元区间,都会作为回收区块(bbox_source: "unclaimed_tokens")追加回来,于是丢掉的段落会出现在输出里,而不是从输出里消失。

什么时候该关掉模型

useLlm: false 给你纯视觉转写:即时、无模型成本,代价是区块按原始 OCR 顺序。对于顺序本就正确的单栏页面,或只需判断关键词是否存在的批量处理,这是划算的取舍。

当顺序承载含义时就留着它——多栏排版、带侧栏的表单、有倾斜的扫描件,以及要给人看而不只是拿去匹配的文本。

如果你要的是结构而不是行文(标题作为标题、表格作为表格),那是另一个端点:POST /ocr/markdown 会返回保留版式的 Markdown,带同样的逐元素坐标与同样的 text_verified

相关文章