返回列表

PDF去除图形水印实现思路:从候选聚合到精准删除的工程复盘

当前专题

PDF图形水印
PDF图形水印
实现原理
PDF去水印原理
图形水印识别
PDF矢量水印删除
大PDF优化

2026-04-22

6分钟

墩墩

在线体验地址:https://www.douyacun.com/pdf/remove-watermark

刚开始做 PDF 图形水印的时候,我其实也有过一个很天真的判断:既然是重复出现的图形,那就把重复图形找出来删掉,事情应该就差不多了。

后来很快就发现,问题根本没这么简单。图形水印和正文里的图标、流程图节点、装饰线、页眉角标,底层上经常是同一种东西,都是 path、fill、stroke 那一套。你要是删得粗一点,水印是没了,正文也跟着少一块。你要是删得保守一点,正文保住了,水印又会残留。

所以图形水印真正难的地方,不是“能不能找到重复图形”,而是“到底哪一组图形才值得删,而且删完不能伤正文”。

开篇结论

我现在对这个问题的看法很直接:图形水印不是一个“检测到就删”的问题,而是一个“先判断,再精确动手”的问题。

只靠单一规则基本不够用。只看位置,很容易把页眉页脚也当进去;只看颜色,浅灰色正文图形会一起进来;只看重复性,又会把模板化的页面元素错认成水印。真要把效果做稳,还是得先回到 PDF 底层,把真实图形对象提出来,再把零碎 occurrence 整成能看懂的候选,最后只删确认过的对象,不碰整页。

这也是我后来越来越确定的一点:图形水印尽量不要退回到“把页面截图出来处理”的思路里。只要还能在 PDF 对象层解决,就应该留在对象层,因为那里最不容易伤正文。

图形水印识别策略

图形水印识别,第一步其实就和很多人想的不一样。不是先在截图上画框,而是先去读 PDF 里面的 drawings、graphic blocks 和内容流。

真正有用的信息,不是“这块看起来像个水印”,而是它到底是哪几个 path 组成的、颜色是什么、是不是填充图形、是不是从独立的 stream 里出来的、对象引用在哪。只有这些信息留住了,后面才谈得上精确删除。

我后来比较认同的一套做法,是先把单页的图形对象全部拉平,再做一次候选聚合。原因很简单,真实 PDF 里的图形水印很少是一个对象,多半是一组小图形拼起来的。你要是把原始块直接丢给前端,用户面对的会是满屏碎片,根本无从判断。把位置接近、形状接近、颜色接近、重复模式也接近的对象先聚成一类,事情就清楚很多了。

再往后,才轮到判断“像不像水印”。这时候会综合看几个很实际的信号:是不是总出现在几个固定位置、颜色是不是比正文淡、结构是不是重复得很像模板、是不是来自比较独立的对象层。到这里为止,依然不急着自动删,而是优先把明显像水印的候选推到前面,把高风险的留给人工再确认一眼。

下面这张图,就是 亿图图示水印.pdf 的真实第一页。肉眼看得出来它有一层重复的灰色图形水印,但程序只有先走完对象抽取和聚合,后面的删除才有可能安全。

原始图形水印 PDF

光看原页还不够,最好再把“识别出来的到底是什么”单独拿出来看。下面这张图不是手动画的示意图,而是从真实识别结果里直接导出的 SVG。也就是说,程序最终锁定的,就是这一组图形对象。

识别出的图形水印 SVG

如何做到精准去除图形水印

图形水印去除这件事,最怕的就两种结果:一种是删不干净,另一种是删得太猛。

前者很常见,尤其是只按 bbox 或截图区域去处理的时候,看起来框住了,实际上对象没删全。后者更麻烦,正文里的图标、线条、节点结构会被一起带走。

所以我后来基本不再相信“给一个矩形框就去掉水印”这类做法。更稳的是把前面识别到的 graphic_itemsobject_refgraphic_rect 这些定位信息一直带到 apply 阶段,然后只改用户确认过的那批对象。说白了,删的是对象,不是图片。

亿图图示水印.pdf 这个样本就很典型。第一页最后聚合成了 16 个图形候选,真正落到删除动作上,对应的是 62 个实际图形对象。用户看到的是更像“这块水印”的候选卡片,后面真正动手的时候,还是精确到内容流块级别去改。这样一来,正文图标不会因为靠得近就被顺手处理,页面也不用整张栅格化回写。

下面这张局部预览图,就是聚合后的一个真实候选。它不是单一路径,而是一组重复图形的组合预览,正好适合作为人工确认入口。

图形水印候选预览

当候选确认下来之后,真正被处理的也只有那批对应对象,其他图形层不动。结果就是,水印能去掉,正文也还保持完整。

图形水印去除结果预览

这件事做到后面会越来越明确:图形水印的“精准”,不是靠更花哨的图像修补,而是靠更扎实的对象定位。

大 PDF 为什么慢,以及怎么优化

大 PDF 慢,通常慢的也不是最后那一下删除,而是前面的图形分析和 preview 生成。

这个问题我踩过几次坑之后,感觉规律挺明显。页数一多、path 一多,光是提取对象和做签名匹配就已经不轻了。再加上图形候选特别容易膨胀,如果每个原始 occurrence 都单独返回,前端很难看,后端还得跟着生成一堆 preview、paths、base64,接口一下就变重了。

还有一种情况也很典型:不是所有复杂页都值得全量预览。有些页一看就是高复杂度,再硬算下去意义不大,还不如先给一个精简候选,再按需看单个 preview。

所以后来真正有效的优化,并不是一味加并发,而是学会降级。先判断页面复杂度,复杂页不要走完整 preview;候选能聚合就先聚合;preview 能延后就延后;就算分析降级了,删除阶段也还是坚持对象级 apply,不能为了省事退回整页处理。

下面这张图来自 图形水印-大文件_韩顺平.pdf。这类文件页数多、体积大,真正要优化的是“如何减少无效分析和无效预览”,而不是单纯追求一次性把所有页面全算完。

大 PDF 图形水印案例

同样地,高考密练条约.pdf 这种看起来更规整的页面,往往只需要少量稳定候选就能完成 review,不必把简单样本和复杂样本都套进同一套重流程。

图形水印案例-高考密练条约

实际案例与效果图

这次文章里用到的配图都来自真实样本,而不是模拟页面。

案例一

亿图图示水印.pdf 是最适合讲主链路的一个例子。第一页能聚成 16 个候选,最后展开到 62 个真实图形对象。下面这 3 张图,分别对应原页、识别出的 SVG、以及对象级删除后的结果。

案例1 原 PDF 截图

案例1 识别出的图形水印 SVG

案例1 去除后截图

案例二

高考密练条约.pdf 更像是一个轻量样本。第一页只形成 1 个聚合候选,展开后是 7 个真实图形对象。它很适合说明:不是每个图形页都需要重分析,结构规整的时候,整条链会轻很多。

案例2 原 PDF 截图

案例2 识别出的图形水印 SVG

案例2 去除后截图

案例三

图形水印-大文件_韩顺平.pdf 主要是用来说明大文件场景。这里补这 3 张图,不只是为了证明能识别,也是在说明即使是体积和页数都比较大的 PDF,第一页照样能走完整的“原页 -> 识别对象 -> 去除后”链路。

案例3 原 PDF 截图

案例3 识别出的图形水印 SVG

案例3 去除后截图

把这三个案例放在一起看,证明链就比较完整了:原页里确实有图形水印,识别阶段确实锁定到了具体图形对象,删除之后页面也确实发生了对应变化。

适用范围与边界

现在这套做法,最适合的还是那种“水印本身就是 PDF 图形对象”的文件。只要它在多页之间有一定重复性,或者至少带一点模板感,就比较容易处理。

相反,如果水印已经烘焙进扫描底图,那就不是图形水印问题了,而是图像域问题。还有一种麻烦场景,是正文图形本身就很复杂,而且和水印风格特别接近,这时候再激进自动删,风险就很高。

所以我现在更愿意把这个能力理解成一句话:能在对象层精确解决的,就尽量精确解决;风险高的样本,宁可保守一点,也别假装自己什么都能自动删干净。