ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

RAG文档解析实战:用bbox搞定多栏排版与水印PDF

RAG文档解析实战:用bbox搞定多栏排版与水印PDF 从去年开始我陆续接触了不少 RAG 知识库项目说实话文档解析这一环最容易让人崩溃。前阵子客户丢过来一批双栏排版的行业报告外加几百页带样例水印的 PDF我一开始用常规的 PDF 文本提取库直接抽文字结果检索出来的答案前言不搭后语甚至把样例内部资料这种水印词都当成了正文内容。折腾了两天最后是靠 bbox 才把问题解决的。这篇文章是 RAG 文档解析系列的第三篇重点聊聊 bbox 实战怎么用边界框信息处理多栏排版和水印 PDF。内容适合正在搭 RAG 知识库、被文档解析质量折磨的开发者也适合想从能解析走向解析得对的工程师。先说清楚一个观点PDF 解析从来不是简单地把文字抽出来,而是要把版面结构还原成符合人类阅读顺序的语义流bbox 就是干这件事的关键工具。1. 为什么多栏排版和水印 PDF 会让 RAG 解析翻车1.1 多栏排版对阅读顺序的破坏先说一个很多人踩过的坑你以为 PDF 天然有文本层直接 extract_text 就能拿到干净文本对单栏排版的内容可能还行一旦遇到双栏论文、三栏报纸、杂志式排版结果就完全不能看了。原因在于 PDF 的文本流是按物理对象存储的不是按人类阅读顺序存储的。渲染引擎把文字块排列在页面上提取工具就按对象的位置依次输出。放在双栏页面里文本流基本是这个鬼样子先输出左栏第一行紧接着输出右栏第一行然后跳回左栏第二行再跳到右栏第二行。你用 PDF 解析库抽出来的文字就像一把被反复洗过的扑克牌每两个句子之间硬生生插进来另一篇文章的内容。这种乱序对 RAG 是致命的。我之前把这种文本直接丢给切片器按 500 token 切块结果很多 chunk 里前半段是 A 论文的左栏内容后半段是右栏内容语义完全断裂。向量化之后这些 chunk 的 embedding 被污染检索时经常把毫不相关的两个段落当成一个整体召回回答质量自然雪崩。1.2 水印对文本质量的隐性污染水印问题比多栏排版更隐蔽。文字水印最常见的形态是样例机密内部资料这类短词以低透明度平铺在页面中间或者斜穿正文图片水印则是公司 logo 反复贴在每一页。扫描件场景下水印是直接烧进图像里的你根本没有文本层可提取只能走 OCR。这时候问题就来了OCR 引擎分不清哪个是水印哪个是正文会把样例两个字和正文一起识别出来。如果一页上有 4 个水印几千页文档积累下来向量数据库里就会混入上万条垃圾 token。更让人头疼的是半透明水印会覆盖在正文文字上导致 OCR 识别出来的字符发生粘连或错乱。比如正文里的一句本项目于2023年启动如果中间刚好叠了个水印识别结果可能变成本项 目于2023年启 动甚至直接漏掉几个字。这种隐性污染最难排查因为你不看原图根本发现不了问题在哪。1.3 对 RAG 检索链路的连锁影响把解析和检索串起来看问题就更清楚了。RAG 的质量链路是文档解析 → 文本切片 → 向量化 → 检索召回 → 生成回答。解析环节出的每一个乱序、每一段噪声都会沿着这条链路不断放大。切片的逻辑一般是按 token 数量和上下文重叠来定的。如果解析文本里混入了水印词这些词会被切进多个 chunk导致 embedding 里出现大量干扰信号。检索的时候用户问内部资料的审批流程结果召回了含内部资料字样的正文段落但实际内容和审批流程毫无关系——这就是水印噪声的典型危害。多栏乱序的连锁反应更直接RAG 回答依赖检索到的上下文片段如果上下文本身就是断的生成器再聪明也只能基于错误信息做推理。这就是为什么很多人反馈RAG 检索命中率看着还行但生成的答案逻辑混乱问题往往就出在源头解析上。2. bbox 与版面分析先搞清楚坐标再谈解析2.1 bbox 是什么从 OCR 输出到版面结构bbox 全称 bounding box中文叫边界框或包围盒。在文档解析场景里它表示的是版面上每个文本块、图像块、表格块的位置范围通常用坐标点描述。拿 OCR 来说PaddleOCR 这类引擎的识别结果里每一行文本都会附带一个四点坐标类似[[x1, y1], [x2, y2], [x3, y3], [x4, y4]]这四个点围出来的就是这一行文字的最小外接矩形。有些场景下是水平矩形只需要左上角和右下角两个点就能表示遇到倾斜文本OCR 会给旋转框需要四角点。理解 bbox 的时候你可以把它想象成给一张报纸盖了个透明网格每个文字块占据哪些格子坐标就是格子的位置描述。有了这套坐标文本就不再是一串无意义的字符流而是带有空间信息的对象——我们可以知道这个句子在页面的哪个位置、左边有什么、右边有什么、和上下文的距离是多少。这里有个细节容易混淆PDF 内置的坐标单位是磅pt以页面左下角为原点而 OCR 输出的坐标是图像的像素坐标以图片左上角为原点。做 bbox 实战时建议先统一坐标系。我一般习惯在渲染 PDF 为图片时规定一个固定的 DPI比如 200这样像素和物理位置的关系就是确定的算起栏距、页边距都不会乱。2.2 用 bbox 判断多栏排版的三种信号多栏排版不是靠猜的bbox 里给了很明确的信号主要有三种。第一种是 x 中心聚类。统计页面上所有文本行中心的 x 坐标双栏排版会出现两个明显的聚集簇左栏文本的中心集中在页面 1/4 宽附近右栏集中在 3/4 宽附近。用直方图就能看到两个波峰。第二种是栏间距。观察所有文本块 x_min 的分布会发现中间有一段区域是空白的没有任何文本起点落在这个区间这就是左右栏之间的空白带。栏间距的宽度和页面总宽度之比是判断栏数的重要依据。第三种是行起点分布。正常情况下左栏每一行的起始 x 坐标都对齐在某一个固定值附近右栏同理。如果某个文本块的 x_min 明显偏离所有栏的起点集合它可能是跨栏标题、居中插图或者表格。实际做的时候我不会只用单一信号而是把三种信号交叉验证。特别是遇到页面上既有正文又有侧边栏、注释栏的复杂布局时单一阈值很容易翻车多信号联合判断能显著降低误判率。2.3 水印在 bbox 维度上的典型特征水印在 bbox 维度上同样有迹可循抓住几个特征就很好识别。位置固定是最重要的信号。同一个水印文本比如样例两个字几乎每页都出现在相同坐标。我统计过一批带水印的报告水印 bbox 的中心点跨页偏差通常在 5 个像素以内。位置校验是水印识别里最可靠的规则之一。内容重复是第二个信号。水印是模板化产物同一文档里会反复出现完全相同的短文本。把全文档的文本行做个词频统计高频且短小的文本基本可以列为水印候选。尺寸一致是第三个信号。水印的字体、字号固定所以识别出来的行高bbox 的 y_max 减 y_min高度一致。如果发现某个文本在所有页面上的行高几乎相同那就是个很强的提示信号。还有两个辅助信号一是 OCR 置信度偏低因为水印通常颜色浅、透明度高OCR 引擎识别这类低对比度文字时分数普遍不高二是旋转框常见的斜向水印会有 45 度左右的旋转角度而正文文本基本都是水平排布的。把这些信号组合起来就能在 bbox 层面把水印从正文里剥离出来。3. 实战一多栏排版 PDF 的 bbox 切分与重排3.1 环境准备与解析流程设计这次实战我用的方案是 PaddleOCR 加简单的版面规则处理。选择 PaddleOCR 是因为它开箱即用、中文识别效果好、返回结果天然带 bbox对大多数项目来说够用了。如果你在意部署体积可以换 RapidOCR它是 PaddleOCR 模型的 onnx 版本API 类似不过这篇的重点是处理方法换引擎不影响思路。安装阶段没什么特别的pip 装 paddleocr 和 paddlepaddle 就行。这里提醒一句PaddleOCR 的版本迭代比较快2.7 之后的版本推荐用ocr.predict()新接口返回结构是结构化字典而 2.6 及以前常用的是ocr.ocr()接口返回的是嵌套列表。我这篇示例代码基于经典接口写逻辑清晰你根据自己的版本微调即可。整个解析流程我建议这么设计PDF 按页渲染成高分辨率图片200 DPI 起步低于这个值小字号容易糊OCR 识别每一页拿到文本行内容和 bbox基于 bbox 做版面分析判断栏数、定位页眉页脚、识别跨栏标题按阅读顺序重排文本输出结构化文本后处理清洗噪声、合并同一段落的行这个流程里最核心的一步是第 3 步文本行有了坐标后续的栏切分和排序才有依据。我见过不少团队跳过版面分析直接按 y 坐标排序遇到单栏文档没问题多栏文档就废了。3.2 核心代码栏检测、切分与阅读顺序重排先看怎么从 OCR 结果里提取标准化的 bbox 信息import numpy as np def extract_text_boxes(ocr_result): 把 PaddleOCR 返回结果解析成统一的文本行结构 ocr_result 形如: [[bbox, (text, score)], ...] boxes [] for line in ocr_result: bbox line[0] # 四点坐标 text line[1][0] # 识别文本 score line[1][1] # 置信度 xs [p[0] for p in bbox] ys [p[1] for p in bbox] boxes.append({ text: text, score: score, x_min: min(xs), x_max: max(xs), y_min: min(ys), y_max: max(ys), cx: float(np.mean(xs)), # 中心点 x cy: float(np.mean(ys)), # 中心点 y height: max(ys) - min(ys), }) return boxes拿到这些文本行之后下一步是判断页面有没有分栏。我用一个最直观的方法对每行文本的中心点 x 画直方图观察波峰数量。import matplotlib.pyplot as plt def show_column_distribution(boxes): cx_values [b[cx] for b in boxes if b[height] 5] # 过滤掉噪声行 plt.hist(cx_values, bins50, edgecolorblack) plt.xlabel(text center x (pixel)) plt.ylabel(line count) plt.show()如果直方图出现两个明显的波峰就可以确定是双栏。确定栏数之后我用 KMeans 对中心点 x 做一维聚类把每行文本归入对应的栏。这里有一个关键点KMeans 的簇数要和页面实际栏数对齐。三栏页面就设成 3设置错了后续全乱。from sklearn.cluster import KMeans def split_columns(boxes, n_cols2): cx np.array([b[cx] for b in boxes]).reshape(-1, 1) kmeans KMeans(n_clustersn_cols, random_state42, n_init10) labels kmeans.fit_predict(cx) # 按簇中心 x 从小到大排序保证左栏在前、右栏在后 centers sorted( {i: kmeans.cluster_centers_[i][0] for i in range(n_cols)}.items(), keylambda item: item[1] ) label_to_order {label: order for order, (label, _) in enumerate(centers)} columns {i: [] for i in range(n_cols)} for label, box in zip(labels, boxes): col_idx label_to_order[label] columns[col_idx].append(box) return columns分栏完成之后每个栏内部的排序逻辑就很简单了同一栏内按 y 坐标从上到下排序y 相同再按 x 从左到右。最后按左栏到右栏的顺序拼接文本这就还原了人类的阅读顺序。def rebuild_reading_order(columns): ordered_lines [] col_count len(columns) for col_idx in sorted(columns.keys()): lines columns[col_idx] lines.sort(keylambda b: (round(b[y_min], 1), b[x_min])) for b in lines: ordered_lines.append(b[text].strip()) return \n.join(ordered_lines)注意我的排序里把 y_min 做了 round 处理精度取 0.1。这么做是防止 OCR 在同一行内把左右两个文本块识别成两行导致 y 坐标有细微抖动排序错乱。3.3 参数调优栏间距、页边距与噪声过滤KMeans 是无监督聚类不需要手动指定阈值但实际项目里我对它还是做了不少约束。原因很简单版面越复杂聚类越不靠谱。比如页面顶部有一个跨栏的居中标题它的中心 x 正好落在页面中间KMeans 会把它随便分到某一栏排序就错了。我的处理办法是先识别跨栏元素再分栏。判断逻辑不复杂如果某个文本行的 x_min 接近页面左边缘x_max 接近右边缘或者它的宽度超过页面宽度的 60%就认为它是跨栏内容单独挑出来放在正文流的最前面。还有一个容易翻车的点页边距噪声。OCR 经常会把页码、页眉页脚、甚至页边残留的杂点识别成文本行。这些噪声如果不处理会被排进正文流污染 RAG 切片。我用的是 y 坐标范围过滤渲染图片时记录页面内容区的高度把 y_min 落在页面顶部 8% 和底部 8% 区域内的文本行剔除页码和页眉就基本清掉了。栏间距的判定我放在聚类之后做验证。算每个簇中心之间的距离如果两栏中心距离小于页面宽度的 25%我会认为这不是真正的双栏排版而是单栏页面里的一些缩进造成的误判。这种情况下我会降级为单栏处理所有文本统一按 y 排序。这个校验阈值我调过很多次25% 是我对所有测试集表现最稳的值你可以根据自己文档类型做微调。4. 实战二水印 PDF 的识别与过滤4.1 文字水印基于跨页统计 bbox 位置过滤文字水印最可靠的特征是全文档高频重复所以我处理水印的第一原则是不在单页上判断而是把整个文档的所有文本行汇总到内存里做跨页统计。核心思路分三步。第一步遍历每一页的 OCR 结果把文本内容归一化后计数关注那些长度小于 20 个字符的短文本。第二步找出出现在大量页面上的高频短文本阈值我一般设在出现页数 ≥ 总页数的 80%。第三步对这个候选集合做位置校验把候选文本在每一页的 bbox 中心点坐标存下来计算它们跨页的位置偏差如果偏差很小基本可以断定是水印。这个判断逻辑是有现实依据的。正常正文很少会在 80% 以上的页面里出现完全相同的短句子就算出现位置也往往分散在页面不同区域不会固定在同一个坐标附近。水印则恰好相反内容和位置都是模板化的两个特征天然满足。有个边界情况要注意某些文档的页眉公司名、页码样式也会满足高频 位置固定的条件。如果一刀切删掉页眉信息就丢了。我会在最后加一道放行规则如果这个文本行的字号明显大于正文平均行高或者它的位置落在页面最顶部且和章节标题结构相关就保守地保留。4.2 图片水印与扫描件水印图像预处理思路图片水印在文本层面是无迹可寻的因为 OCR 根本识别不出来 logo 里的内容它只会安静地贴在版面上。扫描件水印更麻烦它直接成为图像的一部分唯一的应对方式是在 OCR 之前做图像预处理。最常见的图片水印形态是半透明、浅色的 logo 平铺。这种水印有一个特点它和背景的对比度很低但正文文字的对比度通常很高。利用这个差异我可以用 OpenCV 在 HSV 色彩空间里做一次亮度阈值过滤。import cv2 import numpy as np def remove_light_watermark(image_path, output_path, brightness_threshold200): img cv2.imread(image_path) hsv cv2.cvtColor(img, cv2.COLOR_BGR2HSV) # 提取 V 通道亮度浅色水印的亮度通常很高 v_channel hsv[:, :, 2] # 亮度高于阈值的区域视为背景/浅色水印置为白色 mask v_channel brightness_threshold img_processed img.copy() img_processed[mask] [255, 255, 255] cv2.imwrite(output_path, img_processed)这个方案的原理很简单把接近白色的像素全部抹掉。半透明水印因为颜色浅、亮度高会被当成背景清理掉而正文文字通常是深色亮度低不受影响。实话说这个方法对浅灰、浅蓝、浅红色水印效果好但如果水印颜色很深、对比度很高阈值过滤就会误伤正文这种情况要换思路。如果水印是深色的我会改用形态学操作或频域滤波。频域方案是把图像做傅里叶变换找到周期性重复的噪点水印平铺在频域上会形成规律的亮斑在频域里把这些亮斑滤除后再反变换回空间域。这个方案理论可行但实现复杂度高、参数敏感我实际项目里用得不多除非客户对水印清除率有硬性要求。4.3 代码实现水印文本过滤与正文保真图像预处理做完之后扫描件上的浅色水印会被清理掉一部分。但总有漏网之鱼尤其是那些恰好和正文重叠的水印区域图像层面即使把浅色像素抹掉了残留的边缘还是可能被 OCR 识别出来。这时候就要靠 bbox 层做兜底过滤。我整理了一份完整的水印过滤代码把跨页统计 位置校验 置信度辅助组合在一起from collections import Counter def filter_watermarks(pages_boxes, min_page_ratio0.8, max_position_var3): pages_boxes: list of list每个元素是一页的 text boxes total_pages len(pages_boxes) page_text_counter Counter() text_positions {} for page_id, boxes in enumerate(pages_boxes): for b in boxes: text b[text].strip() if not text or len(text) 20: continue page_text_counter[text] 1 if text not in text_positions: text_positions[text] [] text_positions[text].append( (page_id, round(b[cx], 1), round(b[cy], 1)) ) watermark_texts set() for text, cnt in page_text_counter.items(): if cnt total_pages * min_page_ratio: continue positions text_positions[text] cxs [p[1] for p in positions] cys [p[2] for p in positions] # 位置固定性校验中心点标准差很小 if np.std(cxs) max_position_var and np.std(cys) max_position_var: watermark_texts.add(text) return watermark_texts过滤时我额外加了一条保真规则如果某条候选水印文本和正文文本在 bbox 上有大面积重叠IoU 大于 0.3我不直接删掉整行而是把置信度低的那个结果丢弃、保留置信度高的结果。这种重叠区域二选一策略能显著降低正文被误删的概率。实际项目里我把图像预处理和 bbox 过滤组合成一条完整管线先渲染 PDF → 浅色水印图像清理 → OCR 识别 → bbox 水印过滤 → 版面重排 → 输出结构化文本。这套管线跑下来我处理过的一份 500 页带机密水印的文档水印残留率从最初的 90% 降到了 5% 以下正文文字基本无损。5. 常见翻车现场与排查技巧实录5.1 翻车场景一栏切错了正文串行这是多栏处理里最高频的翻车现场。症状很明显重排出来的文本左一句右一句上一行还在讲技术方案下一行突然跳到结论阅读体验完全是乱的。我排查这个问题时第一件事永远是可视化。把 OCR 识别出的每一行文本和它的 bbox 画出来生成一张标注图一眼就能看出聚类出了什么问题。常见原因有两个一是页面中央有个大插图或大表格把左右栏的文本在视觉上隔开了KMeans 聚类时把表格两侧的碎片归错类二是页面本身是三栏但你按双栏逻辑做了聚类。解决办法是引入更多版面信息。大插图区域通常没有文本行所以聚类算法看到的只是两堆文本这时候需要先检测页面中的空白带把连通的文本区域划分出来再做聚类。另外我建议把跨栏表格的单元格单独处理——表格的读取顺序规则和正文完全不同强行混在正文流里RAG 解析质量一定会受损。5.2 翻车场景二水印误杀正文水印误杀正文是最让人头大的问题。有些正文里确实存在高频短句比如一本操作手册里面反复出现的请确认设备状态这句话每页都有、位置也差不多如果统计逻辑太粗暴它就会被当成水印删掉。我踩过这个坑之后在过滤逻辑里加了三个保护条件。第一位置固定性校验的阈值不能放得太宽中心点标准差超过一定值就要放行第二文本长度和行高校验水印文本通常很短、行高一致正文即使是短句行高也会有波动第三上下文校验如果一个文本行两侧有明显的上下文关联上一行和下一行能组成完整句子大概率不是水印而是正文强调。还有一个非常实用的技巧只过滤完全不参与语义连贯性的候选水印。具体做法是把候选水印文本从正文里剔除后重新拼接相邻文本行如果前后文本能流畅衔接说明剔除正确如果拼起来语句不通那说明可能删错了。这个后验校验成本很低但对降低误杀率帮助很大。5.3 翻车场景三表格、页眉页脚干扰表格和多栏排版的组合是解析里的重灾区。表格单元格的文本位置散落在各个坐标如果把表格当成普通文本流按栏排序后单元格内容会七零八落完全没法用。我的经验是表格需要在版面分析阶段单独识别。有两条路可以走一条是纯规则——根据文本行 x_min 和 x_max 的对齐规律、单元格之间的等距特征来猜表格区域另一条是上模型——用 DocLayout-YOLO 或 PP-StructureV3 这类版面分析模型它们内置了表格检测能力可以输出表格区域的 bbox。我自己的生产管线现在默认走模型路线规则只做兜底。页眉页脚相对好解决按照我前面说的 y 坐标范围过滤就行。但有一种情况例外页眉上有章节标题页脚上有版权信息如果整页都过滤掉部分需要按章节检索的场景会丢失分类信息。稳妥的做法是把页眉内容单独提取出来作为页级元数据不混入正文字段但保存在文档结构里供检索使用。5.4 快速自检清单我把这些年积累的检查经验整理成一份清单每个新文档类型接入解析管线时我都会先过一遍检查项判断方法异常处理多栏判定是否准确统计各栏文本行数占比双栏应近似 50/50占比偏差超过 15% 需检查聚类逻辑栏内顺序是否正确抽取前 5 行文本人工看是否语义连贯不连贯则检查 y 排序是否被抖动干扰水印过滤是否误伤正文随机抽 20 页逐页比对原图与输出文本有误伤则收紧位置校验阈值页眉页脚是否混入正文检索结果中出现第 X 页反复出现增加页面边缘区域过滤跨栏标题是否归位检查页面中部是否为完整标题文本标题需单独提取并置于段落开头OCR 识别率是否达标抽样 10 段文本核对错字率错字率高需提高渲染 DPI 或降噪这份清单是面向可解释性的不是为了走形式。文档解析的坑最怕隐藏一旦进了 RAG 链路就非常难回查所以在解析阶段做人工抽查是值得的。6. bbox 解析对 RAG 检索效果的实战影响6.1 解析质量如何传导到检索质量总有人问我解析阶段花这么大力气处理 bbox到底对 RAG 检索有多大影响我可以负责任地说影响极大而且是决定性影响。RAG 的检索效果取决于两个核心环节一是切片质量二是向量质量而这两个环节的上游都是解析输出。一个语义完整的切片需要 chunk 内部上下文连续、主题聚焦。多栏乱序会导致切片里的上下文错位同一段落被切到不同 chunk检索时语义匹配就会失效。水印噪声则会让 chunk 里混入与主题无关的 tokenembedding 被稀释检索召回的相关性分数被拉低。量化地说我处理过一个客户机器翻译文档集原始 PDF 是双栏排版加页眉 logo 水印。直接提取文本进 RAG 之后Top-5 检索命中率只有 41%用 bbox 重排加水印过滤后同样的测试集命中率提升到了 76%。这个差距在真实问答场景里的体现是回答从摸不着头脑变成了能直接引用原文段落。6.2 实测同一 PDF 在三种解析方案下的召回差异为了把问题说得更具体我做了一个横评实验拿 50 篇双栏学术论文和 30 篇带样例文字水印的行业报告组成测试集对比三种解析方案在 RAG 检索链路里的表现。三种方案分别是方案 A 直接用 pdfplumber 提取文本不做任何版面处理方案 B 用 PaddleOCR 裸识别按 y 坐标从上到下排序输出方案 C 用本文这套 bbox 版面分析多栏重排加水印过滤。检索指标用 MRRMean Reciprocal Rank平均倒数排名和 Recall5 来评估。解析方案文本乱序率水印混入率Recall5MRRApdfplumber 直接提取68%0%45%0.31BPaddleOCR 裸识别 简单排序52%83%38%0.27Cbbox 版面分析 水印过滤6%4%74%0.62方案 B 的 Recall 反而比 A 低原因很直接OCR 把水印文本混进了正文字流高频水印词污染了部分 chunk 的向量表示检索时噪声干扰比乱序更伤相关性。到方案 C 这里乱序率降到 6%水印混入率降到 4%MRR 几乎是方案 A 的两倍。这个结果充分说明了版面解析在 RAG 里的地位它不是锦上添花的可选项而是检索质量是否达标的决定性前置步骤。6.3 经验总结与扩展方向最后聊点项目层面的心得。这套 bbox 方案在我手里迭代了很多版本踩过的所有坑总结下来就是一句话永远不要把解析当文本提取要当版面重建。PDF 解析的本质是把二维版面还原成一维语义流而 bbox 就是连接二维和语义之间的桥梁。我的生产推荐组合是渲染层用 PyMuPDFfitz把 PDF 转成高分辨率图片版面分析层用 DocLayout-YOLO 或 PP-StructureV3 模型识别出正文、标题、表格、水印等区域的 bbox最后叠加规则做细调。纯规则方案对小规模验证够用生产环境还是建议模型加规则双轮驱动稳定性会高很多。这套能力往后还能继续扩展。一个方向是保留图片和表格的 bbox 裁剪结果把版面里的图表单独存成图片并在切片时建立文本引用图片的关联这样 RAG 就能覆盖图文结合的问题也就是现在常说的多模态 RAG 方向。另一个方向是把解析出的版面结构输出成 Markdown 或 JSON 格式保留标题层级和段落关系让下游切片逻辑可以按章节语义来做而不是单纯按 token 数量硬切。我在实际使用中还有一个很深的体会解析方案一定不要一上来就追求完美。先跑通一条基础管线准备一份覆盖主要障碍的小样本集快速做出可视化标注然后针对真正的错误样本迭代。版面解析永远是数据驱动的活规则是死的人是活的参数调优、阈值设定都需要拿你自己的文档类型说话。这篇分享的代码和方法是我能稳定复现的方案但迁移到你的场景时一定要过一遍前面那份自检清单你的文档长什么样最终决定你的解析策略长什么样。
返回列表