跳到正文
OpenRouter Announcements·· 1 天前精选AI 评分66

OpenRouter 实测扫描件 OCR:Claude Opus 5.5 最准,Mistral OCR 性价比最高

Best OCR Model for Scanned Documents, Measured

AI 导读

OpenRouter 用自建的八页扫描件测试 15 个视觉模型、Mistral OCR 和 Tesseract,Claude Opus 5.5 在 264 项关键值中答对 262 项,每千页成本 16.42 美元,准确率最高。

推荐理由

作者自建八页扫描件实测 15 个视觉模型与 Mistral OCR,给出准确率、成本与可复用的交叉校验代码。

正文 · AI 翻译

把扫描文档转成可靠的文本很难。Tesseract 这类经典 OCR 引擎在褪色印刷、手写和传真件上会失败。视觉模型能很好地读取这些页面,但有时会写出页面上并不存在的值。我们用自己撰写的文本制作了八个测试页面,这样每个字符都能被核对,然后把它们交给 OpenRouter 上的 15 个视觉模型和 Mistral OCR,以及 Tesseract 处理。

我们测试中最准确的 OCR 模型是 Claude Opus 5.5,264 个关键值中对了 262 个,每 1,000 页 $16.42。Mistral OCR 性价比最高:264 个中对了 258 个,费用 $2.02。本文展示结果、需要注意的错误,以及捕捉这些错误的代码。

这些结果只是一个起点。我们的八个页面不是你的文档,所以要知道哪个模型最适合你的工作流,唯一可靠的办法是在你自己的扫描件上测试候选模型。

Tl;dr

  • 最准确:anthropic/claude-opus-5.5。性价比最高:mistral-ocr。最便宜的好结果:关闭推理的 inclusionai/ling-3.0-flash-vl,264 个中对了 254 个,每 1,000 页 $0.04。
  • 在一张褪色收据上,GPT-6 Luna 和 GPT-6.1 Sol 编造了卡号数字、wifi 密码和服务器名称。错误率不会显示这一点,所以要检查你实际会用到的值。
  • 关闭推理让我们测试的五个模型中有四个变得更快、更便宜,且没有丢失关键值。
  • 标记来自不同实验室的两个廉价模型意见不一致的单词,以每 1,000 页 $0.62 的成本捕捉到了 35 个错误单词中的 34 个。

我们如何测试

我们把每个页面从已知文本渲染出来,然后像扫描件一样劣化:轻微旋转、模糊、噪点和 JPEG 压缩。五个页面是普通类型:一张收据、一封含两个故意拼写错误的信件、一张勾选了复选框的表格、一份双栏通讯和一张工整的手写便条。三个页面被设计得很难:一张褪色的热敏收据、快速潦草的手写,以及一份由形近字符(0/O、1/I/L、5/S、8/B)构成的 12 个序列号的低分辨率传真,这样模型无法从上下文猜测它们。

Scanned thermal receipt from Cafe Verdigris, faded towards the bottom, with the card number and wifi password barely legible

我们用两种方式给每份转录打分。字符错误率(CER)是到真实文本的编辑距离除以文本长度,忽略换行和 Markdown。关键值是流水线会使用的 88 个字符串,每页 10 到 15 个,例如总额、日期、卡号数字、序列号和拼错的单词。每个值必须在三次运行中的每一次都完全出现,这样每个模型有 264 项检查。成本是每次请求计费的 usage.cost。模型来自截至 2026 年 10 月 8 日的 OpenRouter 目录。

哪个 OCR 模型在扫描文档上最准确?

表格按关键值正确数排序。

路线关键值正确数CER,所有页面CER,3 个困难页面每 1,000 页成本中位秒数
anthropic/claude-opus-5.5264 个中 262 个0.02%0.06%$16.427.2
mistral-ocr(PDF 引擎)264 个中 258 个0.14%0.26%$2.023.8
anthropic/claude-sonnet-5.5264 个中 256 个1.34%3.49%$7.684.2
google/gemini-3.8-flash264 个中 254 个0.15%0.28%$7.509.3
stepfun/step-5-preview264 个中 254 个0.42%1.11%$5.489.3
inclusionai/ling-3.0-flash-vl264 个中 254 个1.05%2.56%$0.127.2
anthropic/claude-haiku-5.5264 个中 253 个1.19%2.89%$0.463.8
x-ai/grok-4.7264 个中 252 个0.13%0.35%$12.8511.3
z-ai/glm-5.3-flashx264 个中 252 个0.33%0.86%$1.455.5
openai/gpt-6.1-sol264 个中 252 个0.65%1.74%$6.038.4
deepseek/deepseek-v4.1-flash264 个中 248 个0.20%0.48%$0.204.8
qwen/qwen3.8-flash264 个中 248 个0.74%1.49%$0.298.1
qwen/qwen3.8-27b264 个中 246 个0.92%2.31%$1.5410.8
xiaomi/mimo-v2.6-flash264 个中 246 个2.02%5.34%$0.583.1
mistralai/mistral-large-4-0264 个中 240 个4.61%12.19%$6.8047.9
openai/gpt-6-luna264 个中 232 个1.49%3.95%$0.547.8
Tesseract 5.5(本地,1 次运行)88 个中 46 个26.06%64.39%$058.1

在五个普通页面上,每个视觉模型的 CER 都低于 0.3%,而且全部 15 个模型都完美转录了工整的手写。大部分差异来自那张褪色收据。Opus 5.5 和 Mistral OCR 在该页面的全部 33 项检查中都正确,而 GPT-6 Luna 只对了 16 项。模型对图像 token 的计数方式不同(中位页面 DeepSeek 为 1,070 个提示 token,Grok 为 3,314 个),所以要在你自己的页面上测量成本。

哪个 OCR 模型适合你的工作

如果你的大部分工作是从以下开始原因
每个值都重要的困难扫描件Claude Opus 5.5264 个关键值中对了 262 个,每 1,000 页 $16.42
以合理价格处理大批量Mistral OCR(mistral-ocr PDF 引擎)264 个关键值中对了 258 个,每 1,000 页 $2.02
成本最低Ling 3.0 Flash VL,关闭推理264 个关键值中的 254 个,每 1,000 页 $0.04
含形近字符的序列号和 IDClaude Haiku 5.5 由 DeepSeek V4.1 Flash 复核这对组合在 35 个错误单词中标记出 34 个,每 1,000 页 $0.62
干净的打印页面,离线Tesseract 5.5在信件和简报上完美,且免费,但对褪色收据没有返回任何内容

当视觉模型读取一张难处理的扫描件时,会出什么问题?

最严重的错误是一个看起来合理、但页面上并不存在的值。以下是褪色收据底部的印刷内容,随后是一次 GPT-6 Luna 运行的结果:

CONTACTLESS 32.95
CARD ****9026
SERVED BY: PRIYA
WIFI: verdigris-guest / tide2026
NET                                 27.46

CONTACTLESS
CARD **** 9027

SERVED BY: SHEVA
WIFI: verdigris_guest / 1freecoffee

收据上没有 NET 这一行。卡号以 9026 结尾,姓名和密码是编造的。GPT-6 Luna 在三次运行中都更改了服务器名称和年份。GPT-6.1 Sol 在每次运行中都写入了不同的编造密码(flatwhite24、flat9am!、flatleaf)。我们还要求模型写入 [?] 而不是猜测。这没有帮助:七个模型中有四个从未使用该标记。

其他重复出现的错误:

  • O 和 0:15 个模型中有 13 个在至少一次运行中把两个传真序列号读错。只有 Claude Sonnet 5.5 和 GPT-6.1 Sol 每次都读对了。
  • 拼写修正:信件署名是“Senior Assosiate”,提示要求逐字复制拼写。45 份转录中有 16 份仍然“纠正”了它。
  • 勾选框:Claude Sonnet 5.5 和 Haiku 5.5 复制了表单的复选框标签,但丢失了哪个框被勾选。

第 1 步:将扫描件发送给视觉模型

视觉模型生成转录的方式与语言模型生成任何文本的方式相同,因此它倾向于写出文本通常会说出的内容,而不是页面上实际印刷的内容。提示要求它精确复制。这减少了问题,但并未消除:45 份转录中仍有 16 份纠正了信件中故意拼错的单词。你需要在 OPENROUTER_API_KEY 中提供一个 OpenRouter API key。图像以 base64 image_url 部分传入(参见 image inputs),因此 OpenRouter 上的每个视觉模型都接受同样的请求。将其保存为 transcribe.py,因为第 3 步和第 4 步会从中导入:

import base64
import mimetypes
import os

import requests

API = "https://openrouter.ai/api/v1/chat/completions"
HEADERS = {"Authorization": f"Bearer {os.environ['OPENROUTER_API_KEY']}"}
PROMPT = ("Transcribe all of the text in this scanned document, in reading order. "
          "Copy spelling, numbers and punctuation exactly as they appear, even if they "
          "look wrong. Return plain text only, with no Markdown and no commentary.")


def transcribe(path, model="deepseek/deepseek-v4.1-flash", reasoning={"effort": "none"}):
    mime = mimetypes.guess_type(path)[0] or "image/jpeg"
    with open(path, "rb") as f:
        image = f"data:{mime};base64," + base64.b64encode(f.read()).decode()
    body = {
        "model": model,
        "max_tokens": 16000,
        "messages": [{"role": "user", "content": [
            {"type": "text", "text": PROMPT},
            {"type": "image_url", "image_url": {"url": image}},
        ]}],
    }
    if reasoning:
        body["reasoning"] = reasoning
    response = requests.post(API, headers=HEADERS, json=body, timeout=300)
    response.raise_for_status()
    data = response.json()
    choice = data["choices"][0]
    if choice["finish_reason"] != "stop":
        raise ValueError(f"finish_reason={choice['finish_reason']}: the transcript is incomplete")
    return choice["message"]["content"], data["usage"]["cost"]


if __name__ == "__main__":
    text, cost = transcribe("receipt.jpg")
    print(text)
    print(f"\n${cost:.5f}")
HARBOR LANE MARKET
1180 Harbor Lane, Unit 4
Portland OR 97209
Tel (503) 555-0148
...
TOTAL 62.86
VISA ****4471 62.86
AUTH 08813C
...

$0.00022

每个字符都正确返回,成本约为百分之二美分。数据 URL 从文件扩展名获取类型,因此 PNG 和 WebP 扫描件也可以使用。要比较模型,只需更改 model 字符串,并对第 2 步中无法关闭推理的模型传入 reasoning=None。

finish_reason 检查之所以存在,是因为推理 token 会计入 max_tokens。在限制为 4,000 的情况下,Gemini 3.8 Flash 每次运行都花费 3,841 个 token 推理传真内容,并在序列号 4 中途停止。请求仍然返回了 200 OK 和 finish_reason: "length",因此如果没有该检查,流水线会在未察觉的情况下存储不完整的转录。

第 2 步:为转录关闭推理

当模型需要规划答案时,推理是有帮助的。从页面复制文本不需要规划,因此额外的 token 主要增加成本和时间。这就是 transcribe 默认发送 "reasoning": {"effort": "none"} 的原因(参见 reasoning tokens)。我们对所有允许关闭推理的模型重新运行了完整测试:

模型关键值,开启 → 关闭CER,开启 → 关闭每 1,000 页成本,开启 → 关闭中位秒数,开启 → 关闭
mistralai/mistral-large-4-0240 → 2494.61% → 0.15%$6.80 → $1.6847.9 → 5.6
inclusionai/ling-3.0-flash-vl254 → 2541.05% → 0.19%$0.12 → $0.047.2 → 4.0
openai/gpt-6-luna232 → 2321.49% → 1.08%$0.54 → $0.227.8 → 3.8
deepseek/deepseek-v4.1-flash248 → 2490.20% → 0.23%$0.20 → $0.164.8 → 4.5
qwen/qwen3.8-27b246 → 2440.92% → 2.28%$1.54 → $0.7710.8 → 8.9

开启推理时,一次 Mistral Large 4 请求推理了 626 秒且没有返回文本(finish_reason: "error")。Qwen3.8 27B 在开启推理时表现更好,因此请测试你自己的页面。Gemini 3.8 Flash、Grok 4.7 和 GLM 5.3 FlashX 不允许关闭推理,因此传入 reasoning=None 以省略该字段。否则请求会失败并显示:

400 {"error":{"message":"Reasoning is mandatory for this endpoint and cannot be disabled.","code":400,"metadata":{"provider_name":null}}}

第 3 步:交叉检查来自不同实验室的两个模型

在模糊图像中,O 和 0 这类形近字符很难区分。每个模型依据自身训练做出判断,因此来自不同实验室的两个模型很少做出相同的错误选择。它们的转录结果出现分歧之处,通常就是图像不清晰的地方。我们测试了六个模型中的每一对组合。表现最好的是 Claude Haiku 5.5,由 DeepSeek V4.1 Flash 进行核对。Haiku 在 2,964 个词中出现了 35 个词错误,而 DeepSeek(如第 2 步中那样关闭推理)对其中的 34 个持不同意见,并有 10 次误报。这一组合每 1,000 页花费 $0.62,是 Mistral OCR 的三分之一。两者使用相同的 API,因此核对只需额外一次调用。调用 Haiku 时使用 reasoning=None,因为它在我们的表格中就是这样运行的:

import difflib

from transcribe import transcribe  # the function from step 1

a, _ = transcribe("fax.jpg", model="anthropic/claude-haiku-5.5", reasoning=None)
b, _ = transcribe("fax.jpg", model="deepseek/deepseek-v4.1-flash")
a, b = a.split(), b.split()
for tag, i1, i2, j1, j2 in difflib.SequenceMatcher(None, a, b, autojunk=False).get_opcodes():
    if tag != "equal":
        print("check:", " ".join(a[i1:i2]), "vs", " ".join(b[j1:j2]))
check: 8LI5-QS0B5O vs 8LI5-QS0B50
check: 30HY-1OHLG7 vs 30HY-10HLG7

这些是大多数模型都读错的两个序列号。如果两个模型以相同方式误读某个字符,就不会有任何标记。在我们的运行中这种情况出现过一次,一个 O 被两者都读成了零,因此请尽可能对照列表或校验和来验证 ID。这种比较还假设两个模型以相同顺序读取页面。在三次运行中的一次,模型以不同顺序列出了传真中的两栏,结果每个序列号都显示为不一致。

第 4 步:使用 Mistral OCR 作为专用引擎

Mistral OCR 是一个仅针对文档 OCR 训练的模型。在 OpenRouter 上,它作为 file-parser 插件中的引擎运行,该插件接受 PDF,价格为每 1,000 页 $2。该插件会在你的模型看到文件之前对文件进行 OCR。它将文本作为响应上的文件注释返回,因此你指定的模型只需回复即可。将扫描件包在一页 PDF 中,并用任何廉价模型发送:

import base64
import html
import io
import os

import requests
from PIL import Image, ImageSequence

from transcribe import API, HEADERS  # from step 1


def mistral_ocr(path):
    if path.lower().endswith(".pdf"):  # scanned PDFs go as they are
        with open(path, "rb") as f:
            raw = f.read()
    else:  # wrap an image in a PDF in memory, one page per frame of a multipage TIFF
        pages = [p.convert("RGB") for p in ImageSequence.Iterator(Image.open(path))]
        buf = io.BytesIO()
        pages[0].save(buf, "PDF", save_all=True, append_images=pages[1:])
        raw = buf.getvalue()
    pdf = "data:application/pdf;base64," + base64.b64encode(raw).decode()
    pdf_name = os.path.splitext(os.path.basename(path))[0] + ".pdf"
    body = {
        "model": "upstage/solar-mini4",  # any cheap text model; it only has to reply
        "max_tokens": 5,
        "messages": [{"role": "user", "content": [
            {"type": "text", "text": "Reply OK."},
            {"type": "file", "file": {"filename": pdf_name, "file_data": pdf}},
        ]}],
        "plugins": [{"id": "file-parser", "pdf": {"engine": "mistral-ocr"}}],
    }
    response = requests.post(API, headers=HEADERS, json=body, timeout=300)
    response.raise_for_status()
    data = response.json()
    parts = data["choices"][0]["message"]["annotations"][0]["file"]["content"]
    texts = [p["text"] for p in parts if p.get("type") == "text"]  # skip extracted image parts
    text = "\n".join(t for t in texts if not t.startswith(("<file", "</file")))
    return html.unescape(text), data["usage"]["cost"]


text, cost = mistral_ocr("receipt_faded.jpg")
print(text)
print(f"\n${cost:.5f}")
CAFE VERDIGRIS
22 Quayside Row
Bristol BS1 4QA
VAT No. GB 318 7742 09

27/09/2026 08:17 TILL 2 CHK 0193
...
|  CONTACTLESS | 32.95  |
|  CARD ***9026 |   |

SERVED BY: PRIYA
WIFI: verdigris-guest / tide2026

$0.00202

褪色收据上的每个值都正确返回,包括卡号数字和密码。Mistral OCR 旨在保留布局,因此其输出为 Markdown:收据行变成表格,标题获得 ##,而 & 返回为 &amp;。这就是该函数反转义 HTML 实体的原因。如果你需要纯文本,请去除 Markdown;如果接下来由 LLM 读取结果,则保留它。

对于 OCR,LLM 比 Tesseract 更好吗?

是的,但在干净的打印页面上除外。Tesseract 5.5 逐行读取字符形状,因此它在清晰的印刷体上表现良好,但在噪声和手写体上会失效。它完美地转录了信件和新闻简报。对于褪色收据,它没有返回任何内容;在杂乱的手写体上,11 个值中只返回 1 个;在传真上,15 个值中只返回 4 个。

常见问题

哪个 LLM 最适合 OCR?

在我们的测试中是 Claude Opus 5.5。Gemini 3.8 Flash 和 Grok 4.7 的错误率几乎同样低,但漏掉了更多序列号。DeepSeek V4.1 Flash 和 Ling 3.0 Flash VL 以每 1,000 页低于 $0.20 的价格接近了它们。

如何对扫描版 PDF 进行 OCR?

如第 4 步那样,使用 mistral-ocr 引擎发送它。或者,将每一页渲染为图像供视觉模型使用,或者将 PDF 直接发送给可读取 PDF 的模型。免费的文本解析器无法处理扫描件,因为扫描件没有文本层。

最准确的 OCR 模型是什么?

Claude Opus 5.5 在我们的八份扫描件中正确识别了 264 个关键值中的 262 个,字符错误率为 0.02%。Mistral OCR 以 264 个中正确 258 个位居第二,成本仅为八分之一。

使用 LLM 进行 OCR 的成本是多少?

在我们的测试中,成本从每 1,000 页 $0.04(关闭推理的 Ling 3.0 Flash VL)到 $16.42(Claude Opus 5.5)不等。Mistral OCR 的成本为每 1,000 页 $2。模型对图像 token 的计数方式不同,因此请在你自己的页面上测量成本。

Mistral OCR 比视觉模型更好吗?

它是我们测试中性价比最高的:每 1,000 页 2.02 美元,264 个关键值中识别出 258 个,仅次于 Claude Opus 5.5。它返回的 Markdown 保留了版式,而视觉模型则返回你要求的任意格式。

LLM 能读手写体吗?

能。全部 15 个视觉模型都完美转录了我们工整的笔记。对于潦草的手写,大多数模型的字符准确率仍保持在 99% 以上,错误主要出在姓名上(把“Achterberg”识别成“Ackterberg”)。

后续步骤

来源:OpenRouter Announcements · openrouter.ai