Python实战教程:逐步去除PDF文件水印(二)

前言

在日常工作中,我们经常会遇到带有水印的 PDF 文件。水印可能会影响文档的可读性或者在打印时造成不便。为了自动化地去除 PDF 文件中的水印,我们可以使用 Python 及其强大的库,如 PyMuPDF、Pillow 和 OpenCV。本文将详细介绍如何实现这一过程。

所需库

首先,我们需要安装以下 Python 库:

  • PyMuPDF:用于处理 PDF 文件。
  • Pillow:用于图像处理。
  • OpenCV:用于图像操作。
  • 使用以下命令安装这些库:

    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 文件。希望这篇文章能对你有所帮助!

    如果你在实现过程中遇到问题或有更好的改进建议,欢迎在评论区分享。让我们一起学习进步!

     参考资料

  • PyMuPDF 官方文档:链接
  • OpenCV 官方文档:链接
  • Python 官方文档:链接
  • 作者:开源神器

    物联沃分享整理
    物联沃-IOTWORD物联网 » Python实战教程:逐步去除PDF文件水印(二)

    发表回复