note.nkmk.me

Python, OpenCV, NumPyで画像を二値化(しきい値処理)

Date: 2018-02-08 / Modified: 2020-01-23 / tags: Python, OpenCV, 画像処理, NumPy

画像をしきい値で白黒に分ける二値化処理について、OpenCVの関数cv2.threshold()を使う方法と、NumPyの基本的な演算でndarrayを処理する方法について説明する。後者ではOpenCVは不要。

  • OpenCVで二値化(しきい値処理): cv2.threshold()
  • しきい値の自動選択(大津の二値化など)
  • NumPy配列ndarrayを直接処理して二値化

なお、以下のサンプルコードのOpenCVのバージョンは4.2。バージョンが異なる場合は振る舞いが違う可能性があるので注意。各バージョンの公式ドキュメントは以下から。

スポンサーリンク

OpenCVで二値化(しきい値処理): cv2.threshold()

以下の画像を例とする。

import cv2

im = cv2.imread('data/src/lena_square_half.png')

lena

関数cv2.threshold()で画像を二値化できる。

どのように処理されるかは第四引数typeに指定する値によって変わる。

typecv2.THRESH_BINARYとすると、しきい値threshより大きい値がmaxvalに、それ以外の値が0に置き換えられる。

カラー画像の場合は各色(チャンネル)ごとに処理される。白黒の結果がほしい場合は後述のcv2.THRESH_OTSUの例のように先にグレースケールに変換すればよい。

th, im_th = cv2.threshold(im, 128, 255, cv2.THRESH_BINARY)

print(th)
# 128.0

cv2.imwrite('data/dst/opencv_th.jpg', im_th)

lena OpenCV threshold THRESH_BINARY

返り値は使用されたしきい値と処理された配列(出力画像)のタプル。上の例のようにそれぞれの変数に格納できる。

typecv2.THRESH_TOZEROとした場合は、しきい値より大きい値は元のまま、それ以外の値が0に置き換えられる。

th, im_th_tz = cv2.threshold(im, 128, 255, cv2.THRESH_TOZERO)

print(th)
# 128.0

cv2.imwrite('data/dst/opencv_th_tz.jpg', im_th_tz)

lena OpenCV threshold THRESH_TOZERO

第四引数typeに指定できる値は以下を参照。

なお、cv2.THRESH_TOZEROでは引数maxvalの値が使われなかったり、後述のcv2.THRESH_OTSU, cv2.THRESH_TRIANGLEでは引数threshの値が使われなかったりするが、省略はできないので注意。

しきい値の自動選択(大津の二値化など)

cv2.threshold()の引数typecv2.THRESH_OTSU, cv2.THRESH_TRIANGLEとすると、それぞれOtsu法(大津の二値化)、トライアングル法で自動的にしきい値が選択される。

cv2.THRESH_OTSU, cv2.THRESH_TRIANGLEはバージョン4.2.0時点では8bitのシングルチャンネルにしか対応していないので注意。カラー画像(三次元配列)を指定するとエラーになる。

# th, im_th_otsu = cv2.threshold(im, 128, 192, cv2.THRESH_OTSU)
# error: OpenCV(4.2.0) /tmp/opencv-20200105-17262-cwpzm4/opencv-4.2.0/modules/imgproc/src/thresh.cpp:1529: error: (-215:Assertion failed) src.type() == CV_8UC1 in function 'threshold'

グレースケール(白黒)に変換して処理する。

im_gray = cv2.cvtColor(im, cv2.COLOR_BGR2GRAY)

th, im_gray_th_otsu = cv2.threshold(im_gray, 128, 192, cv2.THRESH_OTSU)

print(th)
# 117.0

cv2.imwrite('data/dst/opencv_th_otsu.jpg', im_gray_th_otsu)

lena OpenCV threshold THRESH_OTSU

公式ドキュメントには明記されていないが、自動的に選択されたしきい値より大きい値がmaxvalに、それ以外の値が0に置き換えられる。上の例では説明のためmaxval192としているが、白黒に二値化したい場合は255とすればよい。

NumPy配列ndarrayを直接処理して二値化

単純にしきい値を指定して白黒に二値化するだけなら、NumPyの基本的な演算で実行可能。

NumPyによる二値化処理の基本

簡単のために、まずグレースケール画像を二値化する例を示す。

import numpy as np
from PIL import Image

im_gray = np.array(Image.open('data/src/lena_square_half.png').convert('L'))
print(type(im_gray))
# <class 'numpy.ndarray'>

ここではOpenCVを使わない例としてPillowで画像を読み込んでndarrayに変換しているが、OpenCVで画像を読み込んでも同様の処理が可能。カラー画像を扱う場合は色の順番が違うので注意。

Pillowのメソッドconvert()でグレースケールに変換している。

lena gray

NumPy配列ndarrayに対して比較演算子を使うと、配列の各要素に対して比較した結果のブール値のndarrayが得られる。

thresh = 128

im_bool = im_gray > thresh
print(im_bool)
# [[ True  True  True ...  True  True False]
#  [ True  True  True ...  True  True False]
#  [ True  True  True ...  True False False]
#  ...
#  [False False False ... False False False]
#  [False False False ... False False False]
#  [False False False ... False False False]]

True1False0とみなされるため、任意の値をかけると、比較した結果がTrueの画素値はその値、Falseの画素値は0となる。例のように符号なし8ビット(uint8)のMax値255をかけると、True255(白)、False0(黒)となる。

maxval = 255

im_bin = (im_gray > thresh) * maxval
print(im_bin)
# [[255 255 255 ... 255 255   0]
#  [255 255 255 ... 255 255   0]
#  [255 255 255 ... 255   0   0]
#  ...
#  [  0   0   0 ...   0   0   0]
#  [  0   0   0 ...   0   0   0]
#  [  0   0   0 ...   0   0   0]]

Image.fromarray(np.uint8(im_bin)).save('data/dst/numpy_binarization.png')

lena NumPy binarization THRESH_BINARY

上の例はcv2.threshold()cv2.THRESH_BINARYに相当するが、比較結果のブール値のndarrayにスカラー値ではなく元の配列ndarrayをかけると、比較した結果がTrueの画素値は元のまま、Falseの画素値は0(黒)となり、cv2.THRESH_TOZEROに相当する結果となる。

im_bin_keep = (im_gray > thresh) * im_gray
print(im_bin_keep)
# [[162 161 156 ... 169 169   0]
#  [162 161 156 ... 169 169   0]
#  [164 155 159 ... 145   0   0]
#  ...
#  [  0   0   0 ...   0   0   0]
#  [  0   0   0 ...   0   0   0]
#  [  0   0   0 ...   0   0   0]]

Image.fromarray(np.uint8(im_bin_keep)).save('data/dst/numpy_binarization_keep.png')

lena NumPy binarization THRESH_TOZERO

NumPyによる二値化処理の応用

RGB各色に異なる値をかけた結果を使うと、カラフルな画像にもできる。

np.empty()で三次元の空のndarrayを生成し、各色(各チャンネル)に異なる値をかけた結果を格納する。shapeで取得したサイズ(高さ、幅)を*で展開してnp.emptyの引数に指定している。

im_bool = im_gray > 128
im_dst = np.empty((*im_gray.shape, 3))
r, g, b = 255, 128, 32

im_dst[:, :, 0] = im_bool * r
im_dst[:, :, 1] = im_bool * g
im_dst[:, :, 2] = im_bool * b

Image.fromarray(np.uint8(im_dst)).save('data/dst/numpy_binarization_color.png')

lena NumPy binarization color

ブール値のndarrayに対して否定演算子~を適用することも可能。しきい値より大きい値を0、それ以外を任意の値にできる。

im_bool = im_gray > 128
im_dst = np.empty((*im_gray.shape, 3))
r, g, b = 128, 160, 192

im_dst[:, :, 0] = im_bool * r
im_dst[:, :, 1] = ~im_bool * g
im_dst[:, :, 2] = im_bool * b

Image.fromarray(np.uint8(im_dst)).save('data/dst/numpy_binarization_color2.png')

lena NumPy binarization color2

OpenCVの関数cv2.imwrite()でカラー画像を保存する場合は色の並びをBGRにしておく必要があるので注意。

これまではグレースケール画像をもとに処理したが、上の例と同様の考え方でcv2.threshold()のようにカラー画像を処理することもできる。

空のndarrayを生成し、各色(各チャンネル)にそれぞれの結果を格納する。ここでは元もカラー画像(三次元配列)なのでnp.empty_like()を使う。

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

im_th = np.empty_like(im)

thresh = 128
maxval = 255

for i in range(3):
    im_th[:, :, i] = (im[:, :, i] > thresh) * maxval

Image.fromarray(np.uint8(im_th)).save('data/dst/numpy_binarization_from_color.png')

lena NumPy binarization from color

色によってしきい値を変えたり置換値を変えたり、cv2.threshold()よりも柔軟な処理も可能。上の例のように別々の変数を使ってもいいが、リスト(またはタプル)とzip()を使うとスッキリ書ける。

l_thresh = [64, 128, 192]
l_maxval = [64, 128, 192]

for i, thresh, maxval in zip(range(3), l_thresh, l_maxval):
    im_th[:, :, i] = (im[:, :, i] > thresh) * maxval

Image.fromarray(np.uint8(im_th)).save('data/dst/numpy_binarization_from_color2.png')

lena NumPy binarization from color2

スポンサーリンク
シェア
このエントリーをはてなブックマークに追加

関連カテゴリー

関連記事