OpenRouter 实测最佳 OCR 模型:Claude Opus 5.5 最准,Mistral OCR 性价比最高
Best OCR Model for Scanned Documents, Measured
OpenRouter 用 8 张自制扫描页(264 项关键值)实测 15 个视觉模型、Mistral OCR 和 Tesseract 做 OCR 的表现。
OpenRouter 用可核对的合成扫描页实测 15 个视觉模型做 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。本文展示测试结果、需要警惕的错误,以及用来捕捉这些错误的代码。
这些结果只是一个起点。我们的八张页面不是你的文档,所以想知道哪个模型最适合你的工作流,唯一可靠的办法是拿候选模型在你自己的扫描件上测一遍。
要点速览
- 最准确:
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 密码和服务器名。错误率反映不出这类问题,所以要检查你实际会用到的那些值。
- 关闭推理后,我们测试的五个模型里有四个变得更快、更便宜,关键值一个没丢。
- 把来自不同实验室的两个便宜模型意见不一致的词标出来,能抓住 35 个错词中的 34 个,每 1,000 页花费 $0.62。
我们怎么测的
我们把每页从已知文本渲染出来,再像扫描件那样劣化:轻微旋转、模糊、噪点和 JPEG 压缩。五页是普通内容:一张收据、一封故意写错两个词的信、一张勾了选框的表格、一份双栏通讯和一张工整的手写便条。另外三页是刻意做难的:一张褪色的热敏收据、一段快速潦草的手写,以及一份低分辨率传真,上面有 12 个用形近字符(0/O、1/I/L、5/S、8/B)拼成的序列号,让模型没法靠上下文猜出来。

我们用两种方式给每份转录打分。字符错误率(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 处中途停止。请求仍然返回了带 finish_reason: "length" 的 200 OK,所以没有这项检查,流水线会在不知不觉中存下不完整的转录。
第 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 个。
常见问题
做 OCR 用哪个 LLM 最好?
在我们的测试中是 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 比视觉模型更好吗?
它是我们测试中性价比最高的:每 1000 页 2.02 美元,264 个关键值中识别对了 258 个,仅次于 Claude Opus 5.5。它返回的 Markdown 能保留版面布局,而视觉模型则按你要求的格式返回结果。
大语言模型能识别手写体吗?
能。15 个视觉模型都完美转录了我们工整的笔记。面对潦草的手写,大多数模型的字符准确率仍在 99% 以上,出错的主要是人名(把“Achterberg”认成了“Ackterberg”)。
下一步
来源:OpenRouter:Announcements · openrouter.ai