跳到正文
OpenRouter:Announcements·· 19 小时前精选AI 评分73

OpenRouter 实测最佳 OCR 模型:Claude Opus 5.5 最准,Mistral OCR 性价比最高

Best OCR Model for Scanned Documents, Measured

AI 导读

OpenRouter 用 8 张自制扫描页(264 项关键值)实测 15 个视觉模型、Mistral OCR 和 Tesseract 做 OCR 的表现。

推荐理由

OpenRouter 用可核对的合成扫描页实测 15 个视觉模型做 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。本文展示测试结果、需要警惕的错误,以及用来捕捉这些错误的代码。

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

要点速览

  • 最准确: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)拼成的序列号,让模型没法靠上下文猜出来。

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
外形相似的字符导致的序列号和 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 / 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 处中途停止。请求仍然返回了带 finish_reason: "length" 的 200 OK,所以没有这项检查,流水线会在不知不觉中存下不完整的转录。

第 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 个。

常见问题

做 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