当前位置:首页 > 文章列表 > 科技周边 > 人工智能 > OCR表格结果按单元格坐标重建行列关系的方法

OCR表格结果按单元格坐标重建行列关系的方法

来源:17golang原创 2026-09-20 15:32:40 0浏览 收藏

OCR 识别表格时,结果通常只是“文本 + 坐标框 + 置信度”的无序集合,并不会自动变成可查询的二维表。稳定的做法是先把四点框归一化,再按垂直重叠聚类出行,最后在每一行内按横坐标排序并推断列锚点。这样既能重建行列关系,也能把低置信度单元格留给复核,而不是用空字符串悄悄掩盖识别风险。

要点速览
  • 行判断看垂直重叠和相对高度,固定像素阈值很容易被扫描分辨率影响。
  • 列判断要在行聚类之后进行;先按 x1 排序,再用左边界锚点处理缺失单元格。
  • 置信度属于数据的一部分,输出表格时保留原分数,并单独标记低置信度记录。

统一坐标:先把 OCR 单元格变成可比较的数据

不同 OCR 引擎的字段名称并不统一,有的返回四个角点,有的返回 xmin/ymin/xmax/ymax,置信度也可能是 0 到 1 或 0 到 100。不要直接在聚类代码里兼容所有格式,先定义一个内部记录:textx1y1x2y2cxcywidthheightscore

OCR表格单元格四点坐标转换为矩形边界、中心点宽高和置信度统一记录的静态结构说明图
图1:OCR 单元格坐标归一化说明图,展示文本、框和置信度如何进入统一记录,不是运行截图。

下面的示例把常见四点框转换为外接矩形,同时把百分制置信度收敛到 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.350.45 是工程起点,不是所有票据和扫描件的通用答案。文字行间距很小的文档可以提高垂直重叠要求;拍照透视明显时,应先做透视校正,再聚类。

从坐标聚类到二维表:行和列要分两次判断

得到行之后,再处理横向关系。简单表格可以直接在每个行内按 x1 排序;如果某一行缺少一个单元格,不能仅凭“第几个文本”补列,否则后续行会整体错位。更稳的做法是收集每个单元格的左边界,按相近位置形成列锚点,再把单元格分配到最近锚点。

OCR表格从单元格集合经过行聚类、行内排序和列锚点生成二维表并保留低置信度复核区的静态结构图
图2:OCR 表格行列重建结构图,展示几何分组、列锚点、二维表和复核边界,不是运行结果截图。
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 交给人工或二次识别。

版本声明
本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
Go strconv.Atoi溢出返回错误时的输入校验顺序Go strconv.Atoi溢出返回错误时的输入校验顺序
上一篇
Go strconv.Atoi溢出返回错误时的输入校验顺序
商汤Seko批量产出怎么保持角色统一?模板、命名与抽检方法
下一篇
商汤Seko批量产出怎么保持角色统一?模板、命名与抽检方法
查看更多
最新文章
查看更多
课程推荐
  • 前端进阶之JavaScript设计模式
    前端进阶之JavaScript设计模式
    设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
    543次学习
  • GO语言核心编程课程
    GO语言核心编程课程
    本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
    516次学习
  • 简单聊聊mysql8与网络通信
    简单聊聊mysql8与网络通信
    如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
    500次学习
  • JavaScript正则表达式基础与实战
    JavaScript正则表达式基础与实战
    在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
    487次学习
  • 从零制作响应式网站—Grid布局
    从零制作响应式网站—Grid布局
    本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
    485次学习
查看更多
AI推荐
  • PubMedQA数据集详解:生物医学问答基准、功能与应用指南
    PubMedQA
    深入了解PubMedQA生物医学问答数据集,涵盖其核心功能、使用方法及在临床决策、药物研发等场景的应用,助力提升NLP模型性能。
    136次使用
  • H2O EvalGPT:开源LLM大模型评估与排行榜工具
    H2O EvalGPT
    H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
    202次使用
  • LMArena是什么?伯克利AI模型评估平台使用指南与功能解析
    LMArena
    LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
    147次使用
  • 斯坦福HELM:大语言模型Holistic Evaluation整体评估框架详解
    HELM
    深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
    127次使用
  • CMMLU中文大模型评估基准:功能、使用教程与应用场景解析
    CMMLU
    深入了解CMMLU中文评估基准,涵盖67个学科主题,提供数据集下载、Zero-shot/Five-shot评估方法及排行榜,助力优化中文语言模型性能。
    115次使用
微信登录更方便
  • 密码登录
  • 注册账号
登录即同意 用户协议隐私政策
返回登录
  • 重置密码