top of page

Color Extraction (C++)

 画像中から、特定の色を抽出し、その中心座標を求める方法を解説します。

1. はじめに

 

 OpenCVを使った色抽出の方法を説明します。

今回は以下のような手順で特定の色の物体を検出します。

 

  1. 画像にノイズ処理を行う。

  2. ノイズ処理施した画像のカラーモデルをRGBからHSVに変換する。

  3. HSVに変換した画像を各チャンネルに分ける。

  4. 各チャンネルごとにしきい値処理を行う。

  5. 各チャンネルの画像の論理和を取る。

  6. 抽出した画素の中心位置座標を求める。

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

  1. #include <opencv2/core/core.hpp>

  2. #include <opencv2/imgproc/imgproc.hpp>

  3.  

  4. struct hsv_threshold

  5. {

  6.     unsigned char hue_lower;

  7.     unsigned char hue_upper;

  8.     unsigned char saturation;

  9.     unsigned char value;

  10. };

  11.  

  12. cv::Mat extract_color(cv::Mat image, hsv_threshold threshold);

  13. 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を使って特定の色を抽出する関数とそのサンプルを作成しました。

しきい値を変更することで、他の色も抽出することができるのでぜひ挑戦して下さい。

 

参考文献

  1. OpenCV2プログラミングブック制作チーム(2011)『OpenCV 2 プログラミングブック』マイナビ

  2. 「OpenCV 2.2 C++ リファレンス」<http://opencv.jp/opencv-2svn/cpp/index.html#>(2014/8/14アクセス)

  1. #include "image_processing.h"

  2.  

  3. cv::Mat extract_color(cv::Mat image, hsv_threshold threshold)

  4. {

  5.     cv::Mat gaussian, hsv, channels[3], temp;

  6.     cv::Mat hue, saturation, value, binary;

  7.  

  8.     cv::GaussianBlur(image, gaussian, cv::Size(11,11), 10, 10);

  9.     cv::cvtColor(image, hsv, CV_BGR2HSV);

  10.     cv::split(hsv, channels);

  11.  

  12.     if( threshold.hue_lower <= threshold.hue_upper)

  13.     {

  14.         cv::threshold(channels[0], temp, threshold.hue_lower,  180, CV_THRESH_TOZERO);

  15.         cv::threshold(temp, hue, threshold.hue_upper, 180, CV_THRESH_TOZERO_INV);

  16.     }

  17.  

  18.     else

  19.     {

  20.         cv::Mat temp_1, temp_2;

  21.  

  22.         cv::threshold(channels[0], temp_1, threshold.hue_upper, 180, CV_THRESH_TOZERO_INV);

  23.         cv::threshold(channels[0], temp_2, threshold.hue_lower, 180, CV_THRESH_TOZERO);

  24.         cv::bitwise_or(temp_1, temp_2, hue);

  25.     }

  26.                                 

  27.     cv::threshold(channels[1], saturation, threshold.saturation, 255, CV_THRESH_BINARY);

  28.     cv::threshold(channels[2], value     , threshold.value,      255, CV_THRESH_BINARY);

  29.  

  30.     cv::bitwise_and(hue,  saturation, temp);

  31.     cv::bitwise_and(temp, value,      binary);

  32.  

  33.     cv::dilate(binary, temp, cv::Mat(), cv::Point(-1, -1), 1);

  34.     cv::erode(temp, binary, cv::Mat(), cv::Point(-1, -1), 1);

  35.  

  36.     return binary;

  37. }

  38.  

  39. cv::Point2i calculate_center(cv::Mat gray)

  40. {

  41.     cv::Point2i center = cv::Point2i(0, 0);

  42.  

  43.     cv::Moments moment = moments(gray, true);

  44.         

  45.     if(moment.m00 != 0)

  46.     {

  47.         center.x = (int)(moment.m10 / moment.m00);

  48.         center.y = (int)(moment.m01 / moment.m00);

  49.     }

  50.  

  51.     return center;

  52. }

  1. #include <opencv2/core/core.hpp>

  2. #include <opencv2/highgui/highgui.hpp>

  3. #include <iostream>

  4.  

  5. #include "image_processing.h"

  6.  

  7. const unsigned short width  = 640;

  8. const unsigned short height = 480;

  9.  

  10. int main(int argc, char *argv[])

  11. {

  12.     cv::VideoCapture cap(0);

  13.     cap.set(CV_CAP_PROP_FRAME_WIDTH,  width);

  14.     cap.set(CV_CAP_PROP_FRAME_HEIGHT, height);

  15.     

  16.     if(!cap.isOpened())

  17.     {

  18.          return -1;

  19.     }

  20.  

  21.     cv::namedWindow("Capture", CV_WINDOW_AUTOSIZE|CV_WINDOW_FREERATIO);

  22.  

  23.     hsv_threshold threshold_blue  = {90,  110, 180, 30};

  24.     hsv_threshold threshold_green = {50,  80,  50,  130};

  25.     hsv_threshold threshold_red   = {175, 5,   240, 110};

  26.  

  27.     cv::Mat frame;

  28.     cv::Mat binary_blue, binary_green, binary_red;

  29.  

  30.     cv::Point2i center_blue  = cv::Point2i(0, 0);

  31.     cv::Point2i center_green = cv::Point2i(0, 0);

  32.     cv::Point2i center_red   = cv::Point2i(0, 0);

  33.     

  34.     while(1)

  35.     {

  36.         cap >> frame;

  37.         

  38.         binary_blue  = extract_color(frame, threshold_blue);

  39.         binary_green = extract_color(frame, threshold_green);

  40.         binary_red   = extract_color(frame, threshold_red);         

  41.  

  42.         center_blue  = calculate_center(binary_blue);

  43.         center_green = calculate_center(binary_green);

  44.         center_red   = calculate_center(binary_red);

  45.  

  46.         cv::circle(frame, center_blue,  40, cv::Scalar(200, 0,   0),   4, 4);

  47.         cv::circle(frame, center_green, 40, cv::Scalar(0,   200, 0),   4, 4);

  48.         cv::circle(frame, center_red,   40, cv::Scalar(0,   0,   200), 4, 4);

  49.  

  50.         std::cout << "Blue: "    << center_blue

  51.                       << " Green: " << center_green

  52.                       << " Red: "     << center_red << std::endl;

  53.  

  54.         cv::imshow("Capture", frame);

  55.  

  56.         if(cv::waitKey(30) == 27)

  57.         {

  58.             cv::destroyAllWindows();

  59.             break;

  60.         }

  61.     }

  62. }    
     

カラーボールの検出結果

カラーボールの中心座標

bottom of page