Pillow 利用ノート¶
Python で画像処理といえば、長い間 PIL が活躍していた。だがここ最近は Pillow というフォークパッケージが主流になっているらしい。とある別の作図パッケージのアップグレードの際に、それが Pillow に依存していることに気付いて世代交代を知ったのだった。
そこで、本稿では Pillow の導入方法と、既存の PIL 利用コードの移行手順について記す。なお、PyOpenGL や Pygame との連携で PIL を利用していた既存コードのそれについては、各ノートにて言及していく。
Note
関連リンク¶
- Pillow
公式パッケージ・インストーラー配布元。
- Python Extension Packages for Windows - Christoph Gohlke
非公式インストーラー配布元。
関連ノート¶
- PyOpenGL 利用ノート
Pillow はテクスチャーデータを生成するのに大いに有用だ。
インストール¶
一般のサードパーティー製 Python パッケージのインストール手順 の方針に従ってインストールする。
ちなみに PIL が既に環境に居座っている場合は、Pillow をインストールする前にPIL をアンインストールする必要があるようだ。公式サイトの Installation ページに PIL と Pillow が共存できない旨が明記されている。
アップグレード¶
Anaconda または Miniconda で Python 環境を管理しているのであれば、コマンドラインで Pillow をアップグレードすることは容易い。単に conda update Pillow
と入力するだけでよい。
それ以外の環境では pip を用いることになる。
バージョン確認¶
Anaconda または Miniconda で Python 環境を管理していて、コマンドラインで作業をしているならば、次のように conda ツールを用いる。
bash$ conda list Pillow
# packages in environment at D:\Miniconda3:
#
pillow 3.2.0 py35_1
Python インタラクティブシェル(長いので、以下単にインタープリターと書く)でインストール直後の Pillow のバージョンを確認するならば次のようにする。
>>> import PIL
>>> PIL.VERSION
'1.1.7'
>>> PIL.PILLOW_VERSION
'3.0.0'
ドキュメント¶
まずは Pillow 公式サイトの Guides を一読するとよい。それほどしっかりと読み込む必要はなく、使えそうな機能だけをつまみ読みでよい。
画像処理基本¶
ここでは基本モジュール Image
のクラスと関数を利用した、初歩的な画像処理の実例をいくつか挙げる。
以下の説明で、単にメソッドという用語が現れる場合は、クラス Image
のメソッドの意である。また、次のイメージを各操作デモの原像として多用している。
画像ファイルフォーマット変換(単発)¶
GIF 画像ファイル image.gif
を PNG 形式に変換して、ファイル
image.png
として保存するコードを示す。
>>> from PIL import Image
>>> im = Image.open("image.gif")
>>> im.save("image.png")
関数
open
に画像ファイルパスを渡すと、画像オブジェクトが生成する。メソッド
save
で画像ファイルをファイルシステム上に作成する。上のコードのようにファイルパスだけを指定する場合、ファイルの画像フォーマットは拡張子に基づいて Pillow が決定する。
拡張子に頼らずに画像フォーマットを指定する場合は、キーワード引数
format
から文字列により指定できる。
画像ファイルフォーマット変換(バッチ処理)¶
カレントディレクトリー内の GIF ファイルを PNG ファイルに変換、保存する処理のコードはこうなる。
#!/usr/bin/env python
"""batch.py: Demonstrate converting GIF files to PNG files.
"""
import os.path
import glob
from PIL import Image
for infile in glob.glob("*.gif"):
file, ext = os.path.splitext(infile)
im = Image.open(infile)
im.save(file + ".png")
ちなみに、自分でわざわざ上のようなコードを用意する必要はほぼないだろう。後述するように、Python の Scripts ディレクトリーにインストールされている
pilconvert.py
を使う手がある。公式サイトのドキュメントでは、同様の方式によるサムネイル画像ファイルを一括作成する例が紹介されている。メソッド
open
とsave
の呼び出しの間にメソッドthumbnail
呼び出しをはさむとそうなる。
Pillow がサポートする画像フォーマットを知る¶
Pillow のインストールディレクトリーで grep Image.register_ *.py
するとよい。
画像データをバイト配列として扱う¶
画像データをバイト配列として扱うには、メソッド tobytes
を用いる。PyOpenGL
のプログラムで画像ファイルからテクスチャーを生成する際に必須の方法だ。
戻り値として
bytes
オブジェクトが得られる。メモリレイアウトはキーワード引数
encoder_name
で指定できるが、生のままでよい。例えば透過 PNG ファイルからImage
オブジェクトを生成したときは、tobytes
は RGBARGBA… のようなデータ構造になる。旧メソッド名は
tostring
だったが、現在 deprecated となっている。昔のコードを再利用するときに置換しておこう。
簡易ビューワー¶
インタープリターで Pillow を試しているときに、クラス Image
のオブジェクトをビューワーで確認したくなることがよくある。メソッド show
はこういう場合に利用するのに相応しい。
>>> im = Image.open(...)
>>> ...
>>> im.show()
Windows 版ビルドでは Windows のフォトビューワーが起動して、画像の内容が表示された。おそらくファイル拡張子に関連付けられたプログラムが起動されるものと思われる。
オブジェクトが既存のファイルから得られたものでない場合にも通用する。
ドキュメントによると、このメソッドは効率が悪いらしい。場合によってはオブジェクトをファイルに保存しつつ、再読み込み機能がある画像ビューワーを開いたままにして、デバッグ作業するのもありか。
Note
Linux では im.show()
は ImageMagick の display を介して画像を表示する。特に WSL では VcXsrv X Server などの Windows 用サービスプログラムがWindows 側で稼働していることを必要とする。 im.show()
の呼び出しでエラーメッセージが出るようなら、次を確認する:
(Windows) VcXsrv などのサービスはインストールされているか
(Windows) VcXsrv などに対するファイアーウォールを無効にしたか。
(WSL) ImageMagick はインストールされているか
(WSL) 環境変数
DISPLAY
を正しくセットしたか
export DISPLAY=$(grep -oP "(?<=nameserver ).+" /etc/resolv.conf):0.0
ジャギー解消¶
メソッド resize
や thumbnail
を利用するのならば、キーワード引数
resample
の実引数をデフォルトの Image.NEAREST
以外の値にすることを検討するとよい。
Image.ANTIALIAS
はドキュメントからは消えているが、単にImage.LANCZOS
の別名である。
イメージモノクロ化¶
メソッド convert
を引数 "L"
で呼び出す。各ピクセルの RGB 値を次の式でグレースケール化してモノクロ化するようだ。
>>> ...
>>> im = Image.open("illvelo.png")
>>> im.convert("L")
画像生成¶
ゼロから画像オブジェクトを生成するのに、関数 Image.new
を利用することができる。
>>> from PIL import Image
>>> img = Image.new('RGB', (1024, 768))
引数
mode
でよく指定する値は'RGB'
か'RGBA'
のどちらかになる。引数
size
は画素単位の画像サイズ。幅、高さの順に要素が並ぶtuple
オブジェクトで指定する。キーワード引数
color
でいわゆる背景色を指定できる。デフォルトは黒。
色の表現¶
Pillow はコードでの色の表現手段を多数サポートしている。詳しくはモジュール
ImageColor
の内容を見るとよい。
色名による色指定¶
Pillow のメソッドで色を引数に取るものについては、色成分を列挙した list
や
tuple
だけでなく、モジュール ImageColor
で決められている色名で指定することもできる。色名の型はプログラマーにやさしい形式、つまり文字列である。
>>> from PIL import Image
>>> img = Image.new('RGB', (1024, 768), 'deeppink')
色名は colormap
という dict
インスタンスに保持されている。興味があれば列挙してみるとよい。
>>> from PIL import ImageColor
>>> for i in sorted(ImageColor.colormap.items()):
>>> print(i)
...
('aliceblue', '#f0f8ff')
('antiquewhite', '#faebd7')
('aqua', '#00ffff')
後半略。全部で 147 項目あるのでかなり長い。
色名から RGB 値に変換する¶
関数 ImageColor.getrgb
を利用するほうが早い。
>>> from PIL.ImageColor import getrgb
>>> getrgb('deeppink')
(255, 20, 147)
PyOpenGL プログラムなどで A 値も欲しい場合は関数 getcolor
を用いるという手もある。このとき引数 mode
の明示的な指定を必要とする。
>>> from PIL.ImageColor import getcolor
>>> getcolor('deeppink', 'RGBA')
(255, 20, 147, 255)
透過¶
透過画像生成¶
関数 new
で生成する画像オブジェクトを透過対応する方法は、引数 mode
を
'RGBA'
にすることだ。
さらに、引数 color
を、例えば 4 成分の array-like オブジェクトを渡すとしよう。この場合 color[0:3]
はそれぞれ今までどおり R, G, B 値を指定する。さらなる成分 color[3]
はアルファー値といい、アルファベットの A で示す。この A 値は下限値の 0 が完全透明を意味し、上限値の 0xFF は完全不透明を意味する(ところで英語には opaque という単語があるが、日本語だと否定形で表現するしかなさそうだ)。その中間の値は、まあまあ透明という意味だ。実際はアルファブレンディングという手法で最終的な RGB 色を決定するための値である。
>>> img = Image.new('RGBA', (1024, 768), (0, 0, 0, 0))
このようにすると、真っ黒だが透明という画像オブジェクト img
が生成する。
アルファチャンネルを持つ PNG 画像を青地背景上に重ねたい¶
要するに、透過ピクセルを含む画像をブルーバックの画像の上に乗せると思って欲しい。
#!/usr/bin/env python
"""alphapaste.py: Demonstrate how to composite two images with alpha values.
"""
import os.path
from PIL import Image
SOURCE_PATH = os.path.join(
os.path.dirname(__file__), '../../_images/illvelo.png')
# Layer 1 in Photoshop.
img = Image.open(SOURCE_PATH)
# Background layer in Photoshop.
bkgnd = Image.new('RGBA', img.size, 'blue')
# Blend images with the transparency mask from img.split()[3].
result = Image.alpha_composite(bkgnd, img)
result.show()
いちばん楽なのは関数 alpha_composite
を用いることだ。自分でメソッド
split
を呼んでバンドオブジェクトを用意したり、マスクを指定して関数 paste
を呼ばずに済む。
元画像と処理後の画像はこうなる。ここで、元画像のキャラクターの輪郭の外側は完全透明色、内側領域は完全不透明色である。
グラデーション¶
Pillow は直接的にはグラデーションをサポートしていなそうなので、描きたいときは自作する。ここではグラデーションパターンの生成について、いくつかコツを記す。
線形グラデーション(透過なし)¶
幅 \(1 \times 256\) ピクセルのイメージを作成し、ピクセルカラーをその位置に応じてセットしていく方針で絵を描く。まずは putpixel
メソッドを利用してこれを行い、それから目的のサイズにイメージを拡縮する。
次に示すコードは、サイズが \(320 \times 240\) で、上部が赤で下部が青の線形グラデーションとなるイメージを作成する。
#!/usr/bin/env python
"""gradient1.py: Demonstrate drawing linear gradient.
"""
from PIL import (Image, ImageColor)
import numpy as np
COLOR_START = ImageColor.getrgb('antiquewhite')
COLOR_STOP = ImageColor.getrgb('deeppink')
IMAGE_WIDTH, IMAGE_HEIGHT = 320, 240
WORK_SIZE = 0x100
img = Image.new('RGB', (1, WORK_SIZE))
gradient = np.array(
[np.linspace(i, j, WORK_SIZE) for i, j in zip(COLOR_START, COLOR_STOP)],
dtype=int)
for i, rgb in enumerate(gradient.T):
img.putpixel((0, i), tuple(rgb))
img = img.resize((IMAGE_WIDTH, IMAGE_HEIGHT))
img.show()
線形補間の計算コード記述の手間を少々省くため、NumPy の関数
linspace
を利用した。
線形グラデーション(単色にグレースケール透過)¶
メソッド putalpha
利用版グラデーション。
#!/usr/bin/env python
"""gradient2.py: Demonstrate drawing linear gradient.
"""
from PIL import Image
BASE_COLOR = 'red'
IMAGE_WIDTH, IMAGE_HEIGHT = 320, 240
WORK_SIZE = 0x100
img = Image.new('RGBA', (IMAGE_WIDTH, IMAGE_HEIGHT), BASE_COLOR)
gradient = Image.new('L', (1, WORK_SIZE))
for i in range(WORK_SIZE):
gradient.putpixel((0, i), WORK_SIZE - i)
img.putalpha(gradient.resize(img.size))
img.show()
出力は上部が赤で、下部に至るにつれて透過していく線形グラデーションイメージとなる。
線形グラデーション(さらなる応用)¶
イメージ 3 枚重ね。
#!/usr/bin/env python
"""gradient3.py: Demonstrate drawing linear gradient on an image.
"""
import os.path
from PIL import Image
WORK_SIZE = 0x100
SOURCE_PATH = os.path.join(
os.path.dirname(__file__), '../../_images/illvelo.png')
img = Image.open(SOURCE_PATH)
assert img.mode == 'RGBA'
gradient = Image.new('L', (1, WORK_SIZE))
for i in range(WORK_SIZE):
gradient.putpixel((0, i), i)
alpha = gradient.resize(img.size, Image.ANTIALIAS)
final = Image.new('RGBA', img.size, (0, 0, 0, 0))
final.paste(img, None, mask=alpha)
final.show()
放射状グラデーション¶
放射状のグラデーションを実現する例を示す。
#!/usr/bin/env python
"""gradient4.py: Demonstrate drawing radial gradient.
"""
from PIL import (Image, ImageColor, ImageDraw)
import numpy as np
COLOR_START = ImageColor.getrgb('antiquewhite')
COLOR_STOP = ImageColor.getrgb('deeppink')
WORK_SIZE = 0x100
img = Image.new('RGB', (WORK_SIZE, WORK_SIZE), color='white')
draw = ImageDraw.Draw(img)
gradient = np.array(
[np.linspace(i, j, WORK_SIZE) for i, j in zip(COLOR_START, COLOR_STOP)],
dtype=int)
for i, rgb in enumerate(gradient.T):
draw.ellipse([i, i, WORK_SIZE - i, WORK_SIZE - i], fill=tuple(rgb))
img.show()
クラス ImageDraw.Draw
のメソッド ellipse
をバウムクーヘン方式に描画する。バウムクーヘンを外側から内側に向かって、少しずつ色を変えて塗れば、遠目からはグラデーションのように見えるだろう。
RGB 値の算出に
np.linspace
を用いるのは、前述の方法論による。楕円の形状指定方法は、そのバウンディングボックスを与えることによる。そして、ボックスの形状指定方法は、その左上頂点座標と右下頂点座標を与えることによる。
テキスト¶
テキストを画像として動的に生成する方法を見ていこう。ここでインポートする Pillow
のモジュールは Image
のほかにも ImageDraw
と ImageFont
がある。
Hello, world¶
#!/usr/bin/env python
"""text1.py: Demonstrate how to draw a text with PIL.
"""
from PIL import (Image, ImageDraw)
IMAGE_SIZE = (96, 24)
BKGND_COLOR = 'black'
TEXT_COLOR = 'white'
img = Image.new('RGBA', IMAGE_SIZE, BKGND_COLOR)
draw = ImageDraw.Draw(img)
draw.text((0, 0), 'Hello, world', fill=TEXT_COLOR)
img.show()
上のコードを動作させると、黒地に白い字で Hello, world
と書かれた画像が生成する。これだけだとかなり不満がある。
テキストのサイズ等の情報が事前に予測不可能。
フォントを指定したい。
日本語テキスト¶
コツは 3 つある。
関数
ImageFont.truetype
で日本語対応のフォントオブジェクトを作成する。その際に
encoding
引数に適切なエンコーディングを指示する。text
メソッドの引数にそのフォントを与える。
#!/usr/bin/env python
"""text2.py: Demonstrate how to draw multi-line text using ImageFont.
"""
from PIL import (Image, ImageDraw, ImageFont)
TEXT = '''どうしても会ってもらえませんか?
私はこんなにあなたに会いたいのに…。
お金には余裕があるので心配しないで
ください。
コード780の1102番で、
あなたを待っています。
'''
TEXT_COLOR = 'white'
BKGND_COLOR = 'black'
FONT = ImageFont.truetype('HGRME.TTC', 24, encoding='utf-8')
img = Image.new('RGB', (1024, 256), BKGND_COLOR)
draw = ImageDraw.Draw(img)
width = 0
height = 0
for line in TEXT.splitlines():
draw.text((0, height), line, font=FONT, fill=TEXT_COLOR)
ext = draw.textsize(line, FONT)
width = max(ext[0], width)
height += ext[1]
# Trim extra margin of right and bottom.
final = img.crop((0, 0, width, height))
final.show()
トリム処理を加えることで、出力画像の大きさはテキストにフィットする最小の矩形になるはずだ。
応用¶
少し手間をかけて、画像を加工することを試そう。
スクリーンショット¶
モジュール ImageGrab
を用いると、画面イメージキャプチャーが得られる。このスクリーンショット取得機能は Pillow の Windows 版にだけある。
#!/usr/bin/env python
"""grabdemo.py: Demonstrate how to use function ImageGrab.grab.
"""
from PIL import (Image, ImageGrab)
# Take a snapshot of the whole screen.
img = ImageGrab.grab()
# For the purpose of display, make it to a thumbnail.
img.thumbnail((256, 256), Image.ANTIALIAS)
上下左右ループ壁紙パターン生成¶
気がついたら Pillow のドキュメントにこの技法が載っていたが、ここでは少々凝ったやり方で壁紙の繰り返し単位部分を生成する。
元画像を \(2 \times 2\) 分割して対角線上の区域を入れ替える。
そこへ元画像をブレンドなりオーバーレイなりして重ね合わせる。
スクリプトのリポジトリーを移転したので、次のリンク先を参照。 pattern.py
モジュール ImagePath
¶
これはユーザーが直接利用するのではなさそうなので使うのをやめた。
モジュール ImagePath
は、ドキュメントがないモジュール ImageDraw2
の描画機能(点列のアフィン変換)のために、補助データ構造を提供するもののようだ。
コマンドラインユーティリティー¶
Pillow をインストールすると、パッケージの他に一連のスクリプトファイル群がPython
ディレクトリーの Scripts
フォルダーに格納される。これらのスクリプトは
Pillow の機能を応用した、画像操作のためのささやかなコマンドラインユーティリティーだ。
スクリプト |
何をするのか |
---|---|
|
画像ファイルの画像フォーマットや色モードを変換する。 |
|
画像ファイルの画像の操作をする。対話モードあり。 |
|
画像ファイルの鑑定をする。 |
|
フォントファイルを PIL のラスターフォントフォーマットに変換する。 |
|
画像ファイルを PostScript プリンターに出力する。うまく動かない? |
フォーマット変換¶
コマンドラインで pilconvert.py
を利用する。例えば sample.gif
から PNG 形式のファイル sample.png
を作成するには次のように入力する。
bash$ pilconvert.py sample.gif sample.png
カレントディレクトリーのすべての GIF ファイルから PNG ファイルに変換したいならばこうなる。ちなみにシェルは bash である。
bash$ for name in *.gif ; do \
> pilconvert.py $name ${name%.*}.png ; \
> done
リサイズ¶
コマンドラインで pildriver.py
を利用する。以前にも記したが、バッチモードとインタラクティブモードがある。
bash$ pildriver.py
PILDriver says hello.
pildriver> open illvelo.png
[<PIL.PngImagePlugin.PngImageFile image mode=RGBA size=256x252 at 0xBEF800>]
pildriver> thumbnail 64 64
[<PIL.PngImagePlugin.PngImageFile image mode=RGBA size=64x63 at 0xBEF800>]
pildriver> show
[]
pildriver>