本日はOpenCVを使って、特定の範囲の色を抽出してみたいと思います。
例としては(0,0,100)~(100,100,255)のように、色の範囲を与えてやり、それを基に範囲内の画素のみを抽出してみたいと思います。
色の抽出として、「(1) 普通に画像のRGB値を使う」手もありますが、RGBをHSVに変換し、「(2) HSVから色抽出する」例と今回は両方作成して比較してみたいと思います。
HSV : Hue, Saturation, Value
HSVとは、色相(Hue)、彩度(Saturation)、明度(Value)の三つの成分からなる色の表現法のことで、RGBのような光の3原色(赤・緑・青)とは違う3軸で色を表現します。
① 色相(H):色の種類(例えば赤、青、黄色)
② 彩度(S):色の鮮やかさ。
③ 明度(V):色の明るさ。
HSV色空間は人間が色を知覚する方法と類似しているとされているため、例えば照明条件が変わっても色相にはあまり変化が及ばないなど、色の抽出がしやすくなるケースもあります。
今回は、RGBでの抽出と、RGBをHSVに変換してからの抽出の2つのコードを書きましたので掲載します。
参考サイト:HSV色空間(Wikipedia)
今回の環境
・OS : Windows10(64bit)
・Visual Studio 2015 Community(導入済)
・OpenCV 2.4.11(環境構築済とする)
実装コード
(1) RGBによる色抽出
先に実装コードを掲載します。
#include <iostream> | |
#include <opencv2/highgui/highgui.hpp> | |
#include <opencv2/imgproc/imgproc.hpp> | |
using namespace std; | |
using namespace cv; | |
// 抽出する画像の輝度値の範囲を指定 | |
#define B_MAX 100 | |
#define B_MIN 0 | |
#define G_MAX 100 | |
#define G_MIN 0 | |
#define R_MAX 255 | |
#define R_MIN 100 | |
// メイン関数 | |
int main(void) | |
{ | |
// 入力画像名(ファイルパス) | |
string input_filename = "input.jpg"; | |
// 画像を3チャンネル(BGR)で読み込む | |
Mat input_image_rgb = imread(input_filename, CV_LOAD_IMAGE_COLOR); | |
if (input_image_rgb.empty()) { | |
cerr << "入力画像が見つかりません" << endl; | |
return -1; | |
} | |
// 表示して確認 | |
namedWindow("input_RGB"); | |
imshow("input_RGB", input_image_rgb); | |
// 結果保存用Matを定義 | |
Mat mask_image, output_image_rgb; | |
// inRangeを用いてフィルタリング | |
Scalar s_min = Scalar(B_MIN, G_MIN, R_MIN); | |
Scalar s_max = Scalar(B_MAX, G_MAX, R_MAX); | |
inRange(input_image_rgb, s_min, s_max, mask_image); | |
// マスク画像を表示 | |
namedWindow("mask"); | |
imshow("mask", mask_image); | |
imwrite("mask.jpg", mask_image); | |
// マスクを基に入力画像をフィルタリング | |
input_image_rgb.copyTo(output_image_rgb, mask_image); | |
// 結果の表示と保存 | |
namedWindow("output"); | |
imshow("output", output_image_rgb); | |
imwrite("output.jpg", output_image_rgb); | |
waitKey(0); | |
return 0; | |
} |
今回はinRange関数を利用しました。
inRange関数は、最小値のスカラーと最大値のスカラーを定義したときに、その間に収まる画素のマスクを作ってくれます。今回はコード中のs_minからs_maxまでの範囲の画素が取り出されます。
そして、inRange関数としては出力として、マスク画像を得ます。
今回は「赤が100~255、緑が0~100、青が0~100」のマスク画像を抽出したところ、以下のようになりました(上:原画像/下:マスク画像)。
注意点として、OpenCVではRGBではなく、BGRの順でカラーが設定されていますので、「Scalar(B_MIN, G_MIN, R_MIN);」としました。
このマスク画像の白の部分だけを抽出することで、RGB値による色の抽出を行うことができます。今回はcopyTo関数を用いて、入力画像の中でマスクが画像が白の部分だけを抽出して、表示を行いました。
赤の値が大きいところを抽出する形にしたのですが、実際に赤い部分が抜けていることがわかると思います。
(2) HSVによる色抽出
次に、HSVでの色抽出についても書いてみたいと思います。
RGBをHSVに変換する手順が必要なこと以外は特に大きな変更はありません。コードは以下となります。
#include <iostream> | |
#include <opencv2/highgui/highgui.hpp> | |
#include <opencv2/imgproc/imgproc.hpp> | |
using namespace std; | |
using namespace cv; | |
// 抽出する画像の輝度値の範囲を指定 | |
#define H_MAX 180 | |
#define H_MIN 160 | |
#define S_MAX 255 | |
#define S_MIN 50 | |
#define V_MAX 255 | |
#define V_MIN 50 | |
// メイン関数 | |
int main(void) | |
{ | |
// 入力画像名(ファイルパス) | |
string input_filename = "input.jpg"; | |
// 画像を3チャンネル(BGR)で読み込む | |
Mat input_image_rgb = imread(input_filename, CV_LOAD_IMAGE_COLOR); | |
if (input_image_rgb.empty()) { | |
cerr << "入力画像が見つかりません" << endl; | |
return -1; | |
} | |
// 表示して確認 | |
namedWindow("input_RGB"); | |
imshow("input_RGB", input_image_rgb); | |
// BGRからHSVへ変換 | |
Mat hsv_image, mask_image, output_image; | |
cvtColor(input_image_rgb, hsv_image, CV_BGR2HSV, 3); | |
// HSV変換した画像を表示して確認 | |
namedWindow("input_HSV"); | |
imshow("input_HSV", hsv_image); | |
imwrite("hsv.jpg", hsv_image); | |
// inRangeを用いてフィルタリング | |
Scalar s_min = Scalar(H_MIN, S_MIN, V_MIN); | |
Scalar s_max = Scalar(H_MAX, S_MAX, V_MAX); | |
inRange(hsv_image, s_min, s_max, mask_image); | |
// マスク画像を表示 | |
namedWindow("mask"); | |
imshow("mask", mask_image); | |
imwrite("mask.jpg", mask_image); | |
// マスクを基に入力画像をフィルタリング | |
input_image_rgb.copyTo(output_image, mask_image); | |
// 結果の表示と保存 | |
namedWindow("output"); | |
imshow("output", output_image); | |
imwrite("output.jpg", output_image); | |
waitKey(0); | |
return 0; | |
} |
違いとしては、「cvtColor(input_image_rgb, hsv_image, CV_BGR2HSV, 3);」で変換が必要となることくらいでしょうか。
HSV画像を、普通に表示すると以下のようになります。
BがH値、GがS値、RがV値になっています。よって、暗いところほど赤くなったりしているのがわかると思います。
このHSV値に対してフィルタを掛けますが、注意点があります。
① 色相(H):0~180 (×2すると度数になり、0~360度を示す)
② 彩度(S):0~255
③ 明度(V):0~255
OpenCVでは値が次のように決まっています。彩度と明度が0~255は感覚的に理解しやすいですが、色相(H)は0~180となる点には注意が必要です。この値をオーバーした場合、255までは循環するので0~255の値を使う分には問題ないですが、この認識がないと狙った色を抜くことができません。
色相は
参考サイト:HSV色空間(Wikipedia)
にもあるように、赤(0)からスタートして一回転して赤に戻ります。
今回のサンプルコードでは、赤っぽいところを抜きたかったのでHを160~180にしてみました。結果は以下となります。
一概に比較できるものではありませんが、HSVでも狙った色抽出ができていることがわかると思います。
RGBによる抽出と、状況によって使い分けたりしたいところですね。