Pythonで画像を扱うならPillow(旧PIL):基本を解説

はい、承知いたしました。PythonのPillowライブラリ(旧PIL)に関する詳細な入門記事を約5000語で記述し、ここに直接表示します。


Pythonで画像を扱うならPillow(旧PIL):基本を徹底解説

画像処理は、現代の多くのアプリケーションにおいて不可欠な要素です。ウェブサイトでの画像表示、データサイエンスにおける画像解析、機械学習での画像認識の前処理、さらには簡単な画像編集ツールまで、様々な場面で画像をプログラム的に操作する必要が生じます。Pythonは、その豊富なライブラリエコシステムのおかげで、画像処理においても非常に強力な選択肢となります。そして、Pythonで画像を扱う際のデファクトスタンダードとなっているライブラリが Pillow です。

本記事では、Python初心者から、画像処理の基本を学びたいと考えている方までを対象に、Pillowライブラリの基本的な使い方を、具体的なコード例を交えながら徹底的に解説します。約5000語というボリュームで、Pillowの核心である Image オブジェクトから、画像の読み込み・保存、基本的な操作(リサイズ、切り抜き、回転)、色空間の変換、ピクセルへのアクセス、さらには簡単な描画やフィルター処理まで、Pillowを使いこなす上で欠かせない基礎知識を網羅します。

1. Pillowとは何か? なぜPillowを使うのか? (PILからPillowへ)

まず、Pillowが何であり、なぜPythonで画像処理を行う際にPillowが推奨されるのかを理解しましょう。

Pillowは、「Python Imaging Library (PIL)」という、かつてPythonで画像処理を行うための標準ライブラリだったものの、後継かつアクティブにメンテナンスされているフォーク(派生プロジェクト)です。PILは非常に強力なライブラリでしたが、開発が2009年頃に停止してしまい、新しいPythonのバージョンやオペレーティングシステムへの対応が難しくなっていきました。

そこで登場したのがPillowです。PillowはPILとの互換性を保ちつつ、Python 3への対応、新しい画像フォーマットのサポート、ビルドの容易化など、現代の開発環境に合わせた改良が加えられています。現在、PILをインストールしようとすると、代わりにPillowがインストールされることが一般的です。そのため、今日Pythonで画像処理を行うと言うときは、実質的にPillowを使用することを意味します。

なぜPillowを使うのか?

  • 機能豊富: 画像の読み込み、保存、リサイズ、回転、切り抜きといった基本的な操作はもちろん、色変換、フィルター処理、ピクセル単位の操作、描画機能など、画像処理に必要な多くの機能を提供します。
  • 使いやすさ: 直感的でPythonらしいAPIを提供しており、比較的少ないコード量で複雑な画像処理タスクを実行できます。
  • 幅広いフォーマット対応: JPEG, PNG, GIF, TIFF, BMPなど、主要な画像フォーマットの読み込み・保存をサポートしています。
  • パフォーマンス: 一部の操作はC言語で実装されており、高速な画像処理が可能です。
  • アクティブな開発: PILとは異なり、Pillowは活発にメンテナンスされており、バグ修正や新機能の追加が継続的に行われています。
  • エコシステムとの連携: NumPyと連携することで、より高度なピクセル操作や画像解析が容易になります。

これらの理由から、Pythonで画像処理を始めるにあたって、Pillowは最良の選択肢と言えます。

2. Pillowのインストール

Pillowを使い始めるのは非常に簡単です。Pythonのパッケージ管理システムである pip を使ってインストールします。ターミナルまたはコマンドプロンプトを開き、以下のコマンドを実行してください。

bash
pip install Pillow

これにより、Pillowライブラリとその依存関係がインストールされます。特定の画像フォーマット(例えば、WebPやHEIFなど)をサポートするためには、追加のライブラリが必要になる場合がありますが、基本的なJPEG, PNG, GIFなどの形式は標準インストールで対応しています。

インストールが完了したら、Pythonスクリプト内で from PIL import Image のように記述することで、Pillowの機能を利用できるようになります。慣例として、Pillowの各モジュールは PIL という名前空間の中に含まれています。

3. Pillowの基本概念:Image オブジェクト

Pillowにおけるすべての画像処理は、PIL.Image.Image クラスのインスタンス、すなわち Image オブジェクト を中心に行われます。ファイルから画像を読み込んだり、新しく画像を生成したりすると、その結果は Image オブジェクトとして表現されます。そして、リサイズ、回転、切り抜きなどの操作は、この Image オブジェクトのメソッドを呼び出すことで実行されます。

Image オブジェクトは、画像に関する様々な情報(ピクセルデータ、画像モード、サイズ、フォーマットなど)を持っています。これらの情報は、オブジェクトの属性としてアクセスできます。

3.1. 画像モード (Image Mode)

Pillowで画像を扱う上で最も重要な概念の一つが「画像モード」です。画像モードは、ピクセルデータがどのように表現されているかを定義します。各モードは、ピクセルあたりのビット数と、それが表現する内容(グレースケール、カラー、パレットなど)を示します。主要なモードとその意味は以下の通りです。

  • 'L' (8-bit pixels, grayscale): 8ビットのグレースケール画像です。各ピクセルは0(黒)から255(白)までの単一の値で表現されます。
  • 'RGB' (3×8-bit pixels, true color): 24ビットのトゥルーカラー画像です。各ピクセルは赤(Red)、緑(Green)、青(Blue)の3つのチャンネルを持ち、それぞれ0から255までの値で表現されます。3つのチャンネルの組み合わせで様々な色を表現します。
  • 'RGBA' (4×8-bit pixels, true color with transparency mask): 32ビットのトゥルーカラー画像で、アルファチャンネル(Alpha channel)が追加されています。RGBに加えて、透明度(Alpha)の値(0:完全透明, 255:完全不透明)を持ちます。ウェブサイトなどで透過背景の画像(例:PNG)を扱う際に重要です。
  • 'CMYK' (4×8-bit pixels, color separation): シアン(Cyan)、マゼンタ(Magenta)、イエロー(Yellow)、キープレート(ブラック、Key plate/Black)の4つのチャンネルを持つカラー画像です。印刷分野でよく使用されます。
  • 'P' (8-bit pixels, using a palette): 8ビットのパレット画像です。各ピクセルは0から255までのインデックス値で、このインデックスがパレット(色のリスト)中の特定の色を指し示します。GIF画像などでよく使用され、ファイルサイズを抑えるのに役立ちます。透明度をサポートする場合もあります。
  • '1' (1-bit pixels, black and white): 1ビットの白黒二値画像です。各ピクセルは0(黒)または1(白)のいずれかです。
  • 'I' (32-bit integer pixels): 32ビット整数ピクセルの画像です。科学計算などで使用されることがあります。
  • 'F' (32-bit floating point pixels): 32ビット浮動小数点ピクセルの画像です。科学計算などで使用されることがあります。

画像のモードによって、実行できる操作や処理の効率が変わることがあります。例えば、アルファチャンネルを扱う操作は'RGBA'モードの画像でなければ意味がありませんし、フィルター処理の中には特定のモードでのみ効果を発揮するものがあります。

3.2. 画像のサイズと座標

Image オブジェクトは、画像の幅と高さをピクセル単位で持ちます。これは size 属性としてアクセスでき、(幅, 高さ) というタプルで表現されます。例えば、image.size(width, height) を返します。

Pillowにおける画像の座標システムは、多くの画像処理ライブラリと同様に、画像の 左上隅を原点 (0, 0) とします。X座標は右方向へ増加し、Y座標は下方向へ増加します。これは数学で一般的なデカルト座標系とはY軸の向きが逆であることに注意が必要です。

座標を指定する場合、(x, y) のタプル形式を使用します。例えば、(10, 20) は左上隅から右に10ピクセル、下に20ピクセル移動した地点を指します。領域を指定する場合は、(x1, y1, x2, y2) の4要素タプルを使用します。これは左上隅の座標 (x1, y1) と右下隅の座標 (x2, y2) を示し、この領域には x1 <= x < x2 かつ y1 <= y < y2 のピクセルが含まれます(つまり、x2y2の座標自体は領域に含まれない、半開区間の考え方です)。

3.3. ピクセルへのアクセス

Image オブジェクトのピクセルデータに直接アクセスすることも可能です。これは getpixel((x, y)) メソッドや putpixel((x, y), color) メソッドを使用して、単一のピクセルの値を取得したり設定したりすることで行います。

  • image.getpixel((x, y)) は、指定された座標のピクセルの値を返します。戻り値の形式は画像モードによって異なります。
    • 'L' モード: 整数 (0-255)
    • 'RGB' モード: (R, G, B) のタプル
    • 'RGBA' モード: (R, G, B, A) のタプル
    • 'P' モード: パレットインデックス (0-255)
  • image.putpixel((x, y), color) は、指定された座標のピクセルの値を設定します。color の形式は getpixel の戻り値と同じように、画像モードに合った形式である必要があります。

多数のピクセルを操作する場合、これらのメソッドをループ内で使用するのは非常に低速になる可能性があるため、あまり推奨されません。より効率的な方法としては、後述するNumPyとの連携などが挙げられますが、簡単な確認や少数のピクセル操作には便利です。

4. 画像の読み込みと保存

画像ファイルから画像を読み込み、処理結果をファイルに保存することは、Pillowの最も基本的な機能です。

4.1. 画像の読み込み (Image.open())

画像ファイルを読み込むには、PIL.Image モジュールの open() 関数を使用します。

“`python
from PIL import Image

try:
# 画像ファイルを読み込む (例: sample.jpg)
image_path = ‘sample.jpg’ # 存在しないファイル名を指定するとエラーになります
img = Image.open(image_path)

# 読み込んだ画像の情報にアクセス
print(f"ファイル名: {img.filename}")
print(f"画像フォーマット: {img.format}")
print(f"画像モード: {img.mode}")
print(f"画像サイズ: {img.size} (幅x高さ)")

# 画像を表示 (環境によってはビューアが起動します)
# img.show()

except FileNotFoundError:
print(f”エラー: ファイル ‘{image_path}’ が見つかりません。”)
except Exception as e:
print(f”画像の読み込み中にエラーが発生しました: {e}”)
“`

Image.open() 関数は、指定されたパスの画像を読み込み、Image オブジェクトを返します。ファイルが見つからない場合や、ファイル形式がPillowでサポートされていない場合などに例外が発生する可能性があります。try...except ブロックを使用して、エラーを適切に処理することが重要です。

読み込んだ Image オブジェクトは、以下の属性を持っています。

  • img.filename: 読み込んだファイルのパス。
  • img.format: 画像のフォーマット(例: ‘JPEG’, ‘PNG’, ‘GIF’)。
  • img.mode: 画像のモード(例: ‘RGB’, ‘L’, ‘RGBA’)。
  • img.size: 画像のサイズを (幅, 高さ) のタプルで返します。
  • img.info: フォーマット固有の情報を含む辞書。例えば、JPEGのExifデータやPNGのメタデータなどが含まれることがあります。

注意点: Image.open() は画像をすぐに完全にデコードするわけではありません。ヘッダー情報などを読み込んで Image オブジェクトを作成するだけで、ピクセルデータへのアクセスは必要になった時点で行われます(遅延評価)。これにより、大きな画像を扱う際にメモリを節約できます。ただし、画像を閉じるのを忘れないように、with ステートメントを使うのが推奨される作法です。

“`python
from PIL import Image

image_path = ‘sample.jpg’
try:
with Image.open(image_path) as img:
print(f”ファイル名: {img.filename}”)
print(f”画像サイズ: {img.size}”)
# ここで img を使って画像処理を行う
# …
# withブロックを抜けると、画像は自動的に閉じられます
except FileNotFoundError:
print(f”エラー: ファイル ‘{image_path}’ が見つかりません。”)
“`

with ステートメントを使用すると、ブロックの終了時に画像のファイルハンドルが自動的に閉じられるため、リソースリークを防ぐことができます。

4.2. 画像の保存 (Image.save())

処理済みの Image オブジェクトをファイルに保存するには、Image オブジェクトの save() メソッドを使用します。

“`python
from PIL import Image

適当な画像を開く (実際にはファイルを指定)

この例では新しい画像を生成して保存します

img = Image.new(‘RGB’, (60, 30), color = ‘red’)

画像をファイルに保存 (例: output.png)

output_path = ‘output.png’
img.save(output_path)
print(f”画像を ‘{output_path}’ に保存しました。”)

別のフォーマットで保存

output_path_jpg = ‘output.jpg’
img.save(output_path_jpg, format=’JPEG’) # format引数でフォーマットを指定
print(f”画像を ‘{output_path_jpg}’ に保存しました。”)

JPEG形式で品質を指定して保存 (品質は0-100、デフォルトは75程度)

output_path_jpg_q = ‘output_quality.jpg’
img.save(output_path_jpg_q, format=’JPEG’, quality=90) # quality=90で高品質に
print(f”画像を ‘{output_path_jpg_q}’ に品質90で保存しました。”)

PNG形式で最適化して保存 (ファイルサイズ削減)

output_path_png_opt = ‘output_optimized.png’
img.save(output_path_png_opt, format=’PNG’, optimize=True)
print(f”画像を ‘{output_path_png_opt}’ に最適化して保存しました。”)
“`

save() メソッドの第一引数には保存先のファイルパスを指定します。ファイルパスの拡張子(例: .png, .jpg)に基づいて、Pillowは保存するフォーマットを推測します。明示的にフォーマットを指定したい場合は、format 引数を使用します。

また、save() メソッドはフォーマット固有のオプションをキーワード引数として受け取ることができます。よく使われるオプションには以下のようなものがあります。

  • quality (JPEG): 0(低品質、ファイルサイズ小)から100(高品質、ファイルサイズ大)までの整数で、JPEG圧縮の品質を指定します。
  • optimize (PNG): True に設定すると、ファイルサイズを削減するためにPNGエンコーディングを最適化します。
  • append_images (GIF): GIFアニメーションとして複数のフレームを保存する場合に使用します(後述)。
  • save_all (GIF): アニメーションGIFを保存する際に append_images と共に使用します。

5. 新しい画像の生成 (Image.new())

ファイルから画像を読み込むだけでなく、新しい空の画像をプログラム的に生成することもできます。これは、例えば画像に描画するキャンバスを作成したり、特定の色の画像を生成したりする場合に便利です。PIL.Image モジュールの new() 関数を使用します。

“`python
from PIL import Image

‘RGB’モードで幅200ピクセル、高さ100ピクセルの画像を作成し、黒で塗りつぶす

img_black = Image.new(‘RGB’, (200, 100), color=’black’)
print(f”黒い画像を作成しました。サイズ: {img_black.size}, モード: {img_black.mode}”)
img_black.save(‘new_black.png’)

‘L’モードで幅50ピクセル、高さ50ピクセルの画像を作成し、グレー(128)で塗りつぶす

img_gray = Image.new(‘L’, (50, 50), color=128)
print(f”グレーの画像を作成しました。サイズ: {img_gray.size}, モード: {img_gray.mode}”)
img_gray.save(‘new_gray.png’)

‘RGBA’モードで幅150ピクセル、高さ75ピクセルの画像を作成し、半透明の青で塗りつぶす

colorはRGBAタプル (R, G, B, A)

img_blue_transparent = Image.new(‘RGBA’, (150, 75), color=(0, 0, 255, 128))
print(f”半透明青い画像を作成しました。サイズ: {img_blue_transparent.size}, モード: {img_blue_transparent.mode}”)
img_blue_transparent.save(‘new_blue_transparent.png’)

‘P’モードで画像を作成 (パレットは自動で設定されます)

‘P’モードは通常、パレットを指定するか、既存の画像から変換して作成することが多いです

この例では簡単な単色画像ですが、実際の利用ではより複雑なパレットを設定します

img_palette = Image.new(‘P’, (100, 100), color=1) # color=1はパレットの1番目の色 (デフォルトではほぼ黒)
print(f”‘P’モードの画像を作成しました。サイズ: {img_palette.size}, モード: {img_palette.mode}”)

img_palette.save(‘new_palette.png’) # このままだとパレットが設定されていないため、通常はエラーになります

実際には以下のように、RGB画像を作成してから’P’に変換するなどします

img_rgb_for_p = Image.new(‘RGB’, (100, 100), color=’green’)
img_p_from_rgb = img_rgb_for_p.convert(‘P’)
img_p_from_rgb.save(‘new_palette_from_rgb.png’)
“`

Image.new() 関数の基本的な引数は以下の通りです。

  • mode: 作成する画像のモードを指定します(例: 'RGB', 'L', 'RGBA')。
  • size: 作成する画像のサイズを (幅, 高さ) のタプルで指定します。
  • color (省略可能): 画像全体を塗りつぶす色を指定します。モードに応じた形式(整数、タプル、色名文字列)で指定できます。省略した場合、デフォルトは黒です。

6. 画像の基本的な操作

Pillowを使えば、画像に対して様々な基本的な変形(リサイズ、切り抜き、回転、反転)を行うことができます。これらの操作は、元の Image オブジェクトを変更するのではなく、新しい Image オブジェクトを返すことに注意してください。

6.1. リサイズ (resize(), thumbnail())

画像のサイズを変更するには、resize() メソッドまたは thumbnail() メソッドを使用します。

  • resize((width, height), resample=filter): 指定された正確なサイズ (width, height) に画像をリサイズして、新しい Image オブジェクトを返します。resample 引数でリサンプリングフィルターを指定できます。フィルターは、新しいピクセルの値を計算する際に、周囲の複数のピクセルの情報を使うかどうかなどを決定します。高品質なリサイズには、Image.Resampling.LANCZOSImage.Resampling.BICUBIC がよく使われます。省略した場合のデフォルトフィルターは、Pillowのバージョンによって異なる場合がありますが、通常は Image.Resampling.BICUBICImage.Resampling.BILINEAR です。

“`python
from PIL import Image

適当な画像を開く

try:
img = Image.open(‘large_sample.jpg’) # 存在しない場合は適切な画像パスを指定してください
print(f”元のサイズ: {img.size}”)

# 幅100ピクセル、高さ50ピクセルにリサイズ
# 高品質なLANCZOSフィルターを使用
# Pillow 10.0.0以降では Image.Resampling.LANCZOS と記述
# それ以前のバージョンでは Image.LANCZOS と記述 (非推奨)
# 新しい書き方に対応するため try-except またはバージョンチェックを推奨
try:
    resized_img = img.resize((100, 50), Image.Resampling.LANCZOS)
except AttributeError:
     resized_img = img.resize((100, 50), Image.LANCZOS) # 旧バージョン互換性
     print("旧バージョンのPillowを使用しているため、Image.LANCZOSを使用しました。")


print(f"リサイズ後のサイズ: {resized_img.size}")
resized_img.save('resized_image.png')

# 元の縦横比を維持してリサイズする場合
# 例えば、幅を200ピクセルにしたいが、高さを自動で調整したい場合
original_width, original_height = img.size
new_width = 200
new_height = int(original_height * (new_width / original_width))
resized_img_aspect = img.resize((new_width, new_height), Image.Resampling.LANCZOS if hasattr(Image, 'Resampling') else Image.LANCZOS)
print(f"縦横比を維持して幅200にリサイズ後のサイズ: {resized_img_aspect.size}")
resized_img_aspect.save('resized_aspect_image.png')

except FileNotFoundError:
print(“エラー: large_sample.jpg が見つかりません。適切な画像パスを指定してください。”)
except Exception as e:
print(f”画像処理中にエラーが発生しました: {e}”)
“`

  • thumbnail((max_width, max_height), resample=filter): 画像の縦横比を維持したまま、指定された (max_width, max_height) の範囲内に収まるようにリサイズします。これは、サムネイル画像を作成するのに非常に便利です。resize と異なり、元の Image オブジェクト自身を変更します(インプレース操作)。ただし、新しいオブジェクトを返す resize と同様に使うことも可能で、その場合は返り値を利用します。一般的には元のオブジェクトを変更する用途で使われることが多いです。

“`python
from PIL import Image

try:
img = Image.open(‘large_sample.jpg’) # 存在しない場合は適切な画像パスを指定してください
print(f”元のサイズ: {img.size}”)

# 最大幅128ピクセル、最大高さ128ピクセルにサムネイル化
# 縦横比は維持される
img.thumbnail((128, 128))
print(f"サムネイル化後のサイズ: {img.size}")
img.save('thumbnail_image.png')

# 注意: thumbnail()は元のオブジェクトを変更するため、元の画像を残したい場合は
# copy()してからthumbnail()を呼び出すか、resize()を使用します
img_copy = Image.open('large_sample.jpg').copy() # 元の画像をコピー
img_copy.thumbnail((256, 256))
print(f"コピーをサムネイル化後のサイズ: {img_copy.size}")
img_copy.save('thumbnail_copy_image.png')

except FileNotFoundError:
print(“エラー: large_sample.jpg が見つかりません。適切な画像パスを指定してください。”)
except Exception as e:
print(f”画像処理中にエラーが発生しました: {e}”)
``resizeは指定されたサイズに正確に変換しますが、thumbnailは指定されたサイズに収まるように縦横比を維持して変換します。サムネイル作成ではthumbnailが、固定サイズの出力が必要な場合はresize` が適しています。

6.2. 切り抜き (crop())

画像の一部を切り抜くには、crop() メソッドを使用します。

“`python
from PIL import Image

try:
img = Image.open(‘sample_to_crop.jpg’) # 存在しない場合は適切な画像パスを指定してください
print(f”元のサイズ: {img.size}”)

# 左上 (50, 50) から右下 (200, 150) の領域を切り抜く
# 領域は (x1, y1, x2, y2) のタプルで指定 (x2, y2は含まれない)
# 例: 幅 200-50=150, 高さ 150-50=100 の領域
box = (50, 50, 200, 150)
cropped_img = img.crop(box)

print(f"切り抜き後のサイズ: {cropped_img.size}")
cropped_img.save('cropped_image.png')

except FileNotFoundError:
print(“エラー: sample_to_crop.jpg が見つかりません。適切な画像パスを指定してください。”)
except Exception as e:
print(f”画像処理中にエラーが発生しました: {e}”)
“`

crop() メソッドは、切り抜く領域を (x1, y1, x2, y2) のタプルで受け取ります。このタプルは、左上隅の座標 (x1, y1) と右下隅の座標 (x2, y2) を指定します。重要なのは、指定された領域には x1 <= x < x2 かつ y1 <= y < y2 のピクセルが含まれるという点です。つまり、x2y2 は含まれません(Pythonのスライス表記 [start:end] と同様の考え方です)。

6.3. 回転 (rotate())

画像を回転させるには、rotate() メソッドを使用します。

“`python
from PIL import Image

try:
img = Image.open(‘sample_to_rotate.jpg’) # 存在しない場合は適切な画像パスを指定してください
print(f”元のサイズ: {img.size}”)

# 反時計回りに45度回転
rotated_img_45 = img.rotate(45)
print(f"45度回転後のサイズ (デフォルト: サイズ調整なし): {rotated_img_45.size}")
rotated_img_45.save('rotated_45.png') # サイズ調整なしの場合、角が欠ける可能性があります

# 反時計回りに45度回転し、画像全体が収まるようにサイズを調整
rotated_img_45_expand = img.rotate(45, expand=True)
print(f"45度回転後のサイズ (expand=True): {rotated_img_45_expand.size}")
rotated_img_45_expand.save('rotated_45_expand.png')

# 時計回りに90度回転 (angleに負の値を指定するか、expand=Trueと90を指定)
# または transpose() を使う方が正確です (後述)
rotated_img_neg90 = img.rotate(-90, expand=True)
print(f"-90度回転後のサイズ (expand=True): {rotated_img_neg90.size}")
rotated_img_neg90.save('rotated_neg90_expand.png')

# 90度、180度、270度といった直角回転は transpose() の方が画質劣化が少なく高速です
# rotate(90) と transpose(Image.ROTATE_90) は異なります (rotateは反時計回り)
# 時計回りに90度回転 (=反時計回りに270度回転)
rotated_img_270_expand = img.rotate(270, expand=True)
print(f"270度回転後のサイズ (expand=True): {rotated_img_270_expand.size}")
rotated_img_270_expand.save('rotated_270_expand.png')

except FileNotFoundError:
print(“エラー: sample_to_rotate.jpg が見つかりません。適切な画像パスを指定してください。”)
except Exception as e:
print(f”画像処理中にエラーが発生しました: {e}”)
“`

rotate() メソッドは、引数で回転角度(反時計回りの度数)を指定します。

  • angle: 回転させる角度を度数で指定します。正の値は反時計回り、負の値は時計回りです。
  • resample: リサンプリングフィルターを指定します(resize と同様)。
  • expand: True に設定すると、回転後の画像全体が収まるように出力画像のサイズを調整します。デフォルトは False で、元の画像サイズに合わせて回転するため、画像の一部が欠ける可能性があります。
  • center: 回転の中心を指定します。デフォルトは画像の中心です。

90度、180度、270度といった直角回転を行う場合は、transpose() メソッドの方が画質劣化がなく高速なので推奨されます。

6.4. 反転と直角回転 (transpose())

画像を左右または上下に反転させたり、90度単位で回転させたりするには、transpose() メソッドを使用します。これは、ロスレス変換が可能な場合に特に有用です。

“`python
from PIL import Image

try:
img = Image.open(‘sample_to_transpose.jpg’) # 存在しない場合は適切な画像パスを指定してください
print(f”元のサイズ: {img.size}”)

# 左右反転
flipped_lr = img.transpose(Image.FLIP_LEFT_RIGHT)
print("左右反転した画像を保存しました。")
flipped_lr.save('flipped_lr.png')

# 上下反転
flipped_tb = img.transpose(Image.FLIP_TOP_BOTTOM)
print("上下反転した画像を保存しました。")
flipped_tb.save('flipped_tb.png')

# 反時計回りに90度回転
rotated_90ccw = img.transpose(Image.ROTATE_90)
print("反時計回りに90度回転した画像を保存しました。")
rotated_90ccw.save('rotated_90ccw.png')

# 180度回転
rotated_180 = img.transpose(Image.ROTATE_180)
print("180度回転した画像を保存しました。")
rotated_180.save('rotated_180.png')

# 反時計回りに270度回転 (=時計回りに90度回転)
rotated_270ccw = img.transpose(Image.ROTATE_270)
print("反時計回りに270度回転した画像を保存しました。")
rotated_270ccw.save('rotated_270ccw.png')

# 注意: transposeには FLIP_LEFT_RIGHT, FLIP_TOP_BOTTOM,
# ROTATE_90, ROTATE_180, ROTATE_270 の5種類があります。
# Pillow 10.0.0以降では Image.Transpose.FLIP_LEFT_RIGHT などと記述
try:
    flipped_lr_new = img.transpose(Image.Transpose.FLIP_LEFT_RIGHT)
    print("左右反転した画像を保存しました。(新記法)")
    flipped_lr_new.save('flipped_lr_new.png')
except AttributeError:
    print("旧バージョンのPillowを使用しているため、新記法(Image.Transpose...)は使用できません。")

except FileNotFoundError:
print(“エラー: sample_to_transpose.jpg が見つかりません。適切な画像パスを指定してください。”)
except Exception as e:
print(f”画像処理中にエラーが発生しました: {e}”)
“`

transpose() メソッドには、以下の定数を引数として指定します(Pillow 10.0.0以降は Image.Transpose の属性として、それ以前は Image の属性として定義されています)。

  • Image.FLIP_LEFT_RIGHT (または Image.Transpose.FLIP_LEFT_RIGHT): 左右反転
  • Image.FLIP_TOP_BOTTOM (または Image.Transpose.FLIP_TOP_BOTTOM): 上下反転
  • Image.ROTATE_90 (または Image.Transpose.ROTATE_90): 反時計回りに90度回転
  • Image.ROTATE_180 (または Image.Transpose.ROTATE_180): 180度回転
  • Image.ROTATE_270 (または Image.Transpose.ROTATE_270): 反時計回りに270度回転

これらの操作はピクセルデータを単純に並べ替えるだけであり、ピクセル値の補間を行わないため、画質劣化がありません。直角回転が必要な場合は rotate() よりも transpose() を使う方が一般的です。

7. 色空間とモードの変換 (convert())

画像の色空間(モード)を変換することは、画像処理の多くのステップで必要になります。例えば、特定の色空間でのみ利用可能なアルゴリズムを適用したい場合や、ファイルサイズを削減したい場合などです。Pillowでは convert() メソッドを使用してモード変換を行います。

“`python
from PIL import Image

try:
img_rgb = Image.open(‘sample_color.jpg’) # RGBまたはRGBAの画像
print(f”元のモード: {img_rgb.mode}”)

# RGB画像をグレースケールに変換
img_gray = img_rgb.convert('L')
print(f"グレースケール変換後のモード: {img_gray.mode}")
img_gray.save('sample_gray.png') # グレースケールは通常PNGで保存

# RGB画像をRGBAに変換 (アルファチャンネルを追加)
# この場合、元の画像が不透明であれば、追加されるアルファチャンネルは全て255 (不透明) になります
img_rgba = img_rgb.convert('RGBA')
print(f"RGBA変換後のモード: {img_rgba.mode}")
img_rgba.save('sample_rgba.png')

# RGBA画像からRGBに変換 (アルファチャンネルを破棄)
# 透明な部分は通常黒(0,0,0)になりますが、背景色を指定することも可能です
# transparent_img = Image.open('sample_transparent.png') # アルファチャンネルを持つ画像
# img_rgb_from_rgba = transparent_img.convert('RGB')
# print(f"RGBAからRGB変換後のモード: {img_rgb_from_rgba.mode}")
# img_rgb_from_rgba.save('sample_rgb_from_rgba.jpg') # 透明部分が黒くなる

# RGBAからRGBに変換し、透明部分を白(255,255,255)にする場合
# 新しいRGB画像を白で作成し、その上に元のRGBA画像を合成する方法を使います
# (詳細は後述の画像の合成で説明しますが、convertにもbackgoundオプションがあります)
# img_rgb_from_rgba_whitebg = transparent_img.convert('RGB', background=(255, 255, 255))
# img_rgb_from_rgba_whitebg.save('sample_rgb_from_rgba_whitebg.jpg')

# RGB画像をパレット(P)モードに変換
# ファイルサイズを削減したい場合などに使用
img_palette = img_rgb.convert('P')
print(f"パレット変換後のモード: {img_palette.mode}")
img_palette.save('sample_palette.png')

# パレット(P)画像をRGBに変換 (パレット情報を展開)
img_rgb_from_palette = img_palette.convert('RGB')
print(f"パレットからRGB変換後のモード: {img_rgb_from_palette.mode}")
img_rgb_from_palette.save('sample_rgb_from_palette.png')

except FileNotFoundError:
print(“エラー: sample_color.jpg が見つかりません。適切な画像パスを指定してください。”)
except Exception as e:
print(f”画像処理中にエラーが発生しました: {e}”)
“`

convert() メソッドの引数には、変換したい目標のモードを文字列で指定します。一般的な変換は以下の通りです。

  • 'L': グレースケールへの変換(カラー画像から輝度情報のみを抽出)
  • 'RGB': RGBトゥルーカラーへの変換
  • 'RGBA': RGBAトゥルーカラー(アルファチャンネル付き)への変換
  • 'P': パレットモードへの変換
  • '1': 白黒二値画像への変換(スレッショルド処理が必要な場合があります)

convert() メソッドは、変換後の新しい Image オブジェクトを返します。変換によって情報が失われる場合があることに注意してください(例: RGBからLへの変換では色情報が失われる)。

8. ピクセル単位の操作

個々のピクセル値を取得したり設定したりする必要がある場合、前述の getpixel()putpixel() メソッドを使用します。しかし、これらのメソッドはループ内で多数のピクセルに適用するとパフォーマンスが低下します。より効率的にピクセルデータ全体にアクセスする方法としては、load() メソッド(古い方法)や、NumPy配列として画像データを扱う方法があります。NumPy連携は本記事の範囲を超えるため、ここでは基本的な getpixel/putpixel と、簡単なループ処理について触れます。

“`python
from PIL import Image

try:
img = Image.open(‘sample_pixel.png’) # 存在しない場合は適切な画像パスを指定してください
print(f”画像サイズ: {img.size}, モード: {img.mode}”)

# 画像がRGBまたはRGBAモードであることを確認
if img.mode not in ('RGB', 'RGBA'):
    print("RGBまたはRGBAモードに変換します。")
    img = img.convert('RGB') # 操作しやすいようにRGBに変換

width, height = img.size

# 単一ピクセルの値を取得
pixel_value = img.getpixel((10, 10))
print(f"座標 (10, 10) のピクセル値: {pixel_value}")

# 単一ピクセルの値を設定 (例: 左上隅を赤にする)
img.putpixel((0, 0), (255, 0, 0)) # RGBモードの場合、タプルで色を指定

# 画像全体をループしてピクセルを操作 (非常に遅い可能性あり)
# この例では、画像の右半分をグレースケールにする
img_copy = img.copy() # 元の画像を壊さないようにコピー
pixels = img_copy.load() # ピクセルデータにアクセス (古い方法だが分かりやすい)

for y in range(height):
    for x in range(width // 2, width): # 右半分をループ
        r, g, b = pixels[x, y][:3] # ピクセル値を取得 (RGBAなら最初の3要素)
        # グレースケール変換の計算 (輝度 L = R * 0.2989 + G * 0.5870 + B * 0.1140)
        gray_value = int(r * 0.2989 + g * 0.5870 + b * 0.1140)
        pixels[x, y] = (gray_value, gray_value, gray_value) # ピクセル値を設定

print("画像の右半分をグレースケールにしました。")
img_copy.save('sample_pixel_manipulated.png')

except FileNotFoundError:
print(“エラー: sample_pixel.png が見つかりません。適切な画像パスを指定してください。”)
except Exception as e:
print(f”画像処理中にエラーが発生しました: {e}”)
“`

img.load() は、画像ピクセルへの高速な読み書きアクセスを提供します(内部的にはNumPy配列に似たインターフェースを提供)。これにより、pixels[x, y] のようにインデックスを使ってピクセル値にアクセスできるようになります。ただし、これは画像を完全にメモリに読み込むため、非常に大きな画像を扱う場合は注意が必要です。また、Pillowの新しいバージョンでは、.load() を明示的に呼び出さなくても、ピクセルへのアクセス時に自動的に読み込みが行われるようになっています。しかし、明示的に .load() を呼ぶことで、読み込みを事前に行わせることができます。

より複雑なピクセル操作や数値計算が必要な場合は、Pillow画像をNumPy配列に変換し、NumPyの高速な配列操作機能を利用するのが最も効率的な方法です。

“`python
from PIL import Image
import numpy as np

try:
img = Image.open(‘sample_pixel.png’).convert(‘RGB’) # RGBに変換してNumPyに変換
img_array = np.array(img) # PIL画像をNumPy配列に変換

print(f"NumPy配列の形状: {img_array.shape} (高さ, 幅, チャンネル)")
print(f"座標 (10, 10) のピクセル値 (NumPy): {img_array[10, 10, :]}")

# NumPy配列を直接操作 (例: 赤チャンネルだけを取り出す)
red_channel = img_array[:, :, 0] # 全高, 全幅, 赤チャンネル(インデックス0)

# NumPy配列からPIL画像に戻す
img_red_channel = Image.fromarray(red_channel, 'L') # Lモードのグレースケール画像として作成
img_red_channel.save('red_channel.png')

# 例: 画像を左右反転 (NumPyで簡単にできる)
img_array_flipped = img_array[:, ::-1, :] # 幅方向を反転

# 反転したNumPy配列からPIL画像に戻す
img_flipped_np = Image.fromarray(img_array_flipped, 'RGB') # RGBモードのPIL画像として作成
img_flipped_np.save('sample_flipped_np.png')

except FileNotFoundError:
print(“エラー: sample_pixel.png が見つかりません。適切な画像パスを指定してください。”)
except ImportError:
print(“エラー: numpyライブラリがインストールされていません。pip install numpy でインストールしてください。”)
except Exception as e:
print(f”画像処理中にエラーが発生しました: {e}”)
``
NumPyを使うことで、画像全体に対する一括処理や数学的な操作を効率的に行うことができます。
Image.fromarray()関数はNumPy配列からPillow画像を作成し、np.array(Image)` はPillow画像からNumPy配列を作成します。

9. 画像への描画 (ImageDraw)

画像上に線、図形、テキストなどを描画するには、PIL.ImageDraw モジュールを使用します。まず、描画対象の Image オブジェクトから ImageDraw.Draw オブジェクトを作成し、そのオブジェクトのメソッドを呼び出して描画を行います。

“`python
from PIL import Image, ImageDraw, ImageFont

新しい画像を作成(描画キャンバスとして)

img = Image.new(‘RGB’, (400, 300), color=’white’)

描画オブジェクトを作成

draw = ImageDraw.Draw(img)

線を描画 (始点x, 始点y, 終点x, 終点y)

draw.line([(x1, y1), (x2, y2), …], fill=color, width=width)

draw.line([(0, 0), (399, 299)], fill=’red’, width=2) # 対角線
draw.line([(0, 299), (399, 0)], fill=(0, 255, 0), width=2) # もう一つの対角線

四角形を描画 (左上x, 左上y, 右下x, 右下y)

draw.rectangle([x1, y1, x2, y2], fill=fill_color, outline=outline_color, width=width)

draw.rectangle([50, 50, 350, 250], outline=’blue’, width=3) # 枠線のみ
draw.rectangle([60, 60, 340, 240], fill=(255, 255, 0)) # 黄色で塗りつぶし

円(楕円)を描画 (左上x, 左上y, 右下x, 右下y)

draw.ellipse([x1, y1, x2, y2], fill=fill_color, outline=outline_color, width=width)

draw.ellipse([100, 100, 300, 200], outline=’purple’, width=4) # 外枠

テキストを描画

draw.text((x, y), text, fill=color, font=font_object)

text_position = (10, 10)
text_color = ‘black’
text_string = “Hello, Pillow!”

フォントオブジェクトを作成 (フォントファイルへのパスを指定)

システムにインストールされているフォント、または指定のttc/ttfファイルを使用

Windowsの場合: C:\Windows\Fonts\arial.ttf など

macOSの場合: /Library/Fonts/Arial.ttf など

Linuxの場合: /usr/share/fonts/… など

環境によってフォントパスは異なります。存在しないフォントを指定するとエラーになります。

try:
# 一般的なシステムフォントパスの例 (環境に合わせて変更してください)
font_path = ‘/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf’ # Linux例
# font_path = ‘C:/Windows/Fonts/arial.ttf’ # Windows例
# font_path = ‘/Library/Fonts/Arial.ttf’ # macOS例

# フォントサイズを指定してフォントオブジェクトをロード
# Pillow 10.0.0以降では ImageFont.truetype(font=..., size=...)
# それ以前のバージョンでは ImageFont.truetype(..., size=...)
# 新しい書き方に対応するため try-except またはバージョンチェックを推奨
try:
     font = ImageFont.truetype(font=font_path, size=30)
except TypeError: # font= 引数がない旧バージョン
     font = ImageFont.truetype(font_path, size=30)
except IOError:
     print(f"警告: フォントファイル '{font_path}' が見つかりません。デフォルトフォントで描画します。")
     font = ImageFont.load_default() # フォントファイルが見つからない場合はデフォルトフォントを使用

draw.text(text_position, text_string, fill=text_color, font=font)

except FileNotFoundError:
print(f”警告: フォントファイル ‘{font_path}’ が見つかりません。デフォルトフォントで描画します。”)
font = ImageFont.load_default()
draw.text(text_position, text_string, fill=text_color, font=font)
except Exception as e:
print(f”テキスト描画中にエラーが発生しました: {e}”)
# エラー時でも画像保存は試みる
if ‘font’ not in locals(): # フォント読み込みで完全に失敗した場合
font = ImageFont.load_default() # デフォルトフォントで再試行
draw.text(text_position, text_string, fill=text_color, font=font)

描画結果を保存

img.save(‘drawn_image.png’)
print(“描画を含む画像を保存しました。”)
“`

ImageDraw.Draw(img) で作成される描画オブジェクトは、元の img オブジェクトに直接描画を行います。利用可能な描画メソッドは多数あります。

  • point(xy, fill): 点を描画
  • line(xy, fill, width): 線を描画
  • rectangle(xy, fill, outline, width): 四角形を描画
  • ellipse(xy, fill, outline, width): 楕円(円)を描画
  • polygon(xy, fill, outline): 多角形を描画
  • text(xy, text, fill, font, anchor): テキストを描画

xy 引数は、描画する図形の座標を指定するタプルまたはリストです。fill は塗りつぶし色、outline は枠線の色、width は線の太さを指定します。色は色名文字列(例: 'red', 'blue') やRGB/RGBAタプル(例: (255, 0, 0), (255, 0, 0, 128)) で指定できます。

テキストを描画するには、ImageFont モジュールを使用してフォントオブジェクトを作成し、font 引数として渡す必要があります。システムにインストールされているTrueTypeフォントファイル(.ttf, .ttc)を ImageFont.truetype() で読み込むのが一般的です。

10. 画像フィルターの適用 (ImageFilter)

画像にぼかし、シャープ化、エッジ検出などのフィルターを適用するには、PIL.ImageFilter モジュールを使用します。Pillowにはいくつかの標準的なフィルターが組み込まれています。

“`python
from PIL import Image, ImageFilter

try:
img = Image.open(‘sample_filter.jpg’) # 存在しない場合は適切な画像パスを指定してください
print(f”元の画像モード: {img.mode}”)

# フィルターによってはRGBモードが必要な場合があります
if img.mode != 'RGB':
    img = img.convert('RGB')
    print("RGBモードに変換しました。")


# Gaussian Blur フィルターを適用
# filter() メソッドの引数に ImageFilter の定数を指定
blurred_img = img.filter(ImageFilter.GaussianBlur(radius=5)) # radiusでぼかしの強さを指定
print("ガウシアンブラーフィルターを適用しました。")
blurred_img.save('blurred_image.png')

# Sharpen フィルターを適用
sharpened_img = img.filter(ImageFilter.SHARPEN) # SHARPENはインスタンス化不要な定数
print("シャープフィルターを適用しました。")
sharpened_img.save('sharpened_image.png')

# Edge Enhance フィルターを適用
edge_enhanced_img = img.filter(ImageFilter.EDGE_ENHANCE)
print("エッジ強調フィルターを適用しました。")
edge_enhanced_img.save('edge_enhanced_image.png')

# Smooth フィルターを適用
smoothed_img = img.filter(ImageFilter.SMOOTH)
print("スムージングフィルターを適用しました。")
smoothed_img.save('smoothed_image.png')

# Pillow 10.0.0以降では ImageFilter.Kernel や ImageFilter.BoxBlur など、
# より柔軟なフィルターが利用可能です。
# Box Blur フィルター (Pillow 10.0.0以降)
# try:
#     box_blurred_img = img.filter(ImageFilter.BoxBlur(radius=5))
#     print("ボックスブラーフィルターを適用しました。")
#     box_blurred_img.save('box_blurred_image.png')
# except AttributeError:
#     print("警告: BoxBlurフィルターはPillow 10.0.0以降が必要です。")

except FileNotFoundError:
print(“エラー: sample_filter.jpg が見つかりません。適切な画像パスを指定してください。”)
except Exception as e:
print(f”画像処理中にエラーが発生しました: {e}”)
“`

Image.filter() メソッドは、引数として PIL.ImageFilter モジュールから提供されるフィルターオブジェクト(または定数)を受け取ります。

  • ImageFilter.BLUR: シンプルなぼかし
  • ImageFilter.CONTOUR: 輪郭抽出
  • ImageFilter.DETAIL: ディテール強調
  • ImageFilter.EDGE_ENHANCE: エッジ強調
  • ImageFilter.EDGE_ENHANCE_MORE: より強いエッジ強調
  • ImageFilter.EMBOSS: エンボス(浮き出し)
  • ImageFilter.FIND_EDGES: エッジ検出
  • ImageFilter.SMOOTH: スムージング
  • ImageFilter.SMOOTH_MORE: より強いスムージング
  • ImageFilter.SHARPEN: シャープ化

一部のフィルター(例: GaussianBlur, BoxBlur)はインスタンスとして作成し、半径などのパラメータを指定できます。多くの基本的なフィルターは定数として提供されています。

11. 画像の合成 (paste())

一つの画像を別の画像の上に貼り付けるには、Image オブジェクトの paste() メソッドを使用します。これは、ロゴを画像に合成したり、背景画像に前景画像を重ねたりする場合に非常に便利です。

“`python
from PIL import Image

try:
# 背景画像を開く
background_img = Image.open(‘background.jpg’) # 存在しない場合は適切な画像パスを指定してください
print(f”背景画像サイズ: {background_img.size}”)

# 貼り付けたい画像を開く
# 透明度(アルファチャンネル)がある画像 (PNGなど) を用意すると効果が分かりやすいです
overlay_img = Image.open('overlay.png') # 存在しない場合は適切な画像パスを指定してください
print(f"重ね合わせ画像サイズ: {overlay_img.size}, モード: {overlay_img.mode}")

# 貼り付けたい画像が必要なモードになっているか確認 (RGBAなど)
if overlay_img.mode != 'RGBA':
    print("重ね合わせ画像をRGBAモードに変換します。")
    overlay_img = overlay_img.convert('RGBA')


# 重ね合わせ画像のサイズが背景画像より大きい場合はリサイズする
# あるいは切り抜くなど、適宜調整が必要
bg_width, bg_height = background_img.size
ol_width, ol_height = overlay_img.size

if ol_width > bg_width or ol_height > bg_height:
    print("重ね合わせ画像が背景画像より大きいため、リサイズします。")
    # 背景画像に合わせてリサイズ (縦横比は維持せず、シンプルに合わせる例)
    # 実際には縦横比を維持するか、適切なサイズに調整する方が良い
    overlay_img = overlay_img.resize((bg_width // 2, bg_height // 2), Image.Resampling.LANCZOS if hasattr(Image, 'Resampling') else Image.LANCZOS)
    ol_width, ol_height = overlay_img.size # リサイズ後のサイズを取得

# 貼り付け位置を計算 (例: 背景画像の中央に貼り付ける)
paste_x = (bg_width - ol_width) // 2
paste_y = (bg_height - ol_height) // 2
paste_box = (paste_x, paste_y)

# 画像を合成 (背景画像の上に重ね合わせ画像を貼り付け)
# アルファチャンネルを持つ画像を貼り付ける場合、mask引数にその画像自身のアルファチャンネルを指定します
# RGB画像を貼り付ける場合は mask=None または省略します
if overlay_img.mode == 'RGBA':
     background_img.paste(overlay_img, paste_box, overlay_img) # mask=overlay_img を指定
     print("RGBA画像を合成しました。")
else:
     background_img.paste(overlay_img, paste_box) # RGB画像などを合成
     print("RGB画像を合成しました。")


# 合成結果を保存
background_img.save('pasted_image.png')
print("画像を合成して保存しました。")

except FileNotFoundError as e:
print(f”エラー: 必要な画像ファイルが見つかりません。'{e.filename}'”)
except Exception as e:
print(f”画像処理中にエラーが発生しました: {e}”)
“`

paste() メソッドの基本的な使い方は以下の通りです。

target_image.paste(source_image, box=None, mask=None)

  • source_image: 貼り付けたい Image オブジェクト。
  • box: source_imagetarget_image のどこに貼り付けるかを指定する座標。(x, y) のタプルで左上隅の座標を指定します。(x1, y1, x2, y2) の4要素タプルで領域を指定することもできますが、この場合 source_image は指定された領域に合わせてリサイズされます。省略した場合 (None) は、target_image の左上隅 (0, 0) に貼り付けられます。
  • mask: マスク画像として使用する Image オブジェクトを指定します。通常、アルファチャンネルを持つ source_image を透明度付きで合成したい場合に、mask=source_image または mask=source_image.split()[-1](アルファチャンネルのみを取り出す)を指定します。マスクは '1' または 'L' モードである必要があります。マスクのピクセル値が0に近いほど透明度が高く、255に近いほど不透明度が高くなります。

アルファチャンネルを持つPNG画像などを透過合成する場合、target_image がRGBモードでも source_image がRGBAモードであれば paste() メソッドの mask 引数に source_image 自身を指定することで、自動的にアルファチャンネルがマスクとして使用され、透過合成が実現できます。

12. アニメーションGIFなどの複数フレーム画像の扱い

Pillowは、アニメーションGIFやマルチページTIFFなどの複数フレームを持つ画像ファイルも扱うことができます。Image.open() でこれらのファイルを開いた場合、デフォルトでは最初のフレームのみが読み込まれます。他のフレームにアクセスするには、seek() メソッドと tell() メソッドを使用します。

“`python
from PIL import Image

gif_path = ‘animation.gif’ # 存在しない場合は適切な画像パスを指定してください
output_folder = ‘gif_frames’

import os
os.makedirs(output_folder, exist_ok=True) # フレーム保存用のフォルダを作成

try:
with Image.open(gif_path) as img:
print(f”ファイル名: {img.filename}”)
print(f”画像フォーマット: {img.format}”)
print(f”画像モード: {img.mode}”) # GIFの最初のフレームのモード
print(f”画像サイズ: {img.size}”)

    frame_index = 0
    while True:
        try:
            # 現在のフレームを処理
            print(f"フレーム {frame_index} (モード: {img.mode}, サイズ: {img.size})")

            # フレームを個別の画像として保存
            frame_output_path = os.path.join(output_folder, f'frame_{frame_index:03d}.png')
            # フレームによってはPモードやRGBAモードの場合があります。
            # 他の画像形式(PNG)で保存する際は、必要に応じてRGB/RGBAに変換すると安全です。
            frame_img = img.convert('RGBA') # RGBAに変換して保存する例
            frame_img.save(frame_output_path)
            print(f"  フレーム {frame_index} を保存しました: {frame_output_path}")

            # 次のフレームに進む
            img.seek(frame_index + 1)
            frame_index += 1

        except EOFError:
            # すべてのフレームを読み終えた
            print("すべてのフレームの処理が完了しました。")
            break # ループを終了
        except Exception as e:
            print(f"フレーム {frame_index} の処理中にエラーが発生しました: {e}")
            break # エラーが発生したらループを終了

print(f"全 {frame_index} フレームを '{output_folder}' フォルダに抽出しました。")


# 複数の画像をアニメーションGIFとして保存する例
# 抽出したフレーム画像を読み込み直す
frames_to_save = []
for i in range(frame_index):
    frame_path = os.path.join(output_folder, f'frame_{i:03d}.png')
    try:
         frames_to_save.append(Image.open(frame_path))
    except FileNotFoundError:
         print(f"警告: フレームファイル {frame_path} が見つかりません。")
         continue


if frames_to_save:
    # 最初のフレームをベースに、append_imagesで他のフレームを追加して保存
    # duration: 各フレームの表示時間 (ミリ秒), loop: ループ回数 (0で無限ループ)
    output_animated_gif = 'recreated_animation.gif'
    frames_to_save[0].save(output_animated_gif,
                           format='GIF',
                           append_images=frames_to_save[1:], # 2番目以降のフレームをリストで指定
                           save_all=True, # 全てのフレームを保存することを指定
                           duration=100, # 各フレームを100ms表示
                           loop=0) # 無限ループ
    print(f"抽出したフレームから新しいアニメーションGIF '{output_animated_gif}' を作成しました。")

except FileNotFoundError:
print(f”エラー: GIFファイル ‘{gif_path}’ が見つかりません。適切な画像パスを指定してください。”)
except Exception as e:
print(f”GIF処理中にエラーが発生しました: {e}”)
“`

seek(frame_index) メソッドは、指定されたインデックスのフレームに移動します。tell() メソッドは現在のフレームのインデックスを返します。ファイル終端に達して次のフレームがない状態で seek() を呼び出すと EOFError が発生するので、これを利用してループを終了させます。

複数の Image オブジェクトをアニメーションGIFとして保存するには、save() メソッドの append_images 引数と save_all=True オプションを使用します。append_images には、最初のフレーム以外のフレームのリストを指定します。duration で各フレームの表示時間をミリ秒単位で設定できます。

13. その他の機能と発展的なトピック

Pillowには、上記以外にも多くの機能があります。

  • Exifデータの読み書き: img.getexif() でExifデータを含む辞書を取得できます。Exifデータは画像の向きや撮影日時などの情報を含みます。
  • ヒストグラム: img.histogram() で画像のヒストグラム(ピクセル値の分布)を計算できます。
  • ポイント操作 (point()): 各ピクセルに対して指定した関数やルックアップテーブルを適用できます。色調補正などに利用できます。
  • 結合と分割 (merge(), split()): 画像のチャンネルを分割したり(例: RGB画像をR, G, Bの3つのグレースケール画像に分割)、複数のグレースケール画像を結合してカラー画像にしたりできます。
  • 複数のファイル形式のサポート: Image.registered_extensions()Image.registered_identities() でサポートされているファイル形式を確認できます。

発展的なトピックとしては、以下のようなものがあります。

  • NumPyとの連携: 画像データをNumPy配列として扱うことで、高度な数値計算や配列操作を活用した画像処理が可能になります。OpenCVなどの他の画像処理ライブラリと連携する際にもNumPy配列への変換がよく行われます。
  • 大きな画像の処理: 非常に大きな画像を扱う場合、メモリに一度に読み込むことが難しいことがあります。Pillow自体には高度なタイリング機能などは直接提供されていませんが、領域を指定して処理する crop などを組み合わせて工夫するか、GDALのような地理空間情報処理ライブラリなどを検討する必要があります。
  • パフォーマンス最適化: 大量の画像処理を行う場合やリアルタイム処理が必要な場合は、効率的なアルゴリズムの選択やNumPy/Cythonなどの利用を検討します。
  • カスタムフィルター: ImageFilter.Kernel を使用して独自のコンボリューションフィルターを定義できます。

これらの発展的なトピックについては、Pillowの公式ドキュメントや、より専門的な画像処理の文献を参照することをお勧めします。

14. まとめと次のステップ

本記事では、PythonのPillowライブラリ(旧PIL)の基本的な使い方を幅広く解説しました。Pillowは、画像の読み込み・保存から、リサイズ、切り抜き、回転、色空間変換、描画、フィルター適用、画像合成、複数フレーム画像の扱いまで、画像処理の基本的なニーズのほとんどを満たす強力で使いやすいライブラリです。

学んだ主な内容を振り返ってみましょう。

  • PillowはPILの後継であり、Pythonで画像処理を行う際の標準ライブラリです。
  • 画像は Image オブジェクトとして扱われ、モードやサイズといった属性を持ちます。
  • Image.open() で画像を読み込み、Image.save() で保存します。with ステートメントを使うのが安全です。
  • Image.new() で新しい画像を生成できます。
  • resize(), thumbnail(), crop(), rotate(), transpose() メソッドで画像のサイズ変更、切り抜き、回転、反転を行います。
  • convert() メソッドで画像の色空間(モード)を変換します。
  • getpixel(), putpixel(), load() (またはNumPy連携) でピクセルデータにアクセス・操作できます。
  • PIL.ImageDraw を使って画像上に図形やテキストを描画できます。PIL.ImageFont でフォントを扱います。
  • PIL.ImageFilter を使って様々な組み込みフィルターを画像に適用できます。
  • paste() メソッドで画像を合成(貼り付け)できます。アルファチャンネルによる透過合成も可能です。
  • seek()tell() メソッドを使って、アニメーションGIFなどの複数フレーム画像を処理できます。save()append_imagessave_all=True で複数フレーム画像を保存できます。

Pillowの基本を習得すれば、Pythonを使った画像処理の可能性は大きく広がります。簡単なバッチ処理で大量の画像サイズを変更したり、ウェブアプリケーションでユーザーがアップロードした画像を加工したり、データ分析の前処理として画像データを準備したりと、様々なタスクを効率的にこなすことができるようになります。

次のステップとしては、以下のことに挑戦してみることをお勧めします。

  • 公式ドキュメントを読む: Pillowの公式ドキュメントは非常に充実しています。今回紹介できなかったより詳細な情報や、高度な機能について学ぶことができます。(https://pillow.readthedocs.io/en/stable/
  • NumPyとの連携を深める: Pillowで画像をNumPy配列に変換し、NumPyの機能を活用したピクセル操作や画像分析のコードを書いてみましょう。
  • 応用例に取り組む: サムネイルギャラリーの作成、透かしの追加、簡単な画像フィルター効果の実装など、具体的なプロジェクトに取り組むことで理解が深まります。
  • 他のライブラリと比較検討する: OpenCVやscikit-imageなど、他のPython画像処理ライブラリにも触れてみて、それぞれの得意なことやPillowとの連携について学んでみましょう。

PythonのPillowライブラリは、画像処理の強力な味方です。ぜひ本記事を参考に、Pillowを使った画像処理のスキルを習得し、様々なアプリケーション開発に活かしてください。


コメントする

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

上部へスクロール