当前位置: 首页 > news >正文

基于AI的PDF复杂表格结构识别与智能解析(方案1)

1 总体思路

本方案采用 “多模态输入 - 分层处理 - 协同输出” 的技术架构,整体分为数据预处理层、核心任务处理层、融合决策层和结果输出层四个部分,各层通过标准化数据接口实现高效协同,具体架构如下:

(1)数据预处理层:统一处理 PDF 原生文件与扫描件输入,将 PDF 文本流与图像流分离,对扫描件执行超分辨率重建与倾斜校正;采用 OCR 技术(如 PaddleOCR)提取图像中的文本信息及坐标,构建 “文本 - 坐标 - 图像特征” 三位一体的基础数据结构。​

(2)核心任务处理层:针对合并单元格识别、表头层级恢复、跨页表格合并、版面分析四大核心任务,分别搭建专项处理模块,通过模块化设计实现任务解耦与独立优化。​

(3)融合决策层:引入多模态注意力机制,融合各核心任务模块的输出结果,解决任务间的逻辑冲突(如跨页合并与单元格识别的坐标匹配问题),生成统一的表格结构与数据关联关系。​

(4)结果输出层:支持 JSON、Excel、HTML 等多格式输出,其中 JSON 包含完整的表格结构元数据(合并单元格范围、表头层级、跨页来源等),Excel/HTML 实现结构化数据的可视化呈现。

2. 技术方案

2.1 表格与文本区域提取

(1)模型选型:采用跨版本兼容的 YOLO 检测模型,统一在 backbone 中引入 ResNet-50 与 Transformer 混合结构 ——ResNet-50 负责提取局部纹理特征(如表格线、文本块边缘),Transformer 捕捉全局版面关联(如多栏表格的区域排布),增强复杂版面的特征表征能力,适配 YOLOv8 至 v13 等主流版本的结构规范。。​

(2)训练优化:构建包含政府公文、金融报表、科研论文等多领域的版面数据集(涵盖 10 万 + 样本),标注表格、标题、正文、图片等 8 类区域;采用难例挖掘策略,重点优化表格与密集文本区域的边界识别精度。​

(3)推理流程:输入预处理后的文档图像或 PDF 页面,模型输出各区域的边界框坐标与类别标签;通过非极大值抑制(NMS)去除冗余框,最终生成包含区域类型、坐标、文本内容的版面结构图。

2.2 结构关系重构,识别合并单元格

(1) 双阶段识别策略:​

A. 第一阶段(候选区域生成):采用 U-Net++ 模型对表格图像进行像素级分割,识别表格线(横线、竖线)与单元格区域,通过线条断裂检测定位合并单元格候选区域(无分隔线的连续区域)。​

B. 第二阶段(关系确认):构建表格拓扑图,以单元格为节点,线条连接关系为边;针对候选区域,结合文本对齐特征(横向 / 纵向文本排布一致性)与相邻单元格尺寸比例,判断合并类型(横向 / 纵向)及合并范围。​

(2)  跨模态辅助验证:

利用 OCR 提取的文本内容,通过 BERT 模型计算候选合并区域内文本的语义连贯性,排除非合并的密集文本区域干扰,识别准确率提升 3%-5%。

2.3  层级结构恢复,识别表头

(1)表头区域定位:采用 BiLSTM-CRF 模型对表格第一行 / 列的文本进行序列标注,结合版面分析中 “表格标题 - 表格主体” 的位置关系,定位表头起始与结束边界;针对跨多行 / 多列表头,引入自注意力机制捕捉文本与位置的关联特征。​

(2)层级结构解析:​

A. 构建表头语义树:利用 RoBERTa 模型提取表头文本的语义特征,通过余弦相似度计算判断父子层级关系(如 “年度” 与 “2023 年”“2024 年”)。​

B. 结构可视化:将语义树转化为层级矩阵,明确每个表头单元格对应的下级列 / 行范围,支持多级表头(最多 5 级)的准确恢复。​

(2)  优化策略:引入领域知识图谱(如金融领域的 “资产负债表表头体系”),对语义树进行校验与修正,解决专业术语导致的层级误判问题。

2.4 跨页表格的合并与还原

(1)跨页表格检测:​

A. 特征匹配:提取当前页表格的表头特征、列宽比例、文本风格特征,与前 N 页(N≤3)表格特征进行相似度匹配,设定阈值(0.85)筛选候选关联表格。​

B. 边界判断:通过表格底部线条完整性检测(断裂率>60% 为跨页候选)及 “续表”“接下页” 等关键词识别,辅助确认跨页关系。​

(2)  逻辑合并实现:​

A. 坐标校准:以第一页表格的列宽为基准,对后续页表格进行拉伸 / 压缩校准,确保列对齐精度≤1 个像素。​

B. 数据拼接:保留第一页表头,删除后续页重复表头,将数据行按页码顺序拼接;针对跨页断裂的合并单元格,结合单元格识别结果进行跨页范围补全。

3. 开放挑战:多模态协同与 Agent 框架应用

3.1 多模态融合增强​

(1)  视觉 - 语言预训练模型应用:引入 LayoutLMv3 模型,将表格的图像特征(版面布局、线条分布)、文本特征(内容、位置)与语言特征(语义关联)进行深度融合,提升复杂表格的结构理解能力,尤其对无表格线的纯文本表格识别准确率提升 10% 以上。​

(2)  跨模态注意力机制:在融合决策层设计双注意力模块,分别计算 “视觉 - 文本”“文本 - 语义” 的注意力权重,动态调整各模态特征对最终结果的贡献度。​

3.2  Agent 框架搭建​

(1)  智能决策 Agent 设计:​

A. 任务规划器:接收用户需求(如 “提取某金融报表的季度数据”),拆解为 “版面分析→表格识别→表头提取→数据筛选” 等子任务,并规划执行顺序。​

B. 工具调用器:集成各核心任务模块作为 “工具”,根据任务规划结果自动调用对应模块,如检测到跨页表格时触发跨页合并工具。​

C. 结果校验器:利用大语言模型(如 LLaMA 3)对输出结果进行逻辑校验(如 “资产总计 = 流动资产 + 非流动资产”),发现异常时回溯调整工具参数。​

(2) 人机交互优化:支持用户通过自然语言修正识别结果(如 “该单元格为横向合并”),Agent 记录修正数据并更新模型参数,实现增量学习。

4. 模型训练与系统部署​

4.1 数据集构建与训练策略​

(1)  数据集组成:包含公开数据集(PubTabNet、ICDAR2019)与自建数据集(涵盖政府、金融、科研领域,共 20 万 + 样本),自建数据集标注合并单元格、表头层级等细粒度信息。​

(2)  训练优化:采用迁移学习策略,基于预训练模型(如 LayoutLMv3、ResNet-50)进行领域微调;使用混合精度训练与梯度累积技术,提升训练效率与模型泛化能力。​

4.2 系统部署与性能优化​

(1)  部署架构:采用 Docker 容器化部署,前端为 Web 交互界面,后端通过 FastAPI 提供接口服务,支持批量处理(≤1000 页 / 批)与实时处理(响应时间≤2 秒 / 页)。​

(2) 性能优化:对模型进行量化压缩(INT8)与剪枝,降低推理显存占用 30%;采用 Redis 缓存高频访问的表格特征,提升重复文档处理效率。​

5. 方案优势与预期效果​

(1) 核心指标预期:合并单元格识别准确率≥92%,表头层级恢复准确率≥95%,跨页表格合并准确率≥90%,版面分析 F1 值≥0.94。​

(2)  实用性优势:支持多领域复杂表格处理,适配 PDF 原生文件与扫描件,输出结果兼容主流数据处理工具,可直接对接政府、金融等领域的自动化办公系统。


接下来开始实施吧

SciTSR 数据集模型训练

先用SciTSR科研表格数据集来跑一遍这个方案

1 先明确:用 SciTSR 训练模型的核心逻辑

SciTSR 的核心优势是提供了精细的单元格行列范围、文本块坐标、邻接关系标注,完美适配 “合并单元格识别”“表格结构重构” 两大核心任务。因此模型选型需紧扣 “结构建模” 与 “关系学习”,推荐 “基础检测模型 + 关系建模模块” 的两阶段架构,具体适配如下:

核心任务​

适配模型架构​

依赖 SciTSR 的关键数据​

合并单元格识别​

U-Net++(表格线分割)+ 规则判断​

structure.json 的 start/end 行列范围​

表格结构重构​

Graph Neural Network(GNN)+ BERT​

rel 文件的邻接关系、chunk 的文本特征​

表头层级恢复​

BiLSTM-CRF + RoBERTa​

structure.json 的单元格内容 + 位置​

​2. 模型训练前的 2 项准备工作

(1)数据预处理:将 SciTSR 转为模型输入格式​

先编写脚本解析 SciTSR 的structure和chunk文件,生成训练所需的 “特征 - 标签” 对:

import json
import os
from typing import List, Dictdef parse_scitsr_data(data_root: str, split: str = "train") -> List[Dict]:"""解析SciTSR数据集,生成模型输入数据split: "train" 或 "test"返回: 每个样本含table_feature(特征)和labels(标签)"""data = []structure_dir = os.path.join(data_root, split, "structure")chunk_dir = os.path.join(data_root, split, "chunk")for filename in os.listdir(structure_dir):if not filename.endswith(".json"):continueid = filename.split(".")[0]# 1. 解析结构标签(获取合并单元格、行列位置)with open(os.path.join(structure_dir, filename), "r", encoding="utf-8") as f:struct = json.load(f)# 合并单元格标签:1=合并,0=未合并merge_labels = []cell_positions = []  # (start_row, end_row, start_col, end_col)cell_contents = []for cell in struct["cells"]:sr, er = cell["start_row"], cell["end_row"]sc, ec = cell["start_col"], cell["end_col"]cell_positions.append((sr, er, sc, ec))cell_contents.append(" ".join(cell["content"]))merge_labels.append(1 if (sr != er or sc != ec) else 0)# 2. 解析文本块(获取坐标与文本特征)chunk_path = os.path.join(chunk_dir, f"{id}.chunk")if not os.path.exists(chunk_path):continuewith open(chunk_path, "r", encoding="utf-8") as f:chunk_data = json.load(f)chunk_features = [(c["pos"], c["text"]) for c in chunk_data["chunks"]]# 3. 组装样本data.append({"id": id,"table_feature": {"cell_positions": cell_positions,"cell_contents": cell_contents,"chunk_features": chunk_features},"labels": {"merge_labels": merge_labels,  # 合并单元格标签"cell_positions": cell_positions  # 结构重构标签(行列范围)}})return data# 示例:加载训练数据
train_data = parse_scitsr_data(data_root="/path/to/SciTSR", split="train")
print(f"加载训练样本数:{len(train_data)}")  # 应输出12000左右

(2) 数据集加载器:适配 PyTorch 训练流程​

基于预处理后的数据,编写Dataset类,实现批量加载与特征转换:

import torch
from torch.utils.data import Dataset, DataLoader
from transformers import BertTokenizerclass SciTSRTableDataset(Dataset):def __init__(self, data, tokenizer, max_seq_len=128):self.data = dataself.tokenizer = tokenizerself.max_seq_len = max_seq_lendef __len__(self):return len(self.data)def __getitem__(self, idx):sample = self.data[idx]cell_contents = sample["table_feature"]["cell_contents"]cell_positions = sample["table_feature"]["cell_positions"]merge_labels = sample["labels"]["merge_labels"]# 1. 文本特征:用BERT tokenizer编码单元格文本text_encoding = self.tokenizer(cell_contents,padding="max_length",truncation=True,max_length=self.max_seq_len,return_tensors="pt")input_ids = text_encoding["input_ids"].squeeze(0)  # (num_cells, max_seq_len)attention_mask = text_encoding["attention_mask"].squeeze(0)# 2. 位置特征:将行列范围归一化为0-1(假设表格最大100行100列)pos_features = []for (sr, er, sc, ec) in cell_positions:pos_features.append([sr/100, er/100, sc/100, ec/100,(er-sr)/100, (ec-sc)/100  # 合并长度特征])pos_features = torch.tensor(pos_features, dtype=torch.float32)  # (num_cells, 6)# 3. 标签:合并单元格标签转为tensormerge_labels = torch.tensor(merge_labels, dtype=torch.long)  # (num_cells,)return {"input_ids": input_ids,"attention_mask": attention_mask,"pos_features": pos_features,"merge_labels": merge_labels}# 初始化加载器
tokenizer = BertTokenizer.from_pretrained("bert-base-uncased")
train_dataset = SciTSRTableDataset(train_data, tokenizer)
train_loader = DataLoader(train_dataset,batch_size=8,shuffle=True,collate_fn=lambda x: {  # 处理不同表格的单元格数量差异"input_ids": torch.stack([item["input_ids"] for item in x]),"attention_mask": torch.stack([item["attention_mask"] for item in x]),"pos_features": torch.stack([item["pos_features"] for item in x]),"merge_labels": torch.stack([item["merge_labels"] for item in x])}
)

3 核心模型选型与训练代码

任务1 模型选型:BERT + 位置特征融合的分类模型​

利用 SciTSR 的cell_contents(文本语义)和cell_positions(行列范围),判断单元格是否合并:

import torch.nn as nn
from transformers import BertModelclass MergeCellDetector(nn.Module):def __init__(self, bert_path="bert-base-uncased", pos_dim=6, hidden_dim=128):super().__init__()# 1. 文本特征提取(BERT)self.bert = BertModel.from_pretrained(bert_path)self.text_dim = self.bert.config.hidden_size# 2. 位置特征融合self.pos_encoder = nn.Sequential(nn.Linear(pos_dim, hidden_dim),nn.ReLU(),nn.Dropout(0.1))# 3. 融合分类self.fusion_linear = nn.Linear(self.text_dim + hidden_dim, hidden_dim)self.classifier = nn.Linear(hidden_dim, 2)  # 0=未合并,1=合并def forward(self, input_ids, attention_mask, pos_features):# 文本特征:(batch_size, num_cells, max_seq_len) → (batch_size, num_cells, text_dim)bert_out = self.bert(input_ids=input_ids, attention_mask=attention_mask)text_features = bert_out.last_hidden_state[:, :, 0]  # 取[CLS]向量# 位置特征:(batch_size, num_cells, pos_dim) → (batch_size, num_cells, hidden_dim)pos_features = self.pos_encoder(pos_features)# 融合:(batch_size, num_cells, text_dim+hidden_dim)fusion_features = torch.cat([text_features, pos_features], dim=-1)fusion_features = self.fusion_linear(fusion_features)fusion_features = nn.ReLU()(fusion_features)# 分类输出logits = self.classifier(fusion_features)  # (batch_size, num_cells, 2)return logits

训练代码:基础训练循环

# 初始化模型、优化器、损失函数
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
model = MergeCellDetector().to(device)
optimizer = torch.optim.AdamW(model.parameters(), lr=2e-5)
criterion = nn.CrossEntropyLoss()# 训练循环(示例10轮)
epochs = 10
for epoch in range(epochs):model.train()total_loss = 0.0for batch in train_loader:# 数据移至设备input_ids = batch["input_ids"].to(device)attention_mask = batch["attention_mask"].to(device)pos_features = batch["pos_features"].to(device)merge_labels = batch["merge_labels"].to(device)# 前向传播logits = model(input_ids, attention_mask, pos_features)  # (batch, num_cells, 2)loss = criterion(logits.view(-1, 2), merge_labels.view(-1))  # 展平为二维# 反向传播optimizer.zero_grad()loss.backward()optimizer.step()total_loss += loss.item() * batch["input_ids"].size(0)# 打印每轮损失avg_loss = total_loss / len(train_loader.dataset)print(f"Epoch {epoch+1}/{epochs}, Loss: {avg_loss:.4f}")# 保存模型torch.save(model.state_dict(), f"merge_cell_detector_epoch_{epoch+1}.pth")

任务 2:表格结构重构

模型选型:GNN(图神经网络)+ 关系预测​

利用 SciTSR 的rel文件构建表格图,预测单元格间的横向 / 纵向关系:

import torch_geometric.nn as gnn
from torch_geometric.data import Data, DataLoader as GeoDataLoaderclass TableStructureGNN(nn.Module):def __init__(self, in_dim=768+6, hidden_dim=128, out_dim=2):super().__init__()# GCN层(处理单元格节点关系)self.gcn1 = gnn.GCNConv(in_dim, hidden_dim)self.gcn2 = gnn.GCNConv(hidden_dim, hidden_dim)# 关系预测头(横向=0,纵向=1)self.relation_head = nn.Linear(hidden_dim * 2, out_dim)  # 拼接两个节点特征def forward(self, x, edge_index):# x: (num_nodes, in_dim) → 节点特征(文本+位置)# edge_index: (2, num_edges) → 边索引x = self.gcn1(x, edge_index)x = nn.ReLU()(x)x = self.gcn2(x, edge_index)# 预测边关系:拼接边两端节点特征edge_src = x[edge_index[0]]  # (num_edges, hidden_dim)edge_dst = x[edge_index[1]]edge_features = torch.cat([edge_src, edge_dst], dim=-1)relation_logits = self.relation_head(edge_features)  # (num_edges, 2)return relation_logits# 适配SciTSR的rel文件构建图数据(简化示例)
def build_graph_from_rel(sample_id, data_root):# 1. 加载节点特征(复用之前的text+pos特征)# 2. 加载rel文件构建边索引rel_path = os.path.join(data_root, "train", "rel", f"{sample_id}.rel")edges = []relations = []with open(rel_path, "r") as f:for line in f:id1, id2, rel_info = line.strip().split()rel_id = int(rel_info.split(":")[0]) - 1  # 转为0=横向,1=纵向edges.append([int(id1), int(id2)])relations.append(rel_id)edge_index = torch.tensor(edges, dtype=torch.long).t().contiguous()relation_labels = torch.tensor(relations, dtype=torch.long)# 3. 构建PyG Data对象node_features = torch.randn(10, 774)  # 示例:10个节点,768+6维特征return Data(x=node_features, edge_index=edge_index, y=relation_labels)

4. 评估与优化:复用 SciTSR 的官方工具​

4.1 用官方脚本评估​

直接调用 SciTSR 提供的eval.py脚本,将模型预测结果转为List[Relation]格式:

from scitsr.eval import json2Relations, eval_relations, Table2Relations
from scitsr.structure import Table# 1. 加载模型预测的结构结果(假设已转为Table对象)
# 示例:从structure.json构建Table(实际应替换为模型输出)
with open("/path/to/structure/1.json", "r") as f:struct_json = json.load(f)
pred_table = Table.from_json(struct_json)  # 模型输出需转为该格式
pred_relations = Table2Relations(pred_table)# 2. 加载真实标签
gt_relations = json2Relations(struct_json, splitted_content=True)# 3. 计算指标
precision, recall = eval_relations(gt=[gt_relations], res=[pred_relations], cmp_blank=True)
f1 = 2 * precision * recall / (precision + recall) if (precision + recall) > 0 else 0
print(f"Precision: {precision:.4f}, Recall: {recall:.4f}, F1: {f1:.4f}")

4.2 优化方向(针对 SciTSR 的复杂表格)​

  • 难例挖掘:用SciTSR-COMP.list筛选复杂样本,单独增加训练权重(loss 乘 1.5)。​
  • 噪声处理:对chunk文件中的噪声文本块,用 BERT 的文本相似度过滤(与单元格内容相似度<0.5 则剔除)。​
  • 预训练初始化:将 BERT 替换为LayoutLMv2(支持位置特征),进一步提升结构建模能力。

模型训练完成后,核心工作就会转入模型应用开发阶段—— 这一步的目标是将训练好的 “合并单元格识别”“表格结构重构” 等模型,封装为可落地、可交互的实用功能,最终实现从 “模型文件” 到 “可用工具 / 系统” 的转化。

在正式开发前,需先完成模型的 “工程化预处理”,确保其能高效适配应用场景:

1. 模型轻量化与部署格式转换

训练好的模型(如merge_cell_detector_epoch_10.pth)通常体积大、推理慢,需先优化:

(1) 轻量化处理
用 PyTorch 的torch.quantization工具做 INT8 量化,或用torch.nn.utils.prune剪枝(移除冗余参数),通常能降低 70%+ 体积,推理速度提升 2-3 倍。
示例(量化合并单元格识别模型):

import torch.quantization# 加载训练好的模型
model = MergeCellDetector()
model.load_state_dict(torch.load("merge_cell_detector_epoch_10.pth"))
model.eval()# 动态量化(适合Transformer类模型)
quant_model = torch.quantization.quantize_dynamic(model,{torch.nn.Linear, torch.nn.LayerNorm},  # 对线性层、归一化层量化dtype=torch.qint8
)# 保存量化后的模型
torch.save(quant_model.state_dict(), "merge_cell_detector_quant.pth")

(2)部署格式转换
若需在生产环境快速推理,可转为 ONNX 格式(兼容多框架)或 TensorRT 格式(GPU 加速)。
示例(转为 ONNX):

# 构造虚拟输入(匹配模型输入格式:batch_size=1, num_cells=20, max_seq_len=128)
dummy_input_ids = torch.randint(0, 1000, (1, 20, 128))
dummy_attention_mask = torch.ones(1, 20, 128)
dummy_pos_features = torch.randn(1, 20, 6)# 导出ONNX
torch.onnx.export(model,(dummy_input_ids, dummy_attention_mask, dummy_pos_features),"merge_cell_detector.onnx",input_names=["input_ids", "attention_mask", "pos_features"],output_names=["logits"],dynamic_axes={"input_ids": {1: "num_cells"}, "pos_features": {1: "num_cells"}}  # 支持动态单元格数量
)

(3) 模型推理接口封装

将模型的 “输入预处理→推理→输出后处理” 逻辑封装为标准化函数,方便后续调用:

import torch
from transformers import BertTokenizer# 初始化(加载量化模型、tokenizer)
tokenizer = BertTokenizer.from_pretrained("bert-base-uncased")
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
model = MergeCellDetector()
model.load_state_dict(torch.load("merge_cell_detector_quant.pth"))
model.to(device)
model.eval()def predict_merge_cells(cell_contents, cell_positions):"""应用级推理接口:输入单元格文本和位置,输出合并单元格预测结果cell_contents: 列表,如["Training set", "959", ...]cell_positions: 列表,如[(0,0,1,1), (5,5,1,1), ...]返回: 列表,0=未合并,1=合并"""# 1. 输入预处理(复用Dataset中的逻辑)text_encoding = tokenizer(cell_contents, padding="max_length", truncation=True, max_length=128, return_tensors="pt")input_ids = text_encoding["input_ids"].to(device)  # (1, num_cells, 128)attention_mask = text_encoding["attention_mask"].to(device)pos_features = []for (sr, er, sc, ec) in cell_positions:pos_features.append([sr/100, er/100, sc/100, ec/100, (er-sr)/100, (ec-sc)/100])pos_features = torch.tensor(pos_features, dtype=torch.float32).unsqueeze(0).to(device)  # (1, num_cells, 6)# 2. 推理(关闭梯度计算,提升速度)with torch.no_grad():logits = model(input_ids, attention_mask, pos_features)  # (1, num_cells, 2)# 3. 输出后处理(取概率最大的类别)predictions = torch.argmax(logits, dim=-1).squeeze(0).cpu().numpy().tolist()return predictions# 测试接口
test_contents = ["Training set", "Validation set", "959", "100"]
test_positions = [(0,0,1,1), (0,0,2,2), (5,5,1,1), (5,5,2,2)]
print(predict_merge_cells(test_contents, test_positions))  # 示例输出:[0,0,0,0](均未合并)

应用开发核心任务:从 “单功能” 到 “完整流程”

应用开发需围绕 “PDF 表格解析全流程” 展开,将多个模型接口串联,并补充交互、输出等功能,重点实现 3 类核心应用:

1. 基础功能:单页 PDF 表格解析工具

先实现最核心的 “输入 PDF→输出结构化结果” 功能,串联 “版面分析→文本提取→模型推理→结果整理” 全流程:

import fitz  # PyMuPDF,用于读取PDF
import json
from paddleocr import PaddleOCR  # 用于提取PDF中的文本和坐标# 1. 初始化依赖工具
ocr = PaddleOCR(use_angle_cls=True, lang="en")  # 英文OCR(适配SciTSR的科研表格)def parse_single_pdf_table(pdf_path, page_num=0):"""单页PDF表格解析:从PDF中提取表格,预测合并单元格,输出结构化JSON"""# 步骤1:读取PDF页面,转为图像(用于OCR)doc = fitz.open(pdf_path)page = doc[page_num]mat = fitz.Matrix(2.0, 2.0)  # 放大2倍,提升OCR精度pix = page.get_pixmap(matrix=mat)img_path = f"temp_page_{page_num}.png"pix.save(img_path)# 步骤2:OCR提取文本块(含坐标)ocr_result = ocr.ocr(img_path, cls=True)cell_contents = []cell_positions_pdf = []  # PDF原始坐标(需转换为行列范围,这里简化为虚拟行列)for line in ocr_result[0]:text = line[1][0]x1, y1 = line[0][0]  # OCR坐标x2, y2 = line[0][2]# 简化:假设根据y坐标分组为行,x坐标分组为列(实际需用表格线分割算法)row = int(y1 // 20)  # 每20像素为一行col = int(x1 // 80)  # 每80像素为一列cell_contents.append(text)cell_positions_pdf.append((row, row, col, col))  # 先假设未合并# 步骤3:调用合并单元格识别模型merge_predictions = predict_merge_cells(cell_contents, cell_positions_pdf)# 步骤4:整理结构化结果(适配SciTSR的structure格式)structured_result = {"cells": []}for i, (content, pos, is_merge) in enumerate(zip(cell_contents, cell_positions_pdf, merge_predictions)):sr, er, sc, ec = pos# 若预测为合并,可根据邻接关系调整end_row/end_col(此处简化)if is_merge:er += 1  # 示例:纵向合并1行structured_result["cells"].append({"id": i,"content": content.split(),"start_row": sr,"end_row": er,"start_col": sc,"end_col": ec})# 保存结果with open(f"table_result_page_{page_num}.json", "w", encoding="utf-8") as f:json.dump(structured_result, f, indent=2)return structured_result# 测试:解析SciTSR的样例PDF
parse_single_pdf_table("/path/to/SciTSR/train/pdf/1.pdf", page_num=0)

2. 进阶功能:多页 PDF 跨页表格合并

结合 “跨页表格检测” 逻辑(参考之前的特征匹配方案),将多页解析结果合并为完整表格:

def merge_cross_page_tables(pdf_path, start_page=0, end_page=2):"""合并多页PDF中的跨页表格"""# 1. 解析每页表格page_results = []for page in range(start_page, end_page+1):result = parse_single_pdf_table(pdf_path, page_num=page)page_results.append(result)# 2. 跨页检测(基于表头特征匹配)# 提取第一页表头(假设前2行为表头)first_header = [cell["content"] for cell in page_results[0]["cells"] if cell["start_row"] < 2]merged_table = page_results[0]["cells"].copy()# 3. 合并后续页(跳过重复表头,拼接数据行)for page_result in page_results[1:]:# 识别当前页表头,跳过current_header = [cell["content"] for cell in page_result["cells"] if cell["start_row"] < 2]# 若表头匹配(相似度>0.8),拼接数据行if len(set(first_header) & set(current_header)) / len(set(first_header)) > 0.8:data_rows = [cell for cell in page_result["cells"] if cell["start_row"] >= 2]# 调整行号(累加前几页的总行数)prev_max_row = max([cell["end_row"] for cell in merged_table])for cell in data_rows:cell["start_row"] += prev_max_row + 1cell["end_row"] += prev_max_row + 1merged_table.extend(data_rows)# 保存合并结果with open("merged_cross_page_table.json", "w", encoding="utf-8") as f:json.dump({"cells": merged_table}, f, indent=2)return merged_table

3. 交互功能:Web 界面或本地工具

为了让非技术人员也能使用,需封装为可视化工具:

  • 本地 GUI 工具:用 PyQt开发,支持 “上传 PDF→选择页面→点击解析→下载 Excel/JSON”。
    from PyQt5.QtWidgets import QApplication, QMainWindow, QPushButton, QFileDialogclass TableParserGUI(QMainWindow):def __init__(self):super().__init__()self.setWindowTitle("PDF表格解析工具")self.resize(400, 200)# 解析按钮self.parse_btn = QPushButton("选择PDF并解析", self)self.parse_btn.move(100, 80)self.parse_btn.clicked.connect(self.parse_pdf)def parse_pdf(self):# 选择PDF文件pdf_path, _ = QFileDialog.getOpenFileName(self, "选择PDF", "", "PDF Files (*.pdf)")if not pdf_path:return# 调用解析函数result = parse_single_pdf_table(pdf_path, page_num=0)# 提示成功self.parse_btn.setText("解析完成!结果已保存")if __name__ == "__main__":app = QApplication([])window = TableParserGUI()window.show()app.exec_()

    Web 服务:用 FastAPI 将功能封装为 API,支持批量处理、远程调用:

from fastapi import FastAPI, UploadFile, File
import shutilapp = FastAPI(title="PDF表格解析API")@app.post("/parse-table")
async def parse_table(file: UploadFile = File(...), page_num: int = 0):# 保存上传的PDFpdf_path = f"uploads/{file.filename}"with open(pdf_path, "wb") as f:shutil.copyfileobj(file.file, f)# 解析result = parse_single_pdf_table(pdf_path, page_num=page_num)return {"status": "success", "result": result}# 启动服务:uvicorn main:app --reload

http://www.dtcms.com/a/389556.html

相关文章:

  • CS336第三课
  • 云蝠智能大模型呼叫对话延迟无限接近1秒
  • Datax-web安装 | 配置环境
  • 算法<java>——查找(顺序、二分、插值、分块、斐波那契)
  • Mysql杂志(十九)——InnoDB的索引结构
  • CrowdStrike推出AI驱动新工具 聚焦补丁管理与威胁情报短板
  • 收集飞花令碎片——C语言指针
  • MySQL 初识:架构定位与整体组成
  • 【开发者导航】规范驱动且开源的 AI 时代开发流程工具:GitHub Spec-Kit
  • 区块链加速器:Redis优化以太坊交易池性能方案
  • 资源分布的均衡性(Poisson Disk Sampling)探索
  • STM32开发(中断模式)
  • Qt QPieSlice详解
  • C++多线程编程
  • LangChain 父文档检索器:解决 “文档块匹配准” 与 “信息全” 的矛盾
  • COI实验室技能:基于几何光学的物空间与像空间的映射关系
  • springboot-security安全插件使用故障解析
  • 企业移动化管理(EMM)实战:如何一站式解决设备、应用与安全管控难题?
  • 高频面试题——深入掌握栈和队列的数据结构技巧
  • 【C++ qml】qml页面加载配置文件信息的两种方式
  • 运维笔记:神卓 N600 解决企业远程访问 NAS 的 3 个核心痛点
  • GitHub 热榜项目 - 日榜(2025-09-18)
  • 使用开源免费的组件构建一套分布式微服务技术选型推荐
  • 需求质量检测Prompt之是否涉及异常场景
  • QT按钮和容器
  • Kafka4.0 可观测性最佳实践
  • 深入解析 Spring AI 系列:解析函数调用
  • ​​[硬件电路-245]:电气制图软件有哪些
  • 不会索赔500万的苹果,翻车如期到来,不过已没啥影响了
  • 第十一章:AI进阶之--模块的概念与使用(一)