一个返回可验证边界框的 OCR API
大多数 OCR API 都会返回边界框——但坐标系各不相同,而一个框只告诉你在哪,不告诉你有多确定。这是一份面向开发者的指南:如何获得带源坐标的 OCR,外加一个匹配比例,告诉你每个值有多少真的在页面上被找到了。
边界框是你验证 OCR 的依据。一个光秃秃的字符串只告诉你模型自以为读到了什么;而一个框告诉你它是在页面的什么位置读到的,于是你(或你的审核者、或你的代码)可以拿这个值跟原件核对,而不是盲目相信。如果你要把 OCR 接入任何会被审计的场景——发票、报销、KYC、档案管理——“模型返回了 total: 2,045”是不够的;你得能指出 2,045 究竟是从哪些像素上来的。
好消息是:大多数主流 OCR API 确实会返回边界框。问题在于,一旦你真动手开发,它们会在三个要紧的地方各不相同——坐标系、是否同时给你结构化字段(而不只是原始文本),以及那个逐值置信度到底度量的是什么。本文会把这三点逐一讲透,并展示一个带源坐标、外加一个字符覆盖匹配比例的 OCR API 在实践中是什么样子。
先看证据:可以悬停的框
这是一次真实的提取,其中每一个值都能指回它被读取出来的确切区域。把鼠标悬停在任意字段上——票据上的那个框就是这个值的来源,而每个值都带着一个匹配比例,告诉你这个值的文本有多少真的在页面上被定位到了。

Every value carries a verified on-page location — bbox + 4-point vertices + match_ratio — on a 0–1000 normalized grid (0,0 top-left → 1000,1000 bottom-right), the same shape the live API returns. Hover a field to trace it back to the pixels it came from.
大多数 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,也不再付一次费。