Pythonで画像処理 (PIL, OpenCV)

目次

読み込み

OpenCVで画像を読み込むと、NumPyのndarray型のインスタンスが得られる。
PILで画像を読み込むと、PIL.Image型のインスタンスが得られる。

RGB画像

PASCAL VOC 2012から

PIL

from PIL import Image

image = Image.open('image_rgb.jpg')
print(type(image))  # -> <class 'PIL.JpegImagePlugin.JpegImageFile'>
print(image.size)  # -> (500, 375)
print(image.mode)  # -> RGB

OpenCV

OpenCVでは、画像を読み込んだときにBGRの順のndarrayが得られる。
PILでは非ASCII文字を含むパスの画像ファイルも開くことができるが、OpenCVでは非ASCII文字を含むパスを開こうとするとエラーが発生するので注意が必要。

import cv2

image = cv2.imread('image_rgb.jpg')
print(type(image))  # -> <class 'numpy.ndarray'>
print(image.shape)  # -> (375, 500, 3)

相互変換

image_cv = np.array(image_pil)  # PIL -> NumPy(OpenCV)
image_cv = cv2.cvtColor(image_cv, cv2.COLOR_RGB2BGR)

image_pil = cv2.cvtColor(image_cv, cv2.COLOR_BGR2RGB)
image_pil = Image.fromarray(image_pil)  # NumPy(OpenCV) -> PIL

グレースケール画像

PIL

from PIL import Image

image = Image.open('image_l.png')
print(type(image))  # -> <class 'PIL.PngImagePlugin.PngImageFile'>
print(image.size)  # -> (500, 375)
print(image.mode)  # -> L

OpenCV

import cv2

image = cv2.imread('image_l.png', cv2.IMREAD_GRAYSCALE)
print(type(image))  # -> <class 'numpy.ndarray'>
print(image.shape)  # -> (375, 500)

相互変換

image_cv = np.array(image_pil)  # PIL -> OpenCV(NumPy)

image_pil = Image.fromarray(image_cv)  # OpenCV(NumPy) -> PIL

インデックスカラー画像

PIL

from PIL import Image

image = Image.open('image_p.png')
print(type(image))  # -> <class 'PIL.PngImagePlugin.PngImageFile'>
print(image.size)  # -> (500, 375)
print(image.mode)  # -> P
print(image.getpalette())  # -> [0, 0, 0, 128, 0, 0, 0, 128, 0, 128, 128, 0, ... ]

OpenCV

OpenCVでインデックスカラー画像のインデックスを直接読み込むことはできない。
(パレット情報が各ピクセルに展開されて、3チャンネルのカラー画像として読み込まれる)

相互変換

from PIL import Image

image_pil = Image.open('image_p.png')
palette = image_pil.getpalette()  # パレットを保持(別途定義する場合はこの行は不要)
image_cv = np.array(image_pil)  # PIL -> NumPy(OpenCV)

image_pil = Image.fromarray(image_cv, mode='P')  # NumPy(OpenCV) -> PIL
image_pil.putpalette(palette)  # パレット情報を付与

保存

PIL

画像のmodeに応じた形式で保存される

image.save('output.png')

グレースケール画像(1チャンネル)をインデックスカラー画像として保存する場合

image = Image.fromarray(image, mode='P')  # NumPy -> PIL
image.putpalette(palette)  # 長さ768の配列paletteを与える
image.save('output.png')

OpenCV

3チャンネルならカラー画像として、1チャンネルならグレースケール画像として保存される。

cv2.imwrite('output.png', image)

画像変換

データ拡張に使う主な変換が、PIL・OpenCVでどのように実装されているか紹介する。

リサイズ

new_w, new_h = 500, 150

# PIL
pil_image = pil_image.resize((new_w, new_h))  # 指定しなければNEAREST
pil_image = pil_image.resize((new_w, new_h), Image.BILINEAR)

# OpenCV
cv_image = cv2.resize(cv_image, (new_w, new_h), interpolation=cv2.INTER_NEAREST)  
cv_image = cv2.resize(cv_image, (new_w, new_h))  # 指定しなければINTER_LINEAR(バイリニア)

回転


expand=Trueの場合
angle = 10
fillcolor = (124, 115, 104)  # 回転によって生じた隙間を埋める色を指定する(指定しなければ(0, 0, 0)で埋められる)

# PIL
pil_image = pil_image.rotate(angle, resample=Image.BILINEAR, fillcolor=fillcolor)  # expand=0(デフォルト)でOpenCVと同じ動作
pil_image = pil_image.rotate(angle, expand=True, resample=Image.BILINEAR, fillcolor=fillcolor)  # expand=Trueにすると回転画像全体を覆うサイズで出力される

# OpenCV
h, w, _ = cv_image.shape
matrix = cv2.getRotationMatrix2D((w/2, h/2), angle, 1)
cv_image = cv2.warpAffine(cv_image, matrix, (w, h), flags=cv2.INTER_LINEAR, borderMode=cv2.BORDER_CONSTANT, borderValue=fillcolor[::-1])

左右反転

# PIL
from PIL import ImageOps
pil_image = ImageOps.mirror(pil_image)

# OpenCV
cv_image = cv2.flip(cv_image, 1)

ぼかし

sigma = 3

# PIL
from PIL import ImageFilter
pil_image = pil_image.filter(ImageFilter.GaussianBlur(sigma))  # 標準偏差σを指定

# OpenCV
cv_image = cv2.GaussianBlur(cv_image, (15, 15), sigma)  # 半径と標準偏差σを指定

Padding

fillcolor = (124, 115, 104)
border_pil = (10, 20, 10, 40)  # 左上右下の順
border_cv = (20, 40, 10, 10)  # 上下左右の順

# PIL
from PIL import ImageOps
pil_image = ImageOps.expand(pil_image, border=border_pil, fill=fillcolor)

# OpenCV
cv_image = cv2.copyMakeBorder(cv_image, *border_cv, borderType=cv2.BORDER_CONSTANT, value=fillcolor[::-1])

切り取り

offset_y = 100
offset_x = 150
crop_h = 100
crop_w = 300

# PIL
pil_image = pil_image.crop((offset_x, offset_y, offset_x + crop_w, offset_y + crop_h))

# OpenCV
cv_image = cv_image[offset_y:offset_y + crop_h, offset_x:offset_x + crop_w]