一个返回可验证边界框的 OCR API
大多数 OCR API 都会返回边界框,但坐标系各不相同,而一个框只告诉你在哪、不告诉你有多确定。这是一份面向开发者的指南,讲如何拿到带源坐标的 OCR,外加一个匹配比例,让你知道每个值有多少真的在页面上被找到了。
边界框是你验证 OCR 的依据。一个光秃秃的字符串只告诉你模型自以为读到了什么;而一个框会告诉你它是在页面的什么位置读到的,于是你(或你的审核者、或你的代码)可以拿这个值跟原件核对,而不是盲目相信。如果你要把 OCR 接进任何会被审计的场景——发票、报销、KYC、档案管理——“模型返回了 total: 2,045”是不够的;你得能指出 2,045 到底是从哪些像素上来的。
好消息是,大多数主流 OCR API 确实会返回边界框。问题在于,一旦你真动手开发,它们会在三个要紧的地方各不相同——坐标系、是否一并给你结构化字段(而不只是原始文本),以及那个逐值置信度究竟度量的是什么。本文会把这三点逐一讲透,并展示一个带源坐标、再加上一个字符覆盖匹配比例的 OCR API 在实践中是什么样子。
大多数 OCR API 都返回框——区别在这里
Google Cloud Vision、Tesseract、Amazon Textract 和 Azure AI Document Intelligence 都会随文本一起返回几何信息。它们的分歧在于坐标系、在于你拿到的是结构化字段还是只有原始文本加版式,以及那个置信度数字到底意味着什么。这些都是经过核实的事实,而非营销话术——拿它们来估一估这套集成在你的技术栈里要花多少工。
| API | 坐标系 | 结构化字段 | 逐值置信度 |
|---|---|---|---|
| Google Cloud Vision | boundingPoly 顶点,单位为源图像的像素 | 仅文本 + 几何信息(结构化键值对属于 Google Document AI,是单独的产品) | 每个词/符号的识别置信度(0–1) |
| Tesseract | hOCR / TSV 框,单位为像素(自托管,无 API) | 无——仅原始文本 + 版式 | 每个词的识别置信度(0–100) |
| Amazon Textract | BoundingBox,按页面宽高归一化到 0–1(外加 Polygon) | 表单/表格用 AnalyzeDocument;票据用 AnalyzeExpense | 每个 block 的识别置信度(%) |
| Azure Document Intelligence | 边界多边形,单位为像素(图像)或英寸(PDF) | 预构建/自定义模型 | 每个词的识别置信度 |
| space-ocr | bbox 归一化到 0–1000(外加带方向的 vertices) | 内置模板 + 自定义字段,支持明细行 | match_ratio——该值的字符在页面上被找到的比例——外加 bbox_source |
有两点值得留意。第一,坐标单位没法直接拿去复用——Vision/Tesseract/Azure 的像素框绑死在你发送的那张图像上,而归一化的框(Textract、space-ocr)在缩放后依然有效。第二,置信度这一列的含义各不相同:大多数 API 报的是识别置信度(模型有多确定),这跟“度量一个返回值有多少真的在页面上被定位到”根本不是一回事。
框是怎么推导出来的,和它的格式同样要紧。 在 space-ocr 中,语言模型返回的只是每个字段的文本,以及它用到了哪些词元(word token)的提示,框本身从来不由它给出。引擎随后拿这段文本,去跟视觉 OCR 在页面上实际检测到的符号做逐字符匹配,于是框就落在这些字符被找到的真实像素上,每个值也据此得到一个 match_ratio,表示它被定位到的比例有多高。这些词元提示可能带噪声(有时会在重复出现的行之间张冠李戴),所以系统用列一致性和行一致性检查来验证它们,而不是盲目相信。这正是“模型断言的坐标”和“回过头来跟页面核对过的坐标”之间的区别。
space-ocr 为每个值返回什么
每个提取出的值旁边,你都会拿到四样东西,所以坐标绝不只是一个你不得不信的数字:
bbox——一个轴对齐矩形{ xmin, ymin, xmax, ymax },由整数构成,位于一个 0–1000 归一化网格上(0,0 = 左上角,1000,1000 = 右下角),与图像的像素尺寸无关。vertices——正好四个有序顶点(左上、右上、右下、左下),构成一个带方向的框,会跟随单据的倾斜角度,所以一张拍歪的手机照片也能干净地框住。match_ratio——该值的字符中,实际在页面上被定位到的比例(0–1)。当达到 ≥ 0.85 时,该字段被视为可信匹配;1.0表示每一个字符都被找到了。bbox_source——一个标签,说明坐标是怎么推导出来的(例如逐字符匹配路径对应vision_symbol_match,当匹配比例低于阈值时则为low_confidence)。
{
"total": {
"value": "2,045",
"bbox": { "xmin": 381, "ymin": 803, "xmax": 500, "ymax": 825 },
"vertices": [
{ "x": 380, "y": 804 }, { "x": 500, "y": 801 },
{ "x": 500, "y": 823 }, { "x": 381, "y": 826 }
],
"match_ratio": 1.0,
"bbox_source": "vision_symbol_match"
}
}像素还是归一化?换算一次,缩放就不再出问题
OCR 集成里有一类反复出现的 bug:像素坐标绑死在你上传的那张图像上——一旦为了存储而缩放或重新压缩,或者漏掉一个 EXIF 旋转标志,叠上去的框就会漂移、被裁切,或者落到错误的文字上。归一化坐标能避开一整类这样的 bug:一个 0–1000 的框可以套到同一页面的任意一种渲染上。
要在显示出来的图像上画一个框,只需换算一次:
- SVG 叠加层——给 SVG 设
viewBox="0 0 1000 1000",然后原样画出bbox/vertices。 - 绝对定位的 div——
leftPct = xmin / 1000 * 100、topPct = ymin / 1000 * 100、widthPct = (xmax - xmin) / 1000 * 100、heightPct = (ymax - ymin) / 1000 * 100。 - 换回像素——
pixel_x = bbox_x / 1000 * image_width、pixel_y = bbox_y / 1000 * image_height。
引擎在加载时还会应用 EXIF 方向,所以它返回的坐标一开始就跟显示出来的图像对齐——一张旋转过的手机照片(方向 6/8)也不用你这边再做一遍纠正。
curl -s https://api.space-ocr.com/ocr/fields \
-H "Authorization: Bearer $SPACE_OCR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"image": "https://example.com/receipt.jpg",
"imageType": "url",
"fields": [
{ "name": "vendor", "type": "string" },
{ "name": "total", "type": "string" }
]
}'置信度分数和匹配比例不是一回事
这个区别值得你记牢。大多数 OCR API 都会给出一个识别置信度——一个反映引擎对自己读取结果有多确定的数字,依据的是字体清晰度、图像质量之类的因素。它有用,但说到底是模型在给自己的作业打分。而匹配比例度量的是外部的事实:在模型返回的那个值的字符里,有多少真的能在页面级 OCR 检测到的符号中被找到。一个值可能带着识别置信度被返回,却依然跟页面上的任何东西都对不上;一个偏低的 match_ratio 恰好能逮住这种情况。把它当成一道闸门——对 match_ratio < 0.85 排序或筛选,就能把那少数几个值得人工瞥一眼的值挑出来,而不必把所有东西都重新核对一遍。
先验证,再查询——无需重跑 OCR
数据留得下来,坐标才最有用。用 POST /upload 把图像推入一张表,再用 GET /view 在服务端查询它——where、sort、select、limit、offset——比如把所有 match_ratio 偏低或 total >= 40000 的行拉出来,既不重跑 OCR,也不再付一次费。每个返回值都保留各自的 bbox/vertices(也可以用 boxes=0 把它们去掉,换一个更轻量的响应体)。关于验证工作流的深入讲解,参见 用边界框验证 OCR 和 OCR 审计记录。
如何从 API 拿到可验证的边界框
- 请求字段把图像 POST 到 /ocr/fields,imageType 设为 'url' 或 'base64',再附上一个 templateId 或你自己的 fields 数组。引擎接收的是栅格图像(JPEG、PNG、GIF、BMP、TIFF、WebP)。
- 读取坐标每个值返回一个位于 0–1000 网格上的 bbox { xmin, ymin, xmax, ymax }、四个带方向的顶点、一个 match_ratio,以及一个 bbox_source。
- 叠加或换算用 viewBox 为 '0 0 1000 1000' 的 SVG 画框,或用 pixel_x = bbox_x / 1000 * image_width 换算成像素。EXIF 旋转已经应用过,所以框跟显示出来的图像对齐。
- 对匹配比例设闸门把 match_ratio 在 0.85 及以上当作可信匹配;低于这个的就挑出来供人工瞥一眼,而不必把每个值都重新核对一遍。
- 存储并查询用 /upload 把图像推入一张表,再用 GET /view(where、sort、select)查询它——坐标会保留下来,既不重跑 OCR,也不再付一次费。