Simple Color Extraction RTCs (C++)
Webカメラから取得した画像から、特定の色を抽出するRTコンポーネントを作成する方法を解説します。
▶ 更新履歴
-
15/08/2014 RTCの名称を変更しました。それにともない形名の表記を修正しています。
1. はじめに
今回は、以下のRTCを作成し、OpenCVを使った色抽出の方法を学びます。
-
CameraColorExtraction
Webカメラから画像を取得し、色抽出を行う。
抽出画素の中心座標をTimedPoint2D型で出力する。
-
TimedPoint2DConsoleOut
受け取ったTimedPoint2D型のデータをコンソール上に表示する。
今回は下図のように、青・緑・赤のカラーボールから緑のボールを検出します。
2. CameraColorExtraction RTC
CameraColorExtraciton RTCを作成します。
テンプレートを以下に従って設定てください。
-
モジュール名
CameraColorExtraction
-
ベンダ名
あなたの名前
-
モジュールカテゴリ
CAMERA
-
実行周期
30
-
アクションコールバック
onInitialize
onActivated
onDeactivated
onExecute
-
データポート
・point2dポート
ポート名(OutPort) point2d
データ型 TimedPoint2D
変数名 center_point
表示位置 RIGHT
-
言語
C++
use old build environment. (Linuxの場合)
■Windows OSの場合
テンプレートを生成したらCMakeを行い、ソリューションファイルを生成して下さい。
ソリューションを開いたら、以下の2つのプロジェクトにOpenCVをビルドするための、
追加のインクルード ディレクトリと追加のライブラリ ディレクトリ、追加の依存ファイルを設定して下さい。
-
CameraColorExtractionプロジェクト
-
CameraColorExtractionCompプロジェクト
追加の依存ファイルは、以下の3つになります。
■ソリューションの構成がDebugの場合
・opencv_core246d.lib
・opencv_highgui246d.lib
・opencv_imgproc246d.lib
■ソリューションの構成がReleaseの場合
・opencv_core246.lib
・opencv_highgui246.lib
・opencv_imgproc246.lib
■Linux OSの場合
Makefile.CameraColorExtractionを開いて、OpenCVのヘッダファイルとライブラリを指定します。
Makefileの修正方法は、OpenCVを使用したRTCの作成方法を御覧ください。
プロジェクトの設定が終わったら、CameraColorExtraction.cppに以下のコードを記述します。
以下のコードは抜粋となります。生成したテンプレートに当てはめて下さい。
-
#include "CameraColorExtraction.h"
-
-
// OpenCV
-
#include <opencv2/core/core.hpp>
-
#include <opencv2/highgui/highgui.hpp>
-
#include <opencv2/imgproc/imgproc.hpp>
-
-
#define WIDTH 640
-
#define HEIGHT 480
-
-
cv::VideoCapture cap;
-
-
RTC::ReturnCode_t CameraColorExtraction::onInitialize()
-
{
-
addOutPort("point2d", m_center_pointOut);
-
-
std::cout << "CameraCaptureComp" << std::endl;
-
cap = cv::VideoCapture(0);
-
cap.set(CV_CAP_PROP_FRAME_WIDTH, WIDTH);
-
cap.set(CV_CAP_PROP_FRAME_HEIGHT, HEIGHT);
-
-
if(!cap.isOpened())
-
{
-
return RTC::RTC_ERROR;
-
}
-
-
return RTC::RTC_OK;
-
}
-
-
RTC::ReturnCode_t CameraColorExtraction::onActivated(RTC::UniqueId ec_id)
-
{
-
std::cout << "CreateWindow" << std::endl;
-
cv::namedWindow("Capture", CV_WINDOW_AUTOSIZE|CV_WINDOW_FREERATIO);
-
cv::namedWindow("Binary", CV_WINDOW_AUTOSIZE|CV_WINDOW_FREERATIO);
-
-
return RTC::RTC_OK;
-
}
-
-
RTC::ReturnCode_t CameraColorExtraction::onDeactivated(RTC::UniqueId ec_id)
-
{
-
std::cout << "DestroyWindow" << std::endl;
-
cv::destroyWindow("Capture");
-
cv::destroyWindow("Binary");
-
-
return RTC::RTC_OK;
-
}
-
-
RTC::ReturnCode_t CameraColorExtraction::onExecute(RTC::UniqueId ec_id)
-
{
-
cv::Mat frame;
-
cap >> frame;
-
-
// ------------------------Color extraction------------------------
-
cv::Mat gaussian, hsv, channels[3], hue_temp, hue, saturation, value, binary_temp, binary;
-
-
cv::GaussianBlur(frame, gaussian, cv::Size(11,11), 10, 10);
-
cv::cvtColor(frame, hsv, CV_BGR2HSV);
-
-
cv::split(hsv, channels);
-
-
cv::threshold(channels[0], hue_temp, 55, 255, CV_THRESH_TOZERO);
-
cv::threshold(hue_temp, hue , 77, 255, CV_THRESH_TOZERO_INV);
-
cv::threshold(channels[1], saturation, 51, 255, CV_THRESH_BINARY);
-
cv::threshold(channels[2], value , 153, 255, CV_THRESH_BINARY);
-
-
cv::bitwise_and(hue, saturation, binary_temp);
-
cv::bitwise_and(binary_temp, value, binary);
-
-
cv::Moments moment = moments(binary, true);
-
int center_x = (int)(moment.m10 / moment.m00);
-
int center_y = (int)(moment.m01 / moment.m00);
-
-
cv::circle(frame, cv::Point(center_x, center_y), 80, cv::Scalar(0,0,200), 4, 4);
-
-
cv::imshow("Binary", binary);
-
-
std::cout << "x:" << center_x << " y:" <<center_y << std::endl;
-
m_center_point.data.x = center_x;
-
m_center_point.data.y = center_y;
-
m_center_pointOut.write();
-
// ------------------------Color extraction------------------------
-
-
cv::imshow("Capture", frame);
-
-
cv::waitKey(1);
-
-
return RTC::RTC_OK;
-
}
53-81行目以外のほとんどのコードが、OpenCVを使用したRTCの作成方法で解説したものと同じとなっています。
そこで、説明が今回作成するRTCに特有の部分に絞って解説いたします。
-
6行目
色抽出に使用するフィルタ処理のためにインクルードします。
-
33-34・42-43行目
キャプチャした画面と処理済みの画像を表示するためのウィンドウの生成・破棄を行います。
-
54行目
画像処理に使うMat型の変数を定義しています。
-
56行目
Gaussianフィルタを用いて、画像上のノイズを低減しています。
-
57行目
キャプチャした画像はRGBカラーモデルのデータなので、HSVカラーモデルに変換します。
HSVは色相・彩度・明度のことで、色を用いた画像処理を行う場合にはよく用いられます。
簡単にいえば、HSVは人の感性に近い緑や赤といった色を表現できるモデルとなっています。
-
59行目
HSVの各チャンネルに画像を分割します。
下図は各チャンネルに分割した画像を表示したものです。
各チャンネルの値域は以下のようになります。
・H: 0-180
・S: 0-255
・V: 0-255
H(色相)チャンネルの値域の上限値は255ではないので注意して下さい。
4. 動作確認
作成したRTCを実行して下さい。
下図のように、物体を抽出しコンソールに表示されることを確認できれば完了です。
-
61-64行目
各チャンネルの二値化を行います。
今回使用した緑のカラーボールの色のパラメータは以下のようになっています。
・H:110-140 deg
・S:20 %
・V:60 %
この値を、先ほどの示したOpenCVのHSVモデルの値域に変換します。
値域の変換は以下の式で行うことができます。
・H * 180 / 360.0
・S * 255 / 100.0
・V * 255 / 100.0
上記の式に従い、値を変換すると以下のようになります。
・H:55-77
・S:51
・V:153
それでは以上を踏まえ順番に処理を追っていきます。
cv::threshold(channels[0], hue_temp, 55, 255, CV_THRESH_TOZERO);
double cv::threshold(const Mat& src, Mat& dst, double threshold, double maxValue, int thiresholdType)
channels[0](色相)画像を与え、閾値55より上の値をそのままにし、それ以外を0にします。
二値化処理した画像をhue_tempに保存します。
CV_THRESH_TOZEROは特別な値で、maxValueを与えても関係ありません。
処理を行うと下図のようになります。


Hue(色相)


Saturation(彩度)
Value(明度)
cv::threshold(channels[1], saturation, 51, 255, CV_THRESH_BINARY);
channels[1](彩度)画像を与え、閾値51より上の値を255、それ以外の値を0にします。
二値化処理した画像をsaturationに保存します。
処理を行うと下図のようになります。

channnels[0](色相)
hue_temp

cv::threshold(hue_temp, hue , 77, 255, CV_THRESH_TOZERO_INV);
先ほど処理したhue_temp画像を与え、閾値77より下の値をそのままにし、それ以外を0にします。
二値化処理した画像をhueに保存します。
CV_THRESH_TOZERO_INVは特別な値で、maxValueを与えても関係ありません。
処理を行うと下図のようになります。

hue_temp
Hue

-
69-71行目
抽出した画素の重心を求めています。
この処理は画像のモーメントを求めることで計算しています。
-
73行目
計算した中心画素を基準として、円を描画します。この円はもとの画像であるframeに描画しています。
-
77-80行目
求めた中心座標をOutポートに出力します。
Point2D型のアクセス方法は以下のようになります。
・m_変数名.data.x
・m_変数名.data.y
以上で、CameraColorExtractionのコーディングは終了です。
コードが入力し終わったら、ビルドを行ってください。

channels[1]
saturation

cv::threshold(channels[2], value , 153, 255, CV_THRESH_BINARY);
channels[2](明度)画像を与え、閾値153より上の値を255、それ以外の値を0にします。
二値化処理した画像をvalueに保存します。
処理を行うと下図のようになります。

channels[2]
value

-
66-67行目
二値化処理した各HSVチャンネルをAND演算で合成します。
AND演算は2枚の画像の重なる部分画素に互いに値があれば255になります。
それ以外の場合は0になります。
つまり、3枚の二値化した画像の共通して白い部分が残ることになります。
文章にしてもわかりにくいので、下を見て下さい。
下左中図は緑Value・青Saturation・橙Hueというように着色し、重ね合わせた画像です。
下右図が各チャンネルを合成した結果であるbinaryです。
グレーの部分が下中図の白い部分に対応していることがわかります。

各チャンネル画像
各チャンネルを重ね合わせた画像


binary
3. TimedPoint2DConsoleOut RTC
TimedPoint2DConsoleOut RTCを作成します。
テンプレートを以下に従って設定てください。
-
モジュール名
TimedPoint2DConsoleOut
-
ベンダ名
あなたの名前
-
モジュールカテゴリ
CONSOLE
-
実行周期
100
-
アクションコールバック
onInitialize
onExecute
-
データポート
・point2dポート
ポート名(InPort) point2d
データ型 TimedPoint2D
変数名 point
表示位置 LEFT
-
言語
C++
テンプレートを生成したらCMakeを行い、TimedPoint2DConsoleOut.cppファイルにコードを記述して下さい。
以下のコードは抜粋となります。生成したテンプレートに当てはめて下さい。
-
RTC::ReturnCode_t TimedPoint2DConsoleOut::onExecute(RTC::UniqueId ec_id){
-
if(m_pointIn.isNew())
-
{
-
m_pointIn.read();
-
std::cout<< "x: " << m_point.data.x << "y: " << m_point.data.y << std::endl;
-
}
-
-
return RTC::RTC_OK;
-
}
コードの解説をします。
変数へのアクセス方法以外、ConsoleOutと同じであることがわかると思います。
-
2-5行目
Inポートに値が入力された場合、値を読み込みコンソールに表示します。
おさらいになりますが、TimedPoint2D型のアクセス方法は以下のようになります。
・m_変数名.data.x
・m_変数名.data.y
以上で、TimedPoint2DConsoleOutのコーディングは終了です。
コードが入力し終わったら、ビルドを行ってください。

A1. 簡単なHSV閾値の求め方
ここまでで、今回の目的である色抽出は達成出来ました。
自分で色抽出を行うには、閾値を自分で決定する必要があります。
おまけとして、今回私が行ったパラメータを求める簡単な方法を紹介します。
-
PhotoShop・GIMP (画像処理ソフト)
抽出したい物体のHSVパラメータを知るのは様々な方法があると思いますが、簡単なのは画像処理ソフトを使用することです。
下図はValue画像を画像処理ソフトを用いて2値化パラメータを求めている様子です。
このように、市販のソフトを使えばグラフィカルに閾値を決定できます。

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

5. おわりに
特定の色を抽出するRTCを作成しました。
二値化の閾値を変更することで、他の色も抽出することができるのでぜひ挑戦して下さい。
今回の解説ページのように、処理の途中経過を逐次表示しながらデバッグを行うこと間違いを発見しやすのでおすすめです。
*赤の色相は0をまたいでいるので二値化をサンプルそのままでは行えません。
赤色抽出を実装する場合はここを参考にしてください。
参考文献
-
OpenCV2プログラミングブック制作チーム(2011)『OpenCV 2 プログラミングブック』マイナビ
-
「画像処理ソリューション 色領域の抽出」<http://imagingsolution.blog107.fc2.com/blog-entry-248.html>(2013/9/7アクセス)
-
T.Nakaguchi「Nakaguchi@Home WIKI Tips for OpenCV 2」<http://imagingsolution.blog107.fc2.com/blog-entry-248.html>(2013/9/7アクセス)