PDF文档中的红色印章常影响可读性,本文分享一种通过分析PDF结构实现高效去印章的技术方案。

.
├── input
│ ├── 含印章文档1.pdf
│ └── 含有印章文档.pdf
├── output
│ ├── 含印章文档1.pdf
│ └── 含有印章文档.pdf
└── remove_red_seal.py
最初采用的方案是将PDF页面转换为图片后,使用OpenCV的inpaint功能进行图像修复。这种方法虽然可行,但存在明显缺陷:它破坏了PDF原有的文档结构,导致文字可选中性丧失,且处理效果不够理想。
原始方案的核心代码如下:
def remove_stamp_from_image(img):
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
lower_red = np.array([0, 43, 46])
upper_red = np.array([10, 255, 255])
mask = cv2.inRange(hsv, lower_red, upper_red)
result = cv2.inpaint(img, mask, 3, cv2.INPAINT_TELEA)
return result
该方法存在以下主要问题:
分析发现,测试文档中的印章实际上是独立的图片对象,而非与正文融合的图像。使用pdfplumber检查对象数量显示:
input
含印章文档1.pdf pages=1 chars=451 images=1
含有印章文档.pdf pages=1 chars=562 images=1output
含印章文档1.pdf pages=1 chars=451 images=0
含有印章文档.pdf pages=1 chars=562 images=0
在PDF内容流中,印章通常以类似以下形式出现:
q
260 0 0 360 168 241 cm
/Im1 Do
Q
改进后的方案采用两层策略:
核心流程如下:
for page in doc:
for image in page.get_images(full=True):
xref = image[0]
pix = fitz.Pixmap(doc, xref)
if _looks_like_red_stamp(bgr):
page.delete_image(xref)
为避免误删,识别算法采用了多条件判断:
return (
(r16 > 70)
& (r16 > g16 + 18)
& (r16 > b16 + 18)
& (r16 > (g16 * 1.12))
& (r16 > (b16 * 1.12))
)
新方案的优势在于:
对于印章已与扫描图融合的情况,需采用图像处理流程:
建议的处理优先级为:
PDF 对象层删除 -> 内容流修补 -> 图像级修复 -> 人工复核
本文介绍的技术方案通过分析PDF结构实现了高效去印章,既保留了文档质量又提升了处理效率。对于电子文档,优先删除印章对象是最佳选择,而扫描件则需结合图像处理技术。
python包依赖:
pip install pymupdf opencv-python numpy pillow
源码:
#!/usr/bin/env python3
"""
Remove red seal / stamp images from PDFs in input/ and write clean PDFs to output/.Preferred path:
- PyMuPDF removes the stamp image object from the PDF page, preserving vector text.Fallback path:
- For simple PDFs like the samples in this project, patch the PDF content stream
incrementally and remove only the drawing commands for red image XObjects.Optional install for the preferred path:
pip install pymupdf opencv-python numpy pillow
"""from __future__ import annotationsimport os
import re
import shutil
import zlib
from dataclasses import dataclass
from pathlib import Pathimport cv2
import numpy as np
INPUT_FOLDER = Path("./input")
OUTPUT_FOLDER = Path("./output")
@dataclass(frozen=True)
class PdfObject:
number: