AskOverflow.Dev

AskOverflow.Dev Logo AskOverflow.Dev Logo

AskOverflow.Dev Navigation

  • 主页
  • 系统&网络
  • Ubuntu
  • Unix
  • DBA
  • Computer
  • Coding
  • LangChain

Mobile menu

Close
  • 主页
  • 系统&网络
    • 最新
    • 热门
    • 标签
  • Ubuntu
    • 最新
    • 热门
    • 标签
  • Unix
    • 最新
    • 标签
  • DBA
    • 最新
    • 标签
  • Computer
    • 最新
    • 标签
  • Coding
    • 最新
    • 标签
主页 / coding / 问题 / 79527090
Accepted
Pubg Mobile
Pubg Mobile
Asked: 2025-03-22 15:34:01 +0800 CST2025-03-22 15:34:01 +0800 CST 2025-03-22 15:34:01 +0800 CST

快速将多页 PDF 文件转换为 PNG

  • 772

我有一个文件夹,里面有600 个 PDF 文件,每个 PDF 有20 页。我需要尽快将每页转换为高质量的 PNG 。

我为此任务编写了以下脚本:

import os
import multiprocessing
import fitz  # PyMuPDF
from PIL import Image

def process_pdf(pdf_path, output_folder):
    try:
        pdf_name = os.path.splitext(os.path.basename(pdf_path))[0]
        pdf_output_folder = os.path.join(output_folder, pdf_name)
        os.makedirs(pdf_output_folder, exist_ok=True)

        doc = fitz.open(pdf_path)

        for i, page in enumerate(doc):
            pix = page.get_pixmap(dpi=850)  # Render page at high DPI
            img = Image.frombytes("RGB", [pix.width, pix.height], pix.samples)
            
            img_path = os.path.join(pdf_output_folder, f"page_{i+1}.png")
            img.save(img_path, "PNG")

        print(f"Processed: {pdf_path}")
    except Exception as e:
        print(f"Error processing {pdf_path}: {e}")

def main():
    input_folder = r"E:\Desktop\New folder (5)\New folder (4)"
    output_folder = r"E:\Desktop\New folder (5)\New folder (5)"

    pdf_files = [os.path.join(input_folder, f) for f in os.listdir(input_folder) if f.lower().endswith(".pdf")]

    with multiprocessing.Pool(processes=multiprocessing.cpu_count()) as pool:
        pool.starmap(process_pdf, [(pdf, output_folder) for pdf in pdf_files])

    print("All PDFs processed successfully!")

if __name__ == "__main__":
    main()

问题:

这个脚本太慢了,特别是在处理大量 PDF 时。我尝试了以下优化,但速度并没有显著提高:

  • 稍微降低 DPI – 从1200 DPI降低到850 DPI。(我也测试了 600-800 DPI。)
  • 启用 – 减少内存使用量alpha=False 。 get_pixmap()
  • 用来 ThreadPoolExecutor 代替 multiprocessing.Pool– 没有重大改进。
  • 减少 PNG 压缩optimize=False–保存图像时设置。
  • 将图像转换为灰度- 有点帮助,但我的任务需要彩色图像。

我考虑过的可能的解决方案:

  • 并行处理页面而不是文件——不是一次处理一个文件,而是并行处理每个页面以充分利用 CPU 核心。
  • 使用 ProcessPoolExecutor 而不是 ThreadPoolExecutor– 由于渲染是CPU 密集型的,因此多处理应该更好。
  • 使用 JPEG 而不是 PNG – JPEG保存速度更快,占用存储空间更少,但我需要高质量的图像。
  • 将 DPI 降低至 500-600 – 在速度和质量之间实现平衡。
  • 批量写入文件而不是逐个保存- 减少 I/O 开销。

我需要帮助:

  • 如何在保持高图像质量的同时显著加快PDF 到 PNG 的转换速度 ?
  • 是否有更好的库或技术我应该使用?
  • 有没有办法可以充分利用 CPU 内核?

任何建议都将不胜感激!

python
  • 4 4 个回答
  • 138 Views

4 个回答

  • Voted
  1. Best Answer
    Adon Bilivit
    2025-03-22T17:44:41+08:002025-03-22T17:44:41+08:00

    此过程不仅高度占用 CPU,还需要大量 RAM。在 MacOS (M2) 上,仅使用 4 个 CPU(即可用数量的一半)即可显著提高性能。即便如此,处理页面的平均时间约为 1.3 秒

    为了进行这次测试,我有 80 个 PDF。每个 PDF 最多处理 20 页。

    测试如下:

    import fitz
    from pathlib import Path
    from multiprocessing import Pool
    from PIL import Image
    from time import monotonic
    from os import process_cpu_count
    
    SOURCE_DIR = Path("/Volumes/Spare/Downloads")
    TARGET_DIR = Path("/Volumes/Spare/PDFs")
    
    def cpus() -> int:
        if ncpus := process_cpu_count():
            ncpus //= 2
            return ncpus if ncpus > 1 else 2
        return 2
        
    def process(path: Path) -> tuple[float, int]:
        print(f"Processing {path.name}")
        try:
            with fitz.open(path) as pdf:
                start = monotonic()
                for i, page in enumerate(pdf.pages(), 1):
                    pix = page.get_pixmap(dpi=850)
                    img = Image.frombytes("RGB", (pix.width, pix.height), pix.samples)
                    img_path = TARGET_DIR / f"{path.stem}_page_{i}.png"
                    img.save(img_path, "PNG")
                    if i >= 20:
                        break
                return (monotonic() - start, i)
        except Exception:
            pass
        return (0.0, 0)
    
    def main() -> None:
        TARGET_DIR.mkdir(parents=True, exist_ok=True)
        with Pool(cpus()) as pool:
            sum_d = 0.0
            sum_p = 0
            for duration, page_count in pool.map(process, SOURCE_DIR.glob("*.pdf")):
                sum_d += duration
                sum_p += page_count
            if sum_p > 0:
                print(f"Average duration per page = {sum_d/sum_p:,.4f}s")
            else:
                print("No files were processed")
    
    if __name__ == "__main__":
        main()
    

    不包括文件名的输出:

    Average duration per page = 1.2667s

    概括:

    使用fitz / PyMuPDF以 850dpi 进行渲染很慢。将渲染降低到例如 300dpi 可将每页时间缩短至约 0.17 秒

    • 1
  2. Obedient Timothy
    2025-03-22T21:11:03+08:002025-03-22T21:11:03+08:00

    安装 Ghost 脚本工具

    您可以从此站点下载 Ghost Script PDF 到图像转换软件 -> https://ghostscript.com/releases/gsdnld.html

    在您的系统上安装该软件。

    安装 Ghost Script Python Wrapper

    然后,你需要一个 Python 工具来调用我们在上一步中安装的 SDK 中的 PDF 到图像转换函数,该工具名为ghostscript。运行命令pip install ghostscript

    使用以下 Python 代码进行转换

    import os
    import subprocess
    import multiprocessing
    
    def convert_page(pdf_path, output_folder, page_number, dpi):
        """Converts a single PDF page to PNG using Ghostscript."""
        output_file = os.path.join(output_folder, f"page_{page_number}.png")
        
        gs_command = [
            "gs",  # Ghostscript command
            "-dNOPAUSE", "-dBATCH", "-dSAFER",  # No interruptions
            "-sDEVICE=png16m",  # High-quality 24-bit PNG output
            f"-r{dpi}",  # Set DPI
            f"-dFirstPage={page_number}", f"-dLastPage={page_number}",  # Process only one page
            f"-sOutputFile={output_file}",  # Define output file
            pdf_path
        ]
        
        subprocess.run(gs_command, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
    
    def pdf_to_png(pdf_path, output_folder="output", dpi=900):
        """Converts a PDF into PNG images using multiprocessing for speed."""
        if not os.path.exists(output_folder):
            os.makedirs(output_folder)
    
        # Get total page count
        count_command = [
            "gs", "-q", "-dNODISPLAY", "-c",
            f"({pdf_path}) (r) file runpdfbegin pdfpagecount = quit"
        ]
        result = subprocess.run(count_command, capture_output=True, text=True)
        total_pages = int(result.stdout.strip())
    
        print(f"Total pages to convert: {total_pages}")
    
        # Use multiprocessing to speed up the conversion
        with multiprocessing.Pool(processes=multiprocessing.cpu_count()) as pool:
            pool.starmap(convert_page, [(pdf_path, output_folder, page, dpi) for page in range(1, total_pages + 1)])
    
        print(f"✅ Conversion complete! Images saved in '{output_folder}'")
    
    #call the method to generate the images
    pdf_to_png("sample.pdf")
    
    • 1
  3. K J
    2025-03-23T00:49:42+08:002025-03-23T00:49:42+08:00

    PDF 转换自然很慢,因此仅使用 9 x 20 页文件(因为生命太短暂)。几分钟应该足以计时 9 个文件,因为只有 180 页。

    这些是原始可执行 CLI 时间,没有任何 Python 开销。结果总是会有所不同,具体取决于单个图形处理器,因此具有多线程 GPU 的专用工作站将胜过我的值。因此,仅提供用于比较相同的 20 KB pdf(因此图表的文本大致压缩为每页 1 KB)作为相对“exe 与 exe”测试。

    以下是基于 PDF 打印到图像/纸张设备的一些时间安排,传统上认为每分钟 60 页的速度很高。

    为了进行比较,我使用了 Artifex GS 和 MuPDF。让我们展示一下 MuPdf 的最终读取时间,因为它们最容易报告,而且速度也更快。

    page New folder (4)/file009.pdf 20 469ms (interpretation) 453ms (rendering) 922ms (total)
    total 9188ms (0ms layout) / 20 pages for an average of 459ms
    fastest page 1: 15ms (interpretation) 407ms (rendering) 422ms(total)
    slowest page 19: 484ms (interpretation) 469ms (rendering) 953ms(total)
    

    第 1 页和第 19 页的内容实际上非常相似。但是,读取压缩 PDF 所需的时间各不相同。这里大约需要半秒到整整 1 秒,这是因为必须在(在这个 20 页的 PDF 中)近 140 个离散压缩对象之间来回遍历。单页 PDF 应该更快,而且“来回”次数不会明显减少。具有解压缩内容的较小 PDF 总是比大压缩图像 PDF 更好。

    那么涉及哪些时间限制和矩形体积?

    density 720 dpi for 2304 x 3456 imagery
    page pixels =   7,962,624
    per PNG file=  22.78 MB
    per PDF file= 442.96 MB memory bus IO + source for decompression.
    
    GS start 15:18:46.12 
    GS end   15:21:03.76 
       pages      180
       seconds =  137.64
      600 files= 9176.00
      hours    =    2.55
    
    Mu start 16:11:37.92 
    Mu end   16:13:00.06
       pages      180
       seconds =   82.14
      600 files= 5476.00
      hours    =    1.52
    

    因此 Mu PDF 应该可以节省一个小时的时间。

    那么 Python 应用程序中使用的其他常用工具呢?文件自然会更慢,因为通常会被显著压缩(大约是 MuPDF 大小的 -40%)。

    xpdf
    PDFtoPNG start 17:40:14.63 
    PDFtoPNG end   17:43:14.85 
    9 files in seconds  180.22
    
    PDFtoPPM (-PNG) prefered for its FOSS permisive license
    pdftoppm start 18:33:47.17 
    pdftoppm end   18:37:22.03 
    9 files in seconds  214.86
    

    许多建议说使用多线程,并且上述 MuPDF 时间基于 4 个线程,如果我们更改该设置会怎样?

    4 Threads  82.14
    3 Threads  81.81
    2 Threads  71.45
    1 Thread   79.38 
    

    因此,无需在 3 个或更多线程之间共享时间,即可使用此双核设备上的 2 个线程来改善连续渲染时间。

    再稍微调整一下,我们就可以得到180 pages = 64.54 seconds

    因此,600 个以上 20 页的文件 = 4,302.66 秒 = 预计 1 小时 12 分钟。

    在缓慢的 Windowsfor循环中:

    mutool draw -st -P -T 2 -B 32 -r 720 -F png -o "New folder (5)\%%~nc-page%%2d.png" "New folder (4)/%%~nxc"
    
    • 1
  4. Jorj McKie
    2025-03-22T20:00:11+08:002025-03-22T20:00:11+08:00

    下面是一个示例脚本,它使用 Python 多处理将 PDF 的所有页面渲染为图像。您可以预期总体上比线性执行快 2-4 倍:

    import pymupdf
    import concurrent.futures
    from concurrent.futures import ProcessPoolExecutor
    import time
    
    
    def render_page(x):
        filename, numbers = x
        doc = pymupdf.open(filename)
        for pno in numbers:
            pix = doc[pno].get_pixmap(dpi=300)
            pix.save(f"img-{pno}.jpg")
    
    
    if __name__ == "__main__":
        t0 = time.perf_counter()
        doc = pymupdf.open("adobe.pdf")
        pc = doc.page_count
        with ProcessPoolExecutor(max_workers=10) as executor:
            for i in range(0, pc, 50):
                r = range(i, i + min(50, pc - i))
                executor.submit(render_page, (doc.name, r))
    
        t1 = time.perf_counter()
        print(f"Duration {t1-t0}")
    
    • 0

相关问题

  • 如何将 for 循环拆分为 3 个单独的数据框?

  • 如何检查 Pandas DataFrame 中的所有浮点列是否近似相等或接近

  • “load_dataset”如何工作,因为它没有检测示例文件?

  • 为什么 pandas.eval() 字符串比较返回 False

  • Python tkinter/ ttkboostrap dateentry 在只读状态下不起作用

Sidebar

Stats

  • 问题 205573
  • 回答 270741
  • 最佳答案 135370
  • 用户 68524
  • 热门
  • 回答
  • Marko Smith

    重新格式化数字,在固定位置插入分隔符

    • 6 个回答
  • Marko Smith

    为什么 C++20 概念会导致循环约束错误,而老式的 SFINAE 不会?

    • 2 个回答
  • Marko Smith

    VScode 自动卸载扩展的问题(Material 主题)

    • 2 个回答
  • Marko Smith

    Vue 3:创建时出错“预期标识符但发现‘导入’”[重复]

    • 1 个回答
  • Marko Smith

    具有指定基础类型但没有枚举器的“枚举类”的用途是什么?

    • 1 个回答
  • Marko Smith

    如何修复未手动导入的模块的 MODULE_NOT_FOUND 错误?

    • 6 个回答
  • Marko Smith

    `(表达式,左值) = 右值` 在 C 或 C++ 中是有效的赋值吗?为什么有些编译器会接受/拒绝它?

    • 3 个回答
  • Marko Smith

    在 C++ 中,一个不执行任何操作的空程序需要 204KB 的堆,但在 C 中则不需要

    • 1 个回答
  • Marko Smith

    PowerBI 目前与 BigQuery 不兼容:Simba 驱动程序与 Windows 更新有关

    • 2 个回答
  • Marko Smith

    AdMob:MobileAds.initialize() - 对于某些设备,“java.lang.Integer 无法转换为 java.lang.String”

    • 1 个回答
  • Martin Hope
    Fantastic Mr Fox msvc std::vector 实现中仅不接受可复制类型 2025-04-23 06:40:49 +0800 CST
  • Martin Hope
    Howard Hinnant 使用 chrono 查找下一个工作日 2025-04-21 08:30:25 +0800 CST
  • Martin Hope
    Fedor 构造函数的成员初始化程序可以包含另一个成员的初始化吗? 2025-04-15 01:01:44 +0800 CST
  • Martin Hope
    Petr Filipský 为什么 C++20 概念会导致循环约束错误,而老式的 SFINAE 不会? 2025-03-23 21:39:40 +0800 CST
  • Martin Hope
    Catskul C++20 是否进行了更改,允许从已知绑定数组“type(&)[N]”转换为未知绑定数组“type(&)[]”? 2025-03-04 06:57:53 +0800 CST
  • Martin Hope
    Stefan Pochmann 为什么 {2,3,10} 和 {x,3,10} (x=2) 的顺序不同? 2025-01-13 23:24:07 +0800 CST
  • Martin Hope
    Chad Feller 在 5.2 版中,bash 条件语句中的 [[ .. ]] 中的分号现在是可选的吗? 2024-10-21 05:50:33 +0800 CST
  • Martin Hope
    Wrench 为什么双破折号 (--) 会导致此 MariaDB 子句评估为 true? 2024-05-05 13:37:20 +0800 CST
  • Martin Hope
    Waket Zheng 为什么 `dict(id=1, **{'id': 2})` 有时会引发 `KeyError: 'id'` 而不是 TypeError? 2024-05-04 14:19:19 +0800 CST
  • Martin Hope
    user924 AdMob:MobileAds.initialize() - 对于某些设备,“java.lang.Integer 无法转换为 java.lang.String” 2024-03-20 03:12:31 +0800 CST

热门标签

python javascript c++ c# java typescript sql reactjs html

Explore

  • 主页
  • 问题
    • 最新
    • 热门
  • 标签
  • 帮助

Footer

AskOverflow.Dev

关于我们

  • 关于我们
  • 联系我们

Legal Stuff

  • Privacy Policy

Language

  • Pt
  • Server
  • Unix

© 2023 AskOverflow.DEV All Rights Reserve