Color Extraction (C++)
画像中から、特定の色を抽出し、その中心座標を求める方法を解説します。
1. はじめに
OpenCVを使った色抽出の方法を説明します。
今回は以下のような手順で特定の色の物体を検出します。
-
画像にノイズ処理を行う。
-
ノイズ処理施した画像のカラーモデルをRGBからHSVに変換する。
-
HSVに変換した画像を各チャンネルに分ける。
-
各チャンネルごとにしきい値処理を行う。
-
各チャンネルの画像の論理和を取る。
-
抽出した画素の中心位置座標を求める。
2. 色抽出
色を抽出するextract_color関数と、抽出した画素の中心座標を計算するcalculate_center関数を作成します。
■Windows OSの場合
ソリューションを作成したらここを参考に設定を行い、追加の依存ファイルに次の3つライブラリを追加してください。
*246は自分のOpenCVのバージョンに合わせて変更してください。
■ソリューションの構成がDebugの場合
・opencv_core246d.lib
・opencv_highgui246d.lib
・opencv_imgproc246d.lib
■ソリューションの構成がReleaseの場合
・opencv_core246.lib
・opencv_highgui246.lib
・opencv_imgproc246.lib
プロジェクトの設定が終わったら、以下のコードを記述します。
まずは、ヘッダファイルを記述します。
image_processing.h
-
#include <opencv2/core/core.hpp>
-
#include <opencv2/imgproc/imgproc.hpp>
-
-
struct hsv_threshold
-
{
-
unsigned char hue_lower;
-
unsigned char hue_upper;
-
unsigned char saturation;
-
unsigned char value;
-
};
-
-
cv::Mat extract_color(cv::Mat image, hsv_threshold threshold);
-
cv::Point2i calculate_center(cv::Mat gray);
-
1-2行目
OpenCVのヘッダファイルをインクルードします。
-
4-10行目
HSVの各チャンネルのしきい値を格納するための構造体を宣言します。
色相(hue)は上限値(lower)と下限値(upper)ではさみこんで処理するので、変数を2つ用意しています。
-
12行目
処理済みの画像 extract_color(画像, しきい値)
入力されたしきい値に合わせて抽出された画素(画像)を返します。
-
13行目
画像の重心座標 calcurate_center(画像)
入力されたグレースケール画像の重心座標を計算して返します。
次に、関数の実装を記述します。
color_extraction.cpp
3. 動作確認
作成したプログラムを実行して下さい。
下図のように、カラーボールが検出され、コンソール上にその座標が表示されれば完成です。
-
1行目
色抽出の関数などを定義した、image_processing.hをインクルードします。
-
3-37行目
抽出した画像 extract_color(カラー画像, しきい値)
カラー画像から特定の色の画素を抽出し返します。
・5-6行目
処理中の画像と処理結果の画像を格納するためのcv::Mat型の変数を定義します。
・8行目
ガウシアンフィルタを用いて平滑化します。
・9行目
画像の色空間を変換します。
・10行目
hsvの各チャンネルごとに画像を分けます。
・12-16行目
色相のしきい値の下限値が上限値以下の場合の処理を行います。
・14行目
cv::threshold(入力画像, 出力画像, しきい値, 最大値, 処理方法)
画像の要素に対して、しきい値処理を行います。
CV_THRESH_TOZEROは、しきい値より上の値をそのままにし、それ以外を0にします。
CV_THRESH_TOZEROの場合は、maxValueを与えても関係ありません。
・15行目
画像の要素に対して、しきい値処理を行います。
CV_THRESH_TOZERO_INVは、しきい値より下の値をそのままにし、それ以外を0にします。
CV_THRESH_TOZERO_INVは特別な値で、maxValueを与えても関係ありません。
以上の処理で、特定の色相の画素を抽出することができます。
・18-25行目
色相のしきい値の12-16行目の場合以外の処理を行います。
具体的には、赤のように色相が0をまたぐような場合に使用します。
・20行目
一時的にしきい値処理した画像を格納するcv::Mat型の変数を用意します。
・22-23行目
画像の画素に対して、しきい値処理を行います。
しきい値より下の値をtemp_1に格納し、しきい値より上の値をtemp_2に格納します。
・24行目
temp_1とtemp_2に格納された画素に論理和(OR)をとります。
つまり、temp_1が持つ画素とtemp_2が持つ画素がhueに格納されることになります。
以上の処理で、赤のように0をまたぐ色相の画素を抽出することができます。
・27行目
画像の画素に対して、しきい値処理を行い、しきい値以上の彩度を持つ画素を抽出します。
CV_THRESH_BINARYはしきい値より上の値をmaxValue、それ以外の値を0にします。
・28行目
画像の画素に対して、しきい値処理を行い、しきい値以上の明度を持つ画素を抽出します。
CV_THRESH_BINARYはしきい値より上の値をmaxValue、それ以外の値を0にします。
・30-31行目
特定の色相と彩度、明度を抽出したそれぞれの画像の論理積(AND)をとります。
つまり、特定の色相と彩度、明度をもつ画素のみが残り、特定の色を画像から抽出することができます。
・33-34行目
膨張と収縮を行い、抽出した画像のノイズを除去します。
膨張・収縮はノイズ除去に用いられる一般的な方法です。
・33行目
cv::dilate(入力画像, 出力画像, 構造要素, 構造要素内のアンカー位置, 繰り返し回数)
膨張処理を行います。
他にも引数はありますが、とりあえずは以上の引数がわかれば使用できます。
cv::Mat()は3✕3の句形構造を表し、cv::Point(-1, -1)は構造要素の中心にアンカーがあることを表します。
・34行目
cv::erode(入力画像, 出力画像, 構造要素, 構造要素内のアンカー位置, 繰り返し回数)
収縮処理を行います。
他にも引数はありますが、とりあえずは以上の引数がわかれば使用できます。
cv::Mat()は3✕3の句形構造を表し、cv::Point(-1, -1)は構造要素の中心にアンカーがあることを表します。
・36行目
特定の色の画素を返します。
-
39-52行目
画像中心の座標 calcurate_center(グレースケール画像)
グレースケール画像の重心を計算し、中心座標を返します。
・41行目
中心座標を格納するためcv::Point2i型の変数を定義します。
・42行目
モーメント moments(画像, バイナリイメージフラグ)
画像のモーメントを計算します。
バイナリイメージフラグがtrueの場合は、0でない値を持つ画素は1として扱われます。
・44-48行目
モーメントm00が0でない場合に、求めた空間モーメントから重心を求めます。
・50行目
求めた重心座標を返します。
次に、ここまで作成した関数を用いて、Webカメラからキャプチャした画像からカラーボールの座標を検出します。
今回は青、緑、赤の3つのカラーボールを検出します。
main.cpp
-
50-1-3行目
画像処理に必要なヘッダファイルをインクルードします。
-
3行目
コンソール上に検出したボールの中心座標を表示するのでiostreamをインクルードします。
-
5行目
先ほど作成した関数が定義されているimage_processing.hをインクルードします。
-
6-7行目
キャプチャする画像のサイズを定義します。
使用するカメラに合わせて設定してください。
-
10-62行目
Webカメラからキャプチャした画像から3つのカラーボールを検出します。
・12-19行目
Webカメラを初期化し、設定を行います。
Webカメラに接続できない場合はエラーを返します。
・21行目
処理結果を表示するためのウィンドウを作成します。
・23-25行目
抽出するカラーボールのしきい値を設定します。
OpenCVではHSVの値域は以下のようになります。
・Hue(色相): 0-180
・Saturation(彩度): 0-255
・Value(明度): 0-255
OpenCVではHSVのカラーモデルは六角錘モデルになります。
よくある、円錐や円柱モデルではないので注意してください。
また、Hueの値は0-255ではなく0-180なので値域に注意してください。
extract_color関数では赤のように色相が0をまたぐ場合は、24行目のように上限値と下限値を設定してください。
・27-28行目
Webカメラからのキャプチャ画像と、色抽出した画像を格納するcv::Mat型の変数を用意します。
・30-32行目
検出したカラーボールの中心座標を格納する変数をそれぞれ定義します。
・34-61行目
カメラから取得した画像から毎フレーム、カラーボールを検出します。
・36行目
カメラ画像をキャプチャします。
・38-40行目
extract_color関数を使って、特定の色の画素をそれぞれ抽出します。
・42-44行目
calculate_center関数を使って抽出した特定の画素の中心座標をそれぞれ算出します。
・46-48行目
検出したカラーボールの中心座標を基準として円をそれぞれ描画します。
・50-52行目
検出したカラーボールの中心座標をコンソール上に出力します。
・54行目
カラーボールの検出の処理結果を描画します。
・56-60行目
Escキーが押されたら、全てのウィンドウを閉じループから脱出します。
A1. 簡単なHSV閾値の求め方
ここまでで、今回の目的である色抽出は達成出来ました。
自分で色抽出を行うには、閾値を自分で決定する必要があります。
おまけとして、今回私が行ったパラメータを求める簡単な方法を紹介します。
-
PhotoShop・GIMP (画像処理ソフト)
抽出したい物体のHSVパラメータを知るのは様々な方法があると思いますが、簡単なのは画像処理ソフトを使用することです。
下図はValue画像を画像処理ソフトを用いて2値化パラメータを求めている様子です。
このように、市販のソフトを使えばグラフィカルに閾値を決定できます。

-
Just Color Picker
私がおすすめなのは、Just Color Pickerです。
このソフトウェアはマウスカーソルが指し示す部分のHSVパラメータを下図のように無数に記録することができます。
この機能を使って、画面に表示しておいた対象物体を複数箇所指示することで、色のばらつきを知ることができます。
この情報を元に、閾値を決定します。

4. おわりに
OpenCVを使って特定の色を抽出する関数とそのサンプルを作成しました。
しきい値を変更することで、他の色も抽出することができるのでぜひ挑戦して下さい。
参考文献
-
OpenCV2プログラミングブック制作チーム(2011)『OpenCV 2 プログラミングブック』マイナビ
-
「OpenCV 2.2 C++ リファレンス」<http://opencv.jp/opencv-2svn/cpp/index.html#>(2014/8/14アクセス)
-
#include "image_processing.h"
-
-
cv::Mat extract_color(cv::Mat image, hsv_threshold threshold)
-
{
-
cv::Mat gaussian, hsv, channels[3], temp;
-
cv::Mat hue, saturation, value, binary;
-
-
cv::GaussianBlur(image, gaussian, cv::Size(11,11), 10, 10);
-
cv::cvtColor(image, hsv, CV_BGR2HSV);
-
cv::split(hsv, channels);
-
-
if( threshold.hue_lower <= threshold.hue_upper)
-
{
-
cv::threshold(channels[0], temp, threshold.hue_lower, 180, CV_THRESH_TOZERO);
-
cv::threshold(temp, hue, threshold.hue_upper, 180, CV_THRESH_TOZERO_INV);
-
}
-
-
else
-
{
-
cv::Mat temp_1, temp_2;
-
-
cv::threshold(channels[0], temp_1, threshold.hue_upper, 180, CV_THRESH_TOZERO_INV);
-
cv::threshold(channels[0], temp_2, threshold.hue_lower, 180, CV_THRESH_TOZERO);
-
cv::bitwise_or(temp_1, temp_2, hue);
-
}
-
-
cv::threshold(channels[1], saturation, threshold.saturation, 255, CV_THRESH_BINARY);
-
cv::threshold(channels[2], value , threshold.value, 255, CV_THRESH_BINARY);
-
-
cv::bitwise_and(hue, saturation, temp);
-
cv::bitwise_and(temp, value, binary);
-
-
cv::dilate(binary, temp, cv::Mat(), cv::Point(-1, -1), 1);
-
cv::erode(temp, binary, cv::Mat(), cv::Point(-1, -1), 1);
-
-
return binary;
-
}
-
-
cv::Point2i calculate_center(cv::Mat gray)
-
{
-
cv::Point2i center = cv::Point2i(0, 0);
-
-
cv::Moments moment = moments(gray, true);
-
-
if(moment.m00 != 0)
-
{
-
center.x = (int)(moment.m10 / moment.m00);
-
center.y = (int)(moment.m01 / moment.m00);
-
}
-
-
return center;
-
}
-
#include <opencv2/core/core.hpp>
-
#include <opencv2/highgui/highgui.hpp>
-
#include <iostream>
-
-
#include "image_processing.h"
-
-
const unsigned short width = 640;
-
const unsigned short height = 480;
-
-
int main(int argc, char *argv[])
-
{
-
cv::VideoCapture cap(0);
-
cap.set(CV_CAP_PROP_FRAME_WIDTH, width);
-
cap.set(CV_CAP_PROP_FRAME_HEIGHT, height);
-
-
if(!cap.isOpened())
-
{
-
return -1;
-
}
-
-
cv::namedWindow("Capture", CV_WINDOW_AUTOSIZE|CV_WINDOW_FREERATIO);
-
-
hsv_threshold threshold_blue = {90, 110, 180, 30};
-
hsv_threshold threshold_green = {50, 80, 50, 130};
-
hsv_threshold threshold_red = {175, 5, 240, 110};
-
-
cv::Mat frame;
-
cv::Mat binary_blue, binary_green, binary_red;
-
-
cv::Point2i center_blue = cv::Point2i(0, 0);
-
cv::Point2i center_green = cv::Point2i(0, 0);
-
cv::Point2i center_red = cv::Point2i(0, 0);
-
-
while(1)
-
{
-
cap >> frame;
-
-
binary_blue = extract_color(frame, threshold_blue);
-
binary_green = extract_color(frame, threshold_green);
-
binary_red = extract_color(frame, threshold_red);
-
-
center_blue = calculate_center(binary_blue);
-
center_green = calculate_center(binary_green);
-
center_red = calculate_center(binary_red);
-
-
cv::circle(frame, center_blue, 40, cv::Scalar(200, 0, 0), 4, 4);
-
cv::circle(frame, center_green, 40, cv::Scalar(0, 200, 0), 4, 4);
-
cv::circle(frame, center_red, 40, cv::Scalar(0, 0, 200), 4, 4);
-
-
std::cout << "Blue: " << center_blue
-
<< " Green: " << center_green
-
<< " Red: " << center_red << std::endl;
-
-
cv::imshow("Capture", frame);
-
-
if(cv::waitKey(30) == 27)
-
{
-
cv::destroyAllWindows();
-
break;
-
}
-
}
-
}

カラーボールの検出結果
カラーボールの中心座標
