目次
画像の2値化とは
画像の2値化とは画像を白(255)か黒(0)の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で確認する。
しきい値をトラックバーで選択して確認するスクリプトです。
# -*- 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値化+BGR分解で前景抽出
このひまわりの画像から前景を抽出しましょう。この場合は青色を抜き出して2値化を行うと簡単に前景と背景を分けることができます。thresholdTypeをcv2.THRESH_BINARY_INVに設定しています。こうすることで青が少ない場所が白色(255)になります。"s"を押すと背景が透明化された"result.png"が生成されます。
# -*- 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分解
このコードは画像を色相(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)です。彩度を使うと前景が抽出できそうです。
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
上手くいった例
上のスクリプトを使って、前景抽出してみました。
上手くいかなかった画像
次の画像は全く上手くいきませんでした。
自動で閾値設定(大津の二値化)
OpenCVで大津の2値化
cv2.thresholdのthresholdTypeを閾値のタイプ+ cv2.THRESH_OTSUとすることで 閾値を自動設定してくれます。
大津の2値化を使って前景抽出
# -*- 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)
結果
先ほどの例で私が見て閾値を選んだ結果と大体一致してます。
コメント