Simple Color Extraction RTCs (Python)
Webカメラから取得した画像から、特定の色を抽出するRTコンポーネントを作成する方法を解説します。
▶ 更新履歴
-
15/08/2014 RTCの名称を変更しました。それにともない形名の表記を修正しています。
-
22/12/2013 コード上にOpenCVのimport部分を記載し忘れていたので、追記しました。
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
-
言語
Python
CameraColorExtraction.pyに以下のコードを記述します。
以下のコードは抜粋となります。生成したテンプレートに当てはめて下さい。
-
# Import OpenCV
-
import cv2.cv as cv
-
-
def __init__(self, manager):
-
OpenRTM_aist.DataFlowComponentBase.__init__(self, manager)
-
self._d_center_point = RTC.TimedPoint2D(RTC.Time(0,0), RTC.Point2D(0,0))
-
"""
-
"""
-
self._point2dOut = OpenRTM_aist.OutPort("point2d", self._d_center_point)
-
-
def onInitialize(self):
-
self.addOutPort("point2d",self._point2dOut)
-
print('CameraColorExtractionComp')
-
self.cap = cv.CaptureFromCAM(0)
-
-
return RTC.RTC_OK
-
-
def onActivated(self, ec_id):
-
-
print('CreateWindow')
-
cv.NamedWindow("Capture", cv.CV_WINDOW_AUTOSIZE)
-
cv.NamedWindow("Binary", cv.CV_WINDOW_AUTOSIZE)
-
-
return RTC.RTC_OK
-
-
def onDeactivated(self, ec_id):
-
-
print('DestoryWindow ')
-
cv.DestroyAllWindows()
-
-
return RTC.RTC_OK
-
-
def onExecute(self, ec_id):
-
frame = cv.QueryFrame(self.cap)
-
-
frame_size = cv.GetSize(frame)
-
resize = cv.CreateImage((frame_size[0] / 4, frame_size[1] / 4), 8, 3)
-
cv.Resize(frame, resize)
-
-
hsv = cv.CreateImage(cv.GetSize(resize), 8, 3)
-
temp = cv.CreateImage(cv.GetSize(resize), 8, 1)
-
-
cv.CvtColor(resize, hsv, cv.CV_BGR2HSV);
-
-
hue = cv.CreateImage(cv.GetSize(hsv), 8, 1)
-
saturation = cv.CreateImage(cv.GetSize(hsv), 8, 1)
-
value = cv.CreateImage(cv.GetSize(hsv), 8, 1)
-
-
cv.Split(hsv, hue, saturation, value, None)
-
-
hue_thresh = cv.CreateImage(cv.GetSize(hsv), 8, 1)
-
saturation_thresh = cv.CreateImage(cv.GetSize(hsv), 8, 1)
-
value_thresh = cv.CreateImage(cv.GetSize(hsv), 8, 1)
-
-
cv.Threshold(hue, temp, 55, 255, cv.CV_THRESH_TOZERO)
-
cv.Threshold(temp, hue_thresh, 77, 255, cv.CV_THRESH_TOZERO_INV)
-
cv.Threshold(saturation, saturation_thresh, 51, 255, cv.CV_THRESH_BINARY)
-
cv.Threshold(value, value_thresh, 153, 255, cv.CV_THRESH_BINARY)
-
-
binary = cv.CreateImage(cv.GetSize(resize), 8, 1)
-
cv.Zero(temp)
-
-
cv.And(hue_thresh, saturation_thresh, temp)
-
cv.And(temp, value_thresh, binary)
-
-
mat = cv.GetMat(binary)
-
mm = cv.Moments(mat, 1)
-
m_00 = cv.GetSpatialMoment(mm, 0, 0)
-
m_10 = cv.GetSpatialMoment(mm, 1, 0)
-
m_01 = cv.GetSpatialMoment(mm, 0, 1)
-
-
try:
-
center_x = int(m_10 / m_00) * 4
-
center_y = int(m_01 / m_00) * 4
-
cv.Circle(frame, (center_x, center_y), 80, cv.CV_RGB(255, 0, 0), 2)
-
-
except:
-
center_x = 0
-
center_y = 0
-
-
self._d_center_point.data = RTC.Point2D(center_x, center_y)
-
OpenRTM_aist.setTimestamp(self._d_center_point)
-
self._point2dOut.write()
-
-
print self._d_center_point.data
-
-
cv.ShowImage("Capture", frame)
-
cv.ShowImage("Binary", binary)
-
-
cv.WaitKey(1)
-
-
return RTC.RTC_OK
36-87行目以外のほとんどのコードが、OpenCVを使用したRTCの作成方法で解説したものと同じとなっています。
そこで、説明が今回作成するRTCに特有の部分に絞って解説いたします。
-
6行目
OpenRTM-aist Python版では、いくつかのデータ型の初期化が正しく行われません。
赤字のように書き直し、Point2D型で初期化を行って下さい。
-
20-22・28-29行目
キャプチャした画面と処理済みの画像を表示するためのウィンドウの生成・破棄を行います。
-
36-38行目
キャプチャした画像を縦横それぞれ1/4にリサイズしています。
OpenCV 32bit版ではメモリが1 GB以上使えないため、メモリを節約する目的で行っています。
-
40-43行目
キャプチャした画像はRGBカラーモデルのデータなので、HSVカラーモデルに変換します。
HSVは色相・彩度・明度のことで、色を用いた画像処理を行う場合にはよく用いられます。
簡単にいえば、HSVは人の感性に近い緑や赤といった色を表現できるモデルとなっています。
-
45-49行目
HSVの各チャンネルに画像を分割します。
下図は各チャンネルに分割した画像を表示したものです。
各チャンネルの値域は以下のようになります。
・H: 0-180
・S: 0-255
・V: 0-255
H(色相)チャンネルの値域は255ではないので注意して下さい。
4. 動作確認
作成したRTCを実行して下さい。
下図のように、物体を抽出しコンソールに表示されることを確認できれば完了です。
-
51-58行目
各チャンネルの二値化を行います。
今回使用した緑のカラーボールの色のパラメータは以下のようになっています。
・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(hue, temp, 55, 255, cv.CV_THRESH_TOZERO);
cv.Threshold(src, dst, threshold, maxValue, thiresholdType)
hue(色相)画像を与え、閾値55より上の値をそのままにし、それ以外を0にします。
二値化処理した画像をtempに保存します。
CV_THRESH_TOZEROは特別な値で、maxValueを与えても関係ありません。
処理を行うと下図のようになります。


Hue(色相)


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

channnels[0](色相)
hue_temp

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

hue_temp
Hue

-
66-79行目
抽出した画素の重心を求めています。
この処理は画像のモーメントを求めることで計算しています。
これまでの処理画像は元画像の1/4のサイズなので、座標値を4倍してスケールを元に戻しています。
-
75行目
計算した中心画素を基準として、円を描画します。この円はもとの画像であるframeに描画しています。
-
81-83行目
求めた中心座標をOutポートに出力します。
Point2D型のアクセス方法は以下のようになります。
・self._d_変数名.data = RTC.Point2D(x, y)
以上で、CameraColorExtractionのコーディングは終了です。

channels[1]
saturation

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

channels[2]
value

-
60-64行目
二値化処理した各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
-
言語
Python
TimedPoint2DConsoleOut.pyファイルにコードを記述して下さい。
以下のコードは抜粋となります。生成したテンプレートに当てはめて下さい。
-
def __init__(self, manager):
-
OpenRTM_aist.DataFlowComponentBase.__init__(self, manager)
-
self._d_point = RTC.TimedPoint2D(RTC.Time(0,0), RTC.Point2D(0,0))
-
"""
-
"""
-
self._point2dIn = OpenRTM_aist.InPort("point2d", self._d_point)
-
-
def onExecute(self, ec_id):
-
if self._point2dIn.isNew():
-
self._d_point = self._point2dIn.read()
-
print self._d_point.data
コードの解説をします。
変数へのアクセス方法以外、ConsoleOutと同じであることがわかると思います。
-
3行目
おさらいにですが、OpenRTM-aist Python版では、いくつかのデータ型の初期化が正しく行われません。
赤字のように書き直し、TimedPoint2D型で初期化を行って下さい。
-
9-11行目
Inポートに値が入力された場合、値を読み込みコンソールに表示します。
Point2D型のアクセスは以下のようにもできます。
・self._d_変数名.data.x
・self._d_変数名.data.y
以上で、TimedPoint2DConsoleOutのコーディングは終了です。
A1. 簡単なHSV閾値の求め方
ここまでで、今回の目的である色抽出は達成出来ました。
自分で色抽出を行うには、閾値を自分で決定する必要があります。
おまけとして、今回私が行ったパラメータを求める簡単な方法を紹介します。
-
PhotoShop・GIMP (画像処理ソフト)
抽出したい物体のHSVパラメータを知るのは様々な方法があると思いますが、簡単なのは画像処理ソフトを使用することです。
下図はValue画像を画像処理ソフトを用いて2値化パラメータを求めている様子です。
このように、市販のソフトを使えばグラフィカルに閾値を決定できます。

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


抽出結果


TimedPoint2DConsoleOut
抽出画像
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アクセス)