【Python】iPhoneのHEIC画像をJPGに一括変換するスクリプト開発記 (Pillow / pillow-heif)

Python

こんにちは、Tech Samuraiです!
最近、YOLOのカスタム学習のためにiPhoneで撮影した大量の写真をPCに取り込んだのですが、ある問題に直面しました。画像の拡張子が、すべて「.heic」なのです。

HEIC (High Efficiency Image Container) は、高画質のままファイルサイズを小さくできる優れた形式ですが、Windowsや一部のWebサービス、古いソフトウェアなどではまだ標準サポートされておらず、表示すらできないことがあります。YOLOの学習データ()として使うにも、汎用的なJPG形式の方がはるかに使い勝手が良いです。

そこで今回は、**「指定したフォルダ内のHEIC画像を、サブフォルダの構造を保ったまま、すべてJPGに一括変換する」**という、実用的なPythonスクリプトを開発しました。このツールの開発プロセスとコードの全貌を共有します!


ステップ1:プロジェクトの設計とライブラリ選定

今回のツールの主な要件は以下の通りです。

  • 指定した入力フォルダ(サブフォルダを含む)を**再帰的に検索**したい。
  • 見つけた.heic / .HEICファイルをJPGに変換したい。
  • 変換後のJPGは、元のフォルダ構造を維持したまま、別の出力フォルダに保存したい。
  • JPGの品質を指定できるようにしたい。

この要件を実現するため、以下のライブラリを選定しました。

  • Pillow: Pythonの画像処理ライブラリの王様です。
  • pillow-heif: Pillow本体だけではHEICを読めないため、PillowにHEICの読み書き能力を追加するための必須プラグインです。
  • pathlib: モダンなPythonでファイルパスを扱うための標準ライブラリ。フォルダの再帰的検索(rglob)や拡張子の操作(with_suffix)が非常に直感的です。

ステップ2:HEICからJPGへ、変換のコアロジック

このスクリプトの心臓部は、pillow-heifを使ってHEIC画像を開き、Pillowの機能でJPGとして保存する部分です。

# スクリプトの冒頭で、pillow-heifをPillowに登録
try:
    from pillow_heif import register_heif_opener
    register_heif_opener()
except ImportError:
    print("エラー: 'pillow_heif' がインストールされていません。")
    sys.exit(1)

# ...

def convert_heic_to_jpg(input_dir: Path, output_dir: Path, quality: int):
    # ...
    for i, heic_path in enumerate(heic_files):
        try:
            # ... (出力パスの決定ロジック) ...

            # 3. 画像を開いて変換・保存する
            with Image.open(heic_path) as image:

                # 【重要】HEICは透明情報(RGBA)を持つことがあるが、JPGは持たない(RGB)。
                # .convert('RGB') を呼び出し、JPGで保存できる形式に変換する。
                image.convert('RGB').save(jpg_path, format="JPEG", quality=quality)

            success_count += 1
        except Exception as e:
            fail_count += 1
    # ...

pillow_heif.register_heif_opener()`を実行するだけで、あとはImage.open(heic_path)`と書けば、PillowがHEICファイルを魔法のように開けるようになります。また、image.convert('RGB')`で透明情報を除去し、JPG形式で安全に保存できるようにするのも重要なポイントです。


ステップ3:`pathlib`によるフォルダの再帰的処理

このツールを実用的にするため、pathlib`rglob`メソッドを使って、サブフォルダ内のファイルもすべて見つけ出します。さらに、relative_towith_suffixを使って、元のフォルダ構造を維持したまま出力パスを組み立てます。

# .heic と .HEIC の両方を "再帰的" に検索
heic_files = list(input_dir.rglob('*.heic')) + list(input_dir.rglob('*.HEIC'))

for heic_path in heic_files:
    # 入力ディレクトリからの相対パスを取得
    relative_path = heic_path.relative_to(input_dir)

    # 出力ディレクトリに相対パスを組み合わせ、拡張子を .jpg に変更
    jpg_path = output_dir / relative_path.with_suffix('.jpg')

    # 出力先のサブディレクトリが存在しない場合は作成
    jpg_path.parent.mkdir(parents=True, exist_ok=True)

    # ... (変換処理) ...

このロジックにより、例えばINPUT_DIR/旅行/沖縄/IMG_001.HEIC`というファイルは、OUTPUT_DIR/旅行/沖縄/IMG_001.jpg`として完璧に保存されます。


完成したツールとコード

Gallery

これらのロジックを組み合わせて完成した、HEIC一括変換スクリプトの全コードは、以下のGitHubリポジトリで公開しています。使い方はREADME.md`に詳しく記載しています。

# 最終的なスクリプト (src/heic_2_jpg/convert_heic.py)
import sys
from pathlib import Path
from PIL import Image

# pillow-heif プラグインをPillowに登録します
try:
    from pillow_heif import register_heif_opener
    register_heif_opener()
except ImportError:
    print("エラー: 'pillow_heif' がインストールされていません。")
    print("Rye環境で 'rye add pillow-heif' を実行してください。")
    sys.exit(1)


# --- 設定ここから ---
# 1. HEICファイルが保存されている親ディレクトリのパス
INPUT_DIR = Path("./src/heic_2_jpg/picture")
# 2. 変換後のJPGファイルを保存するディレクトリのパス
OUTPUT_DIR = Path("./src/heic_2_jpg/output_jpg_files")
# 3. 保存するJPGの品質 (1-100, デフォルトは95)
JPG_QUALITY = 95
# --- 設定ここまで ---


def convert_heic_to_jpg(input_dir: Path, output_dir: Path, quality: int):
    # (関数の中身はステップ2, 3で解説した通り)
    print(f"スキャンを開始します: {input_dir}")
    print(f"保存先: {output_dir}")
    print("-" * 30)

    output_dir.mkdir(parents=True, exist_ok=True)
    heic_files = list(input_dir.rglob('*.heic')) + list(input_dir.rglob('*.HEIC'))

    if not heic_files:
        print("変換対象のHEICファイルが見つかりませんでした。")
        print(f"検索ディレクトリ: {input_dir.resolve()}")
        return

    print(f"{len(heic_files)} 件のHEICファイルが見つかりました。変換を開始します...")

    success_count = 0
    fail_count = 0

    for i, heic_path in enumerate(heic_files):
        try:
            relative_path = heic_path.relative_to(input_dir)
            jpg_path = output_dir / relative_path.with_suffix('.jpg')
            jpg_path.parent.mkdir(parents=True, exist_ok=True)

            with Image.open(heic_path) as image:
                image.convert('RGB').save(jpg_path, format="JPEG", quality=quality)

            print(f"[{i+1}/{len(heic_files)}] 成功: {heic_path.name} -> {jpg_path.name}")
            success_count += 1

        except Exception as e:
            print(f"[{i+1}/{len(heic_files)}] ★失敗★: {heic_path.name} (エラー: {e})")
            fail_count += 1

    print("-" * 30)
    print("変換処理が完了しました。")
    print(f"成功: {success_count} 件")
    print(f"失敗: {fail_count} 件")


if __name__ == "__main__":
    if not INPUT_DIR.exists():
        print(f"エラー: 入力ディレクトリが見つかりません: {INPUT_DIR.resolve()}")
        print("スクリプト内の INPUT_DIR を正しいパスに変更してください。")
        sys.exit(1)

    convert_heic_to_jpg(INPUT_DIR, OUTPUT_DIR, JPG_QUALITY)


まとめ

iPhoneとWindows/Linux環境の橋渡しをする、実用的な画像変換ツールを開発しました。YOLOのデータセット準備()だけでなく、ブログに画像をアップロードする際など、様々な場面でこのツールは役立つはずです。ぜひ、あなたのPCにも導入してみてください!

【PythonでYOLO自動化】面倒なデータセット準備を秒殺!3つの便利スクリプトを公開

コメント

タイトルとURLをコピーしました