2026-06-27 · 2026-07-26 更新

Pillow、WebP、mozjpegを使ったPythonでの画像圧縮方法

Pillow、cwebp、mozjpegを使用してPythonでJPEG、PNG、WebP画像を効率的に圧縮します。画質設定やターゲットサイズループに対応した高速なバッチスクリプトを提供します。

Pillow、WebP、mozjpegを使ったPythonでの画像圧縮方法

最終更新日: July 26, 2026

カメラサイズのJPEGをフォルダに大量に持ち、それをユーザーに配信するビルドステップがあり、その結果ページサイズがどんどん膨れ上がってしまう。画像エディタで手動でこれらのファイルを圧縮しても、スケールしない。本ガイドでは、CIジョブ内、アップロードハンドラ内で、またはスケジュールに基づいて、JPEG、PNG、WebPファイルを縮小させるために必要なPythonの具体的な方法を紹介します。

クイックアンサー:Pythonで画像を圧縮するには?

まずPillowで画像を開き、適切なフォーマットフラグを指定して保存します。写真の場合は、quality=80optimize=Trueprogressive=Trueを使用してJPEGとして保存します。最も小型のモダンなファイルには、quality=80, method=6を使用してWebPとして保存します。フラットグラフィックやスクリーンショットの場合、PNGを維持しつつoptimize=Trueを渡します。

from PIL import Image

def compress_jpeg(src, dst, quality=80):
    img = Image.open(src)
    if img.mode in ("RGBA", "P"):
        img = img.convert("RGB")
    img.save(dst, "JPEG", quality=quality, optimize=True, progressive=True)

この4行の関数だけで、ほとんどのスマートフォンやDSLRのJPEGを40%から70%削減できます。本ガイドの残りの部分は、サイズ目標値、WebP、mozjpeg、およびバッチランナーを追加することで、ディレクトリを指定して放置できる仕組みを提供します。

始める前に何が必要ですか?

Python 3.9以降と、PILのメンテナンスされたフォークであるPillowが必要です。Pillowは、一般的なすべてのプラットフォームでJPEG、PNG、WebPを標準機能として処理できます。

pip install Pillow

最も小さい出力を得るためには、Pythonから呼び出すことができる2つのコマンドラインエンコーダも必要になります:libwebpからのcwebpとmozjpegからのcjpegです。

brew install webp mozjpeg   # macOS; Linux: apt install webp, build mozjpeg

コードを書く前に、それぞれのピースが何のためにあるかを確認してください。

  • Pillow はすべてのフォーマットの読み書きを行い、リサイズも担当します。
  • cwebp は最もコンパクトなWebPファイルを作成し、Pillowよりも多くのチューニングオプションを公開しています。
  • mozjpeg は、Pillowが使用する標準のlibjpegよりも小さなJPEGを再エンコードします。
  • レポートヘルパー は、実行ごとにどれだけバイトを節約したかを出力します。

本ガイド全体で使用されるヘルパーは以下の通りです。

import os

def report(src, dst):
    before, after = os.path.getsize(src), os.path.getsize(dst)
    saved = (1 - after / before) * 100
    print(f"{before/1024:.0f} KB -> {after/1024:.0f} KB ({saved:.0f}% smaller)")

一度限りの圧縮だけで、何もインストールしたくない場合は、ブラウザベースの画像圧縮ツールが同様の処理を数ファイルに対して実行できます。

PillowでJPEGを圧縮する方法は?

JPEGにとって重要な3つのフラグは、qualityoptimize、およびprogressiveです。qualityはエンコーダがどれだけ詳細を破棄するかを制御します。optimize=Trueは、より小さなHuffmanテーブルを構築するために2回目のパスを実行します。progressive=Trueはファイルを再配置し、低解像度から上から下へ描画されるようにするため、接続が遅い場合に速く感じられ、通常はファイルサイズをもう少し縮小させます。

Wooden desk on a laptop showing source code open in an editor

画像がどこで使用されるかに基づいて品質値を選択し、単一のグローバルな数値に頼らないでください:

品質値 典型的な用途 期待される結果
90-95 ヒーロー画像、印刷プルーフ オリジナルに近い、目立って大きいファイル
75-85 ほとんどのWeb写真 安全なデフォルト、大きな削減
60-70 サムネイルとプレビュー 微細なディテールの目立つ軟化
50未満 写真には避ける 輪郭周辺のブロックアーティファクト

ほとんどの人が引っかかる落とし穴があります。JPEGにはアルファチャンネルがないということです。透明性のあるPNGスクリーンショットを開いて、それをそのままJPEGに保存すると、Pillowはcannot write mode RGBA as JPEGというエラーを発生させます。前述の関数内のconvert("RGB")呼び出しは、まず透明性を単色背景上に平坦化するため、すべてのJPEG保存の前に実行されます。

PNGを圧縮しWebPに変換する方法

PNGはロスレスであるため、JPEGで行うような品質とサイズのトレードオフはできません。できることは、Pillowのオプティマイザを実行するか、写真であるものについてはフォーマット自体を完全に切り替えることです。

def compress_png(src, dst):
    Image.open(src).save(dst, "PNG", optimize=True, compress_level=9)

def to_webp(src, dst, quality=80):
    img = Image.open(src)
    img.save(dst, "WEBP", quality=quality, method=6)

method=6は、WebPエンコーダにより小さなファイルを探すために時間をかけるように指示します。画像ごとに処理が遅くなりますが、ビルドステップではそれだけの価値があります。2 MBのPNGから始まった写真の場合、品質80のWebPは、通常の閲覧サイズで見ても同一に見えながら、ルーティンに200 KB以下になります。Google自身の測定によると、WebP documentationによれば、WebPは比較可能なJPEGよりも約25%から35%小さいという結果が出ています。

推測するのではなく、この表を使ってフォーマットを選択してください:

ソースコンテンツ 最適フォーマット 理由
写真 WebPまたはJPEG 滑らかなグラデーションは非可逆で良く圧縮
ロゴ、アイコン、フラットUI PNGまたはロスレスWebP シャープな輪郭が鮮明に保たれる
文字付きスクリーンショット PNGまたはロスレスWebP 非可逆パスによる文字のぼやけを回避
透明が必要な画像 WebPまたはPNG JPEGはアルファチャンネルを保存できない

サイト全体でまだフォーマットを決定している場合は、WebP vs JPEG vs PNGの比較記事がブラウザサポートとトレードオフについてより深く解説しています。

ループ内でターゲットファイルサイズを達成するには?

「200 KB以下にする」という要件は、メールツール、マーケットプレイス、CMSのアップロード制限からよく聞かれます。最初に試行してバイト目標値を達成できる品質値を選ぶことはできません。なぜなら、同じ品質でも、複雑な写真とフラットな画像ではサイズが大きく異なるからです。高品質からループダウンし、キャップ以下に到達するまで調整します:

def compress_to_target(src, dst, target_kb=200, q=90, floor=35):
    img = Image.open(src).convert("RGB")
    while q >= floor:
        img.save(dst, "JPEG", quality=q, optimize=True, progressive=True)
        if os.path.getsize(dst) <= target_kb * 1024:
            return q
        q -= 5
    return q

floor引数は、品質がぼやけた領域に入りすぎる前にループを停止させます。画像がフロア以上で目標に到達できない場合でも、関数は値を返しますので、圧縮し続ける代わりにログに記録して画像をリサイズすることができます。100 KBや50 KBのような固定バイト予算の場合も同じループが機能します。compress images without losing qualityのウォークスルーでは、品質を下げるよりもリサイズが優れている場合を説明しています。

mozjpegとcwebpでJPEGをより小さくする方法は?

Pillowは標準のlibjpegエンコーダを使用してJPEGを保存します。mozjpegはMozillaからのドロップインエンコーダであり、同じ視覚的品質でより小さなファイルを作成し、通常さらに5%から15%削減できます。Pillowがこれをバンドルしていないため、subprocessを通じてcjpegバイナリを呼び出す必要があります。mozjpegのcjpegはPPMを確実に読み込むため、先にPillowで画像を平坦化してください:

import subprocess

def mozjpeg(src, dst, quality=80):
    Image.open(src).convert("RGB").save("_tmp.ppm")
    subprocess.run(["cjpeg", "-quality", str(quality), "-progressive",
                    "-optimize", "-outfile", dst, "_tmp.ppm"], check=True)
    os.remove("_tmp.ppm")

WebPの場合、cwebpを直接呼び出すことで、最大努力のための-m 6やよりクリーンな色エッジのための-sharp_yuvなど、Pillowが公開していないノブ(調整可能な設定)を得ることができます:

def cwebp(src, dst, quality=82):
    subprocess.run(["cwebp", "-q", str(quality), "-m", "6",
                    "-sharp_yuv", src, "-o", dst], check=True)

どのオプションがどのように機能するかをまとめた表を以下に示しますので、何をインストールするか決定できます:

Tool Best for Trade-off
Pillow 一般的なスクリプト、全フォーマット 最小のJPEGではない
mozjpeg 最小のプログレッシブJPEG 追加バイナリ、PPMラウンドトリップ
cwebp(libwebp) 最も厳密なWebP制御 別バイナリのインストールが必要
pillow-simd x86サーバーでより高速なリサイズ ビルドと固定がより困難

mozjpegプロジェクトは、そのエンコーダとベンチマークをmozjpeg repositoryで文書化しており、Pillowの保存オプションの全リストはPillow image file formats referenceにあります。

フォルダ全体を高速バッチ圧縮する方法は?

単一の画像は簡単です。真の課題は数百枚からなるディレクトリです。レイアウトに必要な幅より広いものはすべてリサイズしてから、圧縮し、スレッドを介して処理を実行することで、500枚の画像フォルダが分単位ではなく数秒で完了します。

Developer at a multi-monitor workstation running code while wearing headphones

from pathlib import Path
from concurrent.futures import ThreadPoolExecutor

def batch(in_dir, out_dir, quality=80, max_width=1920):
    out = Path(out_dir); out.mkdir(parents=True, exist_ok=True)
    files = [p for p in Path(in_dir).iterdir()
             if p.suffix.lower() in {".jpg", ".jpeg", ".png"}]
    def work(p):
        img = Image.open(p)
        if img.width > max_width:
            h = round(img.height * max_width / img.width)
            img = img.resize((max_width, h), Image.LANCZOS)
        img.convert("RGB").save(out / f"{p.stem}.jpg", "JPEG",
                                quality=quality, optimize=True, progressive=True)
    with ThreadPoolExecutor() as pool:
        list(pool.map(work, files))
    return len(files)

本番環境でこれを実行した際のいくつかの実用的な注意点:

  1. 圧縮する前にリサイズをしてください。6000 pxの写真を1200 pxの列に配信することは、どの品質設定でも回復できないバイトの浪費です。
  2. Pillowはエンコード中にGILを解放するため、スレッドはこの点で実際の速度向上をもたらします。ほとんどのフォルダではマルチプロセスは必要ありません。
  3. 悪い実行がオリジナルを上書きしないように、別の出力ディレクトリに書き込んでください。
  4. ダウンケーリングにはImage.LANCZOSを使用してください。これはデフォルトフィルターよりもエッジをよりシャープに保ちます。
  5. すべてのファイルをreportヘルパーでログ記録し、サンプルを目視確認するまでオリジナルは保持してください。

もしジョブが一度きりではなく定期的なものである場合は、batch image processingのパターンがキュー、リトライ、命名規則をカバーしています。ファイルZIP全体に対するノーコード実行には、バッチプロセッサーフォーマット変換ツールがスクリプトなしでフォーマット変更を処理します。

どの方法を選ぶべきですか?

常に最も重いツールに頼るのではなく、ジョブに合わせてメソッドを合わせましょう:

  • ビルドステップでのWeb写真: Pillow WebP (quality 80)、または最後の数パーセントが必要な場合はcwebpと-m 6を使用します。
  • 厳格なバイト予算: compress_to_targetループを使用し、フロアに達した場合はリサイズにフォールバックします。
  • 変更できないレガシーJPEGパイプライン: mozjpegで出力を再エンコードすることで無料で節約できます。
  • スクリーンショットと図表: PNGをoptimize=Trueのまま維持するか、ブラウザがサポートしている場合はロスレスWebPを使用します。

これらの設定が機能する理論的背景については、image compression guideで、ロッシー対ロスレス、および量子化テーブルが結果にどのように影響するかを説明しています。

一般的なエラーと修正方法

ほとんどの失敗した実行は、いくつかの例外によるものです:

  • cannot write mode RGBA as JPEG — JPEGに保存する前にimg.convert("RGB")を呼び出してください。
  • Image.DecompressionBombError — Pillowは巨大な画像から保護します。信頼できるファイルでのみ制限を引き上げるには、Image.MAX_IMAGE_PIXELS = 200_000_000を使用してください。
  • OSError: broken data stream — ソースファイルが切り詰められています。これをサルベージするためにImageFile.LOAD_TRUNCATED_IMAGES = Trueを設定し、可能であれば再ダウンロードしてください。
  • 出力が入力より大きい — すでに圧縮されたJPEGを再保存したためです。前のエクスポートではなく、オリジナルから圧縮してください。
  • 色が薄く見える — CMYKのソースを変換せずに保存しました。最初にconvert("RGB")を呼び出してください。
from PIL import Image, ImageFile
Image.MAX_IMAGE_PIXELS = 200_000_000
ImageFile.LOAD_TRUNCATED_IMAGES = True

要点まとめ

Pillowは、3つのJPEGフラグとWebPの保存機能を使ってPythonでの画像圧縮の90%を処理します。可能な限り小さなファイルが必要な場合はmozjpegとcwebpに頼り、バイト予算が固定されている場合はすべてターゲットサイズループでラップし、ボリュームが増加した場合はスレッド化されたバッチ関数を実行してください。オリジナルは保持し、圧縮する前にリサイズを行い、すべての実行で節約量を印刷することで、パイプラインが実際に何をしているのかを常に把握しておきましょう。

ガイドを読みながら無料ツールをお使いください。