業務効率化・自動化の事例として、PythonでPDFを読み込み画像を抽出する方法を解説していきます。
画像のマスク情報も取得して再構成する方法を解説しますので、背景が黒くなったりせず、完全な形で取得することができます。
使用ライブラリ
PythonでPDFファイルを操作するライブラリはPyMuPDF
,PyPDF2
,PDFminer
などがあります。
それぞれのライブラリで得意とする操作が異なるため使い分けていきます。
ライブラリ使い分けをまとめると以下のようになります。
・関連記事:PythonでPDFを読み込む(PyMuPDF, PyPDF2, PDFminer)
今回はPyMuPDF
でPDFの画像を取得する方法を解説します。
インストール:必要
pip
コマンドでPyMuPDF
ライブラリをインストールします。
・関連記事:Pythonライブラリのインストール(pipの使い方)
Successfully installed ・・・・と表示されればインストールは成功です。(依存関係のある他のライブラリも同時にインストールされます。)
PyMuPDFの基本的な使い方については以下の記事を参考にしてください。
・関連記事:PyMuPDFの基本的な使い方
PDFファイルから画像を抽出してpng/jpegで保存する
PDFファイルから画像を抽出する方法を解説していきます。
ここでは統計局の『高等学校における「情報II」のためのデータサイエンス・データ解析入門』の第1章テキストを利用させていただきました。
以下がPDFファイルの画像データを取得して、png, jpegファイルで保存するプログラムです。対象のPDFファイル名は sample.pdf
としています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 | # PRG1:ライブラリ設定 import fitz import os # PRG2:画像の保存先フォルダを設定 filename = 'sample.pdf' dir_name = filename.split('.')[0] img_dir = os.path.join(os.getcwd(),dir_name) if os.path.isdir(img_dir) == False: os.mkdir(img_dir) # PRG3:PDFファイルを読み込む doc = fitz.open(filename) # PRG4:画像情報を格納するリストを作成 images = [] # PRG5:1ページずつ画像データを取得 for page in range(len(doc)): images.append(doc[page].get_images()) # PRG6:ページ内の画像情報を順番に処理 for pageNo, image in enumerate(images): # PRG7:ページ内の画像情報を処理する if image != []: for i in range(len(image)): # PRG8:画像情報の取得 xref = image[i][0] smask = image[i][1] if image[i][8] == 'FlateDecode': ext = 'png' elif image[i][8] == 'DCTDecode': ext = 'jpeg' # PRG9:マスク情報の取得と画像の再構築 pix = fitz.Pixmap(doc.extract_image(xref)["image"]) if smask > 0: mask = fitz.Pixmap(doc.extract_image(smask)["image"]) pix = fitz.Pixmap(pix, 0) pix = fitz.Pixmap(pix, mask) # PRG10:画像を保存 img_name = os.path.join(img_dir, f'image{pageNo+1}_{i}.{ext}') pix.save(img_name) |
PDFファイル内の画像が保存されます。
プログラム解説
プログラムのポイントを解説します。
PRG1:ライブラリ設定
1 2 3 | # PRG1:ライブラリ設定 import fitz import os |
PyMuPDFライブラリを使用するには、fitzをインポートします。(歴史的な理由によりこのように呼び出します)
また、画像を保存するパスを生成するためにosモジュールを呼び出します。
・関連記事:Pythonでファイル名の一括変更(os, globモジュール)
PRG2:画像の保存先フォルダを設定
1 2 3 4 5 6 | # PRG2:画像の保存先フォルダを設定 filename = 'sample.pdf' dir_name = filename.split('.')[0] img_dir = os.path.join(os.getcwd(),dir_name) if os.path.isdir(img_dir) == False: os.mkdir(img_dir) |
ポイントを解説します。
今回はsample.pdfという名称のPDFを読み込みます。違うファイル名を読み込む場合は、ここを読み込みたいファイル名に変更する必要があります。
ファイル名「sample.pdf」から拡張子抜きの文字列「sample」を取得し、保存するフォルダ名に設定します。filename.split('.')
で [‘sample’, ‘pdf’] のリストを取得し[0]
で1番目の要素を取り出しています。
・関連記事:文字列処理ー文字列の分割(split)
画像の保存フォルダパスを設定します。
ここではPythonスクリプトと同じフォルダに、PDFファイル名のフォルダを作成して保存します。os.getcwd()
で現在のフォルダを取得し、os.path.join()
で前に設定したdir_name
と結合します。
これで、スクリプトフォルダ\sample というディレクトリ名を取得できます。getcwd()
については以下の記事を参考にしてください。
・参考記事:Pythonでカレントディレクトリを取得・変更
os.mkdir(img_dir)
os.path.isdir()
で、先程設定した画像保存先フォルダの有無を調べ、存在しない場合はos.mkdir()
でフォルダを作成します。
if 文の使い方については以下の記事を参照してください。
・関連記事:条件分岐(if文)
PRG3:PDFファイルを読み込む
1 2 | # 3:PDFファイルを読み込む doc = fitz.open(filename) |
PyMuPDFのfitz.open()
メソッドでPDFファイルを読み込みます。
PRG4:画像情報を格納するリストを作成
1 2 | # PRG4:画像情報を格納するリストを作成 images = [] |
画像情報を格納する空のリストを作成します。
PRG5:1ページずつ画像データを取得
1 2 3 | # PRG5:1ページずつ画像データを取得 for page in range(len(doc)): images.append(doc[page].get_images()) |
プログラムを解説します。
for文でPDFデータを1ページずつ呼び出します。
get_images()
メソッドでページ内の画像データを取得する事ができます。
取得したデータは、各画像ごとのタプルが格納されたリストになっています。以下の場合は2つの画像がある場合の結果になります。
[(885, 884, 323, 280, 8, ‘DeviceRGB’, ”, ‘Im0’, ‘FlateDecode’),
(887, 886, 1368, 820, 8, ‘DeviceRGB’, ”, ‘Im1’, ‘FlateDecode’)]
タプルの内容は次のようになっています。
xref | 画像オブジェクト番号 | |
smask | ソフトマスクイメージのオブジェクト番号 | |
width | 画像の幅 | |
height | 画像の高さ | |
bpc | コンポーネントあたりのビット数(通常は8) | |
colorspace | 色空間 | |
alt. colorspace | colorspaceの値に応じた代替色空間 | |
name | 画像が参照される記号名 | |
filter | 画像のデコードフィルター |
ページに画像が含まれていない場合は、空のリストを返します。
images.append()
で取得したリストを、images
リストに格納しています。
imagesは以下のような2次元配列になります。
[
[(885, 884, 323, 280, 8, ‘DeviceRGB’, ”, ‘Im0’, ‘FlateDecode’),
(887, 886, 1368, 820, 8, ‘DeviceRGB’, ”, ‘Im1’, ‘FlateDecode’)],
[(4, 3, 1125, 484, 8, ‘DeviceRGB’, ”, ‘Im0’, ‘DCTDecode’)],
[(11, 10, 1047, 482, 8, ‘DeviceRGB’, ”, ‘Im0’, ‘FlateDecode’)],
…
[(114, 113, 609, 321, 8, ‘DeviceRGB’, ”, ‘Im0’, ‘FlateDecode’)]
]
関連記事:リスト(配列)ー リストに要素を追加する
PRG6:ページ内の画像情報を順番に処理
1 2 | # PRG6:ページ内の画像情報を順番に処理 for pageNo, image in enumerate(images): |
enumerateでリストimagesの要素数をpageNo
として取得し、要素内容image
を順番に処理していきます。
for 文の使い方については以下の記事を参照してください。
・関連記事:繰り返し処理(for文)
PRG7:ページ内の画像情報を処理する
1 2 3 | # PRG7:ページ内の画像情報を処理する if image != []: for i in range(len(image)): |
プログラムを解説します。
ページ内に画像が含まれていない場合、空のリストが格納されていますので、!=[]
で空以外の場合のみ処理します。
要素imageはタプルのリストになっているため、要素を一つずつ処理していきます。
PRG8:画像情報の取得
1 2 3 4 5 6 7 | # PRG8:画像情報の取得 xref = image[i][0] smask = image[i][1] if image[i][8] == 'FlateDecode': ext = 'png' elif image[i][8] == 'DCTDecode': ext = 'jpeg' |
1行ずつ解説します。
画像情報のタプルから、画像オブジェクト番号xrefを取得します。
画像情報のタプルから、ソフトマスクイメージのオブジェクト番号smaskを取得します。
このマスクイメージが画像を完全に復元するために必要な情報となります。
PDFの一部の画像にはマスクが付いているため、元の状態に再構築するにはマスクを補完する必要があります。
・参考情報:PyMuPDF documentation – How to Handle Image Masks
マスクイメージを結合しないと、以下のように背景が黒くなってしまい、完全な画像を取得する事ができない場合があります。
ext = ‘png’
elif image[i][8] == ‘DCTDecode’:
ext = ‘jpeg’
デコードフィルター情報を取得して、画像の拡張子を設定しています。
「FlateDecode」の場合はpng形式、「DCTDecode」の場合はjpeg形式になります。
PRG9:マスク情報の取得と画像の再構築
1 2 3 4 5 6 | # PRG9:マスク情報の取得と画像の再構築 pix = fitz.Pixmap(doc.extract_image(xref)["image"]) if smask > 0: mask = fitz.Pixmap(doc.extract_image(smask)["image"]) pix = fitz.Pixmap(pix, 0) pix = fitz.Pixmap(pix, mask) |
1行ずつ解説します。
基本画像のPixmapを作成します。
mask = fitz.Pixmap(doc.extract_image(smask)[“image”])
PRG8で取得したマスク情報が1以上の場合、マスク画像のPixmapを作成します。
マスク画像を合成するために、基本画像のαチャンネルを0(透明)に設定します。
基本画像とマスク画像を合成して、画像を再構築します。
PRG10:画像を保存
1 2 3 | # PRG10:画像を保存 img_name = os.path.join(img_dir, f'image{pageNo+1}_{i}.{ext}') pix.save(img_name) |
1行ずつ解説していきます。
2で設定したimg_dirとPDFのページ番号、ページ内の画像インデックス番号と拡張子をos.path.join()
で結合して画像のパスを設定しています。
設定したパスで画像を保存します。
PDFファイルから画像データを取得する方法を解説しました。
・関連記事:PythonでPDFを読み込む(PyMuPDF, PyPDF2, PDFminer)
・関連記事:PythonでPDFのテキストを抽出する