PillowでCMYK画像を扱う方法
印刷などでRGBではなくCMYK画像を扱いたいことがあります。PythonのPillowライブラリでは、CMYK画像も扱えます。普通CMYK画像は有料の画像編集ソフトを使うことが多いですが、この方法だと無料でできます。それを見ていきましょう。
目次
RGBとCMYK
コンピューターで色を表現するとき、赤(R)、緑(G)、青(B)の光の三原色で表すことがほとんどです。R,G,Bをカラーチャンネルといいますが、各カラーチャンネルの値を0~255の256段階(8ビット)で表すことが多いです。これにより、256^3=1677万色を表現できます。24ビットカラーや、昔はTrue Colorなんても言いましたね。
RGBでの色表現でだいたい事足りるのですが、印刷するときだけはこれがまずいのです。なぜかというと印刷は「インクの混ぜ合わせであって、光の混ぜ合わせではない」からです。例えば、RGBで全ての色を255にすると白になりますが、インクは全部混ぜると黒になります(絵の具の混ぜ合わせを思い出してください)。RGBのような足すと白になる色の表現を加法混色、印刷物のように足すと黒になる色の表現を減法混色といいます。
印刷のような減法混色での三原色は、シアン(C)、マゼンタ(M)、イエロー(Y)になります。印刷ではこの他にキープレート(K:99.99%の例で黒)をあわせて4色を使います。インクジェットプリンターのインクがまさにこれですよね。最近の高級機種だともっと色を使うのがあります。CMY、「CMYK」で色を表現しなければいけないときがあります。
RGBやCMYKのような色の表現方法を専門的には色空間といいます。ここではRGBとCMYKという2つの色空間があるよということを知っておいてくださいね。
RGB色空間の三原色で円を描いたのが左図、CMYKのCMYで円を描いたのが右図です。RGBのほうは3色混ぜると白、CMYKのほうは3色混ぜると黒になっているのがわかります。何も混ぜない(円の外側)は、RGBでは黒、CMYKのほうは白になります。まさに加法混色と減法混色の違いを表しています。
PillowでもCMYK画像を作成することもできるがmatplotlibのバージョンに要注意
前置きは長くなってしまいましたが、CMYK画像をPythonで扱うにはどうするのかというのがこの投稿の目的です。PillowライブラリでもCMYK画像は扱えます。
PillowのImage.newの第一引数で色空間が指定できます。ここを”RGB”とするとRGB色空間に、”CMYK”とするとCMYK画像になります。これは普通のRGB画像です。よくある例ですね。背景色の初期値は0なので(0, 0, 0):黒で塗られます。
from PIL import Image
import matplotlib.pyplot as plt
def main():
with Image.new("RGB", (256, 256)) as img:
plt.imshow(img)
plt.show()
if __name__ == "__main__":
main()
ではこれをCMYKにするとどうでしょうか。背景色は(0, 0, 0, 0)ですが、CMYKなのでこれは白になります。
def main():
with Image.new("CMYK", (256, 256)) as img:
plt.imshow(img)
plt.show()
if __name__ == "__main__":
main()
ここまで見ると、「Imege.newで初期化時の塗りつぶし色で、CMYKの色を渡せばそのとおりに塗られるのかな?」と思います。これは”color”というキーワード引数で指定できます。
from PIL import Image
import matplotlib.pyplot as plt
def main():
# シアンで塗る
with Image.new("CMYK", (256,256), color=(255,0,0,0)) as img:
plt.imshow(img)
plt.show()
if __name__ == "__main__":
main()
ただし、matplotlibのバージョンに注意してください。古いバージョンだとバグがあり、CMYKの画像が表示されません。Pillowのバージョン8.1.0、matplotlibのバージョンが3.0.3では、指定した色がRGB(RGBA)として解釈されました。これはcolorの指定を(255, 0, 0, 255)とした例ですが、CMYKなのに指定色がRGBAとして解釈されています。
matplotlibのバージョンを最新の3.3.3にアップデートしたら正常に表示できました。
CMYK色空間で自在に描く
RGB画像と同様にCMYK画像もPillowから自在に描けます。三原色の円を作ってみましょう。
from PIL import Image, ImageDraw
import matplotlib.pyplot as plt
def main():
# シアンで塗る
with Image.new("CMYK", (700, 600)) as img:
draw = ImageDraw.Draw(img)
draw.ellipse((0, 0, 400, 400), fill=(255, 0, 0, 0))
draw.ellipse((300, 0, 700, 400), fill=(0, 255, 0, 0))
draw.ellipse((150, 200, 550, 600), fill=(0, 0, 255, 0))
plt.imshow(img)
plt.show()
if __name__ == "__main__":
main()
fillの指定がCMYKの画素値に変わっただけです。ただし、これはベタ塗りなので、ピクセル間の画像の加算が取れません。
CMYKの三原色の円を作る
Pillowでも画素単位の加算は取れますが、正直面倒くさいんでNumPy配列を経由させます。
from PIL import Image, ImageDraw
import matplotlib.pyplot as plt
import numpy as np
def main():
x = np.zeros((600, 700, 4), dtype=np.uint8) # 縦、横、ch
data = [
[(0, 0, 400, 400), (255, 0, 0, 0)],
[(300, 0, 700, 400), (0, 255, 0, 0)],
[(150, 200, 550, 600), (0, 0, 255, 0)]
]
for d in data:
with Image.new("CMYK", (x.shape[1], x.shape[0])) as img:
draw = ImageDraw.Draw(img)
draw.ellipse(d[0], fill=d[1])
x += np.array(img)
with Image.fromarray(x, "CMYK") as img:
plt.imshow(img)
plt.show()
if __name__ == "__main__":
main()
想定しているものはこちらだと思います。これは画素値の加算をしているので減法混色の過程を見られます。
ポイントはいくつかありますが、NumPy配列のshapeについて。「縦、横、カラーチャンネル」の順です。横と縦を逆に覚えている方がちらほらいるので注意してください。Pillowの引数では「横, 縦」の順に指定するのでごちゃごちゃしやすいところではあります。NumPy配列の型はuint8です。これ以外だと最後にPillowに読み込ませたときにおかしくなります。
やっていることは各円を描いて元の画像にどんどん足していっているだけなので、そこまで難しくはないでしょう。
最後のImage.fromarrayのところは”CMYK”の指定を入れてください。これがないとRGBやRGBAとして解釈されます。NumPy配列はあくまで配列なので、色空間がどうとかの情報は持っていません。
まとめ
- CMYKはおもに印刷で必要になる
- PillowでもCMYKを指定すれば、RGB画像と同様に扱える
- NumPy配列とPillowのCMYK画像は相互にやり取りできる
この記事は、2020年12月の新刊・インフィニティNumPyの第4章のQ85で紹介した内容をPillowで再現したものです。インフィニティNumpyでは円の描画まで全てNumPyベースでやっています。興味のある方はのぞいてみてください。カラープロファイルによるRGB→CMYK画像の変換の話も掲載しています。
Shikoan's ML Blogの中の人が運営しているサークル「じゅ~しぃ~すくりぷと」の本のご案内
技術書コーナー
北海道の駅巡りコーナー