Python, NumPyで画像処理(読み込み、演算、保存)

Pillow(PIL)で読み込んだ画像をNumPyの配列ndarrayとして格納すると、NumPyの機能を使って様々な画像処理を行うことができます。 画素値の取得や書き換え、スライスでのトリミング、結合などndarrayの操作がそのまま使えるので、NumPyに慣れている人は、OpenCVなどのライブラリを使わなくても結構いろいろなことができます。 OpenCVを使う場合も、PythonのOpenCVでは画像データをndarrayとして扱うので、NumPy(ndarray)での処理を覚えておくと何かと便利。 基本的な画像の読み書きとして、

画像ファイルをNumPy配列ndarrayとして読み込む方法 NumPy配列ndarrayを画像ファイルとして保存する方法

と、NumPy(ndarray)での画像処理の例、

単色化と画像の結合(連結) ネガポジ反転(画素値の逆転) 減色処理 二値化処理 ガンマ補正 スライスでトリミング スライスで貼り付け アルファブレンドとマスク処理

について説明します。 ここではPillowを使った画像ファイルの読み書きについて述べる。OpenCVでの画像ファイルの読み込みと保存については以下の記事参照。

Pillowについては以下の記事も参照。

画像ファイルをNumPy配列ndarrayとして読み込む方法

np.array()にPIL.Image.open()で読み込んだ画像データを渡すとndarrayが得られる。 RGB画像は行(高さ) x 列(幅) x 色の三次元のndarray、白黒画像は行(高さ) x 列(幅)の二次元のndarrayになる。 以下は512px x 512pxのカラー画像を読み込んだ例。次元数ndimが3、行(高さ) x 列(幅) x 色を表す形状shapeが(512, 512, 3)となっています。

from PIL import Image
import numpy as np

im = np.array(Image.open('data/src/lena_square.png'))

print(im.dtype)  # データ型
# uint8

print(im.ndim)  # 次元数
# 3

print(im.shape)  # サイズ(高さ x 幅 x 色数)
# (512, 512, 3)

なお、PillowのPIL.Imageからndarrayに変換した場合、色の並びはRGB(赤、緑、青)となります。OpenCVで読み込んだ場合(BGR)と異なるので注意。並びを変換したい場合は以下の記事を参照。

画像サイズ取得についての詳細は以下の記事を参照。

画像データに対して乗算・除算など小数点を伴う計算をしたい場合は、floatで読み込んでおく。

im_f = np.array(Image.open('data/src/lena_square.png'), np.float)

print(im_f.dtype)  # データ型
# float64

ndarrayなのでデータアクセスも簡単。もちろんmin()やmax()などのメソッドもそのまま使える。 以下の例では(x, y) = (256, 256)の位置の画素値[R, G, B]、および、Rの最小値を取得しています。

print(im[256, 256])  # 指定した座標の画素値(R, G, B) / 原点は左上
# [180  65  72]

print(im[:, :, 0].min())  # Redの最小値
# 54

具体的な処理の例は後述。

NumPy配列ndarrayを画像ファイルとして保存する方法

Image.fromarray()にndarrayを渡すとPIL.Imageが得られる。 Pillowのメソッドで画像処理を行ったり、save()で画像として保存するなどの操作が可能になる。

pil_img = Image.fromarray(im)
pil_img.save('data/temp/lena_square_save.png')

保存するだけなら一行で書いてもいい。

Image.fromarray(im).save('data/temp/lena_square_save.png')

ndarrayのデータ型dtypeがfloatなどの場合は、JPGやPNGで保存するためにuint8(符号なし8ビット整数)に変換します。

pil_img_f = Image.fromarray(im_f.astype(np.uint8))
pil_img_f.save('data/temp/lena_square_save.png')

画素値が0.0〜1.0で表されている場合は255で乗算してからuint8に変換して保存する必要があるので注意。

単色化と画像の結合(連結)

他の色の値を0にして単色画像を生成します。さらに横に並べて結合します。 結合についての詳細は以下の記事参照。

from PIL import Image
import numpy as np

im = np.array(Image.open('data/src/lena_square.png'))

im_R = im.copy()
im_R[:, :, (1, 2)] = 0
im_G = im.copy()
im_G[:, :, (0, 2)] = 0
im_B = im.copy()
im_B[:, :, (0, 1)] = 0

# 横に並べて結合(どれでもよい)
im_RGB = np.concatenate((im_R, im_G, im_B), axis=1)
# im_RGB = np.hstack((im_R, im_G, im_B))
# im_RGB = np.c_['1', im_R, im_G, im_B]

pil_img = Image.fromarray(im_RGB)
pil_img.save('data/dst/lena_numpy_split_color.jpg')

ネガポジ反転(画素値を逆転)

画素値を計算して処理するのも簡単。 Max値(uint8の場合は255)から画素値を引くとネガポジ反転した画像が取得できます。

import numpy as np
from PIL import Image

im = np.array(Image.open('data/src/lena_square.png').resize((256, 256)))

im_i = 255 - im

Image.fromarray(im_i).save('data/dst/lena_numpy_inverse.jpg')

減色処理

//で割り算の余りを切り捨てて再度掛け算すると画素値が飛び飛びの値になり、色数を減らせる。

import numpy as np
from PIL import Image

im = np.array(Image.open('data/src/lena_square.png').resize((256, 256)))

im_32 = im // 32 * 32
im_128 = im // 128 * 128

im_dec = np.concatenate((im, im_32, im_128), axis=1)

Image.fromarray(im_dec).save('data/dst/lena_numpy_dec_color.png')

二値化処理

しきい値に応じて白黒に振り分けることも可能。 詳細は以下の記事を参照。

ガンマ補正

掛け算、割り算、累乗、なんでもできます。

from PIL import Image
import numpy as np

im = np.array(Image.open('data/src/lena_square.png'), 'f')  # floatで読み込み

im_1_22 = 255.0 * (im / 255.0)**(1 / 2.2)
im_22 = 255.0 * (im / 255.0)**2.2

im_gamma = np.concatenate((im_1_22, im, im_22), axis=1)

pil_img = Image.fromarray(np.uint8(im_gamma))  # floatをuintに変換
pil_img.save('data/dst/lena_numpy_gamma.jpg')

スライスでトリミング

スライスで領域を指定すると、矩形にトリミングすることもできます。

from PIL import Image
import numpy as np

im = np.array(Image.open('data/src/lena_square.png'))

print(im.shape)
# (512, 512, 3)

im_trim1 = im[128:384, 128:384]
print(im_trim1.shape)
# (256, 256, 3)

Image.fromarray(im_trim1).save('data/dst/lena_numpy_trim.jpg')

スライスについての詳細は以下の記事を参照。

左上の座標とトリミングする領域の幅・高さで指定する関数を用意しておくと便利かもしれない。

def trim(array, x, y, width, height):
    return array[y:y + height, x:x+width]

im_trim2 = trim(im, 128, 192, 256, 128)
print(im_trim2.shape)
# (128, 256, 3)

Image.fromarray(im_trim2).save('data/dst/lena_numpy_trim2.jpg')

画像のサイズ外を指定すると無視される。

im_trim3 = trim(im, 128, 192, 512, 128)
print(im_trim3.shape)
# (128, 384, 3)

Image.fromarray(im_trim3).save('data/dst/lena_numpy_trim3.jpg')

スライスで貼り付け

スライスを使うと配列の矩形領域を別の配列の矩形領域で置き換えることができます。

これを利用すると画像の一部または全体を別の画像に貼り付けられる。

import numpy as np
from PIL import Image

src = np.array(Image.open('data/src/lena_square.png').resize((128, 128)))
dst = np.array(Image.open('data/src/lena_square.png').resize((256, 256))) // 4

dst_copy = dst.copy()
dst_copy[64:128, 128:192] = src[32:96, 32:96]

Image.fromarray(dst_copy).save('data/dst/lena_numpy_paste.jpg')


dst_copy = dst.copy()
dst_copy[64:192, 64:192] = src

Image.fromarray(dst_copy).save('data/dst/lena_numpy_paste_all.jpg')

左辺と右辺のスライスで同じサイズの領域を指定しないとエラーになるので注意。

アルファブレンドとマスク処理

配列の要素ごとの演算も簡単にできるので、2つの画像をアルファブレンドしたり、別途用意したマスク画像をもとに合成したりできます。詳細は以下の記事を参照。

シェア

関連カテゴリー

Python NumPy 画像処理

NumPy配列ndarrayをシフト(スクロール)させるnp.roll NumPyでRGB画像の色チャンネルを分離して単色化、白黒化、色交換 Python, NumPy(OpenCV)で画像を二値化処理 Python, OpenCV, Pillow(PIL)で画像サイズ(幅、高さ)を取得 Python, NumPyでグラデーション画像を生成 Python, OpenCV, NumPyで画像のアルファブレンドとマスク処理 NumPy配列ndarrayをタイル状に繰り返し並べるnp.tile Pythonでの画像処理、Pillow, NumPy, OpenCVの違いと使い分け NumPyのバージョンを確認(np.version) NumPy配列ndarrayの形状を変換するreshapeの使い方と-1の意味 NumPyで全要素を同じ値で初期化した配列ndarrayを生成 Python, Pillowで画像の上下左右に余白を追加しサイズ変更 NumPy配列ndarrayの最大値・最小値のインデックス(位置)を取得 NumPy配列ndarrayから条件を満たす要素・行・列を抽出、削除 Python, OpenCVで顔検出と瞳検出(顔認識、瞳認識)

Last Updated: 6/26/2019, 10:34:03 PM