OCR表格结果按单元格坐标重建行列关系的方法
OCR 识别表格时,结果通常只是“文本 + 坐标框 + 置信度”的无序集合,并不会自动变成可查询的二维表。稳定的做法是先把四点框归一化,再按垂直重叠聚类出行,最后在每一行内按横坐标排序并推断列锚点。这样既能重建行列关系,也能把低置信度单元格留给复核,而不是用空字符串悄悄掩盖识别风险。
- 行判断看垂直重叠和相对高度,固定像素阈值很容易被扫描分辨率影响。
- 列判断要在行聚类之后进行;先按
x1排序,再用左边界锚点处理缺失单元格。 - 置信度属于数据的一部分,输出表格时保留原分数,并单独标记低置信度记录。
统一坐标:先把 OCR 单元格变成可比较的数据
不同 OCR 引擎的字段名称并不统一,有的返回四个角点,有的返回 xmin/ymin/xmax/ymax,置信度也可能是 0 到 1 或 0 到 100。不要直接在聚类代码里兼容所有格式,先定义一个内部记录:text、x1、y1、x2、y2、cx、cy、width、height 和 score。

下面的示例把常见四点框转换为外接矩形,同时把百分制置信度收敛到 0 到 1。坐标归一化只负责“字段可比较”,不负责判断它属于哪一行。
def normalize_cell(item):
# 兼容四点框:每个点按 [x, y] 存储,先求外接矩形。
points = item["box"]
xs = [point[0] for point in points]
ys = [point[1] for point in points]
x1, x2 = min(xs), max(xs)
y1, y2 = min(ys), max(ys)
# 有些引擎返回 0..100 的分数,这里统一成 0..1。
raw_score = float(item.get("score", 0.0))
score = raw_score / 100 if raw_score > 1 else raw_score
return {
"text": item.get("text", "").strip(),
"x1": x1, "y1": y1, "x2": x2, "y2": y2,
"cx": (x1 + x2) / 2, "cy": (y1 + y2) / 2,
"width": x2 - x1, "height": y2 - y1,
"score": max(0.0, min(score, 1.0)),
}
如果输入已经是矩形坐标,只需在进入这个函数前转换成四个角点即可。空文本、负宽高和明显越界的框应先记录为异常,不要让它们参与行列聚类。
按垂直重叠聚类:先确定哪些单元格属于同一行
行聚类的关键不是比较两个中心点是否“刚好相等”,而是比较它们在 y 轴上的覆盖。扫描倾斜、字体大小变化或同一行中包含粗体文字时,中心点会有偏移;把垂直重叠比例和相对高度结合起来,比固定的 5 像素阈值更稳。
def vertical_overlap(a, b):
# 计算两个单元格在垂直方向的交集长度。
overlap = max(0.0, min(a["y2"], b["y2"]) - max(a["y1"], b["y1"]))
shorter = max(1.0, min(a["height"], b["height"]))
return overlap / shorter
def cluster_rows(cells):
rows = []
for cell in sorted(cells, key=lambda value: (value["cy"], value["x1"])):
matched = None
for row in rows:
# 高度自适应:不同分辨率下不使用固定像素阈值。
tolerance = max(cell["height"], row["height"]) * 0.45
same_band = abs(cell["cy"] - row["cy"]) = 0.35:
matched = row
break
if matched is None:
rows.append({"cy": cell["cy"], "height": cell["height"], "cells": [cell]})
else:
matched["cells"].append(cell)
# 用当前行的中位中心和高度吸收少量倾斜与字号差异。
centers = [item["cy"] for item in matched["cells"]]
heights = [item["height"] for item in matched["cells"]]
matched["cy"] = sorted(centers)[len(centers) // 2]
matched["height"] = sorted(heights)[len(heights) // 2]
return sorted(rows, key=lambda row: row["cy"])
这里的 0.35 和 0.45 是工程起点,不是所有票据和扫描件的通用答案。文字行间距很小的文档可以提高垂直重叠要求;拍照透视明显时,应先做透视校正,再聚类。
从坐标聚类到二维表:行和列要分两次判断
得到行之后,再处理横向关系。简单表格可以直接在每个行内按 x1 排序;如果某一行缺少一个单元格,不能仅凭“第几个文本”补列,否则后续行会整体错位。更稳的做法是收集每个单元格的左边界,按相近位置形成列锚点,再把单元格分配到最近锚点。

def rebuild_table(raw_cells, column_gap=18, review_threshold=0.82):
# 过滤空文本,保留原始置信度供后续复核。
cells = [normalize_cell(item) for item in raw_cells]
cells = [cell for cell in cells if cell["text"] and cell["width"] > 0 and cell["height"] > 0]
rows = cluster_rows(cells)
# 全表按左边界聚类列锚点;column_gap 要按图像尺度调整。
anchors = []
for cell in sorted(cells, key=lambda value: value["x1"]):
anchor = next((value for value in anchors if abs(value - cell["x1"])
输出后的空列应保留为缺失值,而不是把右侧单元格左移。对于合并单元格,可以额外保存 colspan:根据框的宽度覆盖了几个列锚点来估计跨度,再把这个估计标记为待核对。OCR 坐标只能提供几何线索,不能替代业务表头语义。
置信度和异常框要成为可追踪的结果
| 现象 | 优先检查 | 处理方式 |
|---|---|---|
| 一行被拆成两行 | 垂直重叠、拍照倾斜、字号差 | 先校正透视,再调整相对高度阈值 |
| 缺失单元格导致列错位 | 是否按文本序号直接补列 | 使用全表列锚点,保留空列 |
| 文字正确但分数低 | 置信度量纲和阈值 | 统一到 0..1,并把记录送人工复核 |
| 合并单元格被拆开 | 框宽度与相邻锚点覆盖 | 记录 colspan 候选,不强行复制文本 |
建议最终输出两份数据:一份是给下游查询的二维表,另一份是包含原始框、分数、行列编号和异常原因的审计记录。这样当用户发现某个金额或日期不对时,可以回到原始单元格,而不是只能重新跑整页 OCR。
常见问题
为什么不直接按 OCR 返回顺序拼接文本?
返回顺序可能受检测框排序策略影响,不能代表视觉阅读顺序。坐标聚类先恢复几何关系,再决定文本顺序。
行聚类阈值应该固定多少?
不要固定为某个像素值。用单元格高度或垂直重叠比例做相对阈值,再用一组真实扫描样本校准。
低置信度单元格要不要直接删除?
通常不要。删除会破坏行列对齐;保留文本和分数,并通过 needs_review 交给人工或二次识别。
Go strconv.Atoi溢出返回错误时的输入校验顺序
- 上一篇
- Go strconv.Atoi溢出返回错误时的输入校验顺序
- 下一篇
- 商汤Seko批量产出怎么保持角色统一?模板、命名与抽检方法
-
- 科技周边 · 人工智能 | 2小时前 |
- AI Agent工具调用设置超时与幂等键的执行边界
- 305浏览 收藏
-
- 科技周边 · 人工智能 | 4小时前 |
- LLM结构化输出用JSON Schema约束字段缺失的处理方案
- 364浏览 收藏
-
- 科技周边 · 人工智能 | 9小时前 |
- 向量数据库先做元数据过滤再向量召回的实现方法
- 467浏览 收藏
-
- 科技周边 · 人工智能 | 10小时前 | API · 人工智能 · 多模态输入 图片尺寸 Vision input 输入成本
- 多模态输入控制图片尺寸与输入成本的实现方法
- 487浏览 收藏
-
- 科技周边 · 人工智能 | 12小时前 |
- 安全过滤把拒答与业务失败分开记录的实现方法
- 310浏览 收藏
-
- 科技周边 · 人工智能 | 13小时前 | 缓存 · API · 人工智能 · Prompt Caching prompt_cache_key cached_tokens
- Prompt cache拆分稳定前缀与动态变量的实现方法
- 199浏览 收藏
-
- 科技周边 · 人工智能 | 14小时前 | 人工智能 · 回归测试 评测集 LLM evaluation 失败类型
- 评测集按失败类型切分评测集的实现方法
- 173浏览 收藏
-
- 科技周边 · 人工智能 | 15小时前 |
- 批处理推理用批处理吞吐换取响应延迟的实现方法
- 301浏览 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 485次学习
-
- PubMedQA
- 深入了解PubMedQA生物医学问答数据集,涵盖其核心功能、使用方法及在临床决策、药物研发等场景的应用,助力提升NLP模型性能。
- 136次使用
-
- H2O EvalGPT
- H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
- 202次使用
-
- LMArena
- LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
- 147次使用
-
- HELM
- 深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
- 127次使用
-
- CMMLU
- 深入了解CMMLU中文评估基准,涵盖67个学科主题,提供数据集下载、Zero-shot/Five-shot评估方法及排行榜,助力优化中文语言模型性能。
- 115次使用
-
- 纸质档案扫描选 300dpi 还是 600dpi
- 2026-09-06 273浏览
-
- Go encoding/csv处理可变列数文件的容错配置方法
- 2026-09-20 169浏览
-
- CodeGeeX for Jetbrains IDEs正式上线!
- 2023-01-17 284浏览
-
- 技术阿里云实现ocr批量图片和pdf文件表格图片转换excel文档/支持票据图片提取/普通图片文字提取处理
- 2023-01-18 387浏览
-
- 直播预告|FeatureStore Meetup V2
- 2023-01-10 328浏览

