チュートリアル

Imageクラスの使用

Python Imaging Libraryで最も重要なクラスは、同じ名前のモジュールで定義されているImageクラスです。このクラスのインスタンスは、ファイルからの画像の読み込み、他の画像の処理、または画像のゼロからの作成など、いくつかの方法で作成できます。

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

>>> from PIL import Image
>>> im = Image.open("hopper.ppm")

成功すると、この関数はImageオブジェクトを返します。これで、インスタンス属性を使用してファイルの内容を確認できます。

>>> print(im.format, im.size, im.mode)
PPM (512, 512) RGB

format属性は画像のソースを識別します。画像がファイルから読み込まれていない場合、Noneに設定されます。size属性は、幅と高さ(ピクセル単位)を含む2要素のタプルです。mode属性は、画像内のバンドの数と名前、およびピクセルタイプと深度を定義します。一般的なモードには、「L」(輝度)のグレースケール画像、「RGB」のトゥルーカラー画像、「CMYK」の印刷前画像などがあります。

ファイルを開くことができない場合、OSError例外が発生します。

Imageクラスのインスタンスを取得したら、このクラスで定義されているメソッドを使用して画像を処理および操作できます。たとえば、読み込んだ画像を表示してみましょう。

>>> im.show()
../_images/show_hopper.webp

注記

標準版のshow()は、一時ファイルに画像を保存してユーティリティを呼び出して画像を表示するため、効率性が非常に低いです。適切なユーティリティがインストールされていない場合、動作しません。ただし、動作する場合は、デバッグとテストに非常に便利です。

以降のセクションでは、このライブラリで提供されるさまざまな関数の概要を示します。

画像の読み込みと書き込み

Python Imaging Libraryは、さまざまな画像ファイル形式をサポートしています。ディスクからファイルを読み込むには、Imageモジュールのopen()関数を使用します。ファイルを開くためにファイル形式を知る必要はありません。ライブラリは、ファイルの内容に基づいて形式を自動的に判別します。

ファイルを保存するには、Imageクラスのsave()メソッドを使用します。ファイルを保存する際には、ファイル名が重要になります。形式を指定しない限り、ライブラリはファイル名拡張子を使用して使用するファイルストレージ形式を検出します。

ファイルをJPEGに変換

import os, sys
from PIL import Image

for infile in sys.argv[1:]:
    f, e = os.path.splitext(infile)
    outfile = f + ".jpg"
    if infile != outfile:
        try:
            with Image.open(infile) as im:
                im.save(outfile)
        except OSError:
            print("cannot convert", infile)
../_images/hopper.jpg

save()メソッドには、ファイル形式を明示的に指定する2番目の引数を指定できます。標準外の拡張子を使用する場合は、常にこのように形式を指定する必要があります。

JPEGサムネイルの作成

import os, sys
from PIL import Image

size = (128, 128)

for infile in sys.argv[1:]:
    outfile = os.path.splitext(infile)[0] + ".thumbnail"
    if infile != outfile:
        try:
            with Image.open(infile) as im:
                im.thumbnail(size)
                im.save(outfile, "JPEG")
        except OSError:
            print("cannot create thumbnail for", infile)
../_images/thumbnail_hopper.jpg

ライブラリは、本当に必要になるまでラスターデータのデコードや読み込みを行わないことに注意することが重要です。ファイルをオープンすると、ファイルヘッダーが読み取られてファイル形式が決定され、モード、サイズ、およびファイルのデコードに必要なその他のプロパティなどが抽出されますが、ファイルの残りの部分は後で処理されるまで処理されません。

つまり、画像ファイルを開く操作は高速で、ファイルサイズや圧縮タイプとは無関係です。次に、一連の画像ファイルをすばやく識別するための簡単なスクリプトを示します。

画像ファイルの識別

import sys
from PIL import Image

for infile in sys.argv[1:]:
    try:
        with Image.open(infile) as im:
            print(infile, im.format, f"{im.size}x{im.mode}")
    except OSError:
        pass

画像の切り取り、貼り付け、マージ

Imageクラスには、画像内の領域を操作できるメソッドが含まれています。画像からサブ長方形を抽出するには、crop()メソッドを使用します。

画像からのサブ長方形のコピー

box = (0, 0, 64, 64)
region = im.crop(box)

領域は4要素のタプルで定義され、座標は(左、上、右、下)です。Python Imaging Libraryは、左上が(0, 0)の座標系を使用します。また、座標はピクセル間の位置を参照するため、上記の例では領域は正確に64x64ピクセルになります。

この領域は、ある方法で処理し、貼り付けることができます。

../_images/cropped_hopper.webp

サブ長方形の処理と貼り付け

region = region.transpose(Image.Transpose.ROTATE_180)
im.paste(region, box)

領域を貼り付ける際には、領域のサイズが指定された領域と完全に一致する必要があります。さらに、領域は画像の外側に広がることはできません。ただし、元の画像と領域のモードが一致する必要はありません。一致しない場合、領域は貼り付けられる前に自動的に変換されます(詳細については、以下のカラー変換のセクションを参照してください)。

../_images/pasted_hopper.webp

もう1つの例を示します。

画像のロール

def roll(im: Image.Image, delta: int) -> Image.Image:
    """Roll an image sideways."""
    xsize, ysize = im.size

    delta = delta % xsize
    if delta == 0:
        return im

    part1 = im.crop((0, 0, delta, ysize))
    part2 = im.crop((delta, 0, xsize, ysize))
    im.paste(part1, (xsize - delta, 0, xsize, ysize))
    im.paste(part2, (0, 0, xsize - delta, ysize))

    return im
../_images/rolled_hopper.webp

または、2つの画像をより幅の広い画像にマージしたい場合。

画像のマージ

def merge(im1: Image.Image, im2: Image.Image) -> Image.Image:
    w = im1.size[0] + im2.size[0]
    h = max(im1.size[1], im2.size[1])
    im = Image.new("RGBA", (w, h))

    im.paste(im1)
    im.paste(im2, (im1.size[0], 0))

    return im
../_images/merged_hopper.webp

より高度なテクニックとして、貼り付けメソッドにはオプション引数として透過マスクを使用することもできます。このマスクでは、値255は貼り付け画像がその位置で不透明であること(つまり、貼り付け画像はそのまま使用されること)を示します。値0は、貼り付け画像が完全に透明であることを意味します。その間の値は、さまざまな透過レベルを示します。たとえば、RGBA画像を貼り付け、それをマスクとしても使用すると、画像の不透明部分は貼り付けられますが、透明な背景は貼り付けられません。

Python Imaging Libraryを使用すると、RGB画像などのマルチバンド画像の個々のバンドを操作することもできます。`split`メソッドは、元のマルチバンド画像から1バンドずつを含む一連の新しい画像を作成します。`merge`関数はモードと画像のタプルを受け取り、それらを新しい画像に結合します。次のサンプルは、RGB画像の3つのバンドを入れ替えます。

バンドの分割とマージ

r, g, b = im.split()
im = Image.merge("RGB", (b, g, r))

単一バンド画像の場合、split() は画像自体を返します。個々のカラーバンドを操作するには、最初に画像を「RGB」に変換することをお勧めします。

../_images/rebanded_hopper.webp

幾何学的変換

PIL.Image.Imageクラスには、画像をresize()およびrotate()するメソッドが含まれています。前者は新しいサイズを与えるタプルを、後者は反時計回りの角度を度数で受け取ります。

単純な幾何学的変換

out = im.resize((128, 128))
out = im.rotate(45) # degrees counter-clockwise
../_images/rotated_hopper_90.webp

画像を90度単位で回転するには、rotate()メソッドまたはtranspose()メソッドを使用できます。後者は、画像を水平軸または垂直軸を中心に反転するためにも使用できます。

画像の転置

out = im.transpose(Image.Transpose.FLIP_LEFT_RIGHT)
../_images/flip_left_right_hopper.webp
out = im.transpose(Image.Transpose.FLIP_TOP_BOTTOM)
../_images/flip_top_bottom_hopper.webp
out = im.transpose(Image.Transpose.ROTATE_90)
../_images/rotated_hopper_90.webp
out = im.transpose(Image.Transpose.ROTATE_180)
../_images/rotated_hopper_180.webp
out = im.transpose(Image.Transpose.ROTATE_270)
../_images/rotated_hopper_270.webp

transpose(ROTATE)操作は、expandフラグがtrueの場合、rotate()操作と同じように実行できます。これにより、画像のサイズが同じように変更されます。

より一般的な形式の画像変換は、transform()メソッドを使用して実行できます。

相対的なサイズ変更

サイズ変更時に新しい画像のサイズを計算する代わりに、特定のサイズに対して相対的にサイズ変更することもできます。

from PIL import Image, ImageOps
size = (100, 150)
with Image.open("hopper.webp") as im:
    ImageOps.contain(im, size).save("imageops_contain.webp")
    ImageOps.cover(im, size).save("imageops_cover.webp")
    ImageOps.fit(im, size).save("imageops_fit.webp")
    ImageOps.pad(im, size, color="#f00").save("imageops_pad.webp")

    # thumbnail() can also be used,
    # but will modify the image object in place
    im.thumbnail(size)
    im.save("image_thumbnail.webp")

thumbnail()

contain()

cover()

fit()

pad()

指定サイズ

(100, 150)

(100, 150)

(100, 150)

(100, 150)

(100, 150)

結果の画像

../_images/image_thumbnail.webp ../_images/imageops_contain.webp ../_images/imageops_cover.webp ../_images/imageops_fit.webp ../_images/imageops_pad.webp

結果のサイズ

100×100

100×100

150×150

100×150

100×150

カラー変換

Python Imaging Libraryを使用すると、convert()メソッドを使用して、さまざまなピクセル表現間で画像を変換できます。

モード間の変換

from PIL import Image

with Image.open("hopper.ppm") as im:
    im = im.convert("L")

このライブラリは、サポートされている各モードと「L」モードおよび「RGB」モード間の変換をサポートしています。他のモード間で変換するには、中間画像(通常は「RGB」画像)を使用する必要があります。

画像の強化

Python Imaging Libraryは、画像を強化するために使用できる多くのメソッドとモジュールを提供します。

フィルタ

ImageFilterモジュールには、filter()メソッドで使用できる、事前に定義された多くの強化フィルタが含まれています。

フィルタの適用

from PIL import ImageFilter
out = im.filter(ImageFilter.DETAIL)
../_images/enhanced_hopper.webp

ポイント演算

point()メソッドは、画像のピクセル値を変換するために使用できます(例:画像のコントラスト操作)。ほとんどの場合、このメソッドには、1つの引数を期待する関数オブジェクトを渡すことができます。各ピクセルはその関数に従って処理されます。

ポイント変換の適用

# multiply each pixel by 20
out = im.point(lambda i: i * 20)
../_images/transformed_hopper.webp

上記のテクニックを使用して、簡単な式を画像にすばやく適用できます。point()メソッドとpaste()メソッドを組み合わせて、画像を選択的に変更することもできます。

個々のバンドの処理

# split the image into individual bands
source = im.split()

R, G, B = 0, 1, 2

# select regions where red is less than 100
mask = source[R].point(lambda i: i < 100 and 255)

# process the green band
out = source[G].point(lambda i: i * 0.7)

# paste the processed band back, but only where red was < 100
source[G].paste(out, None, mask)

# build a new multiband image
im = Image.merge(im.mode, source)

マスクを作成するために使用される構文に注意してください。

imout = im.point(lambda i: expression and 255)
../_images/masked_hopper.webp

Pythonは、結果を決定するために必要な論理式の部分のみを評価し、評価された最後の値を式の結果として返します。したがって、上記の式がfalse(0)の場合、Pythonは2番目のオペランドを見ず、0を返します。それ以外の場合は、255を返します。

強化

より高度な画像の強化には、ImageEnhanceモジュールのクラスを使用できます。画像から作成されると、強化オブジェクトを使用して、さまざまな設定をすばやく試すことができます。

このようにして、コントラスト、明るさ、カラーバランス、シャープネスを調整できます。

画像の強化

from PIL import ImageEnhance

enh = ImageEnhance.Contrast(im)
enh.enhance(1.3).show("30% more contrast")
../_images/contrasted_hopper.jpg

画像シーケンス

Python Imaging Libraryには、画像シーケンス(アニメーション形式とも呼ばれる)の基本的なサポートが含まれています。サポートされているシーケンス形式には、FLI / FLC、GIF、およびいくつかの実験的な形式が含まれます。TIFFファイルには、複数のフレームを含めることもできます。

シーケンスファイルを開くと、PILは自動的にシーケンスの最初のフレームをロードします。`seek`メソッドと`tell`メソッドを使用して、さまざまなフレーム間を移動できます。

シーケンスの読み取り

from PIL import Image

with Image.open("animation.gif") as im:
    im.seek(1)  # skip to the second frame

    try:
        while 1:
            im.seek(im.tell() + 1)
            # do something to im
    except EOFError:
        pass  # end of sequence

この例でわかるように、シーケンスが終了すると、EOFError例外が発生します。

シーケンスの書き込み

Pillowを使用してアニメーションGIFを作成できます。

from PIL import Image

# List of image filenames
image_filenames = [
    "hopper.jpg",
    "rotated_hopper_270.jpg",
    "rotated_hopper_180.jpg",
    "rotated_hopper_90.jpg",
]

# Open images and create a list
images = [Image.open(filename) for filename in image_filenames]

# Save the images as an animated GIF
images[0].save(
    "animated_hopper.gif",
    save_all=True,
    append_images=images[1:],
    duration=500,  # duration of each frame in milliseconds
    loop=0,  # loop forever
)
../_images/animated_hopper.gif

次のクラスを使用すると、for文を使用してシーケンスをループできます。

Iteratorクラスの使用

from PIL import ImageSequence
for frame in ImageSequence.Iterator(im):
    # ...do something to frame...

PostScript印刷

Python Imaging Libraryには、PostScriptプリンタに画像、テキスト、グラフィックスを印刷する関数が含まれています。簡単な例を次に示します。

PostScriptの描画

from PIL import Image, PSDraw
import os

# Define the PostScript file
ps_file = open("hopper.ps", "wb")

# Create a PSDraw object
ps = PSDraw.PSDraw(ps_file)

# Start the document
ps.begin_document()

# Set the text to be drawn
text = "Hopper"

# Define the PostScript font
font_name = "Helvetica-Narrow-Bold"
font_size = 36

# Calculate text size (approximation as PSDraw doesn't provide direct method)
# Assuming average character width as 0.6 of the font size
text_width = len(text) * font_size * 0.6
text_height = font_size

# Set the position (top-center)
page_width, page_height = 595, 842  # A4 size in points
text_x = (page_width - text_width) // 2
text_y = page_height - text_height - 50  # Distance from the top of the page

# Load the image
image_path = "hopper.ppm"  # Update this with your image path
with Image.open(image_path) as im:
    # Resize the image if it's too large
    im.thumbnail((page_width - 100, page_height // 2))

    # Define the box where the image will be placed
    img_x = (page_width - im.width) // 2
    img_y = text_y + text_height - 200  # 200 points below the text

    # Draw the image (75 dpi)
    ps.image((img_x, img_y, img_x + im.width, img_y + im.height), im, 75)

# Draw the text
ps.setfont(font_name, font_size)
ps.text((text_x, text_y), text)

# End the document
ps.end_document()
ps_file.close()
../_images/hopper_ps.webp

注記

表示目的でPDFに変換されたPostScript

画像の読み取りの詳細

前述のように、open()関数は、画像ファイルを開くために使用されます。Imageモジュールに含まれます。ほとんどの場合、ファイル名を引数として渡すだけです。Image.open()はコンテキストマネージャーとして使用できます。

from PIL import Image
with Image.open("hopper.ppm") as im:
    ...

すべてが正常に実行されると、結果はPIL.Image.Imageオブジェクトになります。それ以外の場合は、OSError例外が発生します。

ファイル名ではなく、ファイルのようなオブジェクトを使用できます。オブジェクトはfile.readfile.seek、およびfile.tellメソッドを実装し、バイナリモードで開いている必要があります。

開いているファイルからの読み取り

from PIL import Image

with open("hopper.ppm", "rb") as fp:
    im = Image.open(fp)

バイナリデータから画像を読み取るには、BytesIOクラスを使用します。

バイナリデータからの読み取り

from PIL import Image
import io

im = Image.open(io.BytesIO(buffer))

ライブラリは、画像ヘッダーを読み取る前にファイル(seek(0)を使用)を巻き戻します。さらに、画像データが(`load`メソッドによって)読み取られるときにも`seek`が使用されます。画像ファイルがtarファイルなどのより大きなファイルに埋め込まれている場合、ContainerIOまたはTarIOモジュールを使用してアクセスできます。

URLからの読み取り

from PIL import Image
from urllib.request import urlopen
url = "https://python-pillow.org/assets/images/pillow-logo.png"
img = Image.open(urlopen(url))

tarアーカイブからの読み取り

from PIL import Image, TarIO

fp = TarIO.TarIO("hopper.tar", "hopper.jpg")
im = Image.open(fp)

バッチ処理

操作は複数の画像ファイルに適用できます。たとえば、現在のディレクトリ内のすべてのPNG画像を、品質を落としてJPEGとして保存できます。

import glob
from PIL import Image

def compress_image(source_path: str, dest_path: str) -> None:
    with Image.open(source_path) as img:
        if img.mode != "RGB":
            img = img.convert("RGB")
        img.save(dest_path, "JPEG", optimize=True, quality=80)


paths = glob.glob("*.png")
for path in paths:
    compress_image(path, path[:-4] + ".jpg")

画像はpathlibモジュールのPathからも開くことができるため、この例はglobモジュールではなくpathlibを使用するように変更できます。

from pathlib import Path

paths = Path(".").glob("*.png")
for path in paths:
    compress_image(path, path.stem + ".jpg")

デコーダの制御

一部のデコーダでは、ファイルから画像を読み取るときに画像を操作できます。これは、サムネイルを作成する場合(速度は通常品質よりも重要である場合)や、モノクロレーザープリンタに印刷する場合(画像のグレースケールバージョンのみが必要な場合)に、デコードの高速化に使用されることがよくあります。

draft()メソッドは、開かれたがまだロードされていない画像を操作し、指定されたモードとサイズに可能な限り近づけます。これは、画像デコーダを再構成することで実現されます。

ドラフトモードでの読み込み

これはJPEGファイルとMPOファイルでのみ使用可能です。

from PIL import Image

with Image.open(file) as im:
    print("original =", im.mode, im.size)

    im.draft("L", (100, 100))
    print("draft =", im.mode, im.size)

これは次のようなものを出力します。

original = RGB (512, 512)
draft = L (128, 128)

生成された画像は、要求されたモードとサイズと完全に一致しない場合があります。画像が指定されたサイズより大きくならないようにするには、代わりにサムネイルメソッドを使用してください。