Pythonは、高度な処理を簡単に実装できるプログラミング言語なので、さまざまな分野で活用されています。画像処理の分野では、「PIL」というライブラリが便利です。ただし、PILはすでに開発停止で使えなくなっているため、現在は「Pillow」という後継の画像ライブラリを通してPILを使う必要があります。
PIL(Pillow)には、画像の加工・編集などに便利な機能が網羅されているため、うまく利用することで高度な画像処理が行えます。しかし、クラスや関数などの種類が膨大なので、「何をどう使うべきか」わからないこともあるでしょう。そこで本記事では、PIL(Pillow)の使い方や応用テクニックについて、サンプルコード付きで解説します。
目次
「PIL(Python Imaging Library)」とは、プログラミング言語Pythonで以前使えた画像処理ライブラリのことです。基本的な画像処理の機能が網羅されているため、さまざまな画像処理がシンプルなソースコードで行えます。たとえば、画像のリサイズ・トリミング・回転などの画像編集や、図形・テキストの描画やフィルターの適用などです。
しかし、PILは2011年に開発が停止しており、現在は使えません。そのため、Pythonで画像の編集や加工などを行うためには、ほかの画像処理ライブラリを利用する必要があります。また、PILは画像の加工や編集に特化したライブラリなので、「OpenCV」のようなAIによる顔・輪郭の検出などの最新技術は利用できません。
「Pillow」は、PILの後継となる画像処理ライブラリで、現在のPythonで使えます。PILの機能性を継承したうえで、より高度な画像処理が行えるようになった魅力的なライブラリです。
現在でも開発が継続されているため、AIや機械学習などの最新技術に関するライブラリと組み合わせて、さまざまな分野で活用されています。前述したOpenCVと並んで、Pythonの主要な画像処理ライブラリとなっています。
Pillowライブラリは、PILを最新の状態にアップデートしたものです。そのため、PythonでPILを使用したい場合は、Pillowを通して使うことになります。
PIL・PillowはPythonの基本的な画像処理ライブラリではありますが、Pythonに標準的に備わっているものではありません。そのため、Pillowを使用する際は、事前にライブラリをインストールしておく必要があります。Pillowのインストールは、pipコマンドで行います。
まずは、タスクバーの検索画面で「cmd」と入力して「コマンドプロンプト」を開き、以下のコマンドを入力してください。Pythonのパッケージである「pip」を、最新バージョンにアップデートできます。アップデートが完了すると、「Successfully installed pip」と表示されます。
py -m pip install --upgrade pip
続いて以下のpipコマンドを入力すると、Pillowライブラリをインストールできます。前述したように、PillowはPILの機能を含むため、Pillowをインストールすることで、PILの機能が使えるようになります。
py -m pip install pillow
PIL・Pillowの基本的な使い方として、まずは以下の7つの知識を身につけておきましょう。
画像処理の始まりは、画像ファイルの読み込みと表示です。PIL・Pillowでは、以下の構文で「Image.open()」関数を使用することで、任意の画像ファイルを読み込めます。
画像データ変数 = Image.open("ファイルパス")
以下の画像を「Flower.png」という名称で、Pythonのメインモジュールがあるフォルダに保存してください。そのうえで、以下のサンプルコードを実行しましょう。
// サンプルプログラム
# coding: UTF-8 # 「PIL」ライブラリから「Image」クラスをimportする from PIL import Image # 画像ファイルを読み込む image = Image.open("Flower.png") # 既定の画像ソフトでデータを表示する image.show()
// 実行結果
重要なポイントは、最初に「PIL」ライブラリをインポートしている点です。前述したように、インストールするライブラリはPillowですが、ソースコード内ではPILをインポートする必要があります。画像関連の機能はImageクラスに入っているため、「from PIL import Image」と記載しましょう。
また、画像の表示は「image.show()」関数で行います。読み込んだ画像データが、システムの既定の画像ソフトで表示されます。たとえば、PNGファイルの既定プログラムが「フォト」に設定されている場合は、PillowでPNGファイルを読み込むと「フォト」で表示されるということです。
Image.open()の戻り値から、以下のような画像の詳細情報を取得できます。
プロパティ | 概要 |
---|---|
image.filename | 画像のファイル名 |
image.format | 画像の拡張子 |
image.size | 画像のサイズ |
image.width | 画像の横幅 |
image.height | 画像の高さ |
image.mode | 画像のピクセルモード |
「mode」プロパティは、画素データの要素の並び方を示します。ここが「RGB」になっている場合は、「R(赤)」「G(緑)」「B(青)」の順に並んでいるということです。アルファチャンネルがある場合は、一般的にRGBAとなります。詳細は以下のサンプルコードのとおりです。
// サンプルプログラム
# coding: UTF-8 # 「PIL」ライブラリから「Image」クラスをimportする from PIL import Image # 画像ファイルを読み込む image = Image.open("Flower.png") # 読み込んだ画像ファイルの詳細情報を表示する print(f"ファイル名:{image.filename}") print(f"フォーマット:{image.format}") print(f"画像サイズ:{image.size}") print(f"画像の横幅:{image.width}") print(f"画像の高さ:{image.height}") print(f"ピクセルモード:{image.mode}")
// 実行結果
以上の情報は、これから画像処理を行う際に活用することがあるため、ぜひ覚えておきましょう。とくに、サイズに関する「size」「width」「height」などの情報は、リサイズ処理などで重要です。
PIL・Pillowでは、既存の画像データを読み込むだけではなく、新規作成することもできます。以下のように「Image.new()」関数を使用すると、新しい画像データ・カンバスを作成できます。
画像データ = Image.new("画像モード", (画像の横幅, 画像の高さ), (背景色R, 背景色G, 背景色B))
第1引数の「画像モード」は、基本的には「RGB」もしくは「RGBA」を指定するのがおすすめです。第2引数は画像サイズ、第3引数は背景色を設定します。詳細は以下のサンプルコードのとおりです。
// サンプルプログラム
# coding: UTF-8 # 「PIL」ライブラリから「Image」クラスをimportする from PIL import Image # 新規の画像データを作成する # 第1引数は「画像モード」⇒今回はRGBの3要素 # 第2引数は「画像サイズ」⇒今回は横512×縦512 # 第3引数は「背景色」⇒今回はRGBそれぞれ128のグレー image = Image.new("RGB", (512, 512), (128, 128, 128)) # 既定の画像ソフトでデータを表示する image.show()
// 実行結果
詳細は後述しますが、Image.new()関数で生成した画像データには、さまざまな図形やテキストを描画したり、別の画像を貼り付けたりできます。
読み込んだ画像データ、もしくは新規に作成した画像データを別の変数にコピーするときは、「copy()」関数を使用します。
コピー後の画像データ = 元の画像データ.copy()
copy()では「ディープコピー」が行われるので、一方に変更を加えても他方は影響を受けません。たとえば、copy()でコピーしてから編集しても、元の画像データは変更されません。しかし、「=演算子」でコピーしようとすると、単に「シャローコピー」が行われるだけなので注意が必要です。copy()関数の使い方は以下のとおりです。
// サンプルプログラム
# coding: UTF-8 # 「PIL」ライブラリから「Image」クラスをimportする from PIL import Image # 画像ファイルを読み込む image = Image.open("Flower.png") # 画像データをコピーする copy = image.copy() # 元の画像データを削除する image.close() # 既定の画像ソフトでデータを表示する copy.show()
// 実行結果
上記のサンプルコードの「close()」は、画像データを解放するためのものです。このように元画像を解放しても、コピー先の画像データは問題なく使えます。copy()ではなく「=演算子」を使用すると、エラーメッセージが表示されます。
読み込んだ画像のRGB各要素の最小値・最大値は、イメージ変数の「getextrema()」関数を呼び出すことで取得できます。RGB(アルファチャンネルなし)の場合は3要素・RGBA(アルファチャンネルあり)の場合は4要素、それぞれの最小値・最大値が2重タプル形式で返ります。以下の画像を「Sky.png」という名称で、Pythonのメインモジュールがあるフォルダに保存し、以下のサンプルコードを実行しましょう。
// サンプルプログラム
# coding: UTF-8 # 「PIL」ライブラリから「Image」クラスをimportする from PIL import Image # 画像ファイルを読み込む image = Image.open("Sky.png") # RGB各画素の最小値と最大値を表示する print(image.getextrema())
// 実行結果
こちらの画像はアルファチャンネルがあるため、RGBAの4要素それぞれの最小値・最大値が表示されます。
指定した画素の色情報は、イメージ変数の「getpixel()」関数を呼び出すことで取得できます。戻り値には画像ファイルの形式に応じて、該当するピクセルの色情報がタプル形式で格納されています。
なお、Pillowでは画像の左上が原点(0, 0)となっているため、右側・下側に行くほど値が大きくなることがポイントです。詳細は以下のサンプルコードのとおりです。
// サンプルプログラム
# coding: UTF-8 # 「PIL」ライブラリから「Image」クラスをimportする from PIL import Image # 画像ファイルを読み込む image = Image.open("Sky.png") # (256, 256)ピクセルの画素を取得する print(image.getpixel((256, 256)))
// 実行結果
「Sky.png」の(256, 256)地点のピクセルは、赤が112・緑が193・青が255・アルファが255ということです。アルファ値は255に近いほど不透明で、0に近いほど透明度が高くなります。さまざまな画像ファイルや座標の組み合わせで試してみましょう。ただし、範囲外のピクセルを指定すると、エラーが出るので注意が必要です。
Pillowでは、以下の構文で「Image.save()」関数を使用することで、画像データをファイルとして書き出せます。
画像データ変数 = Image.save("ファイルパス")
冒頭の「Image.open()」関数と同じように、引数にファイルパスを出力するだけで使えます。フォーマットの変換やピクセルデータの構成などは、関数内部で自動的に行われるので、プログラマが実装する必要はありません。ただし、ファイルの拡張子を存在しないものにすると、エラーが出るので注意が必要です。詳細は以下のサンプルコードのとおりです。
// サンプルプログラム
# coding: UTF-8 # 「PIL」ライブラリから「Image」クラスをimportする from PIL import Image # 画像ファイルを読み込む image = Image.open("Flower.png") # JPG形式で画像を保存する image.save("Flower.jpg")
// 実行結果
ちなみに、save()関数では以下の構文のように「quality」引数を指定すると、保存品質を指定できます。
画像データ変数 = Image.save("ファイルパス", quality = 保存品質)
保存品質の値は「0」から「95」の値で設定し、0が最低・95が最高となります。デフォルト値は75となっており、100のように95を超える値の設定は推奨されていません。quality引数による画像品質の違いについて、以下のサンプルコードで確認しましょう。
// サンプルプログラム
# coding: UTF-8 # 「PIL」ライブラリから「Image」クラスをimportする from PIL import Image # 画像ファイルを読み込む image = Image.open("Flower.png") # JPG形式で画像を保存する(品質95 = 最高) image.save("Flower95.jpg", quality = 95) # JPG形式で画像を保存する(品質0 = 最低) image.save("Flower0.jpg", quality = 0)
// 実行結果
Flower95.jpg(最高品質) Flower0.jpg(最低品質)
以上のように、保存品質を95にすると綺麗な画像が出力されるのに対し、0の場合は荒い画像となることがわかります。また画像サイズは保存品質95が496KB・保存品質0が21KBとなります。qualityを下げると画像サイズが大幅に小さくなるため、できるだけ容量を抑えたい場合はqualityの値を調整してみましょう。
Pillowで画像の編集と加工を行う方法について、以下の4つを解説します。
画像加工の基本は、リサイズおよび解像度の変更です。Pillowでは以下のように「resize()」関数を使うことで、画像のリサイズを簡単に行えます。
リサイズ後の画像データ = image.resize((画像の横幅, 画像の高さ))
resize()の引数には、リサイズ後の画像サイズを「(横幅, 高さ)」というタプル形式で指定します。詳細を以下のサンプルコードで確認しましょう。
// サンプルプログラム
# coding: UTF-8 # 「PIL」ライブラリから「Image」クラスをimportする from PIL import Image # 画像ファイルを読み込む image = Image.open("Flower.png") # 画像データを(512, 512)にリサイズする image = image.resize((512, 512)) # リサイズした画像をファイルに書き出す image.save("FlowerResized.png")
// 実行結果
resize()を使うと、単純なソースコードで画像をリサイズできます。しかし、上記のサンプルプログラムでは横幅と高さに同じ値を指定したため、画像のアスペクト比(縦横比)も変わっています。アスペクト比を維持したままリサイズするためには、以下のように「元の画像サイズに対する比率」を指定しましょう。
// サンプルプログラム
# coding: UTF-8 # 「PIL」ライブラリから「Image」クラスをimportする from PIL import Image # 画像ファイルを読み込む image = Image.open("Flower.png") # 画像データを半分にリサイズする image = image.resize((image.width // 2, image.height // 2)) # リサイズした画像をファイルに書き出す image.save("FlowerResizedFixed.png")
// 実行結果
なお、resize()の引数はint型でないといけないため、通常の「/演算子」ではなく「//演算子」を使って、小数点以下を切り捨てる必要があります。「/演算子」の場合は戻り値がFloat型になるため、エラー画面が表示されます。
なお、resize()には「resample引数」でリサンプリング方式を指定することも可能です。リサンプリング方式として、以下の値が有効です。
基本的には下の方式ほど高品質で、デフォルト状態では「Image.BICUBIC」となっています。リサンプリング方式ごとのリサイズ結果の違いについて、以下のサンプルコードで検証してみましょう。
// サンプルプログラム
# coding: UTF-8 # 「PIL」ライブラリから「Image」クラスをimportする from PIL import Image # 画像ファイルを読み込む image = Image.open("Flower.png") # リサイズ後の画像サイズを算出する size = (int(image.width * 0.5), int(image.height * 0.5)) # 「Image.NEAREST」方式でリサイズする image.resize(size, resample = Image.NEAREST).save("FlowerResizedNEAREST.png") # 「Image.BOX」方式でリサイズする image.resize(size, resample = Image.BOX).save("FlowerResizedBOX.png") # 「Image.BILINEAR」方式でリサイズする image.resize(size, resample = Image.BILINEAR).save("FlowerResizedBILINEAR.png") # 「Image.HAMMING」方式でリサイズする image.resize(size, resample = Image.HAMMING).save("FlowerResizedHAMMING.png") # 「Image.BICUBIC」方式でリサイズする image.resize(size, resample = Image.BICUBIC).save("FlowerResizedBICUBIC.png") # 「Image.LANCZOS」方式でリサイズする image.resize(size, resample = Image.LANCZOS).save("FlowerResizedLANCZOS.png")
// 実行結果
FlowerResizedNEAREST.png FlowerResizedBOX.png FlowerResizedBILINEAR.png FlowerResizedHAMMING.png FlowerResizedBICUBIC.png FlowerResizedLANCZOS.png
比較してみると、NEARESTは画素の荒さが目立ちやすく、BILINEARは輪郭がボケたようになることがわかります。一方で、BICUBICやLANCZOSは輪郭線が滑らかでありながら、全体がはっきりした印象になります。
そのため、輪郭を強調したい場合はNEAREST、できるだけ高品質にしたい場合はBICUBICやLANCZOSを選ぶといいでしょう。
画像の回転もよく行われる画像処理のひとつです。Pillowでは以下のように「rotate()」関数を使うことで、画像を任意の角度で回転させられます。
回転後の画像データ = image.rotate(回転角度)
このように引数に角度を指定するだけでOKですが、正の値が反時計回り・負の値が時計回りとなることに注意が必要です。詳細を以下のサンプルコードで確認しましょう。
// サンプルプログラム
# coding: UTF-8 # 「PIL」ライブラリから「Image」クラスをimportする from PIL import Image # 画像ファイルを読み込む image = Image.open("Flower.png") # 画像を反時計回りに30度回転させて保存する image.rotate(30).save("FlowerRotated+30.png") # 画像を時計回りに30度回転させて保存する image.rotate(-30).save("FlowerRotated-30.png")
// 実行結果
FlowerRotated+30.png FlowerRotated-30.png
上記の実行結果には問題があります。回転後の画像の一部がはみ出し、欠けてしまっていることです。これは、画像を回転させると必要なサイズが大きくなるのに対し、キャンパスのリサイズを行っていないことが原因です。rotate()には「expand引数」があり、これをTrueにすることで、自動的に適切な大きさにリサイズされます。
// サンプルプログラム
# coding: UTF-8 # 「PIL」ライブラリから「Image」クラスをimportする from PIL import Image # 画像ファイルを読み込む image = Image.open("Flower.png") # 画像を適切な大きさにリサイズしたうえで、反時計回りに30度回転させて保存する image.rotate(30, expand = True).save("FlowerRotated+30Ex.png") # 画像を適切な大きさにリサイズしたうえで、時計回りに30度回転させて保存する image.rotate(-30, expand = True).save("FlowerRotated-30Ex.png")
// 実行結果
FlowerRotated+30Ex.png FlowerRotated-30Ex.png
このように画像サイズが自動的に拡張されるため、回転後に画像の端が欠けてしまうことがありません。なお、画像の回転時はピクセルが移動するため、リサンプリングが行われます。
そのためrotate()でもresize()と同じように「resample引数」が使えますが、指定できるのは「Image.NEAREST」「Image.BILINEAR」「Image.BICUBIC」の3つに限られます。詳細は以下のサンプルコードのとおりです。
// サンプルプログラム
# coding: UTF-8 # 「PIL」ライブラリから「Image」クラスをimportする from PIL import Image # 画像ファイルを読み込む image = Image.open("Flower.png") # 「Image.NEAREST」方式で、画像を反時計回りに45度回転させる image.rotate(45, resample = Image.NEAREST, expand = True).save("FlowerRotatedNEAREST.png") # 「Image.BILINEAR」方式で、画像を反時計回りに45度回転させる image.rotate(45, resample = Image.BILINEAR, expand = True).save("FlowerRotatedBILINEAR.png") # 「Image.BICUBIC」方式で、画像を反時計回りに45度回転させる image.rotate(45, resample = Image.BICUBIC, expand = True).save("FlowerRotatedBICUBIC.png")
// 実行結果
FlowerRotatedNEAREST.png FlowerRotatedBILINEAR.png FlowerRotatedBICUBIC.png
なお、rotate()ではNEARESTがデフォルト値となるため、これをBICUBICに変更するとより高品質な結果が得られます。
画像のトリミングや切り抜きを行いたい場合は、以下の構文で「crop()」関数を使います。
トリミング後の画像データ = image.crop((左上X座標, 左上Y座標, 右下X座標, 右下Y座標))
crop()関数の引数は、トリミングする部分が収まる「矩形(四角形)」の座標値となり、左上と右下の部分を指定します。中心座標が分かっている場合は、そこから切り取りたい画像サイズの半分の値を差し引くことで、正確な座標値が求まります。詳細を以下のサンプルコードで確認しましょう。
// サンプルプログラム
# coding: UTF-8 # 「PIL」ライブラリから「Image」クラスをimportする from PIL import Image # 画像ファイルを読み込む image = Image.open("Flower.png") # 画像データをトリミングする # (380, 180)を始点として、X方向に460・Y方向に440トリミングする image = image.crop((380, 180, 380 + 460, 180 + 440)) # トリミングした部分をファイルに書き出す image.save("FlowerTrimmed.png")
// 実行結果
画像を白黒や8ビットカラー(256色)に変換したい場合は、「image.convert()」関数を呼び出します。引数に以下の値を設定すると、画像モードを変換できます。
画像モード | 概要 |
---|---|
“1” | 1ビット/白黒 |
“L” | 8ビット(256色)のグレースケール |
“P” | 8ビット(256色)のカラー |
“RGB” | 24ビット(約1,677万色)のRGBカラー |
“RGBA” | アルファチャンネル付き32ビットRGBA |
通常の画像は24ビットのRGB、もしくはアルファチャンネル付きの32ビットRGBAで表現されます。これを8ビットなどの少ないビット数の画像モードに変更すると、荒い画質になります。詳細を以下のサンプルコードで確認しましょう。
// サンプルプログラム
# coding: UTF-8 # 「PIL」ライブラリから「Image」クラスをimportする from PIL import Image # 画像ファイルを読み込む image = Image.open("Flower.png") # 画像データを「1ビット/白黒」に変換して保存する image.convert("1").save("Flower1.png") # 画像データを「8ビット/256色のグレースケール」に変換して保存する image.convert("L").save("FlowerL.png") # 画像データを「8ビット/256色のカラー」に変換して保存する image.convert("P").save("FlowerP.png") # 画像データを「アルファチャンネル付きRGB」に変換して保存する image.convert("RGBA").save("FlowerRGBA.png")
// 実行結果
Flower1.png FlowerL.png FlowerP.png FlowerRGBA.png
「”1″」や「”L”」にすると、白黒・グレースケールの画像になります。「”P”」にすると8ビットカラーになるので、元の画像や「”RGBA”」に変換したものと比べると、ピクセルの荒さが目立つ画像となります。あえて画像モードを変更することで、表現やデザインに活用できるでしょう。
前述したように、Pillowでは画像データに図形やテキストを描画できます。その際に便利なテクニックとして、以下の7つを解説します。
なお、図形やテキストを描画するときは、「Image.new()」関数で新規の画像データを作成すると便利です。また、Pillowで図形を描画するときは、まず「Drawオブジェクト」を生成する必要があります。
このDrawオブジェクトにある各関数を呼び出すことで、図形を描画できます。Drawオブジェクトは、最初にPILライブラリのImageDrawクラスをインポートしたうえで、以下の構文で生成しましょう。
Drawオブジェクト変数 = ImageDraw.Draw(イメージデータ)
直線・線分・ラインを描画したいときは、以下の構文で「line()」関数を呼び出します。なお今回のサンプルは、前述したDrawオブジェクトを「draw」という変数名で作成した場合のものです。
draw.line((始点X座標, 始点Y座標, 終点X座標, 終点Y座標), fill = (描画色R, 描画色G, 描画色B), width = 線の太さ)
第1引数は直線の描画座標・「fill引数」は描画色・「width引数」は線の太さを指定します。line()関数の使い方について、以下のサンプルコードで確認しましょう。
// サンプルプログラム
# coding: UTF-8 # 「PIL」ライブラリから「Image」クラスをimportする from PIL import Image # 「PIL」ライブラリから「ImageDraw」クラスをimportする from PIL import ImageDraw # 新規の画像データを作成する # 第1引数は「画像モード」⇒今回はRGBの3要素 # 第2引数は「画像サイズ」⇒今回は横512×縦512 # 第3引数は「背景色」⇒今回はRGBそれぞれ255のホワイト image = Image.new("RGB", (512, 512), (255, 255, 255)) # 図形やテキストを描画するための「Draw」オブジェクトを生成する draw = ImageDraw.Draw(image) # 横幅と高さのクォーター値を取得する quarter_width = int(image.width / 4) quarter_height = int(image.height / 4) # 直線を描画する draw.line((0, 0, image.width, image.height), fill = (0, 128, 256), width = 10) # 直線を描画する draw.line((0, image.height, image.width, 0), fill = (0, 128, 256), width = 10) # 直線を描画する draw.line((quarter_width, quarter_height, quarter_width * 3, quarter_height), fill = (0, 256, 128), width = 10) # 直線を描画する draw.line((quarter_width, quarter_height * 3, quarter_width * 3, quarter_height * 3), fill = (0, 256, 128), width = 10) # 直線を描画する draw.line((quarter_width, quarter_height, quarter_width, quarter_height * 3), fill = (256, 0, 64), width = 10) # 直線を描画する draw.line((quarter_width * 3, quarter_height, quarter_width * 3, quarter_height * 3), fill = (256, 0, 64), width = 10) # 既定の画像ソフトでデータを表示する image.show()
// 実行結果
重要なポイントは、最初に「from PIL import ImageDraw」として、ImageDrawクラスをインポートしていることです。そのうえで、「ImageDraw.Draw()」を呼び出して、Drawオブジェクトを作成しています。上記のサンプルプログラムでは、6本の直線を描画しています。画像サイズや描画座標を変更し、実行結果を確認してみましょう。
Pillowで矩形・四角形・長方形・正方形を描画するときは、以下の構文で「rectangle()」関数を呼び出します。
draw.rectangle((左上X座標, 左上Y座標, 右下X座標, 右下Y座標), outline = (枠線色R, 枠線色G, 枠線色B), fill = (描画色R, 描画色G, 描画色B), width = 線の太さ)
第1引数には矩形の描画座標、「outline引数」には枠線色、「fill引数」には描画色、「width引数」には線の太さを指定します。
重要なポイントは、矩形の枠線部分の色と、その内側の塗りつぶし色を個別に選べることです。rectangle()関数の使い方について、以下のサンプルコードで確認しましょう。
// サンプルプログラム
# coding: UTF-8 # 「PIL」ライブラリから「Image」クラスをimportする from PIL import Image # 「PIL」ライブラリから「ImageDraw」クラスをimportする from PIL import ImageDraw # 新規の画像データを作成する # 第1引数は「画像モード」⇒今回はRGBの3要素 # 第2引数は「画像サイズ」⇒今回は横512×縦512 # 第3引数は「背景色」⇒今回はRGBそれぞれ255のホワイト image = Image.new("RGB", (512, 512), (255, 255, 255)) # 図形やテキストを描画するための「Draw」オブジェクトを生成する draw = ImageDraw.Draw(image) # 横幅と高さのクォーター値を取得する quarter_width = int(image.width / 4) quarter_height = int(image.height / 4) # 矩形を描画する(outlineのみ) draw.rectangle((0, 0, quarter_width, quarter_height), outline = (0, 128, 256), width = 10) # 矩形を描画する(outlineのみ) draw.rectangle((quarter_width, 0, quarter_width * 2, quarter_height), outline = (0, 192, 128), width = 10) # 矩形を描画する(outlineのみ) draw.rectangle((quarter_width * 2, 0, quarter_width * 3, quarter_height), outline = (256, 0, 64), width = 10) # 矩形を描画する(outlineのみ) draw.rectangle((quarter_width * 3, 0, quarter_width * 4, quarter_height), outline = (256, 128, 0), width = 10) # 矩形を描画する(fillのみ) draw.rectangle((0, quarter_height, quarter_width, quarter_height * 2), fill = (256, 128, 0), width = 10) # 矩形を描画する(fillのみ) draw.rectangle((quarter_width, quarter_height, quarter_width * 2, quarter_height * 2), fill = (256, 0, 64), width = 10) # 矩形を描画する(fillのみ) draw.rectangle((quarter_width * 2, quarter_height, quarter_width * 3, quarter_height * 2), fill = (0, 192, 128), width = 10) # 矩形を描画する(fillのみ) draw.rectangle((quarter_width * 3, quarter_height, quarter_width * 4, quarter_height * 2), fill = (0, 128, 256), width = 10) # 矩形を描画する(outline + fill) draw.rectangle((0, quarter_height * 2, quarter_width, quarter_height * 3), outline = (0, 128, 256), fill = (0, 192, 128), width = 10) # 矩形を描画する(outline + fill) draw.rectangle((quarter_width, quarter_height * 2, quarter_width * 2, quarter_height * 3), outline = (0, 192, 128), fill = (0, 128, 256), width = 10) # 矩形を描画する(outline + fill) draw.rectangle((quarter_width * 2, quarter_height * 2, quarter_width * 3, quarter_height * 3), outline = (256, 0, 64), fill = (256, 128, 0), width = 10) # 矩形を描画する(outline + fill) draw.rectangle((quarter_width * 3, quarter_height * 2, quarter_width * 4, quarter_height * 3), outline = (256, 128, 0), fill = (256, 0, 64), width = 10) # 既定の画像ソフトでデータを表示する image.show()
// 実行結果
上記のサンプルプログラムは、枠線色と塗りつぶし色の片方、もしくは双方を指定した場合の変化を示すものです。重要なポイントは、矩形のサイズはトータルのサイズになるということです。枠線の色を指定したときは、指定しなかった場合より塗りつぶし部分のサイズが小さくなります。
枠線の太さに応じて、塗りつぶし部分のサイズが自動調整されるので、プログラマが意識する必要はありません。
なお、矩形の頂点を丸めたい場合は、「rounded_rectangle()」関数を使用しましょう。ただし、追加で「radius引数」に丸める部分の半径を指定する必要があります。詳細は以下のサンプルコードのとおりです。
// サンプルプログラム
# coding: UTF-8 # 「PIL」ライブラリから「Image」クラスをimportする from PIL import Image # 「PIL」ライブラリから「ImageDraw」クラスをimportする from PIL import ImageDraw # 新規の画像データを作成する # 第1引数は「画像モード」⇒今回はRGBの3要素 # 第2引数は「画像サイズ」⇒今回は横512×縦512 # 第3引数は「背景色」⇒今回はRGBそれぞれ255のホワイト image = Image.new("RGB", (512, 512), (255, 255, 255)) # 図形やテキストを描画するための「Draw」オブジェクトを生成する draw = ImageDraw.Draw(image) # 横幅と高さのクォーター値を取得する quarter_width = int(image.width / 4) quarter_height = int(image.height / 4) # 頂点を丸めた矩形を描画する(outlineのみ) draw.rounded_rectangle((0, 0, quarter_width, quarter_height), radius = 30, outline = (0, 128, 256), width = 10) # 頂点を丸めた矩形を描画する(outlineのみ) draw.rounded_rectangle((quarter_width, 0, quarter_width * 2, quarter_height), radius = 30, outline = (0, 192, 128), width = 10) # 頂点を丸めた矩形を描画する(outlineのみ) draw.rounded_rectangle((quarter_width * 2, 0, quarter_width * 3, quarter_height), radius = 30, outline = (256, 0, 64), width = 10) # 頂点を丸めた矩形を描画する(outlineのみ) draw.rounded_rectangle((quarter_width * 3, 0, quarter_width * 4, quarter_height), radius = 30, outline = (256, 128, 0), width = 10) # 頂点を丸めた矩形を描画する(fillのみ) draw.rounded_rectangle((0, quarter_height, quarter_width, quarter_height * 2), radius = 30, fill = (256, 128, 0), width = 10) # 頂点を丸めた矩形を描画する(fillのみ) draw.rounded_rectangle((quarter_width, quarter_height, quarter_width * 2, quarter_height * 2), radius = 30, fill = (256, 0, 64), width = 10) # 頂点を丸めた矩形を描画する(fillのみ) draw.rounded_rectangle((quarter_width * 2, quarter_height, quarter_width * 3, quarter_height * 2), radius = 30, fill = (0, 192, 128), width = 10) # 頂点を丸めた矩形を描画する(fillのみ) draw.rounded_rectangle((quarter_width * 3, quarter_height, quarter_width * 4, quarter_height * 2), radius = 30, fill = (0, 128, 256), width = 10) # 頂点を丸めた矩形を描画する(outline + fill) draw.rounded_rectangle((0, quarter_height * 2, quarter_width, quarter_height * 3), radius = 30, outline = (0, 128, 256), fill = (0, 192, 128), width = 10) # 頂点を丸めた矩形を描画する(outline + fill) draw.rounded_rectangle((quarter_width, quarter_height * 2, quarter_width * 2, quarter_height * 3), radius = 30, outline = (0, 192, 128), fill = (0, 128, 256), width = 10) # 頂点を丸めた矩形を描画する(outline + fill) draw.rounded_rectangle((quarter_width * 2, quarter_height * 2, quarter_width * 3, quarter_height * 3), radius = 30, outline = (256, 0, 64), fill = (256, 128, 0), width = 10) # 頂点を丸めた矩形を描画する(outline + fill) draw.rounded_rectangle((quarter_width * 3, quarter_height * 2, quarter_width * 4, quarter_height * 3), radius = 30, outline = (256, 128, 0), fill = (256, 0, 64), width = 10) # 既定の画像ソフトでデータを表示する image.show()
// 実行結果
なお、rectangle()とrounded_rectangle()のいずれも、矩形のサイズを調整することで長方形も描画できます。
円周の一部である「円弧」は、「arc()」関数で自由に描画できます。arc()の構文は以下のとおりです。
draw.arc((左上X座標, 左上Y座標, 右下X座標, 右下Y座標), start = 始点角度, end = 終点角度, fill = (描画色R, 描画色G, 描画色B), width = 線の太さ)
arc()関数は、引数の内容が少しわかりづらいので注意が必要です。第1引数には、rectangle()と同じように矩形の座標を指定します。この矩形に当てはまるように、円弧の描画位置と直径が自動的に調整されます。
「start引数」と「end引数」は、それぞれ円弧の始点・終点の角度を設定するためのものです。12時方向ではなく、3時方向が「0」となり、時計回りの方向が正の値となります。たとえば、startに0・endに180を指定すると、3時方向から9時方向まで円弧が描画されます。arc()関数の詳細は、以下のサンプルコードのとおりです。
// サンプルプログラム
# coding: UTF-8 # 「PIL」ライブラリから「Image」クラスをimportする from PIL import Image # 「PIL」ライブラリから「ImageDraw」クラスをimportする from PIL import ImageDraw # 新規の画像データを作成する # 第1引数は「画像モード」⇒今回はRGBの3要素 # 第2引数は「画像サイズ」⇒今回は横512×縦512 # 第3引数は「背景色」⇒今回はRGBそれぞれ255のホワイト image = Image.new("RGB", (512, 512), (255, 255, 255)) # 図形やテキストを描画するための「Draw」オブジェクトを生成する draw = ImageDraw.Draw(image) # 横幅と高さのハーフ値を取得する half_width = int(image.width / 2) half_height = int(image.height / 2) # 45度~135度までの円弧を描画する draw.arc((0, 0, half_width, half_height), start = 45, end = 135, fill = (0, 128, 256), width = 10) # 270度~0度までの円弧を描画する draw.arc((half_width, 0, half_width * 2, half_height), start = 270, end = 0, fill = (0, 128, 256), width = 10) # 0度~180度までの円弧を描画する draw.arc((0, half_height, half_width, half_height * 2), start = 0, end = 180, fill = (0, 128, 256), width = 10) # 0度~360度までの円弧を描画する draw.arc((half_width, half_height, half_width * 2, half_height * 2), start = 0, end = 360, fill = (0, 128, 256), width = 10) # 既定の画像ソフトでデータを表示する image.show()
// 実行結果
なお、円弧の内側を塗りつぶしたい場合は、以下のように「chord()」関数を使用しましょう。chord()の引数は、先ほどのarc()と同じです。
// サンプルプログラム
# coding: UTF-8 # 「PIL」ライブラリから「Image」クラスをimportする from PIL import Image # 「PIL」ライブラリから「ImageDraw」クラスをimportする from PIL import ImageDraw # 新規の画像データを作成する # 第1引数は「画像モード」⇒今回はRGBの3要素 # 第2引数は「画像サイズ」⇒今回は横512×縦512 # 第3引数は「背景色」⇒今回はRGBそれぞれ255のホワイト image = Image.new("RGB", (512, 512), (255, 255, 255)) # 図形やテキストを描画するための「Draw」オブジェクトを生成する draw = ImageDraw.Draw(image) # 横幅と高さのハーフ値を取得する half_width = int(image.width / 2) half_height = int(image.height / 2) # 45度~135度までの円弧を描画して内部を塗りつぶす draw.chord((0, 0, half_width, half_height), start = 45, end = 135, fill = (0, 128, 256), width = 10) # 270度~0度までの円弧を描画して内部を塗りつぶす draw.chord((half_width, 0, half_width * 2, half_height), start = 270, end = 0, fill = (0, 128, 256), width = 10) # 0度~180度までの円弧を描画して内部を塗りつぶす draw.chord((0, half_height, half_width, half_height * 2), start = 0, end = 180, fill = (0, 128, 256), width = 10) # 0度~360度までの円弧を描画して内部を塗りつぶす draw.chord((half_width, half_height, half_width * 2, half_height * 2), start = 0, end = 360, fill = (0, 128, 256), width = 10) # 既定の画像ソフトでデータを表示する image.show()
// 実行結果
さらに、単に内側を塗りつぶすのではなく、スライスしたときのように「扇型」の図形を描画したい場合は、pieslice()関数を使用しましょう。引数はarc()やchord()と同じです。
// サンプルプログラム
# coding: UTF-8 # 「PIL」ライブラリから「Image」クラスをimportする from PIL import Image # 「PIL」ライブラリから「ImageDraw」クラスをimportする from PIL import ImageDraw # 新規の画像データを作成する # 第1引数は「画像モード」⇒今回はRGBの3要素 # 第2引数は「画像サイズ」⇒今回は横512×縦512 # 第3引数は「背景色」⇒今回はRGBそれぞれ255のホワイト image = Image.new("RGB", (512, 512), (255, 255, 255)) # 図形やテキストを描画するための「Draw」オブジェクトを生成する draw = ImageDraw.Draw(image) # 横幅と高さのハーフ値を取得する half_width = int(image.width / 2) half_height = int(image.height / 2) # 45度~135度までの円弧を描画して、内部を扇型に塗りつぶす draw.pieslice((0, 0, half_width, half_height), start = 45, end = 135, fill = (0, 128, 256), width = 10) # 270度~0度までの円弧を描画して、内部を扇型に塗りつぶす draw.pieslice((half_width, 0, half_width * 2, half_height), start = 270, end = 0, fill = (0, 128, 256), width = 10) # 0度~180度までの円弧を描画して、内部を扇型に塗りつぶす draw.pieslice((0, half_height, half_width, half_height * 2), start = 0, end = 180, fill = (0, 128, 256), width = 10) # 0度~360度までの円弧を描画して、内部を扇型に塗りつぶす draw.pieslice((half_width, half_height, half_width * 2, half_height * 2), start = 0, end = 360, fill = (0, 128, 256), width = 10) # 既定の画像ソフトでデータを表示する image.show()
// 実行結果
なお、今回のサンプルプログラムでは正円を描画していますが、arc()・chord()・pieslice()のいずれも矩形のサイズ調整で楕円の円弧を描画できます。
円や楕円を描画する場合は、以下のように「ellipse()」関数を使用します。
draw.ellipse((左上X座標, 左上Y座標, 右下X座標, 右下Y座標), outline = (枠線色R, 枠線色G, 枠線色B), fill = (描画色R, 描画色G, 描画色B), width = 線の太さ)
第1引数は、先ほどのarc()・chord()・pieslice()と同じく、円が収まる矩形を指定します。矩形を長方形にすることで、正円ではなく楕円を描画できます。その他の引数は、rectangle()と同様に、枠線と塗りつぶし色を個別に指定可能です。ellipse()の使い方について、以下のサンプルコードで確認しましょう。
// サンプルプログラム
# coding: UTF-8 # 「PIL」ライブラリから「Image」クラスをimportする from PIL import Image # 「PIL」ライブラリから「ImageDraw」クラスをimportする from PIL import ImageDraw # 新規の画像データを作成する # 第1引数は「画像モード」⇒今回はRGBの3要素 # 第2引数は「画像サイズ」⇒今回は横512×縦512 # 第3引数は「背景色」⇒今回はRGBそれぞれ255のホワイト image = Image.new("RGB", (512, 512), (255, 255, 255)) # 図形やテキストを描画するための「Draw」オブジェクトを生成する draw = ImageDraw.Draw(image) # 横幅と高さのクォーター値を取得する quarter_width = int(image.width / 4) quarter_height = int(image.height / 4) # 円を描画する(outlineのみ) draw.ellipse((0, 0, quarter_width, quarter_height), outline = (0, 128, 256), width = 10) # 円を描画する(outlineのみ) draw.ellipse((quarter_width, 0, quarter_width * 2, quarter_height), outline = (0, 192, 128), width = 10) # 円を描画する(outlineのみ) draw.ellipse((quarter_width * 2, 0, quarter_width * 3, quarter_height), outline = (256, 0, 64), width = 10) # 円を描画する(outlineのみ) draw.ellipse((quarter_width * 3, 0, quarter_width * 4, quarter_height), outline = (256, 128, 0), width = 10) # 円を描画する(fillのみ) draw.ellipse((0, quarter_height, quarter_width, quarter_height * 2), fill = (256, 128, 0), width = 10) # 円を描画する(fillのみ) draw.ellipse((quarter_width, quarter_height, quarter_width * 2, quarter_height * 2), fill = (256, 0, 64), width = 10) # 円を描画する(fillのみ) draw.ellipse((quarter_width * 2, quarter_height, quarter_width * 3, quarter_height * 2), fill = (0, 192, 128), width = 10) # 円を描画する(fillのみ) draw.ellipse((quarter_width * 3, quarter_height, quarter_width * 4, quarter_height * 2), fill = (0, 128, 256), width = 10) # 円を描画する(outline + fill) draw.ellipse((0, quarter_height * 2, quarter_width, quarter_height * 3), outline = (0, 128, 256), fill = (0, 192, 128), width = 10) # 円を描画する(outline + fill) draw.ellipse((quarter_width, quarter_height * 2, quarter_width * 2, quarter_height * 3), outline = (0, 192, 128), fill = (0, 128, 256), width = 10) # 円を描画する(outline + fill) draw.ellipse((quarter_width * 2, quarter_height * 2, quarter_width * 3, quarter_height * 3), outline = (256, 0, 64), fill = (256, 128, 0), width = 10) # 円を描画する(outline + fill) draw.ellipse((quarter_width * 3, quarter_height * 2, quarter_width * 4, quarter_height * 3), outline = (256, 128, 0), fill = (256, 0, 64), width = 10) # 既定の画像ソフトでデータを表示する image.show()
// 実行結果
形のある図形ではなく点やドットを描画したい場合は、「point()」関数を実行しましょう。
draw.point(座標値のリスト, fill = 描画色)
第1引数には、「X座標・Y座標」という並びのリストを引き渡します。「fill引数」はほかの関数と同じく、描画色を指定します。詳細は以下のサンプルコードのとおりです。
// サンプルプログラム
# coding: UTF-8 # 「PIL」ライブラリから「Image」クラスをimportする from PIL import Image # 「PIL」ライブラリから「ImageDraw」クラスをimportする from PIL import ImageDraw # 「random」ライブラリをimportする import random # points()関数|座標値をランダムに生成する def points(): points = [] # 10000個の座標値を生成する for i in range(10000): points.append(random.randint(0, image.width)) # X座標 points.append(random.randint(0, image.height)) # Y座標 return points # 新規の画像データを作成する # 第1引数は「画像モード」⇒今回はRGBの3要素 # 第2引数は「画像サイズ」⇒今回は横512×縦512 # 第3引数は「背景色」⇒今回はRGBそれぞれ255のホワイト image = Image.new("RGB", (512, 512), (255, 255, 255)) # 図形やテキストを描画するための「Draw」オブジェクトを生成する draw = ImageDraw.Draw(image) # 点を描画する draw.point(points(), fill = (0, 128, 256)) # 点を描画する draw.point(points(), fill = (0, 192, 128)) # 点を描画する draw.point(points(), fill = (256, 0, 64)) # 点を描画する draw.point(points(), fill = (256, 128, 0)) # 既定の画像ソフトでデータを表示する image.show()
// 実行結果
なお、上記のサンプルプログラムではrandomライブラリの関数を使用して、無数の座標値をランダムに生成しています。
多角形の描画は、以下の構文で「polygon()」関数を呼び出すことで行えます。
draw.polygon(座標値のリスト, outline = (枠線色R, 枠線色G, 枠線色B), fill = (描画色R, 描画色G, 描画色B))
第1引数には、多角形の頂点リストを指定します。重要なポイントは、頂点リストの順番どおりに描画されることです。頂点の順番が崩れていると、図形がねじれるなどして正常に表示されなくなります。時計回り・反時計回りはどちらでも問題ないので、頂点の順番を意識しましょう。詳細は以下のサンプルコードのとおりです。
// サンプルプログラム
# coding: UTF-8 # 「PIL」ライブラリから「Image」クラスをimportする from PIL import Image # 「PIL」ライブラリから「ImageDraw」クラスをimportする from PIL import ImageDraw # 新規の画像データを作成する # 第1引数は「画像モード」⇒今回はRGBの3要素 # 第2引数は「画像サイズ」⇒今回は横512×縦512 # 第3引数は「背景色」⇒今回はRGBそれぞれ255のホワイト image = Image.new("RGB", (512, 512), (255, 255, 255)) # 図形やテキストを描画するための「Draw」オブジェクトを生成する draw = ImageDraw.Draw(image) # 横幅と高さのオクターバー値を取得する octava_width = int(image.width / 8) octava_height = int(image.height / 8) # 多角形の頂点を定義する points = [octava_width * 4, octava_height * 1, octava_width * 6, octava_height * 3, octava_width * 7, octava_height * 5, octava_width * 4, octava_height * 7, octava_width * 1, octava_height * 5, octava_width * 2, octava_height * 3, octava_width * 4, octava_height * 1] # 多角形を描画する draw.polygon(points, outline = (0, 128, 256), fill = (0, 192, 128)) # 既定の画像ソフトでデータを表示する image.show()
// 実行結果
なお、正多角形を描画する場合は「regular_polygon()」関数を利用すると、頂点をひとつずつ手動で設定しなくても、「境界円」と「頂点数」だけ指定すれば自動的に描画できます。regular_polygon()の構文は以下のとおりです。
draw.regular_polygon((境界円の中心X座標, 境界円の中心Y座標, 境界円の半径), n_sides = 頂点数, rotation = 回転角度, outline = (枠線色R, 枠線色G, 枠線色B), fill = (描画色R, 描画色G, 描画色B))
第1引数には、境界円の座標とサイズを設定します。描画される図形は、この境界円の内部に収まるように、座標とサイズが自動的に調整されます。図形の頂点数については、「n_sides引数」で指定することがポイントです。回転角度は「rotation引数」で指定します。regular_polygon()関数の使い方は、以下のサンプルコードのとおりです。
// サンプルプログラム
# coding: UTF-8 # 「PIL」ライブラリから「Image」クラスをimportする from PIL import Image # 「PIL」ライブラリから「ImageDraw」クラスをimportする from PIL import ImageDraw # 新規の画像データを作成する # 第1引数は「画像モード」⇒今回はRGBの3要素 # 第2引数は「画像サイズ」⇒今回は横512×縦512 # 第3引数は「背景色」⇒今回はRGBそれぞれ255のホワイト image = Image.new("RGB", (512, 512), (255, 255, 255)) # 図形やテキストを描画するための「Draw」オブジェクトを生成する draw = ImageDraw.Draw(image) # 横幅と高さのクォーター値を取得する quarter_width = int(image.width / 4) quarter_height = int(image.height / 4) # 境界円の半径を算出する radius = min(quarter_width, quarter_height) # 正多角形を描画する(正5角形) draw.regular_polygon((quarter_width, quarter_height, radius), n_sides = 5, rotation = 0, outline = (0, 128, 256), fill = (0, 192, 128)) # 正多角形を描画する(正7角形) draw.regular_polygon((quarter_width * 3, quarter_height, radius), n_sides = 7, rotation = 30, outline = (0, 192, 128), fill = (0, 128, 256)) # 正多角形を描画する(正8角形) draw.regular_polygon((quarter_width, quarter_height * 3, radius), n_sides = 8, rotation = 60, outline = (256, 0, 64), fill = (256, 128, 0)) # 正多角形を描画する(正10角形) draw.regular_polygon((quarter_width * 3, quarter_height * 3, radius), n_sides = 10, rotation = 90, outline = (256, 128, 0), fill = (256, 0, 64)) # 既定の画像ソフトでデータを表示する image.show()
// 実行結果
テキスト・文字列・ラベルを描画するためには、まずフォントオブジェクトを生成する必要があります。「PILライブラリ」から「ImageFontクラス」をインポートしたうえで、以下の構文で「Fontオブジェクト」を作成しましょう。
フォントオブジェクト = ImageFont.truetype("フォントファイルのパス", フォントサイズ)
第1引数には、フォントのファイルパスを設定します。ここには、OSのシステムファイルに格納されている、実際のフォントファイルを指定しないといけません。Windows PCの場合は、「C:\Windows\Fonts」を開くとフォントファイルの一覧を確認できます。
必要なフォントファイルを右クリックし、「開く」もしくは「新しいウィンドウで開く」を選択します。フォントの種類が表示されるので、今度は「プロパティ」へ進みましょう。
プロパティ画面で実際のファイル名が表示されるので、確認しておきましょう。メイリオフォントの場合、ファイルパスは「C:\Windows\Fonts\メイリオ\meiryo.ttc」になるということです。
フォントを作成したら、以下の構文で「text()」関数を呼び出し、テキストの詳細を設定します。
draw.text((フォントの描画開始X座標, フォントの描画開始Y座標), text = "描画テキスト", font = フォントオブジェクト, fill = (描画色R, 描画色G, 描画色B))
Pillowでフォントを生成し、テキストを描画する方法について、サンプルコードを交えて解説します。
// サンプルプログラム
# coding: UTF-8 # 「PIL」ライブラリから「Image」クラスをimportする from PIL import Image # 「PIL」ライブラリから「ImageDraw」クラスをimportする from PIL import ImageDraw # 「PIL」ライブラリから「ImageFont」クラスをimportする from PIL import ImageFont # 新規の画像データを作成する # 第1引数は「画像モード」⇒今回はRGBの3要素 # 第2引数は「画像サイズ」⇒今回は横512×縦512 # 第3引数は「背景色」⇒今回はRGBそれぞれ255のホワイト image = Image.new("RGB", (512, 512), (255, 255, 255)) # 図形やテキストを描画するための「Draw」オブジェクトを生成する draw = ImageDraw.Draw(image) # テキストを描画するための「Font」オブジェクトを生成する font = ImageFont.truetype("C:\Windows\Fonts\メイリオ\meiryo.ttc", 64) # 画面上にテキストを描画する draw.text((0, 0), text = "サンプルテキスト", font = font, fill = (0, 128, 256)) # 横幅と高さのクォーター値を取得する quarter_width = int(image.width / 4) quarter_height = int(image.height / 4) # 画面上にテキストを描画する # 改行文字「\n」を挟むと改行できる # 「align引数」は"left"・"center"・"right"の3種類から選び、改行時の位置調整を行う draw.text((0, quarter_height), text = "サンプル\nテキスト\n改行\n可能", font = font, fill = (256, 0, 64), align = "right") # 既定の画像ソフトでデータを表示する image.show()
// 実行結果
前述した方法で、ほかのフォントも利用して、実行速度を確認してみましょう。なお、テキストに改行文字「\n」を挟むことで、その部分に改行を挿入できます。複数行を描画する場合は、「align引数」を「”left”」「”center”」「”right”」いずれかにすることで、改行時のテキストを左寄せ・中央寄せ・右寄せに設定できます。なお、Pillowには「multiline_text()」という関数もありますが、引数や実行結果はtext()と同じです。
背景にボックスなどを配置して、そのうえにテキストを描画したいこともあるでしょう。その場合は、以下の構文で「textbbox()」もしくは「multiline_textbbox()」を使用すると、テキストが収まるサイズの矩形を取得できます。
矩形 = draw.textbbox((フォントの描画開始X座標, フォントの描画開始Y座標), text = "描画テキスト", font = フォントオブジェクト)
以上の方法で取得した矩形を、先ほど解説したrectangle()に引き渡すことで、適切なサイズの矩形を描画したうえでテキストを表示できます。
// サンプルプログラム
# coding: UTF-8 # 「PIL」ライブラリから「Image」クラスをimportする from PIL import Image # 「PIL」ライブラリから「ImageDraw」クラスをimportする from PIL import ImageDraw # 「PIL」ライブラリから「ImageFont」クラスをimportする from PIL import ImageFont # 新規の画像データを作成する # 第1引数は「画像モード」⇒今回はRGBの3要素 # 第2引数は「画像サイズ」⇒今回は横512×縦512 # 第3引数は「背景色」⇒今回はRGBそれぞれ255のホワイト image = Image.new("RGB", (512, 512), (255, 255, 255)) # 図形やテキストを描画するための「Draw」オブジェクトを生成する draw = ImageDraw.Draw(image) # テキストを描画するための「Font」オブジェクトを生成する font = ImageFont.truetype("C:\Windows\Fonts\メイリオ\meiryo.ttc", 64) # 描画するテキストを設定する text = "サンプル\nテキスト\n改行\n可能" # テキストが収まるサイズの矩形を取得する box = draw.textbbox((0, 0), text, font = font) # 取得したサイズの矩形を描画する draw.rectangle(box, fill = (0, 128, 256), width = 10) # 画面上にテキストを描画する draw.text((0, 0), text, font = font, fill = (256, 256, 256), align = "center") # 既定の画像ソフトでデータを表示する image.show()
// 実行結果
発展的な画像処理テクニックについて、以下の7つを解説します。
ある画像に別の画像を貼り付けることも、主要な画像処理のひとつです。Pillowでは以下のように「paste()」関数を使うことで、対象の画像に任意の画像を貼り付けられます。
image.paste(貼り付けたい画像, (左上X座標, 左上Y座標, 右下X座標, 右下Y座標))
第1引数には貼り付けたい画像のデータ、第2引数には貼り付ける先の座標を指定します。ただし、画像の一部分だけを貼り付けることはできないので、第2引数に指定する矩形の大きさは画像サイズと一致していないといけません。
またpaste()は画像データを上書きするため、元画像を保持したい場合は前述した「copy()」でコピーしておく必要があります。paste()の使い方について、以下のサンプルコードで確認しましょう。
// サンプルプログラム
# coding: UTF-8 # 「PIL」ライブラリから「Image」クラスをimportする from PIL import Image # 画像ファイルを読み込む image = Image.open("Flower.png") # トリミングする画像のサイズを設定する trim_width = 460 trim_height = 440 # 画像データをトリミングする # (380, 180)を視点として、X方向に460・Y方向に440トリミングする trimmed = image.crop((380, 180, 380 + trim_width, 180 + trim_height)) # トリミングした画像を最初の画像の左下に貼り付ける image.paste(trimmed, (0, image.height - trim_height, trim_width, image.height)) # 作成した部分をファイルに書き出す image.save("FlowerPasted.png") # 既定の画像ソフトでデータを表示する image.show()
// 実行結果
上記のサンプルプログラムは、先ほど紹介したtrim()関数でトリミングした画像を、元の画像に貼り付けたものです。このように関数で指定した部分に、別の画像を重ねて表示できます。なお、paste()関数には「mask引数」を指定することもできます。切り抜きたい部分をマスク画像で指定することで、一部のみ貼り付けることが可能です。
マスク画像とは、以下のような画像を指し、白に近い部分ほど色が濃くなります。
便利な機能ではありますが、マスク画像は貼り付ける画像と同じサイズであるうえに、以下のいずれかの画像形式でなければいけません。
「1」の場合は、完全な白と黒の2種類しか選べないので、色の合成はできません。「RGBA」の場合は、アルファ値で細かなマスクを指定できます。マスク画像でよく使われるのが「L」で、0~255までの256段階でマスク値を指定可能です。
上記のマスク画像で、黒の部分は貼り付けに反映されず、白の部分が完全に反映されます。一方で、グレーの部分は元画像と貼り付け画像がブレンドされます。詳細は以下のサンプルコードのとおりです。
// サンプルプログラム
# coding: UTF-8 # 「PIL」ライブラリから「Image」クラスをimportする from PIL import Image # 画像ファイルを読み込む image = Image.open("Flower.png") # トリミングする画像のサイズを設定する trim_width = 460 trim_height = 440 # 画像データをトリミングする # (380, 180)を視点として、X方向に460・Y方向に440トリミングする trimmed = image.crop((380, 180, 380 + trim_width, 180 + trim_height)) # マスク用の画像ファイルを読み込む mask = Image.open("Mask.png") # トリミングした画像にマスクを適用したうえで、最初の画像の左下に貼り付ける # なお、マスク画像は「8ビットのグレースケール画像」なので、0~255の間でマスクレベルを指定できる # 255に近いほど貼り付け元の色に近くなり、0に近いほど貼り付け先の色に近くなる image.paste(trimmed, (0, image.height - trim_height, trim_width, image.height), mask) # 作成した部分をファイルに書き出す image.save("FlowerPastedMasked.png") # 既定の画像ソフトでデータを表示する image.show()
// 実行結果
以上のように、貼り付けたい画像を自由な形に切り抜いたうえで、貼り付けられます。マスク画像のグレーの三角形の部分は、両者の色が半分ずつくらいの割合でブレンドされています。
このようにマスク画像の値を操作することでブレンドの比率を指定できるので、ぜひ活用してみましょう。
2枚の画像をアルファチャンネル付きで合成したい場合は、「Image.composite()」関数を利用しましょう。
合成後の画像データ = Image.composite(1枚目の画像データ, 2枚目の画像データ, マスク画像)
// サンプルプログラム
# coding: UTF-8 # 「PIL」ライブラリから「Image」クラスをimportする from PIL import Image # 画像ファイルを読み込む # 2枚目の画像サイズは1枚目に合わせる # 2枚の画像サイズが一致していない場合は、Image.composite()でエラーが発生する flower = Image.open("Flower.png") sky = Image.open("Sky.png").resize(flower.size) # グレースケールのマスク画像を背景色64で生成する mask = Image.new("L", sky.size, 64) # 1枚目の画像に、マスクを適用した2枚目の画像を合成する image = Image.composite(flower, sky, mask) # 合成した画像データを保存する image.save("composite_flower_sky.png")
// 実行結果
なお、「composite()」関数は2枚の画像サイズが一致していないとエラーになります。異なるサイズの画像を合成する場合は、前述した「paste()」関数を使用しましょう。paste()であれば、2枚目とマスク画像のサイズは一致している必要がありますが、1枚目と2枚目のサイズは異なっていてもOKです。
画像の上下左右を反転させる必要があるときは、以下の構文で「transpose()」関数を使用しましょう。
反転後の画像データ = 画像データ.transpose(反転方法)
引数には以下の7種類の値を指定できます。詳細について、サンプルコードを交えて解説します。
// サンプルプログラム
# coding: UTF-8 # 「PIL」ライブラリから「Image」クラスをimportする from PIL import Image # 画像ファイルを読み込む image = Image.open("Flower.png") # 画像の左右を反転させる left_right = image.transpose(Image.FLIP_LEFT_RIGHT) left_right.save("left_right.png") # 画像の上下を反転させる top_bottom = image.transpose(Image.FLIP_TOP_BOTTOM) top_bottom.save("top_buttom.png") # 画像を90度回転させる rotate_90 = image.transpose(Image.ROTATE_90) rotate_90.save("rotate_90.png") # 画像を180度回転させる rotate_180 = image.transpose(Image.ROTATE_180) rotate_180.save("rotate_180.png") # 画像を270度回転させる rotate_270 = image.transpose(Image.ROTATE_270) rotate_270.save("rotate_270.png") # 画像の上下左右を反転させる transpose = image.transpose(Image.TRANSPOSE) transpose.save("transpose.png") # 画像の上下左右を反転させる transverse = image.transpose(Image.TRANSVERSE) transverse.save("transverse.png")
// 実行結果
left_right.png
top_bottom.png
rotate_90.png
rotate_180.png
rotate_270.png
transpose.png
transverse.png
反転方法の値によって、実行結果がそれぞれ異なります。単純なソースコードで画像の向きを反転できるので、ぜひ活用してみましょう。
画像の明度操作は、「point()」関数で行えます。point()は、各ピクセルの成分を操作するための関数であり、明度については以下の構文のとおりです。
変換後の画像データ = 画像データ.point(lambda x: x * 明度の倍率)
引数内部には、「ラムダ式」を使って処理内容を記載する。上記の場合は、ラムダ式の内部で「画素の値に倍率をかける」ものです。詳細を以下のサンプルコードで確認しましょう。
// サンプルプログラム
# coding: UTF-8 # 「PIL」ライブラリから「Image」クラスをimportする from PIL import Image # 画像ファイルを読み込む image = Image.open("Flower.png") # 画像の明るさを2倍にする light_image = image.point(lambda x: x * 2.0) light_image.save("light_image.png") # 画像の明るさを半分にする dark_image = image.point(lambda x: x * 0.5) dark_image.save("dark_image.png")
// 実行結果
light_image.png
dark_image.png
上記のように、2.0倍にしたときは画像が明るくなり、0.5倍の場合は暗くなります。詳細は後述しますが、point()は画素をさまざまな方法で操作できる非常に便利な関数です。
「ネガポジ反転」とは、画像の色を反転させる操作です。たとえば、白い部分は黒くなり、黒い部分は白くなります。カラーの部分については、「255」から現在の値を引いて求めます。たとえば、RGBが(80, 150, 210)の部分をネガポジ反転させると、(175, 105, 45)です。PIL・Pillowでは、「ImageOps.invert()」関数を使うことで、ネガポジ反転を行うことが可能です。
変換後の画像データ = ImageOps.invert(元の画像データ)
引数には画像データを指定し、戻り値として変換後の画像が返ります。ImageOps.invert()の使い方は、以下のサンプルコードのとおりです。
// サンプルプログラム
# coding: UTF-8 # 「PIL」ライブラリから「Image」クラスをimportする from PIL import Image # 「PIL」ライブラリから「ImageOps」クラスをimportする from PIL import ImageOps # 画像ファイルを読み込む image = Image.open("Flower.png") # ネガポジを反転させる inverted = ImageOps.invert(image) # 変換した画像データを保存する inverted.save("inverted.png")
// 実行結果
上記のように、カラー画像をネガポジ反転させると、「ネガフィルム」のような色調になります。
カラー画像は、基本的に「RGB」の3成分で構成されています。それらを分解することで、画像処理に役立つことがあります。画像成分の分解は、「split()」関数で行うことが可能です。
R要素, G要素, B要素 = 画像データ.split()
split()は戻り値として各成分を返すため、コンマで区切って変数に代入します。詳細は以下のサンプルコードのとおりです。
// サンプルプログラム
# coding: UTF-8 # 「PIL」ライブラリから「Image」クラスをimportする from PIL import Image # 画像ファイルを読み込む image = Image.open("Flower.png") # 画像データをR・G・Bの成分ごとに抽出する r, g, b = image.split() # 抽出したデータを画像として保存する r.save("red_elements.png") g.save("green_elements.png") b.save("blue_elements.png")
// 実行結果
red_elements.png
green_elements.png
blue_elements.png
画像の2値化・閾値処理も、PIL・Pillowで行えます。ただし、モノクロ画像とカラー画像の場合で方法が異なるので、注意が必要です。モノクロ画像で2値化する場合は、以下の構文でpoint()関数を使用します。
変換後の画像データ = 画像データ.point(lambda x: 1 if x >= 閾値 else 0, mode = "1")
あらかじめ画像をモノクロ化しておかないと2値化できないので、まず「image = image.convert(“L”)」としておきましょう。閾値については、128前後にするのが一般的です。
// サンプルプログラム
# coding: UTF-8 # 「PIL」ライブラリから「Image」クラスをimportする from PIL import Image # 画像ファイルを読み込む image = Image.open("Flower.png") # 画像データをモノクロに変換する image = image.convert("L") # 閾値を64にしてカラー画像を2値化する threshold = 64 threshold_mono_64 = image.point(lambda x: 1 if x >= threshold else 0, mode = "1") # 2値化したデータを画像として保存する threshold_mono_64.save("threshold_mono_64.png") # 閾値を128にしてカラー画像を2値化する threshold = 128 threshold_mono_128 = image.point(lambda x: 1 if x >= threshold else 0, mode = "1") # 2値化したデータを画像として保存する threshold_mono_128.save("threshold_mono_128.png") # 閾値を192にしてカラー画像を2値化する threshold = 192 threshold_mono_192 = image.point(lambda x: 1 if x >= threshold else 0, mode = "1") # 2値化したデータを画像として保存する threshold_mono_192.save("threshold_mono_192.png")
// 実行結果
threshold_mono_64.png
threshold_mono_128.png
threshold_mono_192.png
RGBの各要素ごとに2値化した画像を生成する場合は、以下のサンプルコードのように進めましょう。
// サンプルプログラム
# coding: UTF-8 # 「PIL」ライブラリから「Image」クラスをimportする from PIL import Image # 画像ファイルを読み込む image = Image.open("Flower.png") # 画像データをR・G・Bの成分ごとに抽出する r, g, b = image.split() # 閾値を設定する threshold = 128 # 各成分を2値化する r = r.point(lambda x: 1 if x >= threshold else 0, mode = "1") g = g.point(lambda x: 1 if x >= threshold else 0, mode = "1") b = b.point(lambda x: 1 if x >= threshold else 0, mode = "1") # 抽出したデータを画像として保存する r.save("red_elements_binary.png") g.save("green_elements_binary.png") b.save("blue_elements_binary.png")
// 実行結果
red_elements_binary.png
green_elements_binary.png
blue_elements_binary.png
カラー画像の2値化・閾値処理を行う場合は、point()関数を以下のとおり使用しましょう。
threshold_color_64 = image.point(lambda x: 255 if x >= threshold else 0)
モノクロ画像の場合は「0」もしくは「1」の値で処理しましたが、カラーの場合は「0」と「255」で2値化を行うことが重要です。詳細は以下のサンプルコードのとおりです。
// サンプルプログラム
# coding: UTF-8 # 「PIL」ライブラリから「Image」クラスをimportする from PIL import Image # 画像ファイルを読み込む image = Image.open("Flower.png") # 閾値を64にしてカラー画像を2値化する threshold = 64 threshold_color_64 = image.point(lambda x: 255 if x >= threshold else 0) # 2値化したデータを画像として保存する threshold_color_64.save("threshold_color_64.png") # 閾値を128にしてカラー画像を2値化する threshold = 128 threshold_color_128 = image.point(lambda x: 255 if x >= threshold else 0) # 2値化したデータを画像として保存する threshold_color_128.save("threshold_color_128.png") # 閾値を192にしてカラー画像を2値化する threshold = 192 threshold_color_192 = image.point(lambda x: 255 if x >= threshold else 0) # 2値化したデータを画像として保存する threshold_color_192.save("threshold_color_192.png")
// 実行結果
threshold_color_64.png
threshold_color_128.png
threshold_color_192.png
PIL・Pillowは、さまざまな「画像処理フィルター」も使える方法です。本章では、PIL・Pillowで使える以下の6種類の画像処理フィルターについて、その使い方をサンプルコード付きでわかりやすく解説します。
なお、画像処理フィルターは以下の構文で「filter()」関数を呼び出して使用します。引数には、これから紹介するフィルターを指定します。
フィルター適用後の画像 = 画像データ.filter(フィルターの種類)
「ImageFilter.BLUR」は、画像をぼかすためのフィルターです。ImageFilter.BLURの使い方について、以下のサンプルコードで確認しましょう。
// サンプルプログラム
# coding: UTF-8 # 「PIL」ライブラリから「Image」クラスをimportする from PIL import Image # 「PIL」ライブラリから「ImageFilter」クラスをimportする from PIL import ImageFilter # 画像ファイルを読み込む image = Image.open("Flower.png") # 「ImageFilter.BLUR」フィルターで画像をぼかす image = image.filter(ImageFilter.BLUR) # 既定の画像ソフトでデータを表示する image.show()
// 実行結果
「ImageFilter.SMOOTH」は、画像を滑らかにするためのフィルターです。ImageFilter.SMOOTHの使い方について、以下のサンプルコードで確認しましょう。
// サンプルプログラム
# coding: UTF-8 # 「PIL」ライブラリから「Image」クラスをimportする from PIL import Image # 「PIL」ライブラリから「ImageFilter」クラスをimportする from PIL import ImageFilter # 画像ファイルを読み込む image = Image.open("Flower.png") # 「ImageFilter.SMOOTH」フィルターで画像を滑らかにする image = image.filter(ImageFilter.SMOOTH) # 既定の画像ソフトでデータを表示する image.show()
// 実行結果
なお、強力なぼかしフィルターを使用したい場合は、以下のように「ImageFilter.SMOOTH_MORE」フィルターを指定しましょう。
// サンプルプログラム
# coding: UTF-8 # 「PIL」ライブラリから「Image」クラスをimportする from PIL import Image # 「PIL」ライブラリから「ImageFilter」クラスをimportする from PIL import ImageFilter # 画像ファイルを読み込む image = Image.open("Flower.png") # 「ImageFilter.SMOOTH_MORE」フィルターで画像をさらに滑らかにする image = image.filter(ImageFilter.SMOOTH_MORE) # 既定の画像ソフトでデータを表示する image.show()
// 実行結果
「ImageFilter.SHARPEN」は、画像をシャープにするためのフィルターです。ImageFilter.SHARPENの使い方について、以下のサンプルコードで確認しましょう。
// サンプルプログラム
# coding: UTF-8 # 「PIL」ライブラリから「Image」クラスをimportする from PIL import Image # 「PIL」ライブラリから「ImageFilter」クラスをimportする from PIL import ImageFilter # 画像ファイルを読み込む image = Image.open("Flower.png") # 「ImageFilter.SHARPEN」フィルターで画像をシャープにする image = image.filter(ImageFilter.SHARPEN) # 既定の画像ソフトでデータを表示する image.show()
// 実行結果
ImageFilter.SHARPENに類似するフィルターとして、「ImageFilter.DETAIL」があります。こちらは、画像を高画質化するためのもので、使い方は以下のとおりです。
// サンプルプログラム
# coding: UTF-8 # 「PIL」ライブラリから「Image」クラスをimportする from PIL import Image # 「PIL」ライブラリから「ImageFilter」クラスをimportする from PIL import ImageFilter # 画像ファイルを読み込む image = Image.open("Flower.png") # 「ImageFilter.DETAIL」フィルターで画像を高画質にする image = image.filter(ImageFilter.DETAIL) # 既定の画像ソフトでデータを表示する image.show()
// 実行結果
「ImageFilter.EDGE_ENHANCE」は、画像の輪郭を強調するためのフィルターです。ImageFilter.EDGE_ENHANCEの使い方について、以下のサンプルコードで確認しましょう。
// サンプルプログラム
# coding: UTF-8 # 「PIL」ライブラリから「Image」クラスをimportする from PIL import Image # 「PIL」ライブラリから「ImageFilter」クラスをimportする from PIL import ImageFilter # 画像ファイルを読み込む image = Image.open("Flower.png") # 「ImageFilter.EDGE_ENHANCE」フィルターで画像の輪郭を強調する image = image.filter(ImageFilter.EDGE_ENHANCE) # 既定の画像ソフトでデータを表示する image.show()
// 実行結果
なお、強力な輪郭強調フィルターを使用したい場合は、以下のように「ImageFilter.EDGE_ENHANCE_MORE」フィルターを指定しましょう。
// サンプルプログラム
# coding: UTF-8 # 「PIL」ライブラリから「Image」クラスをimportする from PIL import Image # 「PIL」ライブラリから「ImageFilter」クラスをimportする from PIL import ImageFilter # 画像ファイルを読み込む image = Image.open("Flower.png") # 「ImageFilter.EDGE_ENHANCE_MORE」フィルターで画像の輪郭を強調する image = image.filter(ImageFilter.EDGE_ENHANCE_MORE) # 既定の画像ソフトでデータを表示する image.show()
// 実行結果
「ImageFilter.FIND_EDGES」は、画像の輪郭を抽出するためのフィルターです。ImageFilter.FIND_EDGESの使い方について、以下のサンプルコードで確認しましょう。
// サンプルプログラム
# coding: UTF-8 # 「PIL」ライブラリから「Image」クラスをimportする from PIL import Image # 「PIL」ライブラリから「ImageFilter」クラスをimportする from PIL import ImageFilter # 画像ファイルを読み込む image = Image.open("Flower.png") # 「ImageFilter.FIND_EDGES」フィルターで画像の輪郭を抽出する image = image.filter(ImageFilter.FIND_EDGES) # 既定の画像ソフトでデータを表示する image.show()
// 実行結果
ImageFilter.FIND_EDGESを利用すると、輪郭部分が彫り込まれた版画のようなイメージになります。一方で、「ImageFilter.CONTOUR」フィルターを使用すると、以下のように輪郭が浮かび上がるようになります。
// サンプルプログラム
# coding: UTF-8 # 「PIL」ライブラリから「Image」クラスをimportする from PIL import Image # 「PIL」ライブラリから「ImageFilter」クラスをimportする from PIL import ImageFilter # 画像ファイルを読み込む image = Image.open("Flower.png") # 「ImageFilter.CONTOUR」フィルターで画像の輪郭を抽出する image = image.filter(ImageFilter.CONTOUR) # 既定の画像ソフトでデータを表示する image.show()
// 実行結果
「ImageFilter.EMBOSS」は、画像にエンボス加工を行うためのフィルターです。エンボス加工とは、圧力をかけて凹凸をつける技法を指し、自動車のナンバープレートや各種カードの加工などに活用されています。ImageFilter.EMBOSSの使い方について、以下のサンプルコードで確認しましょう。
// サンプルプログラム
# coding: UTF-8 # 「PIL」ライブラリから「Image」クラスをimportする from PIL import Image # 「PIL」ライブラリから「ImageFilter」クラスをimportする from PIL import ImageFilter # 画像ファイルを読み込む image = Image.open("Flower.png") # 「ImageFilter.EMBOSS」フィルターで画像をエンボス加工する image = image.filter(ImageFilter.EMBOSS) # 既定の画像ソフトでデータを表示する image.show()
// 実行結果
これまで紹介した画像処理フィルターは、filter()の引数で値を指定するだけで使えました。一方でこれから紹介する画像処理フィルターは、引数で設定を変更することで高度な処理が行えるものです。
本章では、PIL・Pillowで使える以下の9種類の画像処理フィルターについて、その使い方をサンプルコード付きでわかりやすく解説します。
なお、これらの画像処理フィルターも「filter()」関数を呼び出して使用する点では同じですが、引数で関数を呼び出すことになります。また、一部のフィルター関数は複雑な処理を行うため、実行結果が得られるまでに時間がかかることがあります。
「ImageFilter.GaussianBlur()」は、「ガウスぼかし」をかけるためのフィルター関数です。ガウスぼかしは、「ガウス関数」と呼ばれるアルゴリズムを活用して、画像をぼかす処理を指します。画像からノイズを除去したり、前述したエッジ抽出の前処理などに活用されています。ImageFilter.GaussianBlur()の構文は以下のとおりです。
フィルター適用後の画像 = 画像データ.filter(ImageFilter.GaussianBlur(radius = 半径))
引数には、ガウスぼかしの半径を指定します。この値を大きくするほど、強力なぼかし処理が行われるようになります。ImageFilter.GaussianBlur()の使い方について、以下のサンプルコードで確認しましょう。
// サンプルプログラム
# coding: UTF-8 # 「PIL」ライブラリから「Image」クラスをimportする from PIL import Image # 「PIL」ライブラリから「ImageFilter」クラスをimportする from PIL import ImageFilter # 画像ファイルを読み込む image = Image.open("Flower.png") # 「ImageFilter.GaussianBlur」フィルターで画像に任意の「ガウスぼかし」をかける image = image.filter(ImageFilter.GaussianBlur(radius = 5)) # 既定の画像ソフトでデータを表示する image.show()
// 実行結果
なお、radius引数を50に設定した場合、以下のようにガウスぼかしの効果が強くなります。
「ImageFilter.BoxBlur()」は、「ボックスぼかし」をかけるためのフィルター関数です。ボックスぼかしは、指定した範囲内において、隣接ピクセルの平均カラーに基づいたぼかし処理を行います。ボックスをまたぐ部分は影響を受けないため、ボックス上のエフェクトが発生することが特徴です。ImageFilter.BoxBlur()は、以下の構文で使えます。
フィルター適用後の画像 = 画像データ.filter(ImageFilter.BoxBlur(radius = 半径))
先ほどのImageFilter.GaussianBlur()と同じく、引数にはぼかし処理の対象とする範囲の半径を指定します。ImageFilter.BoxBlur()の使い方について、以下のサンプルコードで確認しましょう。
// サンプルプログラム
# coding: UTF-8 # 「PIL」ライブラリから「Image」クラスをimportする from PIL import Image # 「PIL」ライブラリから「ImageFilter」クラスをimportする from PIL import ImageFilter # 画像ファイルを読み込む image = Image.open("Flower.png") # 「ImageFilter.BoxBlur」フィルターで画像に任意の「ボックスぼかし」をかける image = image.filter(ImageFilter.BoxBlur(radius = 5)) # 既定の画像ソフトでデータを表示する image.show()
// 実行結果
ボックスぼかしでは、自身のピクセルを囲む9つのピクセルの平均値を使用するため、ボックス上の形状が見えます。radius引数を50にすると、以下のようにボックス効果がわかりやすくなります。
「ImageFilter.UnsharpMask()」は、「アンシャープマスク」をかけるためのフィルター関数です。アンシャープマスクは、個々のピクセルの周囲にある「異なる色情報を持つピクセル」を検索し、指定した量だけコントラストを上げる処理を行います。ImageFilter.UnsharpMask()の構文は以下のとおりです。
フィルター適用後の画像 = 画像データ.filter(ImageFilter.UnsharpMask(radius = 半径, percent = 量, threshold = 閾値))
アンシャープマスクをかけるときは、「量」・「半径」・「しきい値」を設定することが特徴で、これらの値を大きくするほど効果が強くなります。ImageFilter.UnsharpMask()の使い方について、以下のサンプルコードで確認しましょう。
// サンプルプログラム
# coding: UTF-8 # 「PIL」ライブラリから「Image」クラスをimportする from PIL import Image # 「PIL」ライブラリから「ImageFilter」クラスをimportする from PIL import ImageFilter # 画像ファイルを読み込む image = Image.open("Flower.png") # 「ImageFilter.UnsharpMask」フィルターで画像に任意の「アンシャープマスク」をかける # 半径10・量100・閾値3の場合 unsharp_mask_1 = image.filter(ImageFilter.UnsharpMask(radius = 10, percent = 100, threshold = 3)) unsharp_mask_1.save("unsharp_mask_1.png") # 「ImageFilter.UnsharpMask」フィルターで画像に任意の「アンシャープマスク」をかける # 半径100・量150・閾値3の場合 unsharp_mask_2 = image.filter(ImageFilter.UnsharpMask(radius = 100, percent = 150, threshold = 3)) unsharp_mask_2.save("unsharp_mask_2.png") # 「ImageFilter.UnsharpMask」フィルターで画像に任意の「アンシャープマスク」をかける # 半径150・量300・閾値5の場合 unsharp_mask_3 = image.filter(ImageFilter.UnsharpMask(radius = 150, percent = 300, threshold = 5)) unsharp_mask_3.save("unsharp_mask_3.png")
// 実行結果
unsharp_mask_1.png
unsharp_mask_2.png
unsharp_mask_3.png
上記のように、値を大きくするほど画像が明るくなることがわかります。アンシャープマスクは、値の設定範囲や自由度が高いフィルターなので、さまざまなパターンを試してみましょう。
「ImageFilter.Kernel()」は、「畳み込み」の処理を行うためのフィルター関数です。畳み込みとは、格子状の数値データ「カーネル」と、それと同サイズの部分画像「ウィンドウ」について、要素ごとの積と和を算出して変換する処理を指します。ImageFilter.Kernel()の構文は以下のとおりです。
フィルター適用後の画像 = 画像データ.filter(ImageFilter.Kernel(size = カーネルのサイズ, kernel = カーネルのリスト))
「size引数」は、(3, 3)または(5, 5)であることが必要で、「kernel引数」はsize引数と一致する形状のリストでないといけません。「scal引数」と「offset引数」も設定できますが、これらは基本的にデフォルト設定で問題ありません。ImageFilter.Kernel()の使い方について、以下のサンプルコードで確認しましょう。
// サンプルプログラム
# coding: UTF-8 # 「PIL」ライブラリから「Image」クラスをimportする from PIL import Image # 「PIL」ライブラリから「ImageFilter」クラスをimportする from PIL import ImageFilter # 画像ファイルを読み込む image = Image.open("Flower.png") # カーネルの重みを設定する kernel = [1, 5, -2, 1, 5, -7, 1, 5,-10] # 「ImageFilter.Kernel」フィルターで画像に任意の「畳み込み」をかける image = image.filter(ImageFilter.Kernel(size = (3, 3), kernel = kernel)) # 既定の画像ソフトでデータを表示する image.show()
// 実行結果
畳み込みフィルターの設定によっては、上記のサンプルプログラムのように、輪郭がはっきり浮かび上がる実行結果が得られることがあります。
「ImageFilter.RankFilter()」は、「ランクフィルター」をかけるためのフィルター関数です。ランクフィルターは、指定したサイズのウィンドウ(部分画像)のすべてのピクセルを並べ替え、指定した番号のピクセル値を返します。ImageFilter.RankFilter()の構文は以下のとおりです。
フィルター適用後の画像 = 画像データ.filter(ImageFilter.RankFilter(size = 対象部分のサイズ, rank = 適用するピクセルの番号))
ImageFilter.RankFilter()の使い方について、以下のサンプルコードで確認しましょう。
// サンプルプログラム
# coding: UTF-8 # 「PIL」ライブラリから「Image」クラスをimportする from PIL import Image # 「PIL」ライブラリから「ImageFilter」クラスをimportする from PIL import ImageFilter # 画像ファイルを読み込む image = Image.open("Flower.png") # 「ImageFilter.RankFilter」フィルターで画像に任意の「ランクフィルター」をかける # サイズ5・ランク5の場合 rank_filter_1 = image.filter(ImageFilter.RankFilter(size = 5, rank = 5)) rank_filter_1.save("rank_filter_1.png") # 「ImageFilter.RankFilter」フィルターで画像に任意の「ランクフィルター」をかける # サイズ7・ランク15の場合 rank_filter_2 = image.filter(ImageFilter.RankFilter(size = 7, rank = 15)) rank_filter_2.save("rank_filter_2.png") # 「ImageFilter.RankFilter」フィルターで画像に任意の「ランクフィルター」をかける # サイズ15・ランク7の場合 rank_filter_3 = image.filter(ImageFilter.RankFilter(size = 15, rank = 7)) rank_filter_3.save("rank_filter_3.png")
// 実行結果
rank_filter_1.png
rank_filter_2.png
rank_filter_3.png
「ImageFilter.MedianFilter()」は、「メディアンフィルター」をかけるためのフィルター関数です。メディアンフィルターは、指定したウィンドウ内の平均値を算出し、それを基に処理を行います。ImageFilter.MedianFilter()の構文は以下のとおりです。
フィルター適用後の画像 = 画像データ.filter(ImageFilter.MedianFilter(size = 対象部分のサイズ))
これまでの関数と同じように、引数にはウィンドウのサイズを指定します。一般的には、10以下の値を設定することが多いと考えられます。ImageFilter.MedianFilter()の使い方について、以下のサンプルコードで確認しましょう。
// サンプルプログラム
# coding: UTF-8 # 「PIL」ライブラリから「Image」クラスをimportする from PIL import Image # 「PIL」ライブラリから「ImageFilter」クラスをimportする from PIL import ImageFilter # 画像ファイルを読み込む image = Image.open("Flower.png") # 「ImageFilter.MedianFilter」フィルターで画像に任意の「メディアンフィルター」をかける # サイズ5の場合 median_filter_1 = image.filter(ImageFilter.MedianFilter(size = 5)) median_filter_1.save("median_filter_1.png") # 「ImageFilter.MedianFilter」フィルターで画像に任意の「メディアンフィルター」をかける # サイズ15の場合 median_filter_2 = image.filter(ImageFilter.MedianFilter(size = 15)) median_filter_2.save("median_filter_2.png") # 「ImageFilter.MedianFilter」フィルターで画像に任意の「メディアンフィルター」をかける # サイズ25の場合 median_filter_3 = image.filter(ImageFilter.MedianFilter(size = 25)) median_filter_3.save("median_filter_3.png")
// 実行結果
median_filter_1.png
median_filter_2.png
median_filter_3.png
「ImageFilter.MinFilter()」は、「最小値フィルター」をかけるためのフィルター関数です。最小値フィルターは、周辺画素値の大小比較を行ったうえで、最も小さな画素に変換します。ImageFilter.MinFilter()の構文は以下のとおりです。
フィルター適用後の画像 = 画像データ.filter(ImageFilter.MinFilter(size = 対象部分のサイズ))
引数には、画素値の比較を行うウィンドウのサイズを指定します。ImageFilter.MinFilter()の使い方について、以下のサンプルコードで確認しましょう。
// サンプルプログラム
# coding: UTF-8 # 「PIL」ライブラリから「Image」クラスをimportする from PIL import Image # 「PIL」ライブラリから「ImageFilter」クラスをimportする from PIL import ImageFilter # 画像ファイルを読み込む image = Image.open("Flower.png") # 「ImageFilter.MinFilter」フィルターで画像に任意の「最小値フィルター」をかける # サイズ5の場合 min_filter_1 = image.filter(ImageFilter.MinFilter(size = 5)) min_filter_1.save("min_filter_1.png") # 「ImageFilter.MinFilter」フィルターで画像に任意の「最小値フィルター」をかける # サイズ15の場合 min_filter_2 = image.filter(ImageFilter.MinFilter(size = 15)) min_filter_2.save("min_filter_2.png") # 「ImageFilter.MinFilter」フィルターで画像に任意の「最小値フィルター」をかける # サイズ25の場合 min_filter_3 = image.filter(ImageFilter.MinFilter(size = 25)) min_filter_3.save("min_filter_3.png")
// 実行結果
min_filter_1.png
min_filter_2.png
min_filter_3.png
「ImageFilter.MaxFilter()」は、「最大値フィルター」をかけるためのフィルター関数です。最大値フィルターは、周辺画素値の大小比較を行ったうえで、最も大きな画素に変換します。ImageFilter.MaxFilter()の構文は以下のとおりです。
フィルター適用後の画像 = 画像データ.filter(ImageFilter.MaxFilter(size = 対象部分のサイズ))
先ほどのImageFilter.MinFilter()と同じく、画素値の比較を行うウィンドウのサイズを指定します。ImageFilter.MaxFilter()の使い方について、以下のサンプルコードで確認しましょう。
// サンプルプログラム
# coding: UTF-8 # 「PIL」ライブラリから「Image」クラスをimportする from PIL import Image # 「PIL」ライブラリから「ImageFilter」クラスをimportする from PIL import ImageFilter # 画像ファイルを読み込む image = Image.open("Flower.png") # 「ImageFilter.MaxFilter」フィルターで画像に任意の「最大値フィルター」をかける # サイズ5の場合 max_filter_1 = image.filter(ImageFilter.MaxFilter(size = 5)) max_filter_1.save("max_filter_1.png") # 「ImageFilter.MaxFilter」フィルターで画像に任意の「最大値フィルター」をかける # サイズ15の場合 max_filter_2 = image.filter(ImageFilter.MaxFilter(size = 15)) max_filter_2.save("max_filter_2.png") # 「ImageFilter.MaxFilter」フィルターで画像に任意の「最大値フィルター」をかける # サイズ25の場合 max_filter_3 = image.filter(ImageFilter.MaxFilter(size = 25)) max_filter_3.save("max_filter_3.png")
// 実行結果
max_filter_1.png
max_filter_2.png
max_filter_3.png
「ImageFilter.ModeFilter()」は、「モードフィルター」をかけるためのフィルター関数です。モードフィルターは、指定されたサイズのウィンドウ内で、最も出現頻度が高いピクセル値を選択します。ただし、ピクセル値が2回以上発生しない場合は、元の値が保持されます。ImageFilter.ModeFilter()の構文は以下のとおりです。
フィルター適用後の画像 = 画像データ.filter(ImageFilter.ModeFilter(size = 対象部分のサイズ))
引数でウィンドウのサイズを指定する点は、これまで解説したほかのフィルター関数と同じです。ImageFilter.ModeFilter()の使い方について、以下のサンプルコードで確認しましょう。
// サンプルプログラム
# coding: UTF-8 # 「PIL」ライブラリから「Image」クラスをimportする from PIL import Image # 「PIL」ライブラリから「ImageFilter」クラスをimportする from PIL import ImageFilter # 画像ファイルを読み込む image = Image.open("Flower.png") # 「ImageFilter.ModeFilter」フィルターで画像に任意の「モードフィルター」をかける # サイズ5の場合 mode_filter_1 = image.filter(ImageFilter.ModeFilter(size = 5)) mode_filter_1.save("mode_filter_1.png") # 「ImageFilter.ModeFilter」フィルターで画像に任意の「モードフィルター」をかける # サイズ15の場合 mode_filter_2 = image.filter(ImageFilter.ModeFilter(size = 15)) mode_filter_2.save("mode_filter_2.png") # 「ImageFilter.ModeFilter」フィルターで画像に任意の「モードフィルター」をかける # サイズ25の場合 mode_filter_3 = image.filter(ImageFilter.ModeFilter(size = 25)) mode_filter_3.save("mode_filter_3.png")
// 実行結果
mode_filter_1.png
mode_filter_2.png
mode_filter_3.png
PIL・Pillowの実践的な画像編集・加工のテクニックとして、以下の3つを解説します。さまざまな画像処理シーンで役立つ知識なので、ぜひ参考にしてみてください。
PIL・Pillowでは、GIFアニメーション画像を作成することもできます。GIF画像も、冒頭で解説した「save()」関数で行えますが、以下のように特殊な構文が必要なため注意してください。
images[0].save("出力画像ファイル名.gif", save_all = True, append_images = images[1:], optimize = False, duration = 1枚あたりの表示時間, loop = 0)
「optimize引数」をTrueにすると、必要な画像が省略されるなど意図せぬ結果になる可能性があるため、基本的にはFalseにしておくのがおすすめです。また、「loop引数」はループ回数を指し、これを0にすると無限ループとなります。duration引数は各フレームの表示時間で、小さくするほどアニメーションが速くなりますが、小さすぎると正しく反映されません。
PIL・Pillowで滑らかなGIFアニメーション画像を作成する方法について、以下のサンプルコードで確認しましょう。
// サンプルプログラム
# coding: UTF-8 # 「PIL」ライブラリから「Image」クラスをimportする from PIL import Image # 「PIL」ライブラリから「ImageDraw」クラスをimportする from PIL import ImageDraw # 「PIL」ライブラリから「ImageFilter」クラスをimportする from PIL import ImageFilter # create_animation()|GIFアニメーションを生成する def create_animation(color_first, color_second, filter_radius): # イメージ配列を用意する images = [] # 画像サイズと中心座標を設定する size = 512 center = size // 2 # GIF画像のステップ数を指定する step = 8 # ステップ数だけ画像を生成する for i in range(0, int(center * 1.5), step): # 新規画像を生成する image = Image.new("RGB", (size, size), color_first) # Drawオブジェクトを生成して正円を描画する draw = ImageDraw.Draw(image) draw.ellipse((center - i, center - i, center + i, center + i), fill = color_second) # ガウスぼかしを適用する image = image.filter(ImageFilter.GaussianBlur(filter_radius)) # リストに画像を追加する images.append(image) return images # main()|プログラムのエントリーポイント if __name__ == "__main__": # アニメーションの色を設定する color_first = (255, 255, 255) color_second = (0, 128, 256) # ガウスぼかしの半径を設定する radius = 4 # GIFアニメーションの画像リストを生成する images = create_animation(color_first, color_second, radius) + create_animation(color_second, color_first, radius) # GIFアニメーションを保存する images[0].save("animation.gif", save_all = True, append_images = images[1:], optimize = False, duration = 30, loop = 0)
// 実行結果
上記のサンプルプログラムを実行すると、白と青の円が大きくなっていくGIFアニメーション画像が作成されます。これまで解説した関数を駆使して、新規画像の作成と円の描画を行ったうえで、ガウスぼかしを適用していることがポイントです。しかしGIF画像はフルカラーではなく256色しか使えない8ビット形式なので、高画質な画像は生成できません。
画像の一部分、とくに中央部分をトリミングして、サムネイルとして利用したいというシーンは多いでしょう。以下のように、これまで解説したcrop()・ellipse()・ImageFilter.GaussianBlur()などの関数を活用すると、サムネイル画像の作成を自動化できます。実用的なテクニックなので、ぜひサンプルコードを参考にしてみてください。
// サンプルプログラム
# coding: UTF-8 # 「PIL」ライブラリから「Image」クラスをimportする from PIL import Image # 「PIL」ライブラリから「ImageDraw」クラスをimportする from PIL import ImageDraw # 「PIL」ライブラリから「ImageFilter」クラスをimportする from PIL import ImageFilter # crop_center()|画像の中心部分を切り取る def crop_center(image, crop_width, crop_height): # 画像サイズを取得して中心部分を切り抜く width, height = image.size return image.crop(((width - crop_width) // 2, (height - crop_height) // 2, (width + crop_width) // 2, (height + crop_height) // 2)) # mask_circle()|切り取った部分が円形になるようにマスクする def mask_circle(image, radius): # 元画像をグレースケールに変換したうえで、Drawオブジェクトを生成する mask = Image.new("L", image.size, 0) draw = ImageDraw.Draw(mask) # オフセット値を算出して正円を描画する offset = radius * 2 draw.ellipse((offset, offset, image.size[0] - offset, image.size[1] - offset), fill = 255) # マスク画像にガウスぼかしを適用する mask = mask.filter(ImageFilter.GaussianBlur(radius)) # 元画像をコピーしたうえで、マスク画像を適用してアルファ値を追加する result = image.copy() result.putalpha(mask) return result # main()|プログラムのエントリーポイント if __name__ == "__main__": # 画像ファイルを読み込む image = Image.open("Flower.png") # 画像から中心部分を切り抜く square = crop_center(image, 512, 512) # アルファ値を適用した円形のサムネイル画像を生成する thumbnail = mask_circle(square, 8) # サムネイル画像を保存する thumbnail.save("thumbnail.png")
// 実行結果
このように、画像の中央部分をピンポイントにトリミングして、外側の部分にアルファ値を適用した高品質なサムネイル画像を生成できます。切り抜き部分のサイズを設定するだけで、サムネイル画像を自動的に生成できるのでご活用ください。
最後に、2枚の画像を合成して高品質な画像を作成する、実用的なサンプルコードを紹介します。
// サンプルプログラム
# coding: UTF-8 # 「PIL」ライブラリから「Image」クラスをimportする from PIL import Image # 「PIL」ライブラリから「ImageDraw」クラスをimportする from PIL import ImageDraw # 「PIL」ライブラリから「ImageFilter」クラスをimportする from PIL import ImageFilter # crop_center()|画像の中心部分を切り取る def crop_center(image, crop_width, crop_height): # 画像サイズを取得して中心部分を切り抜く width, height = image.size return image.crop(((width - crop_width) // 2, (height - crop_height) // 2, (width + crop_width) // 2, (height + crop_height) // 2)) # mask_circle()|切り取った部分が円形になるようにマスクする def mask_circle(image, radius): # 元画像をグレースケールに変換したうえで、Drawオブジェクトを生成する mask = Image.new("L", image.size, 0) draw = ImageDraw.Draw(mask) # オフセット値を算出して正円を描画する offset = radius * 2 draw.ellipse((offset, offset, image.size[0] - offset, image.size[1] - offset), fill = 255) # マスク画像にガウスぼかしを適用する mask = mask.filter(ImageFilter.GaussianBlur(radius)) # 元画像をコピーしたうえで、マスク画像を適用してアルファ値を追加する result = image.copy() result.putalpha(mask) return result, mask # main()|プログラムのエントリーポイント if __name__ == "__main__": # 花の画像ファイルを読み込む flower = Image.open("Flower.png") # 切り抜きサイズを設定する crop_size = 512 # 画像から中心部分を切り抜く square = crop_center(flower, crop_size, crop_size) # アルファ値を適用した円形のサムネイル画像を生成する thumbnail, mask = mask_circle(square, 8) # 最終的な画像サイズを設定する final_size = (thumbnail.width * 2, thumbnail.height * 2) # 空の画像ファイルを読み込み、サムネイルの倍の画像サイズに変換する sky = Image.open("Sky.png").resize(final_size, resample = Image.BICUBIC) # 画像をコピーしたうえで中央に貼り付ける result = sky.copy() result.paste(thumbnail, (final_size[0] // 2 - crop_size // 2, final_size[1] // 2 - crop_size // 2), mask) # 「ImageFilter.DETAIL」フィルターで画像を高画質にする result = result.filter(ImageFilter.DETAIL) # 合成した画像を保存する result.save("sky_flower_animation.png")
// 実行結果
「composite()」関数は2枚の画像サイズが一致していないと使えないため、今回は「paste()」関数を使います。このように、PIL・Pillowのクラスや関数を駆使することで、高度な画像処理を簡潔なソースコードで実現できます。
今回は、PIL・Pillowを活用した画像処理について、基本的な知識や応用テクニックを解説しました。画像処理というと難しそうなイメージがあるかもしれませんが、便利な関数を組み合わせることで、一般的な画像ソフト並みの処理を簡潔なソースコードで実現できます。サンプルコードを参考にして、PIL・Pillowを活用した画像処理にチャレンジしてみてください。
2024.06.17
子供におすすめのプログラミングスクール10選!学習メリットや教室選びのコツも紹介
#プログラミングスクール
2022.01.06
【完全版】大学生におすすめのプログラミングスクール13選!選ぶコツも詳しく解説
#プログラミングスクール
2024.01.26
【未経験でも転職可】30代におすすめプログラミングスクール8選!
#プログラミングスクール
2024.01.26
初心者必見!独学のJava学習方法とおすすめ本、アプリを詳しく解説
#JAVA
2024.01.26
忙しい社会人におすすめプログラミングスクール15選!失敗しない選び方も詳しく解説
#プログラミングスクール
2022.01.06
【無料あり】大阪のおすすめプログラミングスクール14選!スクール選びのコツも紹介
#プログラミングスクール
2024.01.26
【目的別】東京のおすすめプログラミングスクール20選!スクール選びのコツも徹底解説
#プログラミングスクール
2024.01.26
【無料あり】福岡のおすすめプログラミングスクール13選!選び方も詳しく解説
#プログラミングスクール
2024.01.26
【徹底比較】名古屋のおすすめプログラミングスクール13選!選び方も詳しく解説
#プログラミングスクール
2024.01.26
【徹底比較】おすすめのプログラミングスクール18選!失敗しない選び方も徹底解説
#プログラミングスクール