Python实战教程:逐步去除PDF文件水印(二)
前言
在日常工作中,我们经常会遇到带有水印的 PDF 文件。水印可能会影响文档的可读性或者在打印时造成不便。为了自动化地去除 PDF 文件中的水印,我们可以使用 Python 及其强大的库,如 PyMuPDF、Pillow 和 OpenCV。本文将详细介绍如何实现这一过程。
所需库
首先,我们需要安装以下 Python 库:
使用以下命令安装这些库:
pip install pymupdf pillow opencv-python-headless
实现步骤
导入必要的库
我们首先导入所需的库:
import fitz # PyMuPDF
from PIL import Image
import numpy as np
import cv2
import os
import concurrent.futures
定义去除水印的函数
接下来,我们定义一个函数来去除图像中的水印。该函数利用颜色范围创建掩码,并将掩码区域设置为白色:
def remove_watermark(image, lower_bound, upper_bound):
"""去除水印"""
# 将 PIL 图像转换为 OpenCV 格式
open_cv_image = np.array(image)
open_cv_image = cv2.cvtColor(open_cv_image, cv2.COLOR_RGB2BGR)
# 创建掩码,查找在指定颜色范围内的像素
mask = cv2.inRange(open_cv_image, lower_bound, upper_bound)
# 使用膨胀和侵蚀操作优化掩码
kernel = np.ones((3, 3), np.uint8)
mask = cv2.dilate(mask, kernel, iterations=1)
mask = cv2.erode(mask, kernel, iterations=1)
# 将掩码范围内的像素设为白色
open_cv_image[mask != 0] = [255, 255, 255]
# 将图像转换回 PIL 格式
return Image.fromarray(cv2.cvtColor(open_cv_image, cv2.COLOR_BGR2RGB))
处理单页 PDF 的函数
我们需要一个函数来处理 PDF 文件的每一页,将其转换为图像,去除水印后保存为 PNG 文件:
def process_page(pdf_path, page_num, output_folder, mat, lower_bound, upper_bound):
"""处理单页"""
try:
# 打开 PDF 文件并加载特定页
pdf = fitz.open(pdf_path)
page = pdf.load_page(page_num)
pixmap = page.get_pixmap(matrix=mat)
img = Image.frombytes("RGB", [pixmap.width, pixmap.height], pixmap.samples)
# 调用 remove_watermark 函数去除水印
img = remove_watermark(img, lower_bound, upper_bound)
# 将处理后的图像保存为 PNG 文件
img_path = os.path.join(output_folder, f"{page_num}.png")
img.save(img_path, format="PNG")
print(f"第{page_num}页水印去除完成")
pdf.close()
except fitz.FileDataError:
print(f"无法读取第{page_num}页的数据。")
except fitz.PDFPageError:
print(f"第{page_num}页无法加载。")
except Exception as e:
print(f"处理第{page_num}页时出错: {e}")
主函数
主函数 remove_pdf
负责管理整个 PDF 的处理过程,包括创建输出文件夹、并行处理每一页以及将处理后的图像合并回 PDF:
def remove_pdf(pdf_file, output_folder, output_pdf_path, dpi=1800, lower_bound=(168, 168, 168), upper_bound=(172, 172, 172)):
"""去除 PDF 水印的主函数"""
if not os.path.exists(pdf_file):
print(f"文件 {pdf_file} 未找到。")
return
if not pdf_file.lower().endswith('.pdf'):
print(f"文件 {pdf_file} 不是 PDF 文件。")
return
if not os.path.exists(output_folder):
os.makedirs(output_folder)
zoom = dpi / 72
mat = fitz.Matrix(zoom, zoom)
try:
pdf = fitz.open(pdf_file)
total_pages = len(pdf)
pdf.close()
with concurrent.futures.ThreadPoolExecutor() as executor:
futures = [
executor.submit(process_page, pdf_file, page_num, output_folder, mat, lower_bound, upper_bound)
for page_num in range(total_pages)
]
for future in concurrent.futures.as_completed(futures):
try:
future.result()
except Exception as e:
print(f"处理过程中发生错误: {e}")
image_files = [os.path.join(output_folder, f"{page_num}.png") for page_num in range(total_pages)]
image_list = [Image.open(img_file).convert("RGB") for img_file in image_files]
if image_list:
image_list[0].save(output_pdf_path, save_all=True, append_images=image_list[1:])
print(f"处理后的PDF文件保存为: {output_pdf_path}")
else:
print("没有处理好的图片可以合并为PDF。")
except FileNotFoundError:
print(f"文件 {pdf_file} 未找到。")
except fitz.FileDataError:
print(f"无法读取文件数据 {pdf_file}。")
except Exception as e:
print(f"处理PDF文件时出错: {e}")
主程序入口
在主程序中,我们从用户获取输入路径、输出路径以及水印颜色的上下界,然后调用 remove_pdf
函数:
if __name__ == "__main__":
pdf_path = input("请输入 PDF 地址:")
output_path = input("请输入保存处理后的图片的文件夹地址:")
output_pdf_path = input("请输入保存处理后的 PDF 地址:")
lower_bound = tuple(map(int, input("请输入水印颜色的下界(例如:168,168,168):").split(',')))
upper_bound = tuple(map(int, input("请输入水印颜色的上界(例如:172,172,172):").split(',')))
remove_pdf(pdf_path, output_path, output_pdf_path, lower_bound=lower_bound, upper_bound=upper_bound)
代码解析
以下是本文中使用的完整代码,添加了中文注释,帮助理解每个步骤的功能:
import fitz # PyMuPDF
from PIL import Image
import numpy as np
import cv2
import os
import concurrent.futures
def remove_watermark(image, lower_bound, upper_bound):
"""去除水印"""
# 将 PIL 图像转换为 OpenCV 格式
open_cv_image = np.array(image)
open_cv_image = cv2.cvtColor(open_cv_image, cv2.COLOR_RGB2BGR)
# 创建掩码,查找在指定颜色范围内的像素
mask = cv2.inRange(open_cv_image, lower_bound, upper_bound)
# 使用膨胀和侵蚀操作优化掩码
kernel = np.ones((3, 3), np.uint8)
mask = cv2.dilate(mask, kernel, iterations=1)
mask = cv2.erode(mask, kernel, iterations=1)
# 将掩码范围内的像素设为白色
open_cv_image[mask != 0] = [255, 255, 255]
# 将图像转换回 PIL 格式
return Image.fromarray(cv2.cvtColor(open_cv_image, cv2.COLOR_BGR2RGB))
def process_page(pdf_path, page_num, output_folder, mat, lower_bound, upper_bound):
"""处理单页"""
try:
# 打开 PDF 文件并加载特定页
pdf = fitz.open(pdf_path)
page = pdf.load_page(page_num)
pixmap = page.get_pixmap(matrix=mat)
img = Image.frombytes("RGB", [pixmap.width, pixmap.height], pixmap.samples)
# 调用 remove_watermark 函数去除水印
img = remove_watermark(img, lower_bound, upper_bound)
# 将处理后的图像保存为 PNG 文件
img_path = os.path.join(output_folder, f"{page_num}.png")
img.save(img_path, format="PNG")
print(f"第{page_num}页水印去除完成")
pdf.close()
except fitz.FileDataError:
print(f"无法读取第{page_num}页的数据。")
except fitz.PDFPageError:
print(f"第{page_num}页无法加载。")
except Exception as e:
print(f"处理第{page_num}页时出错: {e}")
def remove_pdf(pdf_file, output_folder, output_pdf_path, dpi=1800, lower_bound=(168, 168, 168), upper_bound=(172, 172, 172)):
"""去除 PDF 水印的主函数"""
if not os.path.exists(pdf_file):
print(f"文件 {pdf_file} 未找到。")
return
if not pdf_file.lower().endswith('.pdf'):
print(f"文件 {pdf_file} 不是 PDF 文件。")
return
if not os.path.exists(output_folder):
os.makedirs(output_folder)
zoom = dpi / 72
mat = fitz.Matrix(zoom, zoom)
try:
pdf = fitz.open(pdf_file)
total_pages = len(pdf)
pdf.close()
with concurrent.futures.ThreadPoolExecutor() as executor:
futures = [
executor.submit(process_page, pdf_file, page_num, output_folder, mat, lower_bound, upper_bound)
for page_num in range(total_pages)
]
for future in concurrent.futures.as_completed(futures):
try:
future.result()
except Exception as e:
print(f"处理过程中发生错误: {e}")
image_files = [os.path.join(output_folder, f"{page_num}.png") for page_num in range(total_pages)]
image_list = [Image.open(img_file).convert("RGB") for img_file in image_files]
if image_list:
image_list[0].save(output_pdf_path, save_all=True, append_images=image_list[1:])
print(f"处理后的PDF文件保存为: {output_pdf_path}")
else:
print("没有处理好的图片可以合并为PDF。")
except FileNotFoundError:
print(f"文件 {pdf_file} 未找到。")
except fitz.FileDataError:
print(f"无法读取文件数据 {pdf_file}。")
except Exception as e:
print(f"处理PDF文件时出错: {e}")
if __name__ == "__main__":
pdf_path = input("请输入 PDF 地址:")
output_path = input("请输入保存处理后的图片的文件夹地址:")
output_pdf_path = input("请输入保存处理后的 PDF 地址:")
lower_bound = tuple(map(int, input("请输入水印颜色的下界(例如:168,168,168):").split(',')))
upper_bound = tuple(map(int, input("请输入水印颜色的上界(例如:172,172,172):").split(',')))
remove_pdf(pdf_path, output_path, output_pdf_path, lower_bound=lower_bound, upper_bound=upper_bound)
通过上面的代码,我们可以实现自动去除 PDF 水印的功能。如果你在实现过程中遇到问题或有更好的改进建议,欢迎在评论区分享。让我们一起学习进步!
对比前后
去除前
去除后
总结
本文介绍了如何使用 Python 去除 PDF 文件中的水印。我们通过结合使用 PyMuPDF、Pillow 和 OpenCV 库,实现了从 PDF 提取页面、处理图像并去除水印的完整流程。该方法自动化程度高,适用于批量处理带有水印的 PDF 文件。希望这篇文章能对你有所帮助!
如果你在实现过程中遇到问题或有更好的改进建议,欢迎在评论区分享。让我们一起学习进步!
参考资料
作者:开源神器