Python OpenCV 画像のしきい値処理・2値化 threshold・adaptiveThreshold

OpenCV

この記事では、PythonのOpenCVで画像をしきい値処理(2値化)する方法を紹介します。

ここではcv2.threshold(), cv2.adaptiveThreshold()を使い、画像をしきい値処理(2値化)します。

関数と主な引数

cv2.threshold(src, thresh, maxval, type)

src画像(NumPy配列ndarray)
threshしきい値
maxval出力画像の画素値の最大値
typeしきい値処理の種類
cv2.THRESH_BINARY:しきい値より大きいとmaxval、それ以外の値には0を返す。
cv2.THRESH_BINARY_INV:しきい値より大きいと0、それ以外の値にはmaxvalを返す。
cv2.THRESH_TRUNC:しきい値より大きいとしきい値、それ以外の値はそのまま返す。
cv2.THRESH_TOZERO:しきい値より大きいと値をそのまま、それ以外の値には0を返す。
cv2.THRESH_TOZERO_INV:しきい値より大きいと0、それ以外の値はそのまま返す。

cv2.adaptiveThreshold(src, maxValue, adaptiveMethod, thresholdType, blockSize, C)

src画像(NumPy配列ndarray)
maxValue出力画像の画素値の最大値
adaptiveMethod適応的しきい値処理のアルゴリズム
cv2.ADAPTIVE_THRESH_MEAN_C : 近傍領域の中央値をしきい値とする。
cv2.ADAPTIVE_THRESH_GAUSSIAN_C : 近傍領域の重み付け平均値をしきい値とする。
重みの値はGaussian分布になるように計算される。
thresholdTypeしきい値処理の種類
cv2.THRESH_BINARY:しきい値より大きいとmaxval、それ以外の値には0を返す
cv2.THRESH_BINARY_INV:しきい値より大きいと0、それ以外の値にはmaxvalを返す
blockSizeしきい値計算に使用する近傍領域のサイズ。
1より大きい奇数を指定する必要がある。
C計算したしきい値から引く定数。
正負どちらの整数でも指定でき、しきい値のバイアスとなる。

使用例

画像をcv2.threshold()でしきい値処理

まず初めに、サンプル画像としてグラジエント画像を生成する。

Code:

import cv2
import matplotlib.pyplot as plt
import numpy as np

# グラジエント画像を生成
ar = np.linspace(0,255,800,dtype=np.uint8)
img = np.stack([ar]*600,axis=0)
img

# 画像の表示
plt.imshow(img, 'gray')
plt.show()

Output:

この画像にしきい値処理を適用する。5種類のしきい値処理方法をそれぞれ第4引数のtypeに指定すると以下のようになる。

Code:

# しきい値処理
ret,thresh1 = cv2.threshold(img,127,255,cv2.THRESH_BINARY)
ret,thresh2 = cv2.threshold(img,127,255,cv2.THRESH_BINARY_INV)
ret,thresh3 = cv2.threshold(img,127,255,cv2.THRESH_TRUNC)
ret,thresh4 = cv2.threshold(img,127,255,cv2.THRESH_TOZERO)
ret,thresh5 = cv2.threshold(img,127,255,cv2.THRESH_TOZERO_INV)

titles = ['Original Image','BINARY','BINARY_INV','TRUNC','TOZERO','TOZERO_INV']
images = [img, thresh1, thresh2, thresh3, thresh4, thresh5]

# 画像の表示
fig, axs = plt.subplots(2,3,figsize=(10,6))
axs = axs.reshape(-1)
for i in range(6):
    axs[i].imshow(images[i], vmin=0, vmax=255, cmap='gray')
    axs[i].set_title(titles[i])

fig.show()

Output:

画像をcv2.adaptiveThreshold()で適応的しきい値処理

上の例ではある画像に対して一つのしきい値で画像全体を処理をしたが、撮影した際に影が入るなど、画像の明るさが領域ごと異なっている画像では、効果的なしきい値処理を期待できない。そういった画像には、適応的しきい値処理を使うと上手くいくことが多い。適応的しきい値処理では、画像を小領域に分け、小領域ごとにしきい値の値を計算する。そのため領域ごとに明るさが変わっているような画像に対して、単純なしきい値処理より良い結果が得られる。

以下のノートの写真に、適応的しきい値処理を適用する。この写真では、画像左下に影が映りこんでいることが分かる。

画像全体を1つのしきい値で処理、適応的しきい値処理(近傍領域の中央値)、適応的しきい値処理(近傍領域の重み付け平均値、重みはGaussian分布)をそれぞれ適用する。

Code:

# 画像の読み込み
img = cv2.imread('./pic/note_shade.jpg', cv2.IMREAD_GRAYSCALE)

# 1つのしきい値で処理
ret,th1 = cv2.threshold(img,127,255,cv2.THRESH_BINARY)
# 適応的しきい値処理(近傍領域の中央値)
th2 = cv2.adaptiveThreshold(img,255,cv2.ADAPTIVE_THRESH_MEAN_C,cv2.THRESH_BINARY,11,10)
# 適応的しきい値処理(近傍領域の重み付け平均値、重みはGaussian分布)
th3 = cv2.adaptiveThreshold(img,255,cv2.ADAPTIVE_THRESH_GAUSSIAN_C,cv2.THRESH_BINARY,11,10)

titles = ['Original Image', 'Global Thresholding (thresh = 127)',
            'Adaptive Mean Thresholding', 'Adaptive Gaussian Thresholding']
images = [img, th1, th2, th3]

# 画像の表示
fig, axs = plt.subplots(2,2,figsize=(8,7))
axs = axs.reshape(-1)
for i in range(4):
    axs[i].imshow(images[i],'gray')
    axs[i].set_title(titles[i])

fig.show()

Output:

画像全体を1つのしきい値で処理した場合は、画像左下の領域が塗りつぶされてしまっていることがわかる。一方で、適応的しきい値処理では画像左下の領域も適切に2値化できている。

次に、適応的しきい値処理の引数blockSize, Cを変化させた場合の影響を確認してみる。しきい値のバイアスであるCを[10, 15, 20]の3通り、分割領域のサイズであるblockSizeを[11, 201, 401]の3通りで変化させ、合計9通りの組み合わせで適応的しきい値処理を実施した。

Code:

# パラメータ設定
C_value = [10, 15, 20]
blocksize = [11, 201, 401]

titles = []
images = []

# 適応的しきい値処理
for bs in blocksize:
    for c in C_value:
        titles.append(f'blocksize = {bs}, C = {c}')
        tmp_img = cv2.adaptiveThreshold(img,255,cv2.ADAPTIVE_THRESH_GAUSSIAN_C,cv2.THRESH_BINARY,bs,c)
        images.append(tmp_img)

# 画像の表示
fig, axs = plt.subplots(3,3,figsize=(15,15))
axs = axs.reshape(-1)

for i in range(9):
    axs[i].imshow(images[i],'gray')
    axs[i].set_title(titles[i])

fig.show()

Output:

Cを大きくすると、しきい値は小さくなる。上記の結果でも、C=20とした方が細かなノイズが低減されていたり、文字が若干細くなっていることが分かる。blockSizeを大きくすると、しきい値が大きくなる傾向がみられる。これは分割領域が大きくなるため、より明るい領域を含むようになるからである。

以上より、適応的しきい値処理の引数blockSize, Cを調整することで、2値化ノイズを低減したり、各2値領域の微修正ができる。

コメント

タイトルとURLをコピーしました