前回はOpenCVで扱える色空間をいくつか紹介しました。今回はその色空間を利用して、画像から特定の色の部分だけを抽出する方法を紹介します。
今回の方法は色で物体を検知したい場合に便利です。簡単なサンプルコードも紹介するので、自分で用意した画像でも試してみることで理解が深まると思います。
今回使用する画像
色抽出がわかりやすいようにカラフルな画像を使ってみます。
画像の読込のコードは以下の通りです。numpyは後々使うのでインポートしておきます。
import numpy as np
import cv2
img = cv2.imread('input/colorImage.jpg')
#画像のサイズを小さくする(前処理)
height = img.shape[0]
width = img.shape[1]
resized_img = cv2.resize(img,(round(width/4), round(height/4)))
RGBで抽出する
まずは最もオーソドックスなRGBで色を指定して抽出します。色を指定するためにはその基準となる色のRGBを知る必要があります。
以下サイトでは、読み込んだ画像の任意の部分のRGBを知ることができます。
https://www.peko-step.com/tool/getcolor.html
注意!!
OpenCVでの色指定はRGBではなくBGRの順なので間違えないようにしてください(以下BGRと表記します)。
青を抽出する
画像の中の青い(水色)部分を抽出してみます。まずはサンプルコードから紹介します。
#青を抽出
bgr = [210,150,40]
thresh = 40
#色の閾値
minBGR = np.array([bgr[0] - thresh, bgr[1] - thresh, bgr[2] - thresh])
maxBGR = np.array([bgr[0] + thresh, bgr[1] + thresh, bgr[2] + thresh])
#画像の2値化
maskBGR = cv2.inRange(resized_img,minBGR,maxBGR)
#画像のマスク(合成)
resultBGR = cv2.bitwise_and(resized_img, resized_img, mask = maskBGR)
cv2.imshow("Result BGR", resultBGR)
cv2.imshow("Result mask", maskBGR)
cv2.waitKey(0)
cv2.destroyAllWindows()
抽出する色はminBGR~maxBGRの範囲で指定します。最初に指定したbgr±threshの範囲で指定すると考えてください。
cv2.inRange()ではオリジナルの画像を指定したBGRの範囲で2値化します。指定したBGRの範囲に該当する部分は255(白)、それ以外は0(黒)で表現されます。2値化した結果は次の通りです。
オリジナル画像で青い部分だけが白で抜き出されていることがわかります。この情報をもとに、 cv2.bitwise_and()ではオリジナル画像をマスクします。その結果が以下です。
オリジナルの画像の色のままで抽出することができました。処理内容としては、オリジナルの画像とmaskBGRの画像を重ね合わせ、maskBGRで0(黒)以外の部分だけを表示しています。
赤を抽出する
次は赤色を抽出してみます。コードは上記と同じです。変数bgrの範囲を変えることで、赤色だけを取り出してみます。
#赤を抽出
bgr = [-30,-30,150]
thresh = 40
bgrは0から255までの範囲ですが、bとgにマイナスの数値を指定しています。これは、各範囲が指定の値±40であることから、0~10の範囲を指定していることに等しくなります。赤とオレンジが非常に似た領域になるため、その境界をきちんと分けるために今回の設定になりました。
実行結果は以下のようになります。
赤い部分だけを取り出すことができました。実際に色を取り出す際には、上記サイトなどである程度のBGRを調べてから、細かい修正を繰り返していくことになると思います。
HSVで抽出する
次はHSVで色抽出をしてみましょう。imread()で読み込んだ画像は基本BGRなので、それをHSVに変換するところから始まります。
import numpy as np
import cv2
img = cv2.imread('input/colorImage.jpg')
#画像のサイズを小さくする(前処理)
height = img.shape[0]
width = img.shape[1]
resized_img = cv2.resize(img,(round(width/4), round(height/4)))
# HSVに変換
resized_img_HSV = cv2.cvtColor(resized_img, cv2.COLOR_BGR2HSV)
cv2.imshow("HSV", resized_img_HSV)
HSVに変換した画像は元の色ではなく以下のように表示されます。
色相を赤、彩度を緑、明度を青で表現した画像になります。直感的な理解は難しいです。各要素に分化した画像であれば、それぞれの特徴を単一色で理解することができます。
Hを扱う際の注意点
H(Hue:色相)は通常0~360°の範囲で表現されることが多いですが、OpenCVでは0~179の範囲で指定します。なので、0~360で調べたHueは0~179の範囲に変換してから使用してください。
ちなみにSとVは0~255の範囲をとります。
青を抽出する
画像の青色の部分を抽出してみましょう。
HSVの上限と下限を指定して抽出します。
#青を抽出
hsv_min = np.array([100,45,70])
hsv_max = np.array([105,255,255])
#画像の2値化
maskHSV = cv2.inRange(resized_img_HSV,hsv_min,hsv_max)
#画像のマスク(合成)
resultHSV = cv2.bitwise_and(resized_img, resized_img, mask = maskHSV)
cv2.imshow("Result HSV", resultHSV)
cv2.imshow("Result mask", maskHSV)
cv2.waitKey(0)
cv2.destroyAllWindows()
マスク対象の元画像はHSVに変換する前の画像なので注意してください。
青色の抽出ができました。上のBGRでの青の抽出よりうまくできている気がします。
赤を抽出する
続いて赤色を抽出してみます。上図で示したように、Hueでは赤色が分断されています(0付近と179付近)。なので、その2か所を足し合わせる必要があります。
#赤を抽出
low_hsv_min = np.array([0, 200,50])
low_hsv_max = np.array([1, 255, 255])
#画像の2値化(Hueが0近辺)
maskHSV_low = cv2.inRange(resized_img_HSV,low_hsv_min,low_hsv_max)
high_hsv_min = np.array([178, 200,0])
high_hsv_max = np.array([179, 255, 255])
#画像の2値化(Hueが179近辺)
maskHSV_high = cv2.inRange(resized_img_HSV,high_hsv_min,high_hsv_max)
#2つの領域を統合
hsv_mask = maskHSV_low | maskHSV_high
#画像のマスク(合成)
resultHSV = cv2.bitwise_and(resized_img, resized_img, mask = hsv_mask)
cv2.imshow("Result HSV", resultHSV)
cv2.imshow("Result mask", hsv_mask)
cv2.waitKey(0)
cv2.destroyAllWindows()
inRange()でのマスク領域の算出を2回実施し、それらを足し合わせたものを元画像に重ね合わせます。
赤色範囲を抽出できました。
OpenCVの学習におすすめ書籍
PythonでOpenCV始めてみようという方におすすめなのが以下書籍です。実装例も豊富なので、1からコードを書かずとも学習を進めることができます。
オライリーの1冊は読み物というより辞書としての利用におすすめです。お値段結構しますが、細かい情報までしっかりと詰め込まれています。
まとめ
OpenCVで特定の色を抽出する方法を紹介しました。
BGRやHSVを使い分けることで、より検出精度を高めることができます。どちらが優れているとかはないので、使用する画像によっていろいろ試してみてください。
OpenCVの基礎を身につけるためのロードマップは以下を参考にしてください。
ではでは👋