↓もし、良かったらSNSでの紹介よろしくお願いします。

OpenCV(Python)の2値化について(利用法、閾値の自動決定)

bootstrap

画像の2値化の処理についてまとめてみました。

画像の2値化とは

画像の2値化とは画像を白(255)か黒(0)の2値の画像に変換する処理です。主にグレースケール画像に対して行われる処理で閾値より大きいところを白、閾値以下ところを黒といった具合に2値化を行います。

2値化の魅力

様々なアルゴリズムを強化できる

2値化はテンプレートマッチングなどの物体認識アルゴリズムと組み合わせることで大きな力を発揮します。

2値化を使いこなすには上手なグレースケール化が重要

カラー画像を単純な方法でグレースケール化をして2値化をするだけだと良い結果が得られません。2値化を上手に使うためにはグレースケール化を工夫する必要があります。これについては具体例を交えて解説していきます。

OpenCVで2値化

cv2.thresholdを使う

ret, result = cv2.threshold(img,thresh,max_val,thresholdType)

retは2値化が成功したかどうかのフラグです。resultは2値化された画像を示しています。imgは入力画像、threshは閾値、max_valは2値化するときの白の値です。thresholdTypeは2値化する方法です。thresholdTypeにcv2.THRESH_BINARYを指定すると閾値以上がmax_valになります。

cv2.thresholdをGUIで確認する。

threading

しきい値をトラックバーで選択して確認するスクリプトです。

# -*- coding: utf-8 -*-
import cv2
thresh = 0
max_val = 255
thresholdType = cv2.THRESH_BINARY
#トラックバーで、しきい値を変更
def changethresh(pos):
    global thresh
    thresh = pos
# 画像をグレースケールで読み込む
img = cv2.imread("img.jpg", 0)
# ウィンドウの名前を設定
cv2.namedWindow("img")
cv2.namedWindow("thresh")
#トラックバーのコールバック関数の設定
cv2.createTrackbar("trackbar", "thresh", 0, 255, changethresh)
while(1):
    cv2.imshow("img", img)
    _, thresh_img = cv2.threshold(img, thresh, max_val, thresholdType)
    cv2.imshow("thresh", thresh_img)
    k = cv2.waitKey(1)
    # Escキーを押すと終了
    if k == 27:
        break

2値化を利用してひまわりを抽出

sun-flower

このひまわりの画像から前景を抽出しましょう。この場合は青色を抜き出して2値化を行うと簡単に前景と背景を分けることができます。thresholdTypeをcv2.THRESH_BINARY_INVに設定しています。こうすることで青が少ない場所が白色(255)になります。”s”を押すと背景が透明化された”result.png”が生成されます。

thresh_sunflower

# -*- coding: utf-8 -*-
import cv2
import numpy as np
thresh = 0
max_val = 255
thresholdType = cv2.THRESH_BINARY_INV
# トラックバーで、しきい値を変更
def changethresh(pos):
    global thresh
    thresh = pos
# 画像を読み込む
img = cv2.imread("sun-flower.jpg")
#BGRで分解
blue_img, green_img, red_img = cv2.split(img)
# ウィンドウの名前を設定
cv2.namedWindow("img")
cv2.namedWindow("thresh")
# トラックバーのコールバック関数の設定
cv2.createTrackbar("trackbar", "thresh", 0, 255, changethresh)
while(1):
    cv2.imshow("img", img)
    _, thresh_img = cv2.threshold(blue_img, thresh, max_val,    thresholdType)
    cv2.imshow("thresh", thresh_img)
    k = cv2.waitKey(1)
    # Escキーを押すと終了
    if k == 27:
        break
    # sを押すと結果を保存
    if k == ord("s"):
        result = cv2.merge(bgr + [thresh_img])
        cv2.imwrite("result.png", result)
        break

HSV分解を使ってみる。

RGB分解よりHSV分解の方が上手くいく?

前の例は前景が黄色(Bがほとんど0)、背景が(Bがほとんど255)という
前景抽出しやすい状況でした。そこで、もっと一般的な画像に応用できそうな方法を考えてみます。ペイントツールを使って色々な方法で画像を編集していたら、HSV分解が使えそうなので試してみます。

HSVとは?

色はRBG(赤青緑)で一般的に表現されますが、HSVで表現することもできます。
HSVとは色相(Hue)、彩度(Saturation)、明度(Value)を表しています。
HSVによる色の表現のほうがRBGに比べて人の直感的な色の認識に近いらしいです。

OpenCVでHSV分解

apple-256261_640

このコードは画像を色相(H)、彩度(S)、明度(V)に分解して、
それぞれをウィンドに表示します。上記の画像を”apple.jpg”として保存して、このコードを実行すると、りんごの画像がHSV分解されます。

# -*- coding: utf-8 -*-
import cv2
#画像を表示するウィンドの用意
cv2.namedWindow("img")
cv2.namedWindow("h")
cv2.namedWindow("s")
cv2.namedWindow("v")
#画像の読み込み
img = cv2.imread("apple.jpg")
#BGRをHSVに変換
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
#HSVに分解
h_img, s_img, v_img = cv2.split(hsv)
#元画像とHSV分解した画像をそれぞれ表示
cv2.imshow("img", img)
cv2.imshow("h", h_img)
cv2.imshow("s", s_img)
cv2.imshow("v", v_img)
cv2.waitKey(-1)

HSV分解の結果

HSVの結果は左から色相(H)、彩度(S)、明度(V)です。彩度を使うと前景が抽出できそうです。

apple_h
apple_s
apple_v

2値化+HSVで前景抽出

サンプルコード

python thresh_hsv.py 画像のパス

で実行すると前の例と同じように前景抽出ができます。

#thresh_hsv.py
# -*- coding: utf-8 -*-
import cv2
import sys
thresh = 0
max_val = 255
thresholdType = cv2.THRESH_BINARY
#トラックバーで、しきい値を変更
def changethresh(pos):
    global thresh
    thresh = pos
filename = sys.argv[1]
#画像を読み込む
img = cv2.imread(filename)
#BGRをHSVに変換
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
#HSVに分解
h_img, s_img, v_img = cv2.split(hsv)
#ウィンドウの名前を設定
cv2.namedWindow("img")
cv2.namedWindow("thresh")
#トラックバーのコールバック関数の設定
cv2.createTrackbar("trackbar", "thresh", 0, 255, changethresh)
while(1):
    cv2.imshow("img", img)
    _, thresh_img = cv2.threshold(s_img, thresh, max_val, thresholdType)
    cv2.imshow("thresh", thresh_img)
    k = cv2.waitKey(1)
    #Escキーを押すと終了
    if k == 27:
        break
    #sを押すと結果を保存
    if k == ord("s"):
        result = cv2.merge(cv2.split(img) + [thresh_img])
        cv2.imwrite(filename[:filename.rfind(".")] + "_result.png", result)
        break

上手くいった例

上のスクリプトを使って、前景抽出してみました。

apple-256261_640
apple_result
jeans-564089_640jeans-564089_640_result
sun-flowersun-flower_result

上手くいかなかった画像

次の画像は全く上手くいきませんでした。

birdleather-shoes-2530462_640

自動で閾値設定

大津の二値化を使って前景抽出

# -*- coding: utf-8 -*-
import cv2
import sys
filename = sys.argv[1]
#画像を読み込む
img = cv2.imread(filename)
#BGRをHSVに変換
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
#HSVに分解
h_img, s_img, v_img = cv2.split(hsv)
#cv2.THRESH_OTSUをフラグに足すと閾値を自動決定してくれます。
_, thresh_img = cv2.threshold(s_img, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
result = cv2.merge(cv2.split(img) + [thresh_img])
cv2.imwrite(filename[:filename.rfind(".")] + "_result_otsu.png", result)

結果

先ほどの例で私が見て閾値を選んだ結果と大体一致してます。

apple-256261_640
apple_result_otsu
jeans-564089_640
jeans-564089_640_result_otsu
sun-flower
sun-flower_result_otsu