Pillow と OpenCV とでそれぞれ二値画像処理および膨張処理

Pillow でOpenCV の膨張処理とを同じ処理がしたかったので、 その方法と処理結果が等しくなるか確認しました。

環境

コード全体

先に今回書いたコードを載せます。

# -*- coding: utf-8 -*-

import cv2
import numpy as np
from PIL import Image
from PIL import ImageFilter

def is_equal(cv_img, pil_img):
    return np.array_equal(cv_img,
            np.asarray(pil_img.convert('L')))

if __name__ == '__main__':
    img_file = 'test.png'
    cv_img = cv2.imread(img_file, cv2.IMREAD_GRAYSCALE)
    _, cv_img = cv2.threshold(cv_img, 127, 255, cv2.THRESH_BINARY)
    pil_img = Image.open(img_file).convert('1', dither=Image.NONE)
    print(is_equal(cv_img, pil_img))

    kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (3, 3))
    cv_img_d = cv2.dilate(cv_img, kernel)
    pil_img_d = pil_img.filter(ImageFilter.MaxFilter(3))
    print(is_equal(cv_img_d, pil_img_d))

    pil_img_d = pil_img.filter(ImageFilter.MaxFilter(5))
    print(is_equal(cv_img_d, pil_img_d))

このコードの結果は以下の通りです。

True
True
False

コードの説明

比較処理

def is_equal(cv_img, pil_img):
    return np.array_equal(cv_img,
            np.asarray(pil_img.convert('L')))

比較については以前、このような記事を書きました。

前回はカラー画像に対して比較しましたが、今回は二値画像です。 Pillow のimage をグレースケールにコンバートしてnumpy の配列に変換することで比較できます。 ただし、OpenCV の二値画像を0 or 255 で持つ必要があります。

二値化処理

_, cv_img = cv2.threshold(cv_img, 127, 255, cv2.THRESH_BINARY)

PythonOpenCV において、cv2.threshold関数は処理結果の画像が2番目の戻り値だということに注意です。

ところで、2番目の引数に指定した閾値について、以下のようにすると膨張処理前の比較結果がFalse になってしまいます。

_, cv_img = cv2.threshold(cv_img, 128, 255, cv2.THRESH_BINARY)

OpenCV のTHRESH_BINARY の説明によると、 閾値以下の値が0、つまり閾値と同じ値を持つピクセルは0になります。 一方、Pillow のbilevel におけるconvert の説明によると、

If dither is NONE, all values larger than 128 are set to 255 (white), all other values to 0 (black).

とあるので、128の値を持つピクセルは0になるのではないかと判断しおります。 しかしこの結果を考えると、Pillow のconvert による二値化では128の値を持つピクセルは255になるのではないかと思います(未検証)。 *1
(2019/2/24追記)以下の記事で検証しました。

膨張処理

OpenCV

kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (3, 3))
cv_img_d = cv2.dilate(cv_img, kernel)

OpenCV の膨張処理はcv2.dilate を使います。 kernel の生成は、以下のような方法も可能です。

kernel = np.ones((3, 3), np.uint8)

Pillow

pil_img_d = pil_img.filter(ImageFilter.MaxFilter(3))

Pillow での膨張処理はImageFilter.MaxFilter を使います。 ちなみにImageFilter.MaxFilter の引数は奇数じゃないとダメっぽいです(4と6で失敗することを確認)。

*1:OpenCV の二値化処理が間違っているとなると、閾値に127を指定して128以下の値が0になる処理ということになります。それは考えにくいので、今回こういう推測に至りました。