Pynote

Python、機械学習、画像処理について

OpenCV - 画像2値化の閾値の決め方

概要

画像の2値化処理では閾値の値によって、得られる結果が変わってくる。
画像の注目する物体が写っている領域とそれ以外の領域をうまくわける閾値を選ぶ必要がある。


f:id:nekobean:20170730202815j:plain

今回使用する画像

手動で確認しながら決める

トラックバーを動かして確認しながら、手動で決める。

import cv2
import numpy as np

# 画像をグレースケールとして読み込む
src = cv2.imread('sign.jpg', 0)
dst = src.copy()

# トラックバーを動かしたときに呼ばれる関数
def threshold(x):
    global dst
    ret, dst = cv2.threshold(src, x, 255, cv2.THRESH_BINARY)


# ウィンドウを作成する
cv2.namedWindow('Image')
cv2.createTrackbar('Threshold', 'Image', 0, 255, threshold)

while(True):
    cv2.imshow('Image', dst)

    # ESC キーを押したら終了する
    key = cv2.waitKey(1)
    if key == 27:
        break

    # get current threshold.
    thresh = cv2.getTrackbarPos('Threshold', 'Image')

cv2.destroyAllWindows()

f:id:nekobean:20170730203031p:plain

画像の輝度値のヒストグラムから決める

前提として、背景と対象物体が区別しやすい画像である必要がある。
この場合、輝度値の分布が2つの山に分かれているので、2つの山をうまくわけられるような閾値を選べばよい。

# -*- coding: utf-8 -*-
import cv2
import numpy as np
import matplotlib.pyplot as plt

# 画像をグレースケールとして読み込む
img = cv2.imread('sign.jpg', 0)
img_flatten = img.flatten()

# 画像と輝度値のヒストグラムをグラフに表示する
fig, [axL, axR] = plt.subplots(1, 2, figsize=(6, 3))
fig.subplots_adjust(wspace=0.5)

axL.set_title('Image')
axL.imshow(img, cmap='gray')
axL.set_xticks([]), axL.set_yticks([])

axR.hist(img_flatten, bins=np.arange(0, 256))
axR.set_title('Brightness Histogram')
axR.set_xlabel('brigtness'), axR.set_ylabel('frequency')

plt.savefig("how_to_decide_threshold_03.png", bbox_inches="tight")

f:id:nekobean:20170730205231p:plain

輝度値のヒストグラムを描写して、手動で決めてもよいが、自動化する場合は次の方法がある。

pタイル法

pタイル (p-percentile / p-tile) 法とは、画像中に占める対象物体の領域の割合がわかっている場合、それを基に閾値を決める方法である。

# -*- coding: utf-8 -*-
import cv2
import numpy as np
import matplotlib.pyplot as plt

# 画像をグレースケールとして読み込む
img = cv2.imread('sign.jpg', 0)
img_flatten = img.flatten()

# 各ビンを [0, 1), [1, 2), ..., [255, 256] とすることで、各輝度値の頻度を計算する。
# np.histogram() のリファレンス参照。
bins = np.arange(0, 257)
hist, bin_edges = np.histogram(img_flatten, bins)
bin_edges = bin_edges[:-1]

# 頻度値の積算を計算する
cumsum = np.cumsum(hist)

# 対象物体が画像に占める割合が 50 %であるとわかっている場合
p = 0.5
for i in range(0, 256):
    pcent = cumsum[i] / cumsum[-1]
    if pcent > p:
        break

# 輝度値のヒストグラムとその積算をグラフに表示する
fig, [axL, axR] = plt.subplots(1, 2, figsize=(6, 3))
fig.subplots_adjust(wspace=0.5)

axL.plot(bin_edges, hist)
axL.set_title('Brightness Histogram')
axR.set_xlabel('brigthness'), axR.set_ylabel('frequency')

axR.plot(bin_edges, cumsum)
axR.set_title('Brightness Integration')
axR.set_xlabel('brightness'), axR.set_ylabel('cumulative sum')
axR.annotate('{} percentile: {}'.format(p, i), xy=(i, 10), xytext=(i, 50000),
             arrowprops=dict(facecolor='black', shrink=0.01))
plt.savefig("how_to_decide_threshold_04.png", bbox_inches="tight")

f:id:nekobean:20170731003731p:plain