Ubuntu/Colab環境でPDFファイルのページを画像化する(pdf2image、pdftoppm、pdftocairo)
本記事では、UbuntuやGoogle Colaboratory環境で、オープンソースライブラリのPoppler(poppler-utilsに含まれるpdftoppm、pdftocairo)あるいはPythonライブラリのpdf2imageを利用して、PDFファイルのページをJPEGなどの画像ファイルに変換する方法を見ていきます。
【目次】
[1]PDFファイルを画像に変換したいとき?
PDF(Portable Document Format)ファイルは広く使われている電子文書です。
PDFファイルは、ブラウザでも表示できますし、無料のAdobe Acrobat Readerをインストールして表示することもできます。
しかし、こういったツールでファイル内容を表示するだけではなく、PDFファイル内のページをJPEGなどの画像ファイルに変換して利用したい場合もあります。
例えば、システム開発において、Webサイトやアプリ内で、PDFファイルの概要をサムネイル表示したいとか、PDFファイルをダウンロードさせたり、ブラウザの別タブでPDFファイルを表示するのではなく、(導線が途切れないように)ページを移動することなく内容を見てもらいたい場合など、いろいろあると思います。
また、本記事では、Google Colaboratory環境で Google Vision APIの記事を書いていますが、PDFファイルからVision APIを用いて特徴抽出した場合、その結果を可視化しようとすると、PDFファイルのページ毎に画像化すると扱いやすいということもあります。
ページの画像化以外にも、PDFファイル内のテキストや注釈、画像その他の情報を抽出したい場合もあると思います。
PDFファイルを扱うツールやライブラリは、無償のものから有償のものまでいろいろありますが、本記事ではオープンソースのツールを用いて、PDFファイルのページを画像化したり情報を抽出する方法を考えてみます。
(参考)
JPEGなどの画像をPDF化する場合については、『Colaboratory環境で画像ファイルをPDFファイルに変換する(img2pdf)』を参照してください。
[2]PDFファイル扱うオープンソースライブラリ
PDFファイルの主な特徴は「特定の環境に左右されずに全ての環境でほぼ同様の状態で文章や画像等を閲覧できる」ことですが、インターネット上での電子文書の公開や配布にとどまらず、データ入力フォームに利用したり、電子署名を行って電子契約に利用するなど、応用範囲が広いです。(Wikipediaの「Portable Document Format」のページで説明されている量を見ても、その多機能さがよくわかります。)
様々な応用範囲をカバーできるように、PDFの仕様はとても大きく、複雑です。本家Adobe社のAcrobatは別として、全ての仕様をカバーしている製品は、有償の製品でも限られるのではないかと想像します。ブラウザにおいても、全ての仕様を実装しているわけではありません。
とはいいつつも、全てのPDFの仕様を満たさないと使い物にならない、というわけではありません。通常の利用では、ブラウザで表示できている内容でも十分実用になっている気がします。つまり、特種用途を除けば、基本的な機能(主にページのレンダリング)が精度高く実装されていればよいと割り切る事もできると思います。
こういった観点と、Colaboratory(Linux系OS)で動作すること、オープンソースであるという条件で、PDFファイルを扱えるライブラリを調べてみました。特に私は以下のライブラリに興味を持ちました。
- Poppler
- https://poppler.freedesktop.org/
- xpdf-3.0を元にしたPDFレンダリングライブラリです。開発元はfreedesktop.orgで、実装言語はC++です。
- ライセンス:
- GPL Version 2
- GNOMEなどのPDFビューア(Evince)のバックエンドでもあります。その他にもPopplerを利用しているビューアがあります。(https://ja.wikipedia.org/wiki/Poppler)
- コマンドラインツールやプログラミング言語から利用するためのライブラリもあります。
- MuPDF
- https://mupdf.com/
- 軽量なPDF、XPS、およびE-bookビューアです。開発元はArtifex Software, Inc.で、実装言語はCです。
- ライセンス:
- dual-licensed (GNU Affero General Public License and commercial permissive license)
- Popplerと同様、コマンドラインツールやプログラミング言語から利用するためのライブラリもあります。
- Apache PDFBox
- https://pdfbox.apache.org/
- PDFドキュメントを操作するためのJavaツールです。開発元は Apache Software Foundation、実装言語はJavaです。
- ライセンス:
- Apache License 2.0
- コマンドラインツールもあります。JavaアプリケーションにPDFファイルの処理を組み込む用途を考えると、サンプルコードも多く公開されているので、利用しやすいと思います。
どれも興味深いのですが、今回はPopplerを取り上げることにします。
なお、本記事はGoogle Colaboratory環境での利用例で書いていますが、Colaboratoryの仮想マシンはUbuntuですので、Linux系の環境であれば同様の使い方ができると思います。
(以下のコマンド例の先頭にある「!」は、Ubuntu等の環境では不要です。)
(参考)ページの画像化と情報抽出について
PDFファイルの各ページには、テキストや画像などの情報が入っています。ツールを利用することで、ファイル内のテキストや画像を取り出すことができます。
ここで注意が必要なのは、PDFファイル内にあるテキストや画像が、ページに表示されるもの(見た目)と一致しているとは限らないことです。例えば、書類などをスキャンしてPDF化したものの中には、画像だけではなく、透明テキストもあります(透明テキストは表示されません)。同じテキストを少しずらして表示することで強調表示のように見せることもあります(同じテキストが複数存在する)。また、複数の画像を組み合わせて一つの画像に見せる場合もあります。
こういったことを考えると、最終的な表示内容に依存する情報を必要とするのか、それとも、単にPDF内にある情報を取り出したいだけなのかによって、必要なツールが変わることになります。
なお、上記のライブラリは、どちらの要件にも対応する機能を持っています。
[3]コマンドラインからの利用:poppler-utils
(1)poppler-utils
Popplerはレンダリングライブラリなので、そのままでは利用できませんが、poppler-utilsパッケージをインストールすることにより、以下のコマンドラインユーティリティを利用できるようになります。
①ページを画像化するコマンド
ページを画像化するコマンドには、pdftoppmとpdftocairoの2つがあります。
- pdftoppm (Ununtu Manpage)
- 出力形式:
- PNM(PPM、PGM、PBM)、JPEG、PNG、TIFF
- pdftocairo (Ununtu Manpage)
- 出力形式:
- JPEG、PNG、TIFF、PDF、PS、EPS、SVG
- toの後の名前がcairo(カイロ)となっています。これはWikipediaによると「デバイスに依存しないベクトルベースの描画APIを提供する、フリーの2Dグラフィックスライブラリである。アンチエイリアスがかかった綺麗な表示が特徴である。(以降省略)」とのことです。
どちらのツールを利用するかは、目的とする出力画像形式で選択することになりますが、JPEG、PNG、TIFF形式については、pdftoppmとpdftocairoのどちらもサポートしているので、どちらを使うべきか迷うところです。
必要とする機能に違いがあれば選択しやすいのですが、Manpageにある機能を比較すると、一般利用においては甲乙つけがたい感じです。
少し試した程度ではありますが、その結果感じたのは、画質などに大きな違いはないものの、(試したサンプルにおいては)処理速度はpdftocairoのほうが、かなり早いということでした。
pdftoppmとpdftocairoについては、以下の節で利用例を見ていきます。
②それ以外のコマンド
ページの画像化以外にも以下のコマンドが利用できます。(本記事では利用例を割愛します)
- pdfdetach (Ununtu Manpage)
- 埋め込みファイル (添付ファイル) の一覧出力または抽出
- pdffonts (Ununtu Manpage)
- フォント分析ツール
- pdfimages (Ununtu Manpage)
- 画像抽出ツール
- pdfinfo (Ununtu Manpage)
- 文書情報
- pdfseparate (Ununtu Manpage)
- ページ抽出ツール
- pdfsig (Ununtu Manpage)
- デジタル署名の検証
- pdftohtml (Ununtu Manpage)
- PDF から HTML への変換ツール
- pdftops (Ununtu Manpage)
- PDF から PostScript (PS) への変換ツール
- pdftotext (Ununtu Manpage)
- テキストの抽出
- pdfunite (Ununtu Manpage)
- 文書の併合ツール
(2)インストール
ツールが使えるようにするために、poppler-utilsをインストールします。加えて、poppler-dataもインストールしておきます。
!apt install poppler-utils poppler-data
(3)pdftoppmの利用例
名前の通り、デフォルトではPPMファイルへ変換しますが、オプションを指定することでPGM、PBM、JPG、PNG、TIFFに変換することが出来ます。
以下では利用例を中心に見てみますが、詳細な説明は、Ubuntu Manpage(http://manpages.ubuntu.com/manpages/bionic/man1/pdftoppm.1.html)を参照してください。
また、PPM、PGM、PBMについては、「PNM (画像フォーマット)」を参照してください。
①基本的な使い方(PPM形式)
コマンド概要
pdftoppm [options] PDF-file PPM-root
デフォルトの出力はPPM形式です。
PPM-root を省略すると、標準出力に出力されます。変換結果をファイルに保存するには、PPM-root を指定します。
PPM-rootの簡単な例を見てみます。(先頭の「!」は、Linux系OSで実行する場合は不要です)
!pdftoppm input.pdf page
この例では PPM-rootが「page」です。これを実行すると、page-01.ppmのように、PPM-rootの後ろに、「ハイフン(‐)+ページ番号+.+拡張子」が連結されたファイル名で出力されます。
- PPM-root
- ファイル名の共通部分にあたるもので、ディレクトリを含んだ絶対パスまたは相対パスを指定することも可能です。
- ページ番号
- 先頭ページを1として、PDFの全ペース数の桁数に合わせて0パディングされます。例えば、総ページ数が1桁の場合は、1~9、総ページ数が2桁の場合は、01~00、総ページ数が3桁の場合は、001~999といった具合です。
- 拡張子
- 変換形式により決定されます。デフォルトではPPM形式での変換なので、拡張子は、ppmとなります。
②ページ指定
デフォルトではPDFファイル内の全ページが出力されます。指定のページのみ出力したい場合は、以下のオプションを組み合わせます。
- -f number
- 出力する最初のページ番号(先頭ページは1)。
- 省略すると、1と解釈されます。
- -l number
- 出力する最後のページ番号。
- 省略すると、PDFファイル内の最後のページのページ番号と解釈されます。
-f と -l を省略すると全ページが範囲になりますが、-f または -l を指定することで出力ページの範囲を指定する感じです。なお、指定の1ページのみ出力したい場合は、-f と -lに同じページ番号を指定します。
以下の例では、先頭ページから3ページまでを出力します。
!pdftoppm -f 1 -l 3 input.pdf page
③PGM、PBM、JPG、PNG、TIFFに変換
以下のオプションを指定することで、PPM以外の画像ファイルに変換できます。
- -mono
- PBMファイルを作成します。(拡張子=pbm)
- -gray
- PGMファイルを作成します。(拡張子=pgm)
- -png
- PNGファイルを作成します。(拡張子=png)
- -jpeg
- JPEGファイルを作成します。(拡張子=jpg)
- -jpegoptオプションで圧縮方法(quality、progressive)を指定することが出来ます。詳細はManpageを参照してください。
- -tiff
- TIFFファイルへ変換します。(拡張子=tif)
- -tiffcompressionオプションで圧縮タイプを指定することもできます(デフォルトは圧縮無し)。詳細はManpageを参照してください。
以下は、qualityを90でプログレッシブJPEGファイルを作成するサンプルです。
!pdftoppm -jpeg -jpegopt 'quality=90,progressive=y' input.pdf page
④解像度(DPI)の指定
デフォルトのDPIは150です。これは -r オプションで変更できます。
以下は、DPIを200でJPEGファイルを作成する作成するサンプルです。
!pdftoppm -jpeg -r 200 input.pdf page
(4)pdftocairoの利用例
pdftocairoは、PDFファイルをPNG/JPEG/TIFF/PDF/PS/EPS/SVGに変換することが出来るツールです。
以下では利用例を中心に見てみますが、詳細な説明は、Ubuntu Manpage(http://manpages.ubuntu.com/manpages/bionic/man1/pdftocairo.1.html)を参照してください。
①基本的な使い方(画像形式の指定)
コマンド概要
pdftocairo [options] PDF-file [output-file]
pdftoppmと違って、デフォルトの変換形式がないため、必ず変換先の画像形式を指定する必要があります。(複数指定することはできません。)
- -png
- PNGファイルを作成します。(拡張子=png)
- -mono オプションを指定するとモノクロにできます。
- -gray オプションを指定するとグレースケールになります。
- -icc オプションに続いてICCファイルを指定できます。
- -jpeg
- JPEGファイルを作成します。(拡張子=jpg)
- -gray オプションを指定するとグレースケールになります。
- -jpegoptオプションで圧縮方法(quality、progressive)を指定することが出来ます。詳細はManpageを参照してください。
- -tiff
- TIFFファイルへ変換します。(拡張子=tif)
- -mono オプションを指定するとモノクロにできます。
- -gray オプションを指定するとグレースケールになります。
- PDFファイルへ変換します。
- -ps
- PSファイルへ変換します。
- オプションとして -level2 と -level3(デフォルト)を指定できます。
- -eps
- EPSファイルへ変換します。但し、EPSファイルへの変換は1ページ分しかサポートされていないため、複数ページのPDFファイルの場合は、-f と -l オプションで1ページ分を指定する必要があります。
- -svg
- SVG (Scalable Vector Graphics) ファイルへ変換します。
[output-file] は変換結果のファイル名です。
PDF、PS、EPS、SVGファイルへ変換する場合は、出力ファイルが1つになるので、[output-file]で指定したファイル名で作成されます。
それ以外は、pdftoppmと同様に、[output-file]の後ろに、「ハイフン(‐)+ページ番号+.+拡張子」が連結されたファイル名で出力されます。
- [output-file]
- ファイル名の共通部分にあたるもので、ディレクトリを含んだ絶対パスまたは相対パスを指定することも可能です。
- ページ番号
- 先頭ページを1として、PDFの全ペース数の桁数に合わせて0パディングされます。例えば、総ページ数が1桁の場合は、1~9、総ページ数が2桁の場合は、01~00、総ページ数が3桁の場合は、001~999といった具合です。
- 拡張子
- 変換形式により決定されます。
以下は、qualityを90でプログレッシブJPEGファイルを作成するサンプルです。
!pdftocairo -jpeg -jpegopt 'quality=90,progressive=y' input.pdf page
②ページ指定
デフォルトではPDFファイル内の全ページが出力されます。指定のページのみ出力したい場合は、pdftoppmと同様に、以下のオプションを組み合わせます。
- -f number
- 出力する最初のページ番号(先頭ページは1)。
- 省略すると、1と解釈されます。
- -l number
- 出力する最後のページ番号。
- 省略すると、PDFファイル内の最後のページのページ番号と解釈されます。
-f と -l を省略すると全ページが範囲になりますが、-f または -l を指定することで出力ページの範囲を指定する感じです。なお、指定の1ページのみ出力したい場合は、-f と -lに同じページ番号を指定します。
以下の例では、先頭ページから3ページまでのPNGファイルを出力します。
!pdftocairo -f 1 -l 3 -png input.pdf page
③解像度(DPI)の指定
デフォルトのDPIは150です。これは -r オプションで変更できます。
以下は、DPIを200でJPEGファイルを作成する作成するサンプルです。
!pdftocairo -jpeg -r 200 input.pdf page
[4]Pythonからの利用:pdf2image
(1)pdf2image
pdf2imageは、poppler-utilsが提供するpdftoppmおよびpdftocairoユーティリティをラップしてPDFを「画像」に変換するPythonモジュールです。
なお、ここでいう「画像」は、JPEGなどの画像ファイルを指しているのではなく、PillowのImageオブジェクトのことを指していると思います。
pdf2imageは、PDFファイルのページをPillowのImageオブジェクトに変換を行うパッケージであり、JPEGなどの画像ファイルへの変換や保存などは、PillowのImageオブジェクトで処理することになります。
- GitHub:pdf2image(https://github.com/Belval/pdf2image)
- pdf2image’s documentation(https://pdf2image.readthedocs.io/en/latest/)
pdf2imageは、PDFを画像に変換することに特化していて、クラス構造も無く、コマンド呼び出しの感覚で簡単に利用できますので、今回はこれを見ていきます。
なお、簡単に利用できるとはいえ、いろいろとオプションもありますので、全ての機能を説明することはできません。詳細は上記のソースコードまたはドキュメントを参照してください。
(参考)
上記のマニュアルは関数リファレンスの情報が少し古いかもしれません。最新の情報は、GitHubのソースコード(https://github.com/Belval/pdf2image/blob/master/pdf2image/pdf2image.py)のコメントを見るのが良い気がします。
(2)インストール
pdf2imageをインストールする前に、あらかじめpoppler-utilsをインストールしておきます。その後、pipでpdf2imageをインストールします。
!pip install pdf2image
(3)最も簡単な使い方
pdf2imageの基本的な使い方は、上記ドキュメントの概要説明(https://pdf2image.readthedocs.io/en/latest/overview.html)が一番分かりやすいと思います。以下では、これを参考に見ていきます。
pdf2imageは、公式には convert_from_path と convert_from_bytes の2つの関数を公開しています。
- convert_from_path
- PDFファイルを指定してPillowのImageオブジェクトに変換します。
- convert_from_bytes
- PDFのバイト列を指定してPillowのImageオブジェクトに変換します。
GitHubの実装コードを見ると、convert_from_bytesは一時ファイルに書き込んでconvert_from_pathを呼び出していますので、実質的にはconvert_from_pathのみです。
以下のコードはconvert_from_pathとconvert_from_bytesの最も簡単な使い方のサンプルです(概要説明からの転記です。但しopen引数に'rb'を加えています)。
from pdf2image import convert_from_path, convert_from_bytes
images = convert_from_path("/home/user/example.pdf")
# OR
with open("/home/user/example.pdf", 'rb') as pdf:
images = convert_from_bytes(pdf.read())
convert_from_pathとconvert_from_bytesの戻り値は、PillowのImageオブジェクトの配列です。
上記の例の場合、PDFファイル内の全ページを画像化しますので、戻り値であるimagesは、全ページ分のImageオブジェクトの配列となっています。
画像オブジェクトが得られたら、続いて画像オブジェクトを使って必要な処理を行います。
例えば、JPEGやPNG形式への変換が目的であれば、saveメソッドで保存します。
for i, image in enumerate(images):
# imageはPillowのImageオブジェクト。必要に応じて処理を行う。
# 例)JPEGファイルとして保存
image.save("out-{}.jpg".format(i))
以上が簡単な使い方の説明となりますが、convert_from_pathとconvert_from_bytesのデフォルト動作について大雑把に補足しておきます。
- デフォルトでは、pdftoppmを利用してppm形式に変換します。
- pdftoppmをサブプロセスで起動しPPM形式で画像化します。その出力を(中間ファイルを作成することなく)、stdout(パイプ)経由で画像データを読み取って、PillowのImage.openに渡してオブジェクト化します。
- dpiのデフォルト値は200です。
つまり、上記の簡単な使い方では、convert_from_pathまたはconvert_from_bytesは、dpi=200でPPM形式に変換し、メモリに画像データがロードされたImageオブジェクトを返します。
なお、PPM形式は圧縮を行わないため、他の形式に比べてデータ量が大きくなります。変換するページ数が少なければ問題ないと思いますが、変換ページ数が多い時は、メモリ使用量に注意が必要になります。
(4)メモリを節約する方法(output_folderを利用する方法)
pdftoppmの出力をパイプ経由ではなく、フォルダを経由することでメモリを節約する方法があります。
convert_from_pathのoutput_folderキーワード引数に作業用フォルダを指定すると、pdftoppmの出力(画像ファイル)をページ毎にoutput_folderに出力します。
そして、各画像ファイルパスをPillowのImage.openに渡してImageオブジェクトを作成して、それらの配列を返します。
PillowのImage.openにファイル名を渡すと、Pillowは画像データ本体をその時にロードするのではなく、いわばファイルパスだけ抑えておいて、必要な時に遅延ロードします。(詳しくは「File Handling in Pillow」を参照してください)
結果として、output_folderを指定すると、Imageオブジェクトは画像データ本体をメモリにロードしない状態で作成されているため、メモリリソースの消費が少なくなります。
また、利用側から見ると、Imageオブジェクトは自動的に遅延ロードされるため、output_folderを指定しない場合と同じように処理できます。
以下のコードは概要説明からの転記したものです。
import tempfile
from pdf2image import convert_from_path
with tempfile.TemporaryDirectory() as path:
images_from_path = convert_from_path("/home/user/example.pdf", output_folder=path)
このコードでは、output_folderに一時ディレクトリを指定しています。
これにより、pdftoppmの出力ファイルが一時ディレクトリ内に作成されますが、withブロックを抜けたときに一時ディレクトリが自動削除されますので、見た目上の動作は、output_folderを指定していない場合と同様になります。
これでリソースが節約できたように見えますが、戻りのImageオブジェクトに対して、画像データが必要となる操作を行った時点で遅延ロードされますので、画像を開放しないで処理を続けると、ループを抜けたら全てメモリにロードされている、という状況になってしまいます。
つまり、pdf2imageの処理においてはメモリリソースが節約されていますが、その後のImageオブジェクトの扱いによっては効果が薄くなります。
これについては、利用シナリオに応じた対応方法はあると思いますが、先のJPGファイルで保存する例に対しては、以下のようにcloseを明示的に入れるとメモリ利用効率は改善すると思います。
import tempfile
from pdf2image import convert_from_path
with tempfile.TemporaryDirectory() as path:
images_from_path = convert_from_path("/home/user/example.pdf", output_folder=path)
# 処理
for i, image in enumerate(images_from_path):
image.save("out-{}.jpg".format(i))
# 使い終わったのでクロースする
image.close()
(5)メモリを節約する方法(fmtを利用する方法)
(4)は、output_folderを利用して遅延ロードによりImageオブジェクトのメモリ使用量を節約する方法でしたが、もう一つ、fmtパラメータを利用して節約する方法も紹介されています。
デフォルト動作では、pdftoppmを利用してPPM形式の画像データを利用しますが、PPM形式は圧縮されないため、データサイズが大きくなります。そこで、PPM形式ではなく、pdftoppmの出力をJPEG形式などの圧縮形式に変更することで、メモリにロードされる画像データを小さくしよう、というものです。
以下のコードは概要説明からの転記したものです。
images_from_path = convert_from_path("/home/user/example.pdf", fmt="jpeg")
fmtには、ppm、 jpeg、 png、tiff が利用できます。(デフォルトはppmです。)
この方法は、(4)のoutput_folderを利用する方法と組み合わせることもできます。
ところで、このfmtで指定する形式は、PillowのImageオブジェクトの元データになるものです。利用シナリオにもよりますが、元データの画質が悪くなると良い結果が得られにくくなると思います。
このため、例えばJPEGを使う場合には、jpegoptというパラメータがあり、quality値の調整などが出来るようになっています。詳細については、以下のリファレンスのjpegoptの項を参照してください。
(6)サムネイルの作成サンプル
convert_from_pathの使い方ではありませんが、Pillowのイメージオブジェクトが返されるメリットの一つのサンプルとして、サムネイルを作成してみます。
以下のコードは、Pillow ImageクラスリファレンスにあるCreate thumbnailsサンプルコードを組み込んだだけですが、簡単にサムネイルを作成できます。
from pdf2image import convert_from_path
images = convert_from_path("/home/user/example.pdf")
# thumbnailを作成
size = 128, 128
for i, image in enumerate(images):
image.thumbnail(size)
image.save("out-{}.thumbnail".format(i), "JPEG")
(7)ページを指定して変換する方法(first_page, last_page)
デフォルトではPDFファイル内の全てのページをImageオブジェクトに変換しますが、変換するページを明示的に指定することが出来ます。
全ページを変換する必要がない場合は、明示的にページを指定することで、作成されるImageオブジェクト数が減るため、メモリ効率と実行速度の向上が期待できます。
(当然、output_folderやfmt引数との組み合わせも可能です。)
ページの指定は、first_pageとlast_pageキーワード引数を利用します。
これは、pdftoppm あるいは pdftocairo の -f と -l オプションに対応しています。
以下は、先頭ページのみ変換する例です。
images = convert_from_path("/home/user/example.pdf",first_page=1,last_page=1)
ちなみに、first_pageを省略するとfirst_pageは1と解釈され、last_pageを省略すると最終ページが指定されたと解釈されます。
このため、例えば、first_pageだけ指定した場合は、そのページから最後のページまでが変換されることになります。
(8)大量ページの変換速度を上げる方法(thread_count)
ページ数が多いPDFファイルを処理する場合は、メモリ効率だけでなく、処理速度を上げたい場合もあると思います。
この用途に、thread_countキーワード引数が利用できます。
考え方は、変換対象ページをthread_countのpdftoppmサブプロセスで並行処理させるものです。(一つ一つの画像変換速度を上げるものではありません。)
実際に試してみたところ、デフォルトの方法でthread_count=2を指定しても思ったほどの効果はありませんでした。理由は恐らく、pdftoppmの結果をメモリ(パイプ)経由で全てロードしているからではないかと思いました。
そこで、下記のように(4)メモリを節約する方法(output_folderを利用する方法)でthread_count=2を指定すると、試してみたサンプルでは処理時間が50%強程度になりました。
import tempfile
from pdf2image import convert_from_path
with tempfile.TemporaryDirectory() as path:
images_from_path = convert_from_path("/home/user/example.pdf", output_folder=path, thread_count=2)
とはいえ、thread_countを増やしただけ早くなるというものでもありません。また、Imageオブジェクトを操作すると遅延ロードが発生するため、後段の処理でのメモリ効率を鑑みながら処理を行う必要があると思います。
(9)解像度(dpi)
pdf2imageのdpiはデフォルトで200になっていますが、dpiキーワード引数で指定すれば解像度を変更できます。内部的には、pdftoppmの -r オプションにdpiの値を指定しているようです。(ちなみに、pdftoppmの-rオプションのデフォルトは150となっているようです。)
リファレンスの説明には、「dpiは高いほどよいけれども、通常、300を超えるものは肉眼では識別できません。 これは、圧縮なしのファイル形式(PPMなど)を使用する場合の出力画像サイズに直接関係することに注意してください。」とあります。
(10)pdftocairo/pdftoppmの画像変換結果を直接利用する(use_pdftocairo)
利用目的がJPEG、PNG、TIFFへの変換のみ、ということであれば、pdf2imageを利用しなくても pdftoppm か pdftocairo を直接利用するので十分という気がします。
とはいえ、Pythonから利用する場合は、 pdftoppm か pdftocairo を実行するプログラムを書く必要がありますので、コマンド実行のラッパーとしてpdf2imageを利用する方法を考えてみます。
pdf2imageは、デフォルトではpdftoppmを利用しますが、use_pdftocairo引数にTrueを設定すれば、pdftocairoを利用するようになります。
(4)のoutput_folder引数にフォルダを指定すれば、ここにpdftoppmまたはpdftocairoによる変換結果が出力されます。
(5)の fmt キーワード引数を利用して JPEGファイルの変換を行うようにします。
なお、fmt引数には、”ppm”、”jpeg”、”png”、”tiff” が指定できますが、pdftocairoを指定する場合は、ppmへの変換は出来ません。
以下のコードは、pdftocairoを利用して、out_imagesディレクトリに、qualityが90のプログレッシブJPEGファイルを作成します。
from pdf2image import convert_from_path
images = convert_from_path("/home/user/example.pdf",
fmt="jpeg", jpegopt={"quality": 90,"progressive": True},
output_folder="out_images", use_pdftocairo=True)
for i, image in enumerate(images):
print("file[{}]={}".format(i,image.filename))
これでoutput_folderに指定したフォルダにJPEGファイルが作成されているので、目標がJPEGなどの画像ファイル作成だけであれば、目標達成です。
ここで、use_pdftocairo=Falseにすると、pdftoppmを利用して同じ処理が行われますが、処理速度の違いがわかると思います。
さらに、ページ数が多ければ、(8)の thread_count に2以上を指定すると、さらに速度が上がると思います。このような複数プロセスに分割して処理してくれるところは、単なるラッパー以上の機能を持っていることが分かります。
この例では、変換したファイル名を表示していますが、ファイル名を表示するだけなら、画像データのロードは発生しませんので、Imageオブジェクトを作成するオーバーヘッドは殆ど無いと思います。むしろ、ファイル操作を行わなくても作成した画像情報が得られますし、必要なら画像処理もできるので、メリットがあると思います。
ただ、上記コードの実行すると分かりますが、ファイル名にUUIDが使われています。画像ファイルはできたけど、ファイル名がイケてない、という意見もありそうです。
ファイル名は、output_file引数で指定できます。デフォルトでは、uuid_generatorが利用されるため、ファイル名の先頭部分がUUIDになります。
(ちなみに、最終的なファイル名は、pdftoppm、pdftocairoのファイル名生成規則により、ハイフン+ページ番号+拡張子が追加されたものになります。)
より詳細に見ると、output_file引数はgeneratorを指定することになっていて、convert_from_pathの呼び出しごとに新しいファイル名を作るようになっています。これにより同じフォルダを指定してconvert_from_pathを複数回呼び出しても区別できるようになっています。
例えば、uuid_generatorならconvert_from_pathの呼び出しごとに異なるUUIDになります。
output_fileには文字列を指定することもできます。これによりファイル名を指定できそうに思えますが、実装としては、counter_generatorにラップされるため、これにより指定した名前の後ろにイテレーション番号が追加されます。(理由はやはり同じフォルダで異なる呼び出しを区別できるようにするためと思われます。)
generatorについては、GitHubの以下のコードを参照してください。
このファイル名の問題への対応方法は、いろいろ考えられるとは思いますが、いくつか書いておきます。
- 対策は必要ない。
- 例えば、ファイル名は単なるIDとして利用するだけのケースでは、指定のフォルダに出力でき、かつ作成されたファイル名も特定できるので問題ないといえます。
- 作成されたファイル名を変更する、あるいは移動する。
- 作成されたファイル名は特定できるので、後からリネーム可能です。
- あるいは、(4)のように一時フォルダに作成して、最終的なフォルダに名前を変えてファイル毎に移動する方法もあります(一時フォルダは自動的に削除できます)。例えば、画像ファイルをS3やGCSのようなストレージに保存する場合は、このような方法もありかと思います。
- output_fileに与えるgeneratorを自作する。
- uuid_generatorあるいはcounter_generator(https://github.com/Belval/pdf2image/blob/master/pdf2image/generators.py)を参考に@threadsafeのgeneratorを自作します。
- 但し、最終的なファイル名は、 pdftoppmまたはpdftocairoがページ番号と拡張子を追加して作成されますので、完全にファイル名を制御できるわけではありません。
(参考)pdfinfo_from_path関数
リファレンスマニュアルに正式に書かれているわけではありませんが、ソースコードを見ると、pdfinfoコマンドをラップしたpdfinfo_from_path関数が実装されています。
(pdfinfo_from_bytes関数もあります。)
convert_from_pathの実装をみると、PDFファイルの全ページ数を得るために、pdfinfoコマンドをラップしたpdfinfo_from_path関数を利用しています。
関数定義は以下のようになっています。
pdfinfo_from_path( pdf_path, userpw=None, poppler_path=None, rawdates=False, timeout=None)
戻り値は、pdfinfoコマンドの出力をディクショナリに変換して利用しやすくなっています。
利用サンプル
from pdf2image import pdfinfo_from_path
dinfo = pdfinfo_from_path("/home/user/example.pdf")
print(dinfo)
pdf2image全体に言えることですが、実装がコンパクトですので、他のコマンドをラップする関数を自作する時の参考になると思います。
コメント
コメントを投稿