OCR API 接入看起来只是“传一张图、拿回文字”,但真正决定项目能不能上线的,往往是另外几件事:图片格式和大小是否合规、该用同步还是异步、坐标和字段怎么定义、识别结果不稳定时怎么兜底。这些问题在 demo 阶段不明显,一到真实票据和扫描件就会暴露。

本文以 OpenAI 兼容接口和常见 OCR 服务为例,讲清从上传、识别到结构化输出的完整链路,并给出 curl、Python 示例、预处理建议、报错排查和迁移检查。

说明:OpenAI、Google、Microsoft、阿里云、腾讯云、百度及其他文中提及的品牌均为其权利人的商标。本文仅用于技术说明,与相关厂商无隶属关系;接口、模型、图片限制和计费规则以各平台官方文档及站内页面为准。

一、接口与鉴权

OCR 接入通常有两种形态,先确认你用的是哪一种:

  • 专用 OCR 接口:平台提供独立的识别端点,输入图片或 PDF,返回文本块、坐标、置信度,部分还支持票据、证件、表格等预设模板。
  • 多模态对话接口:走 /v1/chat/completions,在消息里放图片,让支持视觉的模型输出文字或 JSON。适合复杂版式、手写和需要理解的任务。

OpenAI 兼容接口的鉴权通常是:

POST https://ai.zhongshangqihang.com/v1/chat/completions
Authorization: Bearer sk-你的密钥
Content-Type: application/json

Base URL 只写到 /v1,不要自己拼完整路径后再交给 SDK,否则容易出现重复拼接和 404。部分云厂商使用 Access Key + Secret Key 签名,或者先换 access_token,这类接口不能直接套用 Bearer 写法,以官方文档为准。

二、图片怎么传:URL、Base64 与文件上传

常见上传方式有三种,选择取决于图片在哪里、接口支持什么:

方式 适合 注意事项
公网 URL 图片已在对象存储或 CDN 服务端必须能访问;链接要有足够有效期;避免公开可读的敏感图片
Base64 data URL 本地文件、内网图片、临时测试 体积约增大三分之一;前缀和编码错误会直接 400
multipart 文件上传 厂商专用 OCR 接口 字段名、编码和文件大小限制以文档为准

Base64 的格式通常是 data:image/jpeg;base64,<编码内容>。常见错误是漏掉 data:image/jpeg;base64, 前缀、把换行带进字符串,或者用 URL-safe 编码后没有还原。

格式方面,JPEG、PNG、WebP 的支持最普遍,GIF、TIFF、BMP 和 PDF 是否支持要看具体接口。单张图片的大小限制各家不同,常见在几 MB 到 20 MB 量级;超限时通常返回 413 或 400。分辨率也不是越高越好:文字太小会糊,图片太大则可能被服务端缩放,反而丢失细节。扫描件常用 200 到 300 DPI,手机拍照可以把长边控制在 1600 到 2400 像素左右,再用真实样本验证。

三、同步还是异步

同步接口适合单张图片、低延迟、交互式场景:请求发出去,等服务端返回识别结果。它的风险是超时,尤其是大图、多页 PDF 或服务端排队时,连接可能被网关或客户端提前断开。

异步接口适合批量任务和多页文档,流程通常是:

  1. 提交图片或文件,拿到任务 ID。
  2. 轮询任务状态,或等待回调通知。
  3. 状态完成后,下载或查询识别结果。
  4. 结果通常有保留期限,要及时落库。

选择时不要只看接口是否“简单”。如果一次请求可能跑几十秒,同步调用会占用连接和线程,重试也会放大压力。异步任务则要处理幂等、重复提交、轮询退避和结果过期。生产环境建议给任务 ID 建表,记录提交时间、状态、重试次数和结果位置。

四、curl 调用示例

下面用 OpenAI 兼容的对话接口做一次图片识别。模型名以 模型价格 为准,必须选择支持图片输入的模型:

export ZSQH_API_KEY="sk-你的密钥"

curl "https://ai.zhongshangqihang.com/v1/chat/completions" \
  -H "Authorization: Bearer $ZSQH_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "gpt-5",
    "messages": [
      {
        "role": "user",
        "content": [
          {"type": "text", "text": "识别图片中的文字,按行输出,不要解释。"},
          {"type": "image_url", "image_url": {"url": "https://example.com/invoice.jpg"}}
        ]
      }
    ]
  }'

如果返回 401,先查 Key 和请求头;如果返回 400,先查图片 URL 是否可访问、格式是否受支持;如果返回 404,检查 Base URL 和路径是否重复拼接。

五、Python 调用与结构化输出

真实项目很少只要一段纯文本,更多是要字段。可以让模型按给定 schema 输出 JSON,再在本地校验:

import base64
import json
import os
from openai import OpenAI

client = OpenAI(
    api_key=os.environ["ZSQH_API_KEY"],
    base_url="https://ai.zhongshangqihang.com/v1",
)

with open("invoice.jpg", "rb") as f:
    image_b64 = base64.b64encode(f.read()).decode()

schema_hint = '{"invoice_no": "", "date": "", "amount": "", "seller": ""}'
resp = client.chat.completions.create(
    model=os.environ["OCR_MODEL"],  # 填价格页上支持图片输入的模型名
    messages=[{
        "role": "user",
        "content": [
            {"type": "text", "text": f"识别票据并按此 JSON 输出,找不到的字段填 null:{schema_hint}"},
            {"type": "image_url", "image_url": {"url": f"data:image/jpeg;base64,{image_b64}"}},
        ],
    }],
)
data = json.loads(resp.choices[0].message.content)
print(data)

这段代码能跑通,但不等于能上线。模型输出可能带 Markdown 代码块、解释文字或多余字段,json.loads 会直接失败。实践上要做四件事:优先使用平台支持的 JSON 模式或结构化输出;解析前去掉代码块标记;用 Pydantic 或 jsonschema 校验字段类型;校验失败时把错误信息带回模型重试一次,仍失败就转人工。金额、日期、税号这类字段不能只信模型,至少加正则和交叉校验。

六、坐标、表格与结构化字段

传统 OCR 通常返回每个文本块的内容、坐标和置信度;多模态模型则按提示词输出,坐标不一定可靠。处理坐标时先确认三件事:

  • 坐标格式:是四点多边形、[x1, y1, x2, y2],还是 [x, y, w, h]。
  • 单位:像素还是归一化到 0 到 1000 或 0 到 1。
  • 原点:多数以左上角为原点,但旋转图片和裁剪后的坐标要换算。

如果你在识别前缩放了图片,返回坐标也要按比例映射回原图,否则高亮框会整体偏移。表格场景不要只让模型“输出表格”,最好明确行、列、合并单元格和空值规则;复杂表格建议用专门的表格识别接口,再交给模型做字段整理。

结构化字段要提前定义 schema:字段名、类型、是否必填、空值怎么表示、日期和金额如何规范化。票据识别还要考虑币种、税额、价税合计的勾稽关系。置信度适合用来分流:低置信度字段进入人工复核,而不是直接丢弃或盲目相信。

七、预处理:让识别更稳

多数识别误差在请求发出前就已经决定。可以按下面的顺序处理:

  1. 读取 EXIF 方向并旋转,避免图片横躺。
  2. 裁剪到文档区域,去掉大面积背景和无关物体。
  3. 做小角度纠偏,倾斜明显时再检测边缘。
  4. 控制分辨率:扫描件 200 到 300 DPI,手机图长边 1600 到 2400 像素起步。
  5. 褪色或低对比度文档可转灰度并适度增强对比度。
  6. 轻量降噪和锐化,避免过度处理产生伪影。
  7. 多页 PDF 或长截图按页切分,切分处保留少量重叠。
  8. 上传前按需去除元数据,尤其是含定位信息的手机照片。

注意,模糊图片靠锐化救不回来,低分辨率文字放大也不会凭空出现细节。预处理的目标是减少干扰,不是把图片“修得好看”。每次调整预处理参数,都用同一批真实样本对比字段准确率,不要只看单张效果。

八、常见报错与排查

报错 常见原因 处理
401 Unauthorized Key 错误、过期、请求头写法不对 检查 Bearer 格式、空格和环境变量
403 Forbidden 无模型权限、地区限制、子账号权限不足 检查模型授权和账号策略
400 图片错误 Base64 前缀错、文件损坏、格式不支持 解码验证文件头,换 JPEG 或 PNG
413 Payload Too Large 图片或请求体超限 压缩、缩放、裁剪,或改用文件上传/异步
415 Unsupported Media Type 扩展名与实际格式不符 转换格式,确认 Content-Type
429 Too Many Requests 频率或并发超限 降低并发,指数退避重试
超时或 502 大图、长文档、服务端排队 改用异步任务或缩小请求
结果为空或乱序 方向错误、分辨率低、版式复杂 旋转、提高清晰度、分栏切块

排查时先记录请求 ID、模型名、图片尺寸、格式和耗时。很多“模型效果差”的问题,实际是图片传错、坐标没换算或字段 schema 不一致。

九、迁移与上线检查

从一家 OCR 服务迁到另一家,通常不止改 base_url 和 api_key。上线前至少确认:

  • 新接口是专用 OCR 还是对话式视觉,请求体结构是否兼容。
  • 支持哪些图片格式、单张大小、页数和分辨率限制。
  • 同步还是异步,任务结果保留多久,回调是否需要公网地址。
  • 坐标格式、单位、原点是否与旧服务一致。
  • 字段 schema、语言支持和票据类型是否覆盖现有业务。
  • 限流、并发和计费方式是否满足峰值需求。
  • 是否有灰度方案,可以先切一部分流量对比准确率。
  • 是否有回滚路径,旧 Key 和旧接口在切换后保留一段时间。

工程上建议把 OCR 封装成独立的客户端接口,例如 recognize(image, schema) -> Result,把厂商差异收在适配层里。这样换服务时业务代码不用重写,也便于用同一套评测集对比新旧效果。模型、限制和价格以 模型价格 为准,价格和规格会调整。

常见问题

OCR API 支持 PDF 吗?

取决于接口。专用 OCR 服务通常支持 PDF,多页文档多走异步任务;OpenAI 兼容的对话接口一般按图片处理,PDF 需要先拆页转图,或使用平台提供的文件接口。接入前先看文档。

图片最大能传多大?

各家限制不同,常见是单张几 MB 到 20 MB,同时还有像素和页数限制。与其卡着上限,不如在上传前压缩和缩放,既降低超时概率,也减少传输和计费成本。

为什么识别结果的顺序乱了?

常见原因是多栏版式、表格和图片混排。传统 OCR 按坐标排序,多模态模型按阅读顺序生成,两者都可能出错。可以按栏切块、要求按区域输出,或改用带版式分析的接口。

结构化字段用什么方式最稳?

先定义明确的 schema,再让模型按 JSON 输出,并在本地做类型、正则和业务校验。能使用平台的结构化输出能力就优先使用;没有的话,加一次带错误信息的重试,仍失败转人工复核。

从一家 OCR 服务迁到另一家要改什么?

除了 Base URL 和 Key,还要检查请求结构、同步/异步模式、图片限制、坐标格式、字段 schema 和限流规则。建议先小流量灰度,用真实样本对比字段准确率,再全量切换。

下一步

想先跑通一条请求,可以从 注册页 创建低额度 Key,用一张真实票据或扫描件测试上传、识别和 JSON 输出。正式选型时再看 模型价格,并用一小批标注好的图片对比字段准确率。价格和模型会调整,以站内当前展示为准。

相关阅读: