こしあん
2021-01-17

Pillow(Python)でRGB→CMYKのプロファイル変換


6.8k{icon} {views}


RGBからCMYKへの変換は印刷をする際に必要になりますが、通常はPhotoshopなどの有料の画像編集ソフトを使います。実はこれはPythonで無料でできます。Pillowを使ったやり方を紹介します。バッチ処理にも便利です。

プロファイル変換によるCMYK変換

前回の投稿では、「CMYK画像をPillowでどう扱うか」について簡単な解説をしました。今回は任意のRGB画像をCMYKにいい感じに変換するための処理をPythonで見ていきます。

CMYK画像は印刷所への入稿をする際に必要になりますが、RGB→CMYKは表現できる色が異なるのでくすみが発生します。同人誌の印刷では結構これが問題になります。これを抑える方法はあって、例えばPhotoshopでは「プロファイル変換」をするとその影響を抑えて変換することが可能です。

Photoshop CC2020の場合、次の方法でプロファイル変換ができます。「編集→プロファイル変換」をクリックし、

ソースカラースペース、変換後のカラースペースともに変換前・後の色空間を表します。このケースではRGB→CMYKですね。CMYKでもプロファイルがいろいろありますが、「CMYK – Japan Color 2001 Coated」をCMYKのプロファイルとして使うことがほとんどです。これはコート紙への印刷を目的とした日本における規格です。

マッチング方法は4種類ありますが、「相対的な色域を維持」を選ぶことが多いです。ほかも選べますがこの方法が出力結果が安定しています。

ICCプロファイルのダウンロード

本題はこれをPythonでどのように再現するかということです。Pillowを使うとこれができます。ただしICCプロファイルのダウンロードが必要です。

Adobe ICC Profiles download for Windows
https://www.adobe.com/support/downloads/iccprofiles/iccprofiles_win.html

「for Windows」と書いてありますがLinux環境のPythonでも動きました。これから「ICC profile download for End Users」をダウンロードします。規約を読んでAcceptをクリック、そこからいくつか画面を飛ぶと、「AdobeICCProfilesCS4Win_end-user.zip」というファイルがダウンロードされます。

このZipにはいろいろなICCプロファイルがあるのですが、「AdobeICCProfilesCS4Win_end-user/Adobe ICC Profiles (end-user)/CMYK/JapanColor2001Coated.icc」というファイルを使います。これをカレントディレクトリにコピーします。

ここでは2001Coatedを使いましたが、印刷する用紙がコート紙以外なら、Uncoatedを使ってもいいと思います。いろいろ試してみると楽しいでしょう。

Pillowでのプロファイル変換

Pillowというライブラリが必要です。Python3環境です。インストールが面倒だったらGoogle Colaboratoryを使いましょう。

pip install --upgrade Pillow

サンプル画像は、自分が書いた本の表紙をベースとします。これを「rgb.jpg」とします。

これをCMYKに変換するコードは次のとおりです。

from PIL import ImageCms, Image

def convert_to_cmyk():
    with Image.open("rgb.jpg") as img:
        # sRGBのプロファイル取得
        srgb = ImageCms.createProfile("sRGB")
        # sRGB -> Japan Color 2001 Coated(CMYK)
        img = ImageCms.profileToProfile(
            img, srgb, "JapanColor2001Coated.icc",
            renderingIntent=ImageCms.INTENT_RELATIVE_COLORIMETRIC,
            outputMode="CMYK")
        # ICCプロファイルを埋め込んで保存
        with open("JapanColor2001Coated.icc", "rb") as fp:
            profile = fp.read()
        img.save("cmyk.jpg", icc_profile=profile)

if __name__ == "__main__":
    convert_to_cmyk()

結果は次のようになります。

右上の「くるまなしで」の部分が若干くすんでいますが、これはCMYK変換へのくすみなので仕方ないです。同じ変換をPhotoshopで行った結果が次のとおりです。

PythonとPhotoshopが同じ結果になりました。右上の文字はPhotoshopでもくすむので、局所的にトーンカーブいじって対応でしょうね。

renderingIntentについて

プロファイル変換のに部分に「renderingIntent」というオプションがありますが、これはPhotoshopの「マッチング方法」と対応するようです。次の4種類があります。

  • ImageCms.INTENT_PERCEPTUAL = 0 (DEFAULT)
  • ImageCms.INTENT_RELATIVE_COLORIMETRIC = 1
  • ImageCms.INTENT_SATURATION = 2
  • ImageCms.INTENT_ABSOLUTE_COLORIMETRIC = 3

Photoshopの表記では上から順に次のように対応します。英語と訳語が綺麗に対応しますね。

  1. 知覚的
  2. 相対的な色域を維持
  3. 彩度
  4. 絶対的な色域を維持

プロファイルを埋め込んで保存について

これは変換後のファイルが「どのICCプロファイルか」を示すために明示的に埋め込んでおいたほうが良さそうです。Photoshopでの保存画面でも、

このように「ICCプロファイルを埋め込むかどうか」のオプションがあるので、印刷のように色管理が重要になると埋め込んでおいたほうが無難と言えるでしょう。

sRGBのICCプロファイルについて

先程「JapanColor2001Coated.icc」というファイルを用意しましたが、例えばCMYK→RGBに変換するときに「sRGBのICCプロファイルはいるのか」という疑問は湧きます。結論からいうといりません。なぜなら、Pillowの中にsRGBのICCは組み込まれており、

srgb = ImageCms.createProfile("sRGB")

このコードで取得できるからです。

renderingIntentを変えてプロット

「ImageCms.INTENT_RELATIVE_COLORIMETRICでいいよ」と言いましたが、これを変えてプロットしてみます。

from PIL import ImageCms, Image, ImageDraw
import numpy as np

def compare_rendering_intent():
    icc_names = ["PERCEPTUAL", "RELATIVE_COLORIMETRIC", "SATURATION", "ABSOLUTE_COLORIMETRIC"]
    results = []
    with Image.open("rgb.jpg") as img:
        srgb = ImageCms.createProfile("sRGB")
        # 半分にリサイズ
        img = img.resize((img.width//2, img.height//2), Image.LANCZOS)
        # sRGB -> Japan Color 2001 Coated(CMYK)
        for i in range(4):
            out = ImageCms.profileToProfile(
                img, srgb, "JapanColor2001Coated.icc",
                renderingIntent=i,
                outputMode="CMYK")
            # ICC名を表示
            draw = ImageDraw.Draw(out)
            draw.text((5, 5), icc_names[i], fill=(0, 0, 0, 255)) # 色はCMYK
            results.append(np.array(out))
        # タイル
        results = np.stack(results, axis=0)
        results = results.reshape(2, 2, *results.shape[1:])
        results = results.transpose([0, 2, 1, 3, 4])
        results = results.reshape(img.height * 2, img.width * 2, 4)
        with Image.fromarray(results, "CMYK") as result:
            with open("JapanColor2001Coated.icc", "rb") as fp:
                profile = fp.read()
            result.save("cmyk_tile.jpg", icc_profile=profile)

if __name__ == "__main__":
    compare_rendering_intent()

「絶対的な色域を維持」は白飛びしているようで、「知覚的」は若干色が抜けたような感じがあります。「相対的な色域を維持」と「彩度」は悪くないように思えます。ただし元の画像次第では「彩度」も白飛びしているように見えることがあるので、いろいろ試してみるのをおすすめします。

事前に彩度補正を入れて、「相対的な色域を維持」でプロファイル変換するのがベターかなと今の所思っています。

まとめ

Photoshopでのプロファイル変換を用いたCMYK変換は、実はPillow(Python)でもできる。ただしICCプロファイルのダウンロードや埋め込みを忘れずに。ということでした。

この記事は、2020年12月の新刊・インフィニティNumPyの第4章のQ88-89で紹介した内容を掘り下げたものです。



Shikoan's ML Blogの中の人が運営しているサークル「じゅ~しぃ~すくりぷと」の本のご案内

技術書コーナー

北海道の駅巡りコーナー


Add a Comment

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