PDFに含まれるすべての画像を抽出する方法です。Python3.6+。Windowsで動かしましたがLinux等でも可能だと思います。
01. 環境
pyMuPDFというライブラリを以下のコマンドで入れます:
pip install pymupdf
pyMuPDF は import fitz
でインポートできるライブラリです。PDFだけでなくEPUBなども読めます。公式ドキュメントはここ で [PyPIの統計情報] (https://pypi.org/project/PyMuPDF/) を見ると2021/May が最新更新でGithubでは900を超えるスターが付いてます。Python3.6+で動作します。私はWindows上で動かしています。
02. 基本となる考え方
いくつか知っておくべき項目を列挙します。
- PDFに入っている画像形式は不定です。どの形式の画像を入れるかはPDF作成者が決められます。jpgやpngが多いですが JPEG 2000(拡張子:jpx) が使われることもあります。
- 画像抽出と画像変換は違う: 抽出した画像が意図しない形式の場合, 画像変換をする必要がありますが, 本スクリプトでは対応しません。画像変換は後述しますが ImageMagick を使えばコマンドライン1行で可能です。
- 画像はレンダリングされて表示されているかも?: PDF文書上は1つの画像に見えても内部的には複数の画像を重ね合わせて1つの画像のように見せていることもあります。その場合はページをレンダリングしてからスクリーンショットをとる操作が必要です。06章にその方法を記載したので、以下の方法でうまくいかない場合は試してみてください。
03. スクリプト仕様と実装
test123.pdf
を入力とします。入力ファイルと同じディレクトリに test123/
というディレクトリを作りその中に 0012_1.jpg
などのような ページ数_番号.拡張子
という名称の画像を抽出します。実装はこうです:
import os, sys
import fitz
def main(fname):
dstdir = os.path.splitext(fname)[0]
os.makedirs(dstdir, exist_ok=True)
with fitz.open(fname) as doc:
for i, page in enumerate(doc):
for j, img in enumerate(page.getImageList()):
x = doc.extractImage(img[0])
name = os.path.join(dstdir, f"{i:04}_{j:02}.{x['ext']}")
with open(name, "wb") as ofh:
ofh.write(x['image'])
if __name__ == "__main__":
main(sys.argv[1])
簡単に実装を説明します
-
fitz.open
でPDFのオブジェクトを得ますが、これはイテラブルで, イテレートするとページオブジェクトが得られます。 - ページオブジェクトの
getImageList()
で画像一覧=タプルのリストが得られます。タプルの第一要素が画像IDとなるので、このIDをキーにしてextractImage()
で画像データを取得します。 - 画像データの
ext
属性が拡張子,image
属性が画像データのバイト列です。
04. おまけ(画像変換)
ImageMagickと呼ばれるCLI画像操作ツールがあります。これはめちゃくちゃ有名ですので入れておいて損はありません。Windowsでもポチポチクリックするだけでパスを通してくれます。
私がPDFから抽出した画像はJPEG 2000形式(拡張子:jpx)でした。これをpngに変換するコマンドは以下です:
magick mogrify -format png *.jpx
05. その他
- 画像変換をPillowでやろうとしたのですがpipで入るPillowではjp2のコーデックが入っていませんでした。再コンパイルすれば使えるかもという記述があるのですがWindowsで使うので諦めました
- pymupdf は MuPDFという製品の python bindings です。あまりよく読んでないのですが、GPL系のライセンスです。ソースコードは公開されておりプルリクエスト等はできますが、レポジトリの変更はArtifexという会社の社員のみだそうです。
- ドキュメントを読んで理解したい人は チュートリアル と 画像抽出HOWOTO を読めば十分だと思います。
- pymupdf 自体にも画像変換の機能が備わっています(ドキュメント)。jpeg,png などは上述のように抽出し、JPEG 2000は変換による劣化を甘受しつつスクリプト内で変換するという選択肢もあります
06. それでも上手くいかない時
PDF文書では、埋め込まれた複数の画像を重ね合わせることで一つの画像として表示することがあります。その場合は、上述の方法では上手くいきません。Webページをレンダリングしてからスクリーンショットをとるように、PDF文書もいちどレンダリングしてからスクリーンショット相当のことをする必要があります。そのためのスクリプトを記載します:
import os, sys
import fitz
def main(fname):
dstdir = os.path.splitext(fname)[0]
os.makedirs(dstdir, exist_ok=True)
with fitz.open(fname) as doc:
for page in doc:
pix = page.get_pixmap()
pix.save(os.path.join(dstdir,f"p{page.number:04}.png"))
if __name__ == "__main__":
main(sys.argv[1])