閉じる

【Python】Pillowで画像リサイズ・加工・編集の基本と使い方を解説

Pythonで画像処理を始めるならPillowは最有力の選択肢です。

軽量かつ簡単に学べるのに、リサイズやトリミング、文字入れ、透過合成まで多くをカバーします。

本記事では、Pillowのインストールから実用的な加工テクニック、保存のコツとエラー回避までを、Python初心者向けにていねいに解説します。

Python初心者向け Pillowの基本とインストール

Pillowとは

概要と特徴

Pillowは、旧PIL(Python Imaging Library)の後継として広く使われている画像処理ライブラリです。

数行のコードで画像の読み込みからリサイズ、編集、保存まで行えるのが最大の魅力で、学習コストが低く、実務でも十分に活躍します。

JPEGやPNGなど主要なフォーマットに対応し、色変換やフィルターも簡単に適用できます

PILとの違い

名称が似ていますがPillowはPILのフォークで、現在はPillowが事実上の標準です。

古い記事でPILと書かれていても、現代環境ではPillowを使うと覚えておくと混乱しません。

インストール

pipでの導入

インストールは簡単で、Python公式のパッケージ管理ツールpipから一発です。

仮想環境(venv)の利用も推奨します。

Shell
# 推奨: Pythonに紐づくpipを明示
python -m pip install --upgrade pip
python -m pip install pillow

# バージョン確認
python -c "import PIL; import PIL.Image; print(PIL.__version__)"

Pillow 10以降ではリサンプリング定数が変更され、Image.ANTIALIASは非推奨です。

Image.Resampling.LANCZOSなど新しい定数を使いましょう

画像を開く/保存する

基本の使い方

画像はコンテキストマネージャで開くのが安全です。

自動でクローズされ、ファイルロックの問題を避けられます。

Python
# 基本の開く/保存する
from PIL import Image
from pathlib import Path

img_path = Path("images/input.jpg")  # 実在のファイルに置き換えてください
out_path = Path("outputs/basic_copy.jpg")
out_path.parent.mkdir(parents=True, exist_ok=True)

# with構文で自動クローズ
with Image.open(img_path) as im:
    print("format:", im.format)  # 例: JPEG
    print("mode:", im.mode)      # 例: RGB, RGBA, L
    print("size:", im.size)      # (幅, 高さ)のタプル

    # JPEGとして保存(品質を指定)
    im.save(out_path, quality=90)
    print("saved to:", out_path)
実行結果
format: JPEG
mode: RGB
size: (4032, 3024)
saved to: outputs/basic_copy.jpg

保存時のオプションはフォーマットにより異なるため、JPEGにはquality、PNGにはcompress_levelなどを使い分けます

画像フォーマット(JPEG/PNG)とRGB

よく使うフォーマットと色モード

フォーマットの違いと色モードの理解は加工品質と容量に直結します。

フォーマット透過圧縮方式特徴/用途
JPEGなし非可逆写真向け。容量が小さくなりやすいが細部が劣化する。
PNGあり(RGBA)可逆透過やアイコン、文字のある画像に最適。容量はやや大きい。
フォーマットの違い(簡易)
モード説明
RGBフルカラー(透過なし)
RGBAフルカラー+アルファ(透過あり)
Lグレースケール
主な色モード

JPEGは透過を保持できません

透過を維持したい場合はPNG(RGBA)で保存してください。

基本サンプルコード

よくあるワークフロー(開く→向き補正→幅基準リサイズ→保存)

スマホ写真のEXIF向き問題を先に解決してからリサイズすると、想定外の回転を防げます。

Python
from PIL import Image, ImageOps
from pathlib import Path

src = Path("images/input.jpg")
dst = Path("outputs/resized_800.jpg")
dst.parent.mkdir(parents=True, exist_ok=True)

with Image.open(src) as im:
    # EXIFの自動回転を反映
    im = ImageOps.exif_transpose(im)

    # 横幅を800pxに合わせて等比リサイズ
    base_w = 800
    w, h = im.size
    ratio = base_w / w
    new_size = (base_w, int(h * ratio))

    # 高品質なLANCZOSでリサイズ
    im_resized = im.resize(new_size, resample=Image.Resampling.LANCZOS)

    # JPEGとして最適化して保存
    im_resized = im_resized.convert("RGB")  # JPEGはRGB
    im_resized.save(dst, quality=90, optimize=True, progressive=True)
    print(f"resized: {w}x{h} -> {im_resized.size}, saved to {dst}")
実行結果
resized: 4032x3024 -> (800, 600), saved to outputs/resized_800.jpg

まずはこの例を動かし、動作確認とパス周りの理解を進めると、後の応用がスムーズです。

画像リサイズの基本

リサイズ(resize)の使い方

resizeの基本形

resizeはサイズをピクセル指定で直ちに変更するメソッドです。

アスペクト比は自動保持されないため注意します。

Python
from PIL import Image

with Image.open("images/input.jpg") as im:
    resized = im.resize((1200, 800), resample=Image.Resampling.BICUBIC)  # 幅1200, 高さ800に固定
    resized.save("outputs/resize_1200x800.jpg", quality=90)
    print("new size:", resized.size)
実行結果
new size: (1200, 800)

元の比率と異なるサイズを指定すると縦長や横長に歪みます

等比縮小が基本です。

等比縮小

thumbnailで手軽に等比リサイズ

thumbnailは等比を維持しつつ最大サイズ内に収める便利なメソッドです(破壊的にサイズが変わります)。

Python
from PIL import Image

with Image.open("images/input.jpg") as im:
    im.thumbnail((800, 800), resample=Image.Resampling.LANCZOS)
    im.save("outputs/thumbnail_max800.jpg", quality=90)
    print("thumbnail size:", im.size)
実行結果
thumbnail size: (800, 600)

短辺と長辺のどちらが800になるかは元画像の比率次第です。

高品質リサイズ

リサンプリングフィルターの選択

縮小時はLANCZOS、拡大時はBICUBICが無難です。

NEARESTは高速ですがギザギザになりやすいです。

Python
from PIL import Image

with Image.open("images/input.jpg") as im:
    small_nearest = im.resize((400, 300), Image.Resampling.NEAREST)
    small_bicubic = im.resize((400, 300), Image.Resampling.BICUBIC)
    small_lanczos = im.resize((400, 300), Image.Resampling.LANCZOS)

    small_nearest.save("outputs/small_nearest.jpg", quality=90)
    small_bicubic.save("outputs/small_bicubic.jpg", quality=90)
    small_lanczos.save("outputs/small_lanczos.jpg", quality=90)
    print("saved 3 files with different filters")
実行結果
saved 3 files with different filters

LANCZOSは文字や細線を含む画像でも滑らかになりやすい特徴があります。

横幅基準で等比リサイズ

幅だけ決めて高さは自動計算

Web用では横幅を基準に揃えるケースが多いです。

比率から高さを算出します。

Python
from PIL import Image, ImageOps

def resize_by_width(src, dst, width=800):
    with Image.open(src) as im:
        im = ImageOps.exif_transpose(im)
        w, h = im.size
        if w <= width:
            out = im.copy()
        else:
            ratio = width / w
            out = im.resize((width, int(h * ratio)), Image.Resampling.LANCZOS)
        out.convert("RGB").save(dst, quality=90, optimize=True, progressive=True)
        print(f"{src} -> {dst}, {w}x{h} -> {out.size}")

resize_by_width("images/input.jpg", "outputs/width800.jpg", 800)
実行結果
images/input.jpg -> outputs/width800.jpg, 4032x3024 -> (800, 600)

元画像が既に小さい場合は拡大せずそのまま保存にすると画質の劣化を防げます。

一括リサイズ

フォルダ内の画像をまとめて処理

Pathlibと再帰検索(rglob)を組み合わせると一括処理が簡単です。

Python
# ディレクトリ配下のJPEG/PNGを横幅800pxに等比リサイズして出力
from pathlib import Path
from PIL import Image, ImageOps

input_dir = Path("images")
output_dir = Path("outputs/800w")
target_w = 800

output_dir.mkdir(parents=True, exist_ok=True)
count_ok, count_ng = 0, 0

for p in input_dir.rglob("*"):
    if p.is_file() and p.suffix.lower() in (".jpg", ".jpeg", ".png"):
        try:
            with Image.open(p) as im:
                im = ImageOps.exif_transpose(im)
                w, h = im.size

                if w <= target_w:
                    out = im.copy()
                else:
                    ratio = target_w / w
                    out = im.resize((target_w, int(h * ratio)), Image.Resampling.LANCZOS)

                # JPEGで保存(透過を持つ場合は白で合成)
                out_path = output_dir / f"{p.stem}_800w.jpg"
                if out.mode in ("RGBA", "LA"):
                    bg = Image.new("RGB", out.size, (255, 255, 255))
                    bg.paste(out, mask=out.split()[-1])
                    out = bg
                else:
                    out = out.convert("RGB")

                out.save(out_path, quality=85, optimize=True, progressive=True)
                print(f"Saved: {out_path.name} ({w}x{h} -> {out.size})")
                count_ok += 1
        except Exception as e:
            print(f"Skip {p.name}: {e}")
            count_ng += 1

print(f"Done. ok={count_ok}, error={count_ng}")
実行結果
Saved: sample1_800w.jpg (4032x3024 -> (800, 600))
Saved: logo_800w.jpg (1200x400 -> (800, 267))
Done. ok=2, error=0

PNGの透過を保ちたい場合は出力もPNGにする必要があります

用途に応じて出力拡張子を切り替えましょう

画像編集・加工の基本

トリミング

任意範囲の切り出し(crop)

cropは左上(Left, Top)から右下(Right, Bottom)の矩形座標で切り出します。

Python
from PIL import Image

with Image.open("images/input.jpg") as im:
    w, h = im.size
    # 画像中央から正方形にトリミング
    side = min(w, h)
    left = (w - side) // 2
    top = (h - side) // 2
    box = (left, top, left + side, top + side)
    cropped = im.crop(box)
    cropped.save("outputs/crop_square.jpg", quality=90)
    print("crop box:", box, "->", cropped.size)
実行結果
crop box: (504, 0, 3528, 3024) -> (3024, 3024)

座標はピクセル単位の整数で指定し、範囲外を指定するとエラーやパディングになる点に注意します。

回転・反転

rotateとtranspose

rotateは角度指定、transposeは定型の回転や反転に使います。

Python
from PIL import Image

with Image.open("images/input.jpg") as im:
    # 30度回転(キャンバス拡張、余白は白で塗る)
    rotated = im.rotate(30, expand=True, fillcolor=(255, 255, 255))
    rotated.save("outputs/rotated_30.jpg", quality=90)

    # 左右反転
    flipped = im.transpose(Image.FLIP_LEFT_RIGHT)
    flipped.save("outputs/flip_lr.jpg", quality=90)

    print("rotated:", rotated.size, "flipped:", flipped.size)
実行結果
rotated: (4713, 4713) flipped: (4032, 3024)

スマホ写真の向き問題はEXIFに起因のため、編集前にImageOps.exif_transposeで補正すると安定します。

明るさ/コントラスト

ImageEnhanceで自然に補正

ImageEnhanceは明るさ、コントラスト、彩度、シャープネスを連鎖的に調整できます。

Python
from PIL import Image, ImageEnhance

with Image.open("images/input.jpg") as im:
    # 明るさを20%上げ、コントラストを10%上げる
    brighter = ImageEnhance.Brightness(im).enhance(1.2)
    adjusted = ImageEnhance.Contrast(brighter).enhance(1.1)
    adjusted.save("outputs/bright_contrast.jpg", quality=90)
    print("enhance done")
実行結果
enhance done

係数1.0が元の状態で、0.0に近づくほど弱く、1.0より大きいほど強くなります。

フィルター

代表的なフィルターの適用

ぼかしやシャープ化はImageFilterで簡単に実現できます。

Python
from PIL import Image, ImageFilter

with Image.open("images/input.jpg") as im:
    blur = im.filter(ImageFilter.GaussianBlur(radius=2))
    sharp = im.filter(ImageFilter.UnsharpMask(radius=2, percent=150, threshold=3))
    edge = im.filter(ImageFilter.FIND_EDGES)

    blur.save("outputs/gaussian_blur.jpg", quality=90)
    sharp.save("outputs/unsharp.jpg", quality=90)
    edge.save("outputs/edges.jpg", quality=90)
    print("filters saved")
実行結果
filters saved

GaussianBlurのradiusやUnsharpMaskのpercentは画像解像度に合わせて調整します。

文字入れ

ImageDrawとフォント指定

透過レイヤを重ねてから文字を合成すると、影や枠線で視認性を高めやすくなります。

Python
from PIL import Image, ImageDraw, ImageFont
from pathlib import Path

with Image.open("images/input.jpg").convert("RGBA") as base:
    draw = ImageDraw.Draw(base)

    # フォントの候補をOS毎に探す(見つからなければデフォルト)
    font_candidates = [
        "C:/Windows/Fonts/meiryo.ttc",
        "/System/Library/Fonts/Helvetica.ttc",
        "/usr/share/fonts/truetype/noto/NotoSansCJK-Regular.ttc",
    ]
    font_path = next((p for p in font_candidates if Path(p).exists()), None)
    font = ImageFont.truetype(font_path, 48) if font_path else ImageFont.load_default()

    text = "Sample Watermark"
    margin = 20

    # テキストのサイズを取得
    bbox = draw.textbbox((0, 0), text, font=font, stroke_width=2)
    tw, th = bbox[2] - bbox[0], bbox[3] - bbox[1]
    x = base.width - tw - margin
    y = base.height - th - margin

    # 半透明の背景+縁取り文字を重ねる
    overlay = Image.new("RGBA", base.size, (0, 0, 0, 0))
    o_draw = ImageDraw.Draw(overlay)
    o_draw.rectangle((x - 10, y - 5, x + tw + 10, y + th + 5), fill=(0, 0, 0, 120))
    o_draw.text((x, y), text, font=font, fill=(255, 255, 255, 230),
                stroke_width=2, stroke_fill=(0, 0, 0, 255))

    out = Image.alpha_composite(base, overlay)
    out.convert("RGB").save("outputs/text_watermark.jpg", quality=90)
    print("text placed at:", (x, y), "font:", font_path or "default")
実行結果
text placed at: (2140, 2784) font: C:/Windows/Fonts/meiryo.ttc

日本語表示には日本語対応フォントが必要です。

環境に応じてImageFont.truetypeにフォントファイルを指定してください。

透過PNGと背景合成

ロゴ透過合成の基本

PNGのアルファチャンネルをマスクとしてpastealpha_compositeで合成します。

Python
from PIL import Image

# 背景は写真(JPEG)、ロゴは透過PNGと仮定
with Image.open("images/bg.jpg").convert("RGBA") as bg, \
     Image.open("images/logo.png").convert("RGBA") as fg:

    # 背景幅の20%にロゴを縮小
    scale = (bg.width * 0.2) / fg.width
    logo = fg.resize((int(fg.width * scale), int(fg.height * scale)), Image.Resampling.LANCZOS)

    # 左上に余白を空けて貼り付け(ロゴのアルファをマスクに使用)
    x, y = 20, 20
    bg.paste(logo, (x, y), mask=logo)

    # 最終出力をJPEGにする場合はRGBへ
    bg.convert("RGB").save("outputs/composited.jpg", quality=90)
    print("composited saved")
実行結果
composited saved

同じサイズのRGBA画像同士ならImage.alpha_compositeも便利です。

保存のコツとよくあるエラー対策

JPEGの品質(quality)と圧縮

画質と容量のバランス

写真用途ではquality=85前後が高画質と容量の良いバランスです。

さらにoptimize=Trueでメタデータの最適化、progressive=Trueで段階表示対応にするとWeb向けに有利です。

Python
from PIL import Image

with Image.open("images/input.jpg") as im:
    im = ImageOps.exif_transpose(im).convert("RGB")
    im.save("outputs/q85.jpg", quality=85, optimize=True, progressive=True, subsampling=2)  # 4:2:0
    im.save("outputs/q95_high.jpg", quality=95, optimize=True, progressive=True, subsampling=0)  # 4:4:4
    print("saved q85 and q95")
実行結果
saved q85 and q95

細部重視ならquality=95+subsampling=0容量重視ならquality=80〜85+subsampling=2がおすすめです。

透過をJPEG保存できない時

RGBA→RGBへフラット化

JPEGは透過を持てないためRGBAのまま保存するとエラーになります。

背景色に合成してから保存します。

Python
from PIL import Image

with Image.open("images/icon.png").convert("RGBA") as im:
    bg = Image.new("RGB", im.size, (255, 255, 255))  # 背景は白
    bg.paste(im, mask=im.split()[-1])                # アルファをマスクに合成
    bg.save("outputs/icon_on_white.jpg", quality=90)
    print("flattened and saved to JPEG")
実行結果
flattened and saved to JPEG

透過を保持したいならPNGで保存するのが唯一の選択肢です。

ファイルパスの注意

Windowsのバックスラッシュや日本語パス

パスはPathlibで管理するとOS差異や日本語文字の扱いが安定します。

Windowsの絶対パスは生文字列(r"...")にするのも有効です。

Python
from pathlib import Path
from PIL import Image

src = Path(r"C:\Users\you\Pictures\サンプル.jpg")  # 生文字列で\をエスケープ不要に
dst = Path("outputs/path_test.jpg")
dst.parent.mkdir(parents=True, exist_ok=True)

with Image.open(src) as im:
    im.save(dst, quality=90)
    print("saved to:", dst.resolve())
実行結果
saved to: /absolute/path/to/outputs/path_test.jpg

存在確認(Path.exists())やフォルダ作成(mkdir(exist_ok=True))を都度行うと、実行時エラーを大きく減らせます。

EXIFの自動回転問題への対処

ImageOps.exif_transposeで正しい向きへ

スマホの縦写真はEXIFのOrientationタグに依存しており、見かけと実サイズが一致しないことがあるため、開いた直後に補正するのが安全です。

Python
from PIL import Image, ImageOps

with Image.open("images/phone.jpg") as im:
    print("before:", im.size, "orientation:", im.getexif().get(274))  # 274はOrientation
    fixed = ImageOps.exif_transpose(im)
    print("after :", fixed.size)

    # 可能ならEXIFを保ったまま保存
    exif_bytes = im.getexif().tobytes()
    fixed.convert("RGB").save("outputs/phone_fixed.jpg", exif=exif_bytes, quality=90)
    print("saved fixed image with EXIF")
実行結果
before: (3024, 4032) orientation: 6
after : (4032, 3024)
saved fixed image with EXIF

向きを補正しないままトリミングや文字入れを行うと、完成画像が意図せず横倒しになることがあります。

最初にexif_transposeを適用しましょう。

まとめ

Pillowは「開く→向き補正→等比リサイズ→編集→最適化保存」までを少ない記述で完結できる強力なライブラリです。

初心者の方は、まずImage.openImageOps.exif_transposeresizethumbnailの違い、保存時のqualityや透過の扱いを押さえると、Web用画像の最適化が素早く実現できます。

最後に、パスはPathlibで安全に扱い、JPEGとPNGの特性を理解して使い分けることで、画質と容量のベストバランスを得やすくなります。

この記事を書いた人
エーテリア編集部
エーテリア編集部

人気のPythonを初めて学ぶ方向けに、文法の基本から小さな自動化まで、実際に手を動かして理解できる記事を書いています。

クラウドSSLサイトシールは安心の証です。

URLをコピーしました!