OpenRouter 实测扫描件 OCR:Claude Opus 5.5 最准,Mistral OCR 性价比最高
Best OCR Model for Scanned Documents, Measured
OpenRouter 用自建的八页扫描件测试 15 个视觉模型、Mistral OCR 和 Tesseract,Claude Opus 5.5 在 264 项关键值中答对 262 项,每千页成本 16.42 美元,准确率最高。
作者自建八页扫描件实测 15 个视觉模型与 Mistral OCR,给出准确率、成本与可复用的交叉校验代码。
把扫描文档转成可靠的文本很难。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 个序列号的低分辨率传真,这样模型无法从上下文猜测它们。

我们用两种方式给每份转录打分。字符错误率(CER)是到真实文本的编辑距离除以文本长度,忽略换行和 Markdown。关键值是流水线会使用的 88 个字符串,每页 10 到 15 个,例如总额、日期、卡号数字、序列号和拼错的单词。每个值必须在三次运行中的每一次都完全出现,这样每个模型有 264 项检查。成本是每次请求计费的 usage.cost。模型来自截至 2026 年 10 月 8 日的 OpenRouter 目录。
哪个 OCR 模型在扫描文档上最准确?
表格按关键值正确数排序。
| 路线 | 关键值正确数 | CER,所有页面 | CER,3 个困难页面 | 每 1,000 页成本 | 中位秒数 |
|---|---|---|---|---|---|
anthropic/claude-opus-5.5 | 264 个中 262 个 | 0.02% | 0.06% | $16.42 | 7.2 |
mistral-ocr(PDF 引擎) | 264 个中 258 个 | 0.14% | 0.26% | $2.02 | 3.8 |
anthropic/claude-sonnet-5.5 | 264 个中 256 个 | 1.34% | 3.49% | $7.68 | 4.2 |
google/gemini-3.8-flash | 264 个中 254 个 | 0.15% | 0.28% | $7.50 | 9.3 |
stepfun/step-5-preview | 264 个中 254 个 | 0.42% | 1.11% | $5.48 | 9.3 |
inclusionai/ling-3.0-flash-vl | 264 个中 254 个 | 1.05% | 2.56% | $0.12 | 7.2 |
anthropic/claude-haiku-5.5 | 264 个中 253 个 | 1.19% | 2.89% | $0.46 | 3.8 |
x-ai/grok-4.7 | 264 个中 252 个 | 0.13% | 0.35% | $12.85 | 11.3 |
z-ai/glm-5.3-flashx | 264 个中 252 个 | 0.33% | 0.86% | $1.45 | 5.5 |
openai/gpt-6.1-sol | 264 个中 252 个 | 0.65% | 1.74% | $6.03 | 8.4 |
deepseek/deepseek-v4.1-flash | 264 个中 248 个 | 0.20% | 0.48% | $0.20 | 4.8 |
qwen/qwen3.8-flash | 264 个中 248 个 | 0.74% | 1.49% | $0.29 | 8.1 |
qwen/qwen3.8-27b | 264 个中 246 个 | 0.92% | 2.31% | $1.54 | 10.8 |
xiaomi/mimo-v2.6-flash | 264 个中 246 个 | 2.02% | 5.34% | $0.58 | 3.1 |
mistralai/mistral-large-4-0 | 264 个中 240 个 | 4.61% | 12.19% | $6.80 | 47.9 |
openai/gpt-6-luna | 264 个中 232 个 | 1.49% | 3.95% | $0.54 | 7.8 |
| Tesseract 5.5(本地,1 次运行) | 88 个中 46 个 | 26.06% | 64.39% | $0 | 58.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.5 | 264 个关键值中对了 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 |
| 含形近字符的序列号和 ID | Claude 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 / tide2026NET 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-0 | 240 → 249 | 4.61% → 0.15% | $6.80 → $1.68 | 47.9 → 5.6 |
inclusionai/ling-3.0-flash-vl | 254 → 254 | 1.05% → 0.19% | $0.12 → $0.04 | 7.2 → 4.0 |
openai/gpt-6-luna | 232 → 232 | 1.49% → 1.08% | $0.54 → $0.22 | 7.8 → 3.8 |
deepseek/deepseek-v4.1-flash | 248 → 249 | 0.20% → 0.23% | $0.20 → $0.16 | 4.8 → 4.5 |
qwen/qwen3.8-27b | 246 → 244 | 0.92% → 2.28% | $1.54 → $0.77 | 10.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:收据行变成表格,标题获得 ##,而 & 返回为 &。这就是该函数反转义 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