PDF 里的图表和截图是怎么找出来的
PDF 里的图不是一类东西。有谷大脑把 PDF 结构解析(native candidate)和视觉语言模型检测(VL layout)两路结果融合,只抽真正有信息量的图,跳过 logo 和装饰。
一份研报或技术文档,往往有几十张图:数据图表、架构图、截图、产品配图,还有页眉的公司 logo 和页脚的分割线。
如果你把 PDF 里所有图全部抽出来,用户问一个问题,返回的「相关图片」很可能是页眉 logo 或者装饰分割线。这没有意义,甚至会让用户觉得系统在乱答。
所以问题从「怎么把图抽出来」变成了「怎么只抽有信息量的图」。有谷大脑的做法是:从 PDF 文件结构里找到所有 embedded image 的位置,再用视觉语言模型告诉我们哪些位置是「有意义的图」,两步结果做 IoU 匹配,只保留两路都认可的。
PDF 里的图从哪来:三种完全不同的情况
PDF 里的图像资源不是一种东西,大致分三类:
Embedded bitmap(内嵌位图):PDF 文件里存了完整的图片数据——JPEG、PNG 或 JBIG2 压缩后的像素,有独立的 xref(交叉引用 ID)和内容哈希。这类最好处理,可以用 PyMuPDF 直接把原始像素数据读出来,质量和原图一样,不需要经过渲染。
Vector drawing(向量绘图):PDF 里存的是一堆图形指令:moveto、lineto、fillcolor……这些指令在 PDF 阅读器打开时即时渲染,没有独立的 xref,无法直接抽取原始数据。要获取这类「图」,只能先把整页渲染成高分辨率位图,然后按检测框裁剪。
扫描版 PDF:整页是一张大的扫描 bitmap,文字和图都在里面,PDF 结构层面没有任何图片资源的声明,就是一张大图。这类从 native 路径什么都拿不到。
有谷的 pipeline 需要同时覆盖这三种情况,这正是为什么单靠 PDF 结构解析不够,还需要视觉模型来补。
Native Candidate 采集:从 PDF 文件结构读位置
对 embedded bitmap,用 PyMuPDF 遍历每一页的 get_images(full=True) 方法:
for page_index in range(len(doc)):
page = doc[page_index]
pw, ph = page.rect.width, page.rect.height
seen_xrefs: set[int] = set()
for img in page.get_images(full=True):
xref = img[0]
if xref in seen_xrefs:
continue
seen_xrefs.add(xref)
for rect in page.get_image_rects(xref):
bbox_norm = (
rect.x0 / pw, rect.y0 / ph,
rect.x1 / pw, rect.y1 / ph,
)
candidates.append({
'page': page_index,
'xref': xref,
'bbox': bbox_norm,
'source': 'native',
})
几个重要的细节:
seen_xrefs 去重。同一张图片(比如页眉 logo)在不同页面引用的可能是同一个 xref。PDF 允许一个图片资源被多页引用,不去重的话一份 50 页的文档里 logo 会被记录 50 次,全部入库后检索时只会返回 logo。
bbox 归一化。PyMuPDF 返回的坐标是 PDF 点(points)单位,而视觉模型输出的坐标通常是 [0, 1] 的归一化值或者像素坐标。为了后续做 IoU 计算,有谷在采集阶段就把 native bbox 归一化到 [0, 1],避免单位转换错误。
图片尺寸过滤。PDF 里有大量极小的图片资源:图标、装饰点、1×1px 的透明像素(用于追踪)。有谷在 native 候选阶段过滤掉画面占比低于 1% 的图(即 bbox 面积 < 0.01),这部分基本不含有效信息,提前过滤能减少后续 IoU 计算量。
这一步结束,我们知道「这一页的哪些位置有 PDF 原生图片」,但还不知道它们是图表还是 logo。
VL Layout 检测:视觉语言模型说哪里是图
PDF 的每一页渲染成高分辨率位图(一般 150-200 DPI,在 A4 纸上约 1240×1754px),送给 layout 检测模型处理。
Layout 模型负责识别页面上每个区域的「类型」:figure(图表)、table(表格)、text(正文段落)、title(标题)、header(页眉)、footer(页脚)等。每个检测结果带一个 bbox 和一个置信度分数。
这一步拿到的是语义标签——模型说「这里是一个有价值的图表」,但没有图片内容,只有坐标。对向量图和扫描版 PDF,这是唯一能告诉我们「这里有个图」的来源。
Layout 检测结果过滤 figure 类别,置信度低于阈值(通常 0.5-0.7,根据模型调整)的丢弃,剩下的作为 VL candidates。
IoU Fusion:两路合并
有了 native candidates 和 VL candidates,做 IoU(Intersection over Union,交并比)匹配:
IoU = 两个 bbox 的交集面积 / 两个 bbox 的并集面积
结果分三类:
Fused(IoU ≥ 阈值,通常 0.5):PDF 结构里有图,视觉模型也说这里是 figure。两路都确认,可信度最高,抽取这张图。提取时用 native 路径拿原始像素(质量最好),不从渲染的页面截图里裁剪。
Native Only(只有 native candidate,没有对应 VL bbox):PDF 结构里有图,但视觉模型没把它标成 figure。通常意味着这张图「视觉上不像一个有信息量的图表」——背景图、装饰性图案、品牌 logo、小图标都会落在这里。跳过,不入库。
Layout Only(只有 VL candidate,没有对应 native candidate):视觉模型检测到图区域,但 PDF 里没有对应的 embedded image。这对应向量图(PDF 指令绘制的折线图、饼图)或扫描版 PDF 的某一页。走渲染裁剪路径:从该页的页面渲染图按 VL bbox 裁剪,入库。
这个融合逻辑用一句话概括:视觉模型说「这里有意义」,并且 PDF 结构也可以提供原始像素,优先用原始像素;视觉模型说「这里有意义」但没有原始像素,渲染裁剪;PDF 结构有图但视觉模型说没意义,丢弃。
实际效果:一份典型研报的数据
以一份 30 页的行业研报为例,用 PyMuPDF 扫描到的 embedded images 约 85-120 个,包含大量页眉 logo(每页 2-3 个)、装饰分割线(每页 1-2 个)、数据图表(全文约 15-25 个)。
经过 native only 过滤和 IoU fusion 后,最终入库的图片约 15-20 个,基本都是折线图、柱状图、市场份额饼图和架构示意图。Logo 和分割线几乎全部被 native only 路径过滤掉,因为 layout 模型不会把页眉区域标为 figure。
用户问「这份报告里有没有关于市场份额的图表」,检索结果里会直接附上对应的饼图,而不是页眉 logo。
图片和文本的关联
图片单独入库用处有限——用户问「帮我解释这张图」时,系统需要知道这张图和文档里哪段文字相关,才能给出上下文。
有谷在抽图时保留了 {page_index, xref, bbox} 三元组,和文档的分段(chunk)做了关联:同一页的文本 chunk 和同一页的图片 asset 被绑定在一起。用户检索到某一页的文字内容时,系统可以把该页的图片一起展示,回答附图。
对于「layout only」路径的图(向量图、扫描图),还需要额外保存裁剪后的位图到对象存储,并记录 MinIO 路径,供前端展示时加载。
扫描版 PDF 的特殊问题
扫描版 PDF 的页面是整张 bitmap,native 路径拿到的「image」实际上就是整页——一个 xref 对应整页内容。这和「embedded 的数据图表」的 xref 很难在 native 路径区分。
有谷的处理策略是:如果 native candidate 的 bbox 超过页面面积的 85%(几乎全页),认为这是「扫描页整体」,不按 native embedded image 处理,而是全量走 VL layout 检测,把 figure 区域按 layout only 路径裁剪出来。
这个阈值(85%)是经验值,在测试集上扫描版识别准确率和误判率的权衡下得出。混合 PDF(部分页是排版 PDF,部分页是扫描)两条路在同一份文档里可以共存,逐页判断。
图片去重
同一张图(同一个 xref)可能在文档多页出现,也可能通过不同的 xref 引用了内容完全相同的图。有谷对 fused 图片做了基于内容哈希的去重:提取图片字节后计算 MD5,相同哈希的只入库一次,但在多个 page 的关联记录里都有引用。这样一份 20 页的说明书里重复的产品渲染图不会占满 embedding 空间的配额。
什么类型的 PDF 最受益
这套抽图策略对三类文档最有效:
研报和分析报告:图表密集,数据图对理解核心观点至关重要。用户经常需要「看图说话」,附图的检索结果明显比纯文字结果有用。
技术文档和白皮书:架构图、流程图是设计意图的主要载体,文字往往是辅助说明。把架构图和说明文字关联起来,回答时两者一起展示。
带大量插图的教材和手册:图片是内容的一部分而不是装饰,检索到正文时配套的示意图能大幅提升理解效率。
对「装饰驱动」的 PDF(比如品牌宣传册,主要是大图小文字),这套策略反而效果一般——大量全版背景图会被 layout 模型标为 figure,但实际上没有信息量。这类文档在知识库里的价值本身就有限。