ここでは、データポートのあるコンポーネントを二つ作成し、二つのコンポーネント間でデータの送受信を行ってみます。 作成するコンポーネントの仕様は以下の通りです。
上記の仕様を持つコンポーネントを作成する為に以下のようなシェルスクリプトを gen.sh という名前で用意します。
#!/bin/sh rtc-template -bcxx --module-name=ConsoleIn --module-type='DataFlowComponent' --module-desc='Console input component' --module-version=1.0 --module-vendor='MyName' --module-category=example --module-comp-type=DataFlowComponent --module-act-type=SPORADIC --module-max-inst=10 --outport=out:TimedLong rtc-template -bcxx --module-name=ConsoleOut --module-type='DataFlowComponent' --module-desc='Console output component' --module-version=1.0 --module-vendor='MyName' --module-category=example --module-comp-type=DataFlowComponent --module-act-type=SPORADIC --module-max-inst=10 --inport=in:TimedLong --config="multiply:int:1"
最初の rtc-template の実行でコンポーネント1:ConsoleInComp、次の rtc-template の実行でコンポーネント2:ConsoleOutComp が作成されます。
> sh gen.sh File "ConsoleIn.h" was generated. File "ConsoleIn.cpp" was generated. File "ConsoleInComp.cpp" was generated. File "Makefile.ConsoleIn" was generated. File "ConsoleInComp_vc8.vcproj" was generated. File "ConsoleIn_vc8.vcproj" was generated. File "ConsoleInComp_vc9.vcproj" was generated. File "ConsoleIn_vc9.vcproj" was generated. File "ConsoleIn_vc8.sln" was generated. File "ConsoleIn_vc9.sln" was generated. File "copyprops.bat" was generated. File "user_config.vsprops" was generated. File "README.ConsoleIn" was generated. File "ConsoleIn.yaml" was generated. File "ConsoleOut.h" was generated. File "ConsoleOut.cpp" was generated. File "ConsoleOutComp.cpp" was generated. File "Makefile.ConsoleOut" was generated. File "ConsoleOutComp_vc8.vcproj" was generated. File "ConsoleOut_vc8.vcproj" was generated. File "ConsoleOutComp_vc9.vcproj" was generated. File "ConsoleOut_vc9.vcproj" was generated. File "ConsoleOut_vc8.sln" was generated. File "ConsoleOut_vc9.sln" was generated. "copyprops.bat" already exists. Overwrite? (y/n)y File "copyprops.bat" was generated. "user_config.vsprops" already exists. Overwrite? (y/n)y File "user_config.vsprops" was generated. File "README.ConsoleOut" was generated. File "ConsoleOut.yaml" was generated.
生成されたソースを編集して、ConsoleIn コンポーネントを実装していきます。
このコンポーネントはアクティブ化されたときに、入力待ちを行い入力された値を OutPort から出力するコンポーネントです。 従って、アクティブ状態の時にループ実行される onExecute メンバ関数のみ実装すればよいので、生成された ConsoleIn.h を以下のようにコメントアウトされている onExecute 関数のコメントをはずします。
:略 // The execution action that is invoked periodically // former rtc_active_do() virtual RTC::ReturnCode_t onExecute(RTC::UniqueId ec_id); :略
また、ConsoleIn.h の下の方に、rtc-template で指定した OutPort の変数宣言があります。
:略 // DataOutPort declaration // <rtc-template block="outport_declare"> TimedLong m_out; OutPort<TimedLong> m_outOut; // </rtc-template>
TimedLong m_out と宣言されているのが、OutPort にバインドされる変数。
OutPort<TimedLong> m_outOut と宣言されているのが、OutPort のインスタンスです。
ConsoleInの 実装は簡単です。 コメントアウトされている onExecute のコメントをはずし、以下のように実装します。
RTC::ReturnCode_t ConsoleIn::onExecute(RTC::UniqueId ec_id) { std::cout << "Please input number: "; std::cin >> m_out.data; std::cout << "Sending to subscriber: " << m_out.data << std::endl; m_outOut.write(); return RTC::RTC_OK; }
ConsoleOut コンポーネントは少し複雑です。 InPort に入ってきたデータにコンフィギュレーションパラメーター multiply を掛けた値を格納しなければなりません。 これは、InPort にコールバックオブジェクトをセットするという方法で実現できます。
コールバックオブジェクトとは、InPort や OutPort のバッファにあるイベントが発生したときに呼ばれるoperator()が定義されたオブジェクトです。 今回は、InPort のバッファに書き込まれるときに値を変換する為のコールバック OnWriteConvert を使用します。
RTC::OnWriteConvert を継承して以下のようなクラスを定義します。
class Multiply : public RTC::OnWriteConvert<RTC::TimedLong> { int& m_mul; public: Multiply(int& multiply) : m_mul(multiply) {}; RTC::TimedLong operator()(const RTC::TimedLong& value) { RTC::TimedLong ret(value); ret.data = value.data * m_mul; return ret; }; };
上記のコールバッククラスを ConsoleOut.h の include の行の直後に挿入します。 さらに、このコールバッククラスのインスタンスを ConsoleOut クラスのメンバ変数として宣言します。 場所は、private のすぐ下辺りでよいでしょう。
private: Multiply m_owc; int dummy;
このコンポーネントはアクティブ化されたときに、InPort からデータを読み込み標準出力にデータを表示するコンポーネントです。 従って、アクティブ状態の時にループ実行される onExecute メンバ関数のみ実装すればよいので、生成された ConsoleOut.h を以下のようにコメントアウトされている onExecute 関数のコメントをはずします。
:略 // The execution action that is invoked periodically // former rtc_active_do() virtual RTC::ReturnCode_t onExecute(RTC::UniqueId ec_id); :略
また、ConsoleOut.h の下の方に、rtc-template で指定したコンフィギュレーション変数の宣言と InPort の変数宣言があります。
ConsoleOut では InPort のバッファとして RingBuffer を使用するので、RingBuffer.h をインクルードする必要があります。 ConsoleOut.h の先頭部分をで以下のように RingBuffer.h をインクルードしてください。
#include <rtm/idl/BasicDataTypeSkel.h> #include <rtm/Manager.h> #include <rtm/DataFlowComponentBase.h> #include <rtm/CorbaPort.h> #include <rtm/DataInPort.h> #include <rtm/DataOutPort.h> #include <rtm/RingBuffer.h> //これを追加する
また、InPort の宣言部分でデフォルトでは InPort<TimedLong> m_inIn となっているところを InPort<TimedLong, RTC::RingBuffer> m_inIn のように書き換えて、InPort が RingBuffer を使用するように変更してください。
:略 // Configuration variable declaration // <rtc-template block="config_declare"> int m_multiply; // </rtc-template> // DataInPort declaration // <rtc-template block="inport_declare"> TimedLong m_in; InPort<TimedLong, RTC::RingBuffer> m_inIn;
int m_multiplyと宣言されているのが、コンフィギュレーション「multiply」にバインドされる変数。
TimedLong m_in と宣言されているのが、InPort にバインドされる変数。
InPort<TimedLong> m_inIn と宣言されているのが、InPort のインスタンスです。ConsoleOutクラスのコンストラクタで、先ほど定義した Multiply のインスタンスの初期化を追加します。
ConsoleOut::ConsoleOut(RTC::Manager* manager) : RTC::DataFlowComponentBase(manager), // <rtc-template block="initializer"> m_inIn("in", m_in), // </rtc-template> m_owc(m_multiply), dummy(0)
「m_owc(m_multiply),dummy(0)」 の部分を追加するだけでなく、「m_inIn("in", m_in)」を「m_inIn("in", m_in),」と変更することも忘れずに(カンマ「,」の追加もれに注意)。
さらに、コールバックオブジェクトを InPort に追加する為、コンストラクタ内で以下のように記述します。
m_inIn.setOnWriteConvert(&m_owc); //これを追加 // Registration: InPort/OutPort/Service // <rtc-template block="registration"> // Set InPort buffers registerInPort("in", m_inIn);
コメントアウトされている onExecute のコメントをはずし、以下のように実装します。
RTC::ReturnCode_t ConsoleOut::onExecute(RTC::UniqueId ec_id) { if (m_inIn.isNew()) { m_inIn.read(); std::cout << "Received: " << m_in.data << std::endl; std::cout << "TimeStamp: " << m_in.tm.sec << "[s] "; std::cout << m_in.tm.nsec << "[ns]" << std::endl; } usleep(1000); return RTC::RTC_OK; }
実装が終わったら以下のようにソースをコンパイルします。
> make -f Makefile.ConsoleIn > make -f Makefile.ConsoleOut
コンパイルエラーが出た場合はスペルミスなどがないかどうかチェックして再度コンパイルを行ってください。
corba.nameservers: localhost naming.formats: %h.host_cxt/%n.rtc
ConsoleIn を実行したターミナルで、入力を促す Please input number: という表示が出るので、適当な数字を入力します。
Please input number: 1 Sending to subscriber: 1 Please input number: 2 Sending to subscriber: 2 Please input number: 3 Sending to subscriber: 3
ConsoleOut を実行したターミナルでは、以下のように表示されるはずです。
Received: 1 TimeStamp: 0[s] 0[ns] Received: 2 TimeStamp: 0[s] 0[ns] Received: 3 TimeStamp: 0[s] 0[ns]
次に RtcLink のコンフィギュレーションビューで multiply の値を10に変更してみましょう。すると、ConsoleIn から上記のように入力すると、以下のようにそれぞれ10倍された値が出力されるはずです。
Received: 10 TimeStamp: 0[s] 0[ns] Received: 20 TimeStamp: 0[s] 0[ns] Received: 30 TimeStamp: 0[s] 0[ns]
ここでは、OpenCV ライブラリを VC9 にて RTコンポーネント化する手順を紹介します。
OpenCV とはインテルが開発・公開しているオープンソースのコンピュータービジョン向けライブラリです。
Wikipedia より抜粋。
ここでは、OpenCV ライブラリのうち、画像の反転を行う cvFlip() を VC9 にて RTコンポーネント化します。
以下は、作業の流れです。
cvFlip 関数は、2次元配列を垂直、水平、または両軸で反転します。
void cvFlip(IplImage* src, IplImage* dst=NULL, int flip_mode=0); #define cvMirror cvFlip src 入力配列 dst 出力配列。もし dst = NULL であれば、src が上書きされます。 flip_mode 配列の反転方法の指定内容: flip_mode = 0: X軸周りでの反転(上下反転) flip_mode > 0: Y軸周りでの反転(左右反転) flip_mode < 0: 両軸周りでの反転(上下左右反転)
InPort からの入力画像を反転し OutPort から出力するコンポーネント。
反転の対象軸は、RTC のコンフィギュレーション機能を使用して flipMode という名前のパラメーターで指定します。
flipMode は、反転したい方向に応じて下記のように指定してください。
作成する RTC の仕様は以下のとおりです。
※ CameraImage型は、OpenRTM-aist の InterfaceDataTypes.idl にて下記のように定義されているデータ型です。
struct CameraImage { /// Time stamp. Time tm; /// Image pixel width. unsigned short width; /// Image pixel height. unsigned short height; /// Bits per pixel. unsigned short bpp; /// Image format (e.g. bitmap, jpeg, etc.). string format; /// Scale factor for images, such as disparity maps, /// where the integer pixel value should be divided /// by this factor to get the real pixel value. double fDiv; /// Raw pixel data. sequence<octet> pixels; };
図1は、それぞれの flipMode での画像処理のイメージ図です。
Flip コンポーネントの雛型の生成は、RTCBuilder を用いて行います。
新規ワークスペースを指定して Eclipse を起動すると、以下のような「ようこそ」画面が表示されます。
この「ようこそ」画面左上の「X」をクリックして画面を閉じると、以下の画面が表示されます。 右上の [Open Perspective] ボタンをクリックし、プルダウンの「Other…」を選択します。
「RTC Builder」を選択し、[OK] ボタンをクリックします。
RTCBuilder が起動します。
まず最初に,RT コンポーネントを作成するための Eclipse プロジェクトを作成します。 画面上部のメニューから [ファイル] > [新規] > [プロジェクト] を選択します。
表示された「新規プロジェクト」画面において、[その他] > [RTC ビルダ] を選択し、[次へ] をクリックします。
「プロジェクト名」欄に作成するプロジェクト名を入力して [完了] をクリックします。
指定した名称のプロジェクトが生成され、パッケージエクスプローラー内に追加されます。
生成したプロジェクト内には、デフォルト値が設定された RTC プロファイル XML(RTC.xml) が自動的に生成されます。
RTC プロファイルエディタを開くには、ツールバーの「 [Open New RtcBuilder Editor] ボタンをクリックするか、メニューから [ファイル] > [Open New Builder Editor] を選択します。
1. 「基本」タブを選択し、基本情報を入力します。
2. 「アクティビティ」タブを選択し、使用するアクションコールバックを指定します。
Flip コンポーネントでは、onActivated()、onDeactivated()、onExecute()コ ールバックを使用します。 図14のように①の onAtivated をクリック後に ②のラジオボタンの [ON] にチェックを入れます。onDeactivated、onExecute についても同様の操作を行います。
3. 「データポート」タブを選択し、データポートの情報を入力します。
4. 「コンフィギュレーション」タブを選択し、Configuration の情報を入力します。
ラジオボタンでコンフィギュレーションの変更を行います。
5. 「言語・環境」タブを選択し、プログラミング言語を選択します。
今回は、C++(言語)を選択します。
6. 「基本」タブにある [コード生成] ボタンをクリックし、コンポーネントの雛型を生成します。
※ 生成されるコード群は、Eclipse 起動時に指定したワークスペースフォルダーの中に生成されます。現在のワークスペースは、[ファイル] > [ワークスペースの切り替え] で確認することができます。
Flip コンポーネントでは、InPort から受け取った画像を画像保存用バッファに保存し、その保存した画像を OpenCV の cvFlip() 関数にて変換します。その後、変換された画像を OutPort から送信します。
onActivated()、onExecute()、onDeactivated() での処理内容を図19に示します。
onExecute() での処理を図20に示します。
以下の内容を user_config.vsprops というファイル名で保存し、Flip フォルダーにコピーしてください。
もしくは、下記より vsprops ファイルをダウンロードし、Flip フォルダーに保存してください。
※ 既に Flip フォルダーには user_config.vsprops ファイルが存在していますが、上書きして構いません。
<?xml version="1.0" encoding="shift_jis"?> <VisualStudioPropertySheet ProjectType="Visual C++" Version="8.00" Name="OpenCV21" > <Tool Name="VCCLCompilerTool" AdditionalIncludeDirectories="$(cv_includes)" /> <Tool Name="VCLinkerTool" AdditionalLibraryDirectories="$(cv_libdir)" /> <UserMacro Name="user_lib" Value="$(cv_lib)" /> <UserMacro Name="user_libd" Value="$(cv_libd)" /> <UserMacro Name="cv_root" Value="C:\OpenCV2.1" /> <UserMacro Name="cv_includes" Value=""$(cv_root)\include\opencv"" /> <UserMacro Name="cv_libdir" Value=""$(cv_root)\lib"" /> <UserMacro Name="cv_bin" Value="$(cv_root)\bin" /> <UserMacro Name="cv_lib" Value="cv210.lib cvaux210.lib highgui210.lib cxcore210.lib" /> <UserMacro Name="cv_libd" Value="cv210d.lib cvaux210d.lib highgui210d.lib cxcore210d.lib" /> </VisualStudioPropertySheet>
copyprops.batというファイルを実行することで、rtm_config.vspropsというファイルがコピーされます。
rtm_config.vspropsファイルは、RTコンポーネントをVC++でビルドするために必要なインクルードパスやリンクするライブラリ等が記述されたファイルです。
//OpenCV 用インクルードファイルのインクルード #include<cv.h> #include<cxcore.h> #include<highgui.h>
/*** * * The activated action (Active state entry action) * former rtc_active_entry() * * @param ec_id target ExecutionContext Id * * @return RTC::ReturnCode_t * * */ virtual RTC::ReturnCode_t onActivated(RTC::UniqueId ec_id); /*** * * The deactivated action (Active state exit action) * former rtc_active_exit() * * @param ec_id target ExecutionContext Id * * @return RTC::ReturnCode_t * * */ virtual RTC::ReturnCode_t onDeactivated(RTC::UniqueId ec_id); /*** * * The execution action that is invoked periodically * former rtc_active_do() * * @param ec_id target ExecutionContext Id * * @return RTC::ReturnCode_t * * */ virtual RTC::ReturnCode_t onExecute(RTC::UniqueId ec_id);
IplImage* m_imageBuff; IplImage* m_flipImageBuff;
下記のように、onActivated()、onDeactivated()、onExecute() を実装します。
RTC::ReturnCode_t Flip::onActivated(RTC::UniqueId ec_id) { // イメージ用メモリーの初期化 m_imageBuff = NULL; m_flipImageBuff = NULL; // OutPort の画面サイズの初期化 m_flippedImage.width = 0; m_flippedImage.height = 0; return RTC::RTC_OK; } RTC::ReturnCode_t Flip::onDeactivated(RTC::UniqueId ec_id) { if(m_imageBuff != NULL) { // イメージ用メモリーの解放 cvReleaseImage(&m_imageBuff); cvReleaseImage(&m_flipImageBuff); } return RTC::RTC_OK; } RTC::ReturnCode_t Flip::onExecute(RTC::UniqueId ec_id) { // 新しいデータのチェック if (m_originalImageIn.isNew()) { // InPort データの読み込み m_originalImageIn.read(); // InPort と OutPort の画面サイズ処理およびイメージ用メモリーの確保 if( m_originalImage.width != m_flippedImage.width || m_originalImage.height != m_flippedImage.height) { m_flippedImage.width = m_originalImage.width; m_flippedImage.height = m_originalImage.height; // InPort のイメージサイズが変更された場合 if(m_imageBuff != NULL) { cvReleaseImage(&m_imageBuff); cvReleaseImage(&m_flipImageBuff); } // イメージ用メモリーの確保 m_imageBuff = cvCreateImage(cvSize(m_originalImage.width, m_originalImage.height), IPL_DEPTH_8U, 3); m_flipImageBuff = cvCreateImage(cvSize(m_originalImage.width, m_originalImage.height), IPL_DEPTH_8U, 3); } // InPort の画像データを IplImage の imageData にコピー memcpy(m_imageBuff->imageData,(void *)&(m_originalImage.pixels[0]),m_originalImage.pixels.length()); // InPort からの画像データを反転する。 m_flipMode 0: X軸周り, 1: Y軸周り, -1: 両方の軸周り cvFlip(m_imageBuff, m_flipImageBuff, m_flipMode); // 画像データのサイズ取得 int len = m_flipImageBuff->nChannels * m_flipImageBuff->width * m_flipImageBuff->height; m_flippedImage.pixels.length(len); // 反転した画像データを OutPort にコピー memcpy((void *)&(m_flippedImage.pixels[0]),m_flipImageBuff->imageData,len); // 反転した画像データを OutPort から出力する。 m_flippedImageOut.write(); } return RTC::RTC_OK; }
Flip_vc9.sln ファイルをダブルクリックし、Visual C++ 2008 を起動します。
Visual C++ 2008 の起動後、図21のようにし、コンポーネントのビルドを行います。
ここでは、「OpenCV用RTC群 (Win32用インストーラ)」にてインストールした USBCameraAqcuireComp コンポーネントと、USBCameraMonitorCom コンポーネント、それと、今回作成した Flip コンポーネントを接続し動作確認を行います。
omniORB のネームサービスを起動します。
[スタート] > [すべてのプログラム] > [OpenRTM-aist] > [C++] > [tools] の順に辿り、「Start Naming Service」をクリックしてください。
&color(RED){※ 「Star Naming Service」をクリックしても omniNames が起動されない場合は、フルコンピューター名が14文字以内に設定されているかを確認してください。
RTコンポーネントでは、ネームサーバーのアドレスやネームサーバーへの登録フォーマットなどの情報を rtc.conf というファイルで指定する必要があります。
下記の内容を rtc.conf というファイル名で保存し、Flip\FlipComp\Debug(もしくは、Release)フォルダーに置いて下さい。
※ Eclipse 起動時に workspace をデフォルトのままにしていた場合、Flip フォルダーのパスは、
C:\Documents and Settings\<ログインユーザー名>\workspace となります。
corba.nameservers: localhost naming.formats: %n.rtc
Flip コンポーネントを起動します。
先程 rtc.conf ファイルを置いたフォルダーにある、FlipComp.exe ファイルを実行して下さい。
USB カメラのキャプチャ画像を OutPort から出力する USBCameraAqcuireComp コンポーネントと、InPort で受け取った画像を画面に表示する USBCameraMonitorComp コンポーネントを起動します。
これら2つのコンポーネントは、下記の手順にて起動できます。
[スタート] > [すべてのプログラム] > [OpenRTM-aist] > [components] > [C++] > [OpenCV] を選択し、「USBCameraAqcuireComp」と「USBCameraMonitorComp」をそれぞれクリックして実行します。
図22のように、RTSystemEditor にて USBCameraAqcuireComp、Flip、USBCameraMonitorComp コンポーネントを接続します。
RTSystemEditor の上部にあります「ALL」というアイコンをクリックし、全てのコンポーネントをアクティベートします。
正常にアクティベートされた場合、図23のように黄緑色でコンポーネントが表示されます。
図24のようにコンフィギュレーションビューにてコンフィギュレーションを変更することができます。
Flip コンポーネントのコンフィギュレーションパラメーター「flipMode」を「0」や「-1」などに変更し、画像の反転が行われるかを確認してください。
// -*- C++ -*- /*! * @file Flip.cpp * @brief Flip image component * @date $Date$ * * $Id$ */ #include "Flip.h" // Module specification static const char* flip_spec[] = { "implementation_id", "Flip", "type_name", "Flip", "description", "Flip image component", "version", "1.0.0", "vendor", "AIST", "category", "Category", "activity_type", "PERIODIC", "kind", "DataFlowComponent", "max_instance", "1", "language", "C++", "lang_type", "compile", // Configuration variables "conf.default.flipMode", "1", // Widget "conf.__widget__.flipMode", "radio", // Constraints "conf.__constraints__.flip_mode", "(-1,0,1)", "" }; /*! * @brief constructor * @param manager Maneger Object */ Flip::Flip(RTC::Manager* manager) : RTC::DataFlowComponentBase(manager), m_originalImageIn("originalImage", m_originalImage), m_flippedImageOut("flippedImage", m_flippedImage) { } /*! * @brief destructor */ Flip::~Flip() { } RTC::ReturnCode_t Flip::onInitialize() { // Registration: InPort/OutPort/Service // Set InPort buffers addInPort("originalImage", m_originalImageIn); // Set OutPort buffer addOutPort("flippedImage", m_flippedImageOut); // Bind variables and configuration variable bindParameter("flipMode", m_flipMode, "1"); return RTC::RTC_OK; } RTC::ReturnCode_t Flip::onActivated(RTC::UniqueId ec_id) { // イメージ用メモリーの初期化 m_imageBuff = NULL; m_flipImageBuff = NULL; // OutPort の画面サイズの初期化 m_flippedImage.width = 0; m_flippedImage.height = 0; return RTC::RTC_OK; } RTC::ReturnCode_t Flip::onDeactivated(RTC::UniqueId ec_id) { if(m_imageBuff != NULL) { // イメージ用メモリーの解放 cvReleaseImage(&m_imageBuff); cvReleaseImage(&m_flipImageBuff); } return RTC::RTC_OK; } RTC::ReturnCode_t Flip::onExecute(RTC::UniqueId ec_id) { // 新しいデータのチェック if (m_originalImageIn.isNew()) { // InPort データの読み込み m_originalImageIn.read(); // InPort と OutPort の画面サイズ処理およびイメージ用メモリーの確保 if( m_originalImage.width != m_flippedImage.width || m_originalImage.height != m_flippedImage.height) { m_flippedImage.width = m_originalImage.width; m_flippedImage.height = m_originalImage.height; // InPort のイメージサイズが変更された場合 if(m_imageBuff != NULL) { cvReleaseImage(&m_imageBuff); cvReleaseImage(&m_flipImageBuff); } // イメージ用メモリーの確保 m_imageBuff = cvCreateImage(cvSize(m_originalImage.width, m_originalImage.height), IPL_DEPTH_8U, 3); m_flipImageBuff = cvCreateImage(cvSize(m_originalImage.width, m_originalImage.height), IPL_DEPTH_8U, 3); } // InPort の画像データを IplImage の imageData にコピー memcpy(m_imageBuff->imageData,(void *)&(m_originalImage.pixels[0]),m_originalImage.pixels.length()); // InPort からの画像データを反転する。 m_flipMode 0: X軸周り, 1: Y軸周り, -1: 両方の軸周り cvFlip(m_imageBuff, m_flipImageBuff, m_flipMode); // 画像データのサイズ取得 int len = m_flipImageBuff->nChannels * m_flipImageBuff->width * m_flipImageBuff->height; m_flippedImage.pixels.length(len); // 反転した画像データを OutPortにコピー memcpy((void *)&(m_flippedImage.pixels[0]),m_flipImageBuff->imageData,len); // 反転した画像データを OutPortから出力する。 m_flippedImageOut.write(); } return RTC::RTC_OK; } extern "C" { void FlipInit(RTC::Manager* manager) { coil::Properties profile(flip_spec); manager->registerFactory(profile, RTC::Create<Flip>, RTC::Delete<Flip>); } };
// -*- C++ -*- /*! * @file Flip.h * @brief Flip image component * @date $Date$ * * $Id$ */ #ifndef FLIP_H #define FLIP_H #include <rtm/Manager.h> #include <rtm/DataFlowComponentBase.h> #include <rtm/CorbaPort.h> #include <rtm/DataInPort.h> #include <rtm/DataOutPort.h> #include <rtm/idl/BasicDataTypeSkel.h> #include <rtm/idl/ExtendedDataTypesSkel.h> #include <rtm/idl/InterfaceDataTypesSkel.h> //OpenCV 用インクルードファイルのインクルード #include<cv.h> #include<cxcore.h> #include<highgui.h> using namespace RTC; /*! * @class Flip * @brief Flip image component * */ class Flip : public RTC::DataFlowComponentBase { public: /*! * @brief constructor * @param manager Maneger Object */ Flip(RTC::Manager* manager); /*! * @brief destructor */ ~Flip(); /*** * * The initialize action (on CREATED->ALIVE transition) * formaer rtc_init_entry() * * @return RTC::ReturnCode_t * * */ virtual RTC::ReturnCode_t onInitialize(); /*** * * The activated action (Active state entry action) * former rtc_active_entry() * * @param ec_id target ExecutionContext Id * * @return RTC::ReturnCode_t * * */ virtual RTC::ReturnCode_t onActivated(RTC::UniqueId ec_id); /*** * * The deactivated action (Active state exit action) * former rtc_active_exit() * * @param ec_id target ExecutionContext Id * * @return RTC::ReturnCode_t * * */ virtual RTC::ReturnCode_t onDeactivated(RTC::UniqueId ec_id); /*** * * The execution action that is invoked periodically * former rtc_active_do() * * @param ec_id target ExecutionContext Id * * @return RTC::ReturnCode_t * * */ virtual RTC::ReturnCode_t onExecute(RTC::UniqueId ec_id); protected: // Configuration variable declaration /*! * * - Name: flipMode * - DefaultValue: 1 */ int m_flipMode; // DataInPort declaration CameraImage m_originalImage; /*! */ InPort<CameraImage> m_originalImageIn; // DataOutPort declaration CameraImage m_flippedImage; /*! */ OutPort<CameraImage> m_flippedImageOut; private: // 処理画像用バッファ IplImage* m_imageBuff; IplImage* m_flipImageBuff; }; extern "C" { DLL_EXPORT void FlipInit(RTC::Manager* manager); }; #endif // FLIP_H
ビルド済みパッケージを下記からダウンロードできます。
拡張子を"zip_"としてますので、"zip"にリネームしてから解凍してください。
ここでは、データポートを2つ持つコンポーネント (MRCConvertor) を VC8 にて作成します。
入力デバイスからのデータを車輪2つの速度に変換し、OutPort から出力するコンポーネント。
Joystick等の入力デバイスにて移動ロボット(前輪2、後輪1、前輪だけが駆動)を操作する際に、使用することができる。
作成する RTC の仕様は以下のとおりです。
RtcTemplate にて雛型を生成します。
適当な場所に作業用フォルダーを作成します。
今回は、フォルダー名をコンポーネント名 (MRCConvertor) と同じにします。
rtc-template の実行を簡単にするために、下記のようなバッチファイルを先ほど作成した作業用フォルダーに作成します。
rtc-template.py -bcxx^ --module-name=MRCConvertor --module-desc="Convertor component for MobileRobot component"^ --module-version=1.0.0 --module-vendor=AIST --module-category=Category^ --module-comp-type=DataFlowComponent --module-act-type=PERIODIC^ --module-max-inst=1^ --inport=velFromInput:TimedFloatSeq^ --outport=velToWheel:TimedFloatSeq
下記のように gen.bat ファイルを実行します。
>gen.bat rtc-template.py -bcxx --module-name=MRCConvertor --module-desc="Convertor component for MobileRobot component" --module-version=1.0.0 --module-vendor=AIST --module-category=Category --module-comp-type=DataFlowComponent --module-act-type=PERIODIC --module-max-inst=1 --inport=velFromInput:TimedFloatSeq --outport=velToWheel:TimedFloatSeq File "MRCConvertor.h" was generated. File "MRCConvertor.cpp" was generated. File "MRCConvertorComp.cpp" was generated. File "Makefile.MRCConvertor" was generated. File "MRCConvertorComp_vc8.vcproj" was generated. File "MRCConvertor_vc8.vcproj" was generated. File "MRCConvertorComp_vc9.vcproj" was generated. File "MRCConvertor_vc9.vcproj" was generated. File "MRCConvertor_vc8.sln" was generated. File "MRCConvertor_vc9.sln" was generated. File "copyprops.bat" was generated. File "user_config.vsprops" was generated. File "README.MRCConvertor" was generated. File "MRCConvertor.yaml" was generated.
Eclipse版 RtcTemplate での対応は下記のようになります。
RtcTemplate 実行により、copyprops.bat ファイルが作業用フォルダーに生成されます。
この copyprops.bat ファイルを用い、コンポーネントのビルドに必要な rtm_config.vsprops を作業フォルダーにコピーします。
copyprops.bat ファイルをダブルクリックしてください。
MRCConvertorComp_vc8.vcproj をダブルクリックし、Visual Studio を起動します。
Visual Studio のソリューションエクスプローラーにて、MRCConvertorComp > Header Files の順にクリックし、 MRCConvertor.h をダブルクリックします。
#include <vector> // VC8 にて Math::M_PI を使用するため #define _USE_MATH_DEFINES #include <math.h>
private: float m_k; /*! * @brief InPortからのデータ(X、Y) をMobileRobot用の車輪速度データに変換する。 * m_velFromInput.data[0]とm_velFromInput.data[1]だけを使用する。 */ std::vector<float> convert(float x, float y) { float th = atan2(y,x); float v = m_k * hypot(x,y); std::vector<float> ret_val; ret_val.push_back(v * cos(th - (M_PI/4.0))); // left vel ret_val.push_back(v * sin(th - (M_PI/4.0))); // right vel return ret_val; }
Visual Studio のソリューションエクスプローラーにて、MRCConvertorComp > Source Files の順にクリックし、 MRCConvertor.cpp をダブルクリックします。
/*! * @brief InPort からのデータ (X、Y) を MobileRobot用の車輪速度データに変換し、 * OutPort から出力する。 */ RTC::ReturnCode_t MRCConvertor::onExecute(RTC::UniqueId ec_id) { if (m_velFromInputIn.isNew()) { m_velFromInputIn.read(); if (m_velFromInput.data.length() > 2) { std::vector<float> con_val = this->convert(m_velFromInput.data[0],m_velFromInput.data[1]); for (int i = 0; i < 2; i++) m_velToWheel.data[i] = con_val[i]; m_velToWheelOut.write(); } } return RTC::RTC_OK; }
ここで行われていることは、
Visual Studio のメニューから [ビルド] > [ソリューションのビルド] をクリックしコンポーネントのビルドを行います。
エディタにて以下の内容を記述し、rtc.conf というファイル名で Debug あるいは Release フォルダーに保存します。
corba.nameservers: localhost naming.formats: %n.rtc
ビルドにてエラーが無かった場合、Debug あるいは Release フォルダーに rtc.conf を作成し、MRCConvertorComp.exe を実行します。
MRCConvertorComp.exe を実行する前に NamingService を起動してください。<OpenRTM-aist インストールフォルダー>\bin\rtm-naming.batをダブルクリックしCORBAネームサーバーを起動します。
Debug あるいは Release フォルダーに移動し、MRCConvertorComp.exe を実行します。
OpenRTM-aist-Java の動作に必要な環境は以下のとおりです。
環境 | 備考 |
Java Development Kit 5.0 (JDK 5) (http://java.sun.com/products/archive/j2se/5.0_14/index.html ) |
注意:Java1.4 では動作しません. |
ここでは、Java版 RTコンポーネントの開発を行う場合の手順をご紹介します。サンプルとして以下のような仕様のコンポーネントを取り上げます。
基本プロファイル | |
コンポーネント名 | sample |
説明 | SampleComponent |
バージョン | 1.0 |
ベンダ | AIST |
カテゴリ | example |
コンポーネントの型 | DataFlowComponent |
アクティビティタイプ | SPORADIC |
最大インスタンス数 | 5 |
データInPort | |
名称 | in |
型 | TimedLong |
サービスProvider | |
IDLパス | IDLs/MyService.idl ※ |
ポート名 | MySvcPort |
サービス名 | myservice0 |
サービス型 | MyService |
コンフィギュレーションパラメーター | |
名称 | multiply |
型 | int |
デフォルト値 | 1 |
※ MyService.idl は適当なエディタを用いて以下のような IDL ファイルを作成します。また、上の表中の「IDLパス」はMyService.idl のパスを記述します。Windowsの場合は、この「IDLパス」が MyService.idl へのフルパスでなければなりません。
typedef sequence<string> EchoList; typedef sequence<float> ValueList; interface MyService { string echo(in string msg); EchoList get_echo_history(); void set_value(in float value); float get_value(); ValueList get_value_history(); };
: 「Output directory」欄にプロジェクトのディレクトリーを指定することにより、生成された各種ファイルがプロジェクトへ(自動的に)追加されることになります。|
※ サービスポートを定義している RTコンポーネントの場合、Rtctemplate が生成したファイルだけではエラーが表示されます。これは該当するファイルが、IDL ファイルから自動生成されるクラスを利用しているためです。これらのクラスはビルド実行時に自動生成されます。
※クラスパスが有効となる場所に、OpenRTM-aist-Java がインストールされていない場合、エラーが表示されます。この場合、プロジェクトのプロパティから OpenRTM-aist-Java のインストールフォルダー(ディレクトリー)を指定してください。
corba.nameservers: localhost naming.formats: %n.rtc
$ java -classpath .:$RTM_JAVA_ROOT/jar/OpenRTM-aist-0.4.1.jar:$RTM_JAVA_ROOT/jar/commons-cli-1.1.jar SampleComp あるいは、たとえば bash の場合などは、上記コマンドを分割して、 $ export CLASSPATH=.:$RTM_JAVA_ROOT/jar/OpenRTM-aist-0.4.1.jar:$RTM_JAVA_ROOT/jar/commons-cli-1.1.jar $ java SampleComp
> java -classpath ".;%RTM_JAVA_ROOT%\jar\OpenRTM-aist-0.4.1.jar;%RTM_JAVA_ROOT%\jar\commons-cli-1.1.jar" SampleComp あるいは、上記コマンドを分割して、 > set CLASSPATH=.;%RTM_JAVA_ROOT%\jar\OpenRTM-aist-0.4.1.jar;%RTM_JAVA_ROOT%\jar\commons-cli-1.1.jar > java SampleComp
Java版 RTコンポーネントのソースファイルと、各ファイル内で実行している概略機能との関係を図3-1に示します。また、比較のために C++版 RTコンポーネントおよび OpenRTM-aist-0.3用 Java版 RTコンポーネントの構成も併せて示します。
OpenRTM-aist-Java では、データの受け渡しを行うためにホルダクラス(DataRefクラス)を追加しています。そのため、データポートの定義および利用方法が以下のように変更されております。
Java版RTコンポーネント | C++版RT コンポーネント |
//InPort の定義 protected TimedShort m_in_val; protected DataRef<TimedLong> m_in; protected InPort<TimedLong> m_inIn; //InPort の登録 m_in_val = new TimedLong(); m_in = new DataRef<TimedLong>(m_in_val); m_inIn = new InPort<TimedLong>(“in”,m_in); registerInPort(TimedLong.class,“in”,m_inIn); //InPort からのデータ読み込み m_inIn.read(); 入力データ = m_in.v.data; |
//InPort の定義 TimedShort m_in; InPort<TimedShort> m_inIn; //InPort の登録 m_inIn = new InPort<TimedLong>(“in”,m_in); registerInPort(“in”,m_inIn); //InPort からのデータ読み込み m_inIn.read(); 入力データ = m_in.data; |
//OutPort の定義 protected TimedFloat m_out_val; protected DataRef<TimedFloat> m_out; protected OutPort<TimedFloat> m_outOut; //OutPort の登録 m_out_val = new TimedFloat(); m_out = new DataRef<TimedFloat>(m_out_val); m_outOut = new OutPort<TimedLong>(“out”,m_out); registerOutPort(TimedLong.class,“out”,m_outOut); //OutPort へのデータの書き出し m_out_val.data = 出力データ; m_outOut.write(); |
//OutPort の定義 TimedFloat m_out; OutPort<TimedFLoat> m_outOut; //OutPort の登録 m_outOut = new OutPort<TimedFloat>(“out”,m_out); registerOutPort(“out”,m_outOut); //OutPort へのデータの書き出し m_out.data = 出力データ; m_outOut.write(); |
OpenRTM-aist-Java では、サービスポートを使用するための補助変数(<サービス名>Base)を追加しております。このため、サービスポートの定義、利用方法が以下のように変更されております。詳細につきましては、「SimpleService」のサンプルをご覧ください。
Java版RTコンポーネント | C++版RTコンポーネント |
//Consumer の定義 protected CorbaPort m_MyServicePort; protected CorbaConsumer<MyService> m_myservice0Base = new CorbaConsumer<MyService>(MyService.class); protected MyService m_myservice0; //Consumer の登録 m_MyServiceRef = new CorbaPort(“MyService”); m_MyServicePort.registerConsumer (“myservice0”,”MyService”,m_myservice0Base); registerPort(m_MyServicePort); //Consumer の利用 m_myservice0 = m_myservice0Base._ptr(); m_myservice0.echo(argv[1]); |
//Consumer の定義 RTC::CorbaPort m_MyServicePort; RTC::CorbaConsumer<MyService> m_myservice0; //Consumer の登録 m_MyServicePort = new RTC::CorbaPort(“MyService”); m_MyServicePort.registerConsumer (“myservice0”,“MyService”, m_myservice0); registerPort(m_MyServicePort); //Consumer の利用 m_myservice0->echo(argv[1].c_str()); |
//Provider の定義 protected CorbaPort m_MyServicePort; protected MyServiceSVC_impl m_myservice0 = new MyServiceSVC_impl(); //Provider の登録 m_MyServiceRef = new CorbaPort(“MyService”); m_MyServicePort.registerProvider (“myservice0”,”MyService”,m_myservice0); registerPort(m_MyServicePort); |
//Provider の定義 RTC::CorbaPort m_MyServicePort; MyServiceSVC_impl m_myservice0; //Provider の登録 m_MyServicePort = new RTC::CorbaPort(“MyService”); m_MyServicePort.registerProvider (“myservice0”,“MyService”,m_myservice0); registerPort(m_MyServicePort); |
データポートの場合と同様、コンフィギュレーションを利用する場合もデータの受け渡しのためにホルダクラスを利用しています。このため、コンフィギュレーション・データの定義、利用方法が以下のように変更されております。
Java版RTコンポーネント | C++版RTコンポーネント |
//Configuration Variable の定義 protected IntegerHolder m_int_param0 = new IntegerHolder(); protected StringHolder m_str_param0 = new StringHolder(); protected VectorHolder m_vector_param0= new VectorHolder(); //パラメーターのバインド bindParameter(“int_param0”,m_int_param_0,“0”); bindParameter(“str_param0”,m_str_param_0,“sample”); bindParameter(“vector_param0”,m_vector_param_0,“0.0,1.0,2.0,3.0,4.0”); |
//Configuration Variable の定義 int m_int_param0; str::string m_str_param0; str::vector<double> m_vector_param0; //パラメーターのバインド bindParameter(“int_param0”,m_int_param_0,“0”); bindParameter(“str_param0”,m_str_param_0,“sample”); bindParameter(“vector_param0”,m_vector_param_0,“0.0,1.0,2.0,3.0,4.0”); |
コンフィギュレーション・データの使用方法につきましては、「ConfigSample」のサンプルをご参照ください。
OpenRTM-aist-Javaで、コンフィギュレーション・データ用ホルダとして用意しているホルダクラスの種類とデータ型の関係を表3-1に示します。
データ型 | ホルダ・クラス |
short | jp.go.aist.rtm.RTC.util.ShortHolder |
int | jp.go.aist.rtm.RTC.util.IntegerHolder |
long | jp.go.aist.rtm.RTC.util.LongHolder |
float | jp.go.aist.rtm.RTC.util.FloatHolder |
double | jp.go.aist.rtm.RTC.util.DoubleHolder |
byte | jp.go.aist.rtm.RTC.util.ByteHolder |
String | jp.go.aist.rtm.RTC.util.StringHolder |
C++版 OpenRTM-aist と同様に、OpenRTM-aist-Java におきましてもユーザーが定義した任意の型についてコンフィグレーション・データ用ホルダを作成することができます。
コンフィギュレーション・データ用のホルダを実装する場合は、jp.go.aist.rtm.RTC.util.ValueHolder の stringFrom メソッドを実装し、implements節に Serializable インターフェースを追加する必要があります。
jp.go.aist.rtm.RTC.util.ValueHolder の stringFrom メソッドは、引数で渡された文字列から対象データ型に変換するためのメソッドです。
コンフィグレーション・データ用ホルダにつきましては「ConfigSample」サンプル内の VectorHolder クラスをご参照ください。
CORBA IDL用データ型と Java言語データ型の対応関係を表3-2に示します。
CORBA IDL | Java言語 |
short | short |
long | int |
float | float |
double | double |
long long | long |
long double | double |
char | char |
wchar | char |
octet | byte |
unsigned short | short |
unsigned long | int |
unsigned long long | long |
boolean | boolean |
string | String |
wstring | String |
any | org.omg.CORBA.Any |
void | void |
> eclipse -data /home/devel/OpenRTM/workspace
This product includes software developed by The Apache Software Foundation (http://www.apache.org/ ).
ここでは、OpenCV ライブラリを VC9 にて RTコンポーネント化する手順を紹介します。
OpenCVとはインテルが開発・公開しているオープンソースのコンピュータービジョン向けライブラリです。
Wikipediaより抜粋。
ここでは、OpenCV ライブラリのうち、画像の反転を行う cvFlip() を VC9 にて RTコンポーネント化します。
以下は、作業の流れです。
cvFlip 関数は、2次元配列を垂直、水平、または両軸で反転します。
void cvFlip(IplImage* src, IplImage* dst=NULL, int flip_mode=0); #define cvMirror cvFlip src 入力配列 dst 出力配列。もし dst=NULL であれば、src が上書きされます。 flip_mode 配列の反転方法の指定内容: flip_mode = 0: X軸周りでの反転(上下反転) flip_mode > 0: Y軸周りでの反転(左右反転) flip_mode < 0: 両軸周りでの反転(上下左右反転)
InPort からの入力画像を反転し OutPort から出力するコンポーネント。
反転の対象軸は、RTCのコンフィギュレーション機能を使用して flip_mode という名前のパラメーターで指定します。
flip_mode は、反転したい方向に応じて下記のように指定してください。
作成する RTC の仕様は以下のとおりです。
※ TimedOctetSeq型は、OpenRTM-aist の BasicDataType.idl にて下記のように定義されているデータ型です。
※ octet型とは、CORBA IDL の基本型で、転送時にいかなるデータ変換も施されないことが保証されている8ビット値です。
struct Time { unsigned long sec; // sec unsigned long nsec; // nano sec }; struct TimedOctetSeq { Time tm; sequence<octet> data; };
図1は、それぞれのflip_mode での画像処理のイメージ図です。
Flip コンポーネントの雛型の生成は、RTCBuilder を用いて行います。
新規ワークスペースを指定して Eclipse を起動すると、以下の「ようこそ」画面が表示されます。 この 「ようこそ」画面の左上の「X」をクリックすると以下の画面が表示されます。
右上の [Open Perspective] ボタンをクリックし、プルダウンの「Other…」を選択します。
「RTC Builder」を選択し、[OK] ボタンをクリックします。
RTCBuilder が起動します。
まず最初に、RT コンポーネントを作成するための Eclipse プロジェクトを作成します。 画面上部のメニューから[ファイル] > [新規] > [プロジェクト] を選択します。
表示された「新規プロジェクト」画面において、[その他] > [RTC ビルダ] を選択し、[次へ] をクリックします。
「プロジェクト名」欄に作成するプロジェクト名を入力して [完了] をクリックします。
指定した名称のプロジェクトが生成され、パッケージエクスプローラー内に追加されます。
生成したプロジェクト内には、デフォルト値が設定された RTC プロファイル XML(RTC.xml) が自動的に生成されます。
データポートやサービスポートで使用するデータ型が定義された IDL ファイルが置いてある場所を予め設定しておく必要があります。
※ ここでの設定内容は、ワークスペースを変更しない限り有効ですので、プロジェクト毎に設定する必要はありません。
下記の手順にて、データ型が定義されている IDL ファイルの在り処を設定して下さい。
1. メニューバーの [ウィンドウ] > [設定] をクリックし、設定ダイアログを表示させます。 2. [RtcBuilder] > [Data Type] をクリックし、図12の Data Type 入力画面を出します。 3. Data Type入力画面の [Add] ボタンをクリックし、"IDL File Directories"を入力します。 OpenRTM-aist で定義されているデータ型の IDL ファイルはデフォルトでは下記にインストールされます。 C:\Program Files\OpenRTM-aist\1.0\rtm\idl 4. [OK] ボタンをクリックし、設定を終了します。
RTC プロファイルエディタを開くには、ツールバーの [Open New RtcBuilder Editor] ボタンをクリックするか、メニューの [ファイル] > [Open New Builder Editor] を選択します。
1. 「基本」タブを選択し、基本情報を入力します。
2. 「アクティビティ」タブを選択し、使用するアクションコールバックを指定します。
Flipコンポーネントでは、onActivated()、onDeactivated()、onExecute() コールバックを使用しますので、 図14のようにon_activated、on_deactivated、on_executeの3つにチェックを入れます。
3. 「データポート」タブを選択し、データポートの情報を入力します。
4. 「コンフィギュレーション」タブを選択し、Configuration の変数を入力します。
5. 「言語・環境」タブを選択し、プログラミング言語を選択します。
今回は、C++(言語) を選択します。
6. 「基本」タブにある [コード生成] ボタンをクリックし、コンポーネントの雛型を生成します。
※ 生成されるコード群は、Eclipse 起動時に指定したワークスペースフォルダーの中に生成されます。現在のワークスペースは、[ファイル] > [ワークスペースの切り替え] で確認することができます。
Flip コンポーネントでは、InPort から受け取った画像を画像保存用バッファに保存し、その保存した画像を OpenCV の cvFlip() 関数にて変換します。その後、変換された画像を OutPort から送信します。
onActivated()、onExecute()、onDeactivated()での処理内容を図19に示します。
onExecute() での処理を図20に示します。
以下の内容を user_config.vsprops というファイル名で保存し、Flip フォルダーにコピーしてください。
もしくは、下記より vsprops ファイルをダウンロードし、Flip フォルダーに保存してください。
※ 既にFlipフォルダーには user_config.vsprops ファイルが存在しておりますが、上書きして構いません。
<?xml version="1.0" encoding="shift_jis"?> <VisualStudioPropertySheet ProjectType="Visual C++" Version="8.00" Name="OpenCV" > <Tool Name="VCCLCompilerTool" AdditionalIncludeDirectories="$(cv_includes)" /> <Tool Name="VCLinkerTool" AdditionalLibraryDirectories="$(cv_libdir)" /> <UserMacro Name="user_lib" Value="$(cv_lib)" /> <UserMacro Name="user_libd" Value="$(cv_libd)" /> <UserMacro Name="cv_root" Value="C:\Program Files\OpenCV" /> <UserMacro Name="cv_includes" Value=""$(cv_root)\cv\include";"$(cv_root)\cvaux\include";"$(cv_root)\cxcore\include";"$(cv_root)\otherlibs\highgui";"$(cv_root)\otherlibs\cvcam\include"" /> <UserMacro Name="cv_libdir" Value=""$(cv_root)\lib"" /> <UserMacro Name="cv_bin" Value="$(cv_root)\bin" /> <UserMacro Name="cv_lib" Value="cv.lib cvcam.lib highgui.lib cxcore.lib" /> <UserMacro Name="cv_libd" Value="cv.lib cvcam.lib highgui.lib cxcore.lib" /> </VisualStudioPropertySheet>
copyprops.bat というファイルを実行することで、rtm_config.vsprops というファイルがコピーされます。
rtm_config.vsprops ファイルは、RTコンポーネントを VC++ でビルドするために必要なインクルードパスやリンクするライブラリ等が記述されたファイルです。
//OpenCV 用インクルードファイルのインクルード #include<cv.h> #include<cxcore.h> #include<highgui.h>
/*** * * The activated action (Active state entry action) * former rtc_active_entry() * * @param ec_id target ExecutionContext Id * * @return RTC::ReturnCode_t * * */ virtual RTC::ReturnCode_t onActivated(RTC::UniqueId ec_id); /*** * * The deactivated action (Active state exit action) * former rtc_active_exit() * * @param ec_id target ExecutionContext Id * * @return RTC::ReturnCode_t * * */ virtual RTC::ReturnCode_t onDeactivated(RTC::UniqueId ec_id); /*** * * The execution action that is invoked periodically * former rtc_active_do() * * @param ec_id target ExecutionContext Id * * @return RTC::ReturnCode_t * * */ virtual RTC::ReturnCode_t onExecute(RTC::UniqueId ec_id);
IplImage* m_image_buff; IplImage* m_flip_image_buff;
下記のように、onActivated()、onDeactivated()、onExecute()を実装します。
RTC::ReturnCode_t Flip::onActivated(RTC::UniqueId ec_id) { // イメージ用メモリーの確保 m_image_buff = cvCreateImage(cvSize(m_img_width, m_img_height), IPL_DEPTH_8U, 3); m_flip_image_buff = cvCreateImage(cvSize(m_img_width, m_img_height), IPL_DEPTH_8U, 3); return RTC::RTC_OK; } RTC::ReturnCode_t Flip::onDeactivated(RTC::UniqueId ec_id) { // イメージ用メモリーの解放 cvReleaseImage(&m_image_buff); cvReleaseImage(&m_flip_image_buff); return RTC::RTC_OK; } RTC::ReturnCode_t Flip::onExecute(RTC::UniqueId ec_id) { // 新しいデータのチェック if (m_image_origIn.isNew()) { // InPortデータの読み込み m_image_origIn.read(); // InPortの画像データをIplImageのimageDataにコピー memcpy(m_image_buff->imageData,(void *)&(m_image_orig.data[0]),m_image_orig.data.length()); // InPortからの画像データを反転する。 m_flip_mode 0: X軸周り, 1: Y軸周り, -1: 両方の軸周り cvFlip(m_image_buff, m_flip_image_buff, m_flip_mode); // 画像データのサイズ取得 int len = m_flip_image_buff->nChannels * m_flip_image_buff->width * m_flip_image_buff->height; m_image_flip.data.length(len); // 反転した画像データを OutPort にコピー memcpy((void *)&(m_image_flip.data[0]),m_flip_image_buff->imageData,len); // 反転した画像データを OutPort から出力する。 m_image_flipOut.write(); } return RTC::RTC_OK; }
図21のようにし、コンポーネントのビルドを行います。
ここでは、OpenRTM-aist のサンプルコンポーネントに含まれている USBCameraAqcuireComp コンポーネントと、USBCameraMonitorCom コンポーネント、それと、Flip コンポーネントを接続し動作確認を行います。
omniORB のネームサービスを起動します。
[スタート] > [すべてのプログラム] > [OpenRTM-aist] > [C++] > [examples] をクリックし、[Start Naming Service] をクリックしてください。
RTコンポーネントでは、ネームサーバーのアドレスやネームサーバーへの登録フォーマットなどの情報を rtc.conf というファイルで指定する必要があります。
下記の内容を rtc.conf というファイル名で保存し、Flip\FlipComp\Debug (もしくは、Release) フォルダーに置いてください。
corba.nameservers: localhost naming.formats: %n.rtc
Flip コンポーネントを起動します。
先程 rtc.conf ファイルを置いたフォルダーにある、FlipComp.exe ファイルを実行してください。
USB カメラのキャプチャ画像を OutPort から出力する USBCameraAqcuireComp コンポーネントと、InPort で受け取った画像を画面に表示する USBCameraMonitorCOmp コンポーネントを起動します。
これら2つのコンポーネントは、下記の手順にて起動できます。
[スタート] > [すべてのプログラム] > [OpenRTM-aist] > [C++] > [examples] をクリックし、「USBCameraAqcuireComp」と「USBCameraMonitorCOmp」をそれぞれクリックして実行します。
図22のように、RTSystemEditor にて USBCameraAqcuireComp,Flip、USBCameraMonitorComp コンポーネントを接続します。
図23のようにコンフィギュレーションビューにてコンフィギュレーションを変更することができます。
ELECOM製 の UCAM-DLM 130HWH (白いUSBカメラ) を使用の場合は、image_height と image_width パラメーターを下記のように変更してください。
image_height : 480 image_width : 640
また、ELECOM製 の UCAM-DLM 130HWH (白いUSBカメラ) を使用の場合は、USBCameraMonitor の image_height,image_width パラメーターも 上記のように変更する必要があります。
RTSystemEditor の上部にあります「ALL」というアイコンをクリックし、全てのコンポーネントをアクティベートします。
正常にアクティベートされた場合、図24のように黄緑色でコンポーネントが表示されます。
Flip コンポーネントのコンフィギュレーションパラメーター「flip_mode」を「0」や「-1」などに変更し、画像の反転が行われるかを確認してください。
// -*- C++ -*- /*! * @file Flip.cpp * @brief Flip image component * @date $Date$ * * $Id$ */ #include "Flip.h" // Module specification static const char* flip_spec[] = { "implementation_id", "Flip", "type_name", "Flip", "description", "Flip image component", "version", "1.0.0", "vendor", "AIST", "category", "Category", "activity_type", "PERIODIC", "kind", "DataFlowComponent", "max_instance", "1", "language", "C++", "lang_type", "compile", "exec_cxt.periodic.rate", "1.0", // Configuration variables "conf.default.flip_mode", "1", "conf.default.image_height", "240", "conf.default.image_width", "320", "" }; /*! * @brief constructor * @param manager Maneger Object */ Flip::Flip(RTC::Manager* manager) : RTC::DataFlowComponentBase(manager), m_image_origIn("original_image", m_image_orig), m_image_flipOut("fliped_image", m_image_flip), dummy(0), m_image_buff(0), m_flip_image_buff(0) { // Registration: InPort/OutPort/Service // Set InPort buffers registerInPort("original_image", m_image_origIn); // Set OutPort buffer registerOutPort("fliped_image", m_image_flipOut); } /*! * @brief destructor */ Flip::~Flip() { } RTC::ReturnCode_t Flip::onInitialize() { // Bind variables and configuration variable bindParameter("flip_mode", m_flip_mode, "1"); bindParameter("image_height", m_img_height, "240"); bindParameter("image_width", m_img_width, "320"); return RTC::RTC_OK; } RTC::ReturnCode_t Flip::onActivated(RTC::UniqueId ec_id) { // イメージ用メモリーの確保 m_image_buff = cvCreateImage(cvSize(m_img_width, m_img_height), IPL_DEPTH_8U, 3); m_flip_image_buff = cvCreateImage(cvSize(m_img_width, m_img_height), IPL_DEPTH_8U, 3); return RTC::RTC_OK; } RTC::ReturnCode_t Flip::onDeactivated(RTC::UniqueId ec_id) { // イメージ用メモリーの解放 cvReleaseImage(&m_image_buff); cvReleaseImage(&m_flip_image_buff); return RTC::RTC_OK; } RTC::ReturnCode_t Flip::onExecute(RTC::UniqueId ec_id) { // 新しいデータのチェック if (m_image_origIn.isNew()) { // InPort データの読み込み m_image_origIn.read(); // InPortの 画像データを IplImage の imageData にコピー memcpy(m_image_buff->imageData,(void *)&(m_image_orig.data[0]),m_image_orig.data.length()); // InPortからの画像データを反転する。 m_flip_mode 0: X軸周り, 1: Y軸周り, -1: 両方の軸周り cvFlip(m_image_buff, m_flip_image_buff, m_flip_mode); // 画像データのサイズ取得 int len = m_flip_image_buff->nChannels * m_flip_image_buff->width * m_flip_image_buff->height; m_image_flip.data.length(len); // 反転した画像データを OutPort にコピー memcpy((void *)&(m_image_flip.data[0]),m_flip_image_buff->imageData,len); // 反転した画像データを OutPort から出力する。 m_image_flipOut.write(); } return RTC::RTC_OK; } extern "C" { void FlipInit(RTC::Manager* manager) { RTC::Properties profile(flip_spec); manager->registerFactory(profile, RTC::Create<Flip>, RTC::Delete<Flip>); } };
// -*- C++ -*- /*! * @file Flip.h * @brief Flip image component * @date $Date$ * * $Id$ */ #ifndef FLIP_H #define FLIP_H #include <rtm/Manager.h> #include <rtm/DataFlowComponentBase.h> #include <rtm/CorbaPort.h> #include <rtm/DataInPort.h> #include <rtm/DataOutPort.h> #include <rtm/idl/BasicDataTypeSkel.h> // (1) OpenCV 用インクルードファイルのインクルード #include<cv.h> #include<cxcore.h> #include<highgui.h> using namespace RTC; /*! * @class Flip * @brief Flip image component * */ class Flip : public RTC::DataFlowComponentBase { public: /*! * @brief constructor * @param manager Maneger Object */ Flip(RTC::Manager* manager); /*! * @brief destructor */ ~Flip(); /*! * * The initialize action (on CREATED->ALIVE transition) * formaer rtc_init_entry() * * @return RTC::ReturnCode_t * * */ virtual RTC::ReturnCode_t onInitialize(); /*** * * The activated action (Active state entry action) * former rtc_active_entry() * * @param ec_id target ExecutionContext Id * * @return RTC::ReturnCode_t * * */ virtual RTC::ReturnCode_t onActivated(RTC::UniqueId ec_id); /*** * * The deactivated action (Active state exit action) * former rtc_active_exit() * * @param ec_id target ExecutionContext Id * * @return RTC::ReturnCode_t * * */ virtual RTC::ReturnCode_t onDeactivated(RTC::UniqueId ec_id); /*** * * The execution action that is invoked periodically * former rtc_active_do() * * @param ec_id target ExecutionContext Id * * @return RTC::ReturnCode_t * * */ virtual RTC::ReturnCode_t onExecute(RTC::UniqueId ec_id); protected: // Configuration variable declaration /*! * flip_mode = 0: flipping around x-axis * flip_mode > 1: flipping around y-axis * flip_mode < 0: flipping around both axises * - Name: flip_mode flip_mode * - DefaultValue: 1 */ int m_flip_mode; /*! * * - Name: m_img_height * - DefaultValue: 240 * - 画像の高さ */ int m_img_height; /*! * * - Name: m_img_width * - DefaultValue: 320 * - 画像の幅 */ int m_img_width; // DataInPort declaration TimedOctetSeq m_image_orig; InPort<TimedOctetSeq> m_image_origIn; // DataOutPort declaration TimedOctetSeq m_image_flip; OutPort<TimedOctetSeq> m_image_flipOut; private: int dummy; IplImage* m_image_buff; IplImage* m_flip_image_buff; }; extern "C" { void FlipInit(RTC::Manager* manager); }; #endif // FLIP_H
ビルド済みパッケージを下記からダウンロードできます。
拡張子を"zip_"としてますので、"zip"にリネームしてから解凍して下さい。
OpenCVのライブラリを用いて、物体追跡を行うコンポーネントです。
InPort からの画像データを表示し、マウスで選択されたオブジェクトを追跡するコンポーネント。
OutPort からは、物体追跡画像と、マウスで選択した位置からの移動量を出力する。
画像のサイズと、明度、彩度はコンフィグレーションにて変更可能。
作成する RTC の仕様は以下のとおりです。
// -*- C++ -*- /*! * @file ObjectTracking.cpp * @brief Object tracking component * @date $Date$ * * $Id$ */ #include "ObjectTracking.h" #define HIST_RANGE_MAX 180.0 #define HIST_RANGE_MIN 0.0 // 入力画像用 IplImage IplImage* g_image; // CamShift トラッキング用変数 CvPoint g_origin; CvRect g_selection; // // 初期追跡領域の設定判別フラグ値 (0: 設定なし, 1: 設定あり) int g_select_object; // トラッキングの開始/停止用フラグ値 (0: 停止, -1: 開始, 1: トラッキング中) int g_track_object; // オブジェクト選択判定用フラグ(0: 1以外 1: 選択された直後) int g_selected_flag; // 選択された範囲の中心座標 float g_selected_x; float g_selected_y; // Module specification static const char* objecttracking_spec[] = { "implementation_id", "ObjectTracking", "type_name", "ObjectTracking", "description", "Object tracking component", "version", "1.0.0", "vendor", "AIST", "category", "Category", "activity_type", "PERIODIC", "kind", "DataFlowComponent", "max_instance", "1", "language", "C++", "lang_type", "compile", "exec_cxt.periodic.rate", "1.0", // Configuration variables "conf.default.brightness_max", "256", "conf.default.brightness_min", "10", "conf.default.saturation_min", "30", "conf.default.image_height", "240", "conf.default.image_width", "320", "" }; /*! * @brief constructor * @param manager Maneger Object */ ObjectTracking::ObjectTracking(RTC::Manager* manager) : RTC::DataFlowComponentBase(manager), m_in_imageIn("in_image", m_in_image), m_tracking_imageOut("tracing_image", m_tracking_image), m_displacementOut("displacement", m_displacement), dummy(0), m_hsv(0), m_hue(0), m_mask(0), m_backproject(0), m_hist(0), m_backproject_mode(0),m_hdims(16), m_init_flag(0) { // Registration: InPort/OutPort/Service // Set InPort buffers registerInPort("in_image", m_in_imageIn); // Set OutPort buffer registerOutPort("tracing_image", m_tracking_imageOut); registerOutPort("displacement", m_displacementOut); m_hranges_arr[0] = HIST_RANGE_MIN; m_hranges_arr[1] = HIST_RANGE_MAX; m_hranges = m_hranges_arr; } /*! * @brief destructor */ ObjectTracking::~ObjectTracking() { } RTC::ReturnCode_t ObjectTracking::onInitialize() { // Bind variables and configuration variable bindParameter("brightness_max", m_b_max, "256"); bindParameter("brightness_min", m_b_min, "10"); bindParameter("saturation_min", m_s_min, "30"); bindParameter("image_height", m_img_height, "240"); bindParameter("image_width", m_img_width, "320"); m_displacement.data.length(2); return RTC::RTC_OK; } RTC::ReturnCode_t ObjectTracking::onActivated(RTC::UniqueId ec_id) { m_init_flag = 0; // ウインドウを生成する cvNamedWindow( "ObjectTracking", 1 ); // マウス操作時のコールバック処理の登録 cvSetMouseCallback( "ObjectTracking", on_mouse, 0 ); g_selected_flag = 0; g_selected_x = 0.0;; g_selected_y = 0.0;; return RTC::RTC_OK; } RTC::ReturnCode_t ObjectTracking::onDeactivated(RTC::UniqueId ec_id) { if (m_init_flag) { // メモリーを解放する cvReleaseImage(&g_image); cvReleaseImage(&m_hsv); cvReleaseImage(&m_hue); cvReleaseImage(&m_mask); cvReleaseImage(&m_backproject); } // ウインドウを破棄する cvDestroyWindow("ObjectTracking"); m_init_flag = 0; return RTC::RTC_OK; } RTC::ReturnCode_t ObjectTracking::onExecute(RTC::UniqueId ec_id) { if (m_in_imageIn.isNew()) { m_in_imageIn.read(); if(!m_init_flag) allocateBuffers(); memcpy(g_image->imageData,(void *)&(m_in_image.data[0]),m_in_image.data.length()); cvShowImage("ObjectTracking", g_image); // キャプチャされた画像を HSV 表色系に変換して hsv に格納 cvCvtColor( g_image, m_hsv, CV_BGR2HSV ); // g_track_objectフラグが0以下なら、以下の処理を行う if( g_track_object ) { cvInRangeS( m_hsv, cvScalar(HIST_RANGE_MIN,m_s_min,MIN(m_b_min,m_b_max),0), cvScalar(HIST_RANGE_MAX,256,MAX(m_b_min,m_b_max),0), m_mask ); cvSplit( m_hsv, m_hue, 0, 0, 0 ); if( g_track_object < 0 ) calcHistogram(); // バックプロジェクションを計算する cvCalcBackProject( &m_hue, m_backproject, m_hist ); // backProjectionのうち、マスクが1であるとされた部分のみ残す cvAnd( m_backproject, m_mask, m_backproject, 0 ); // CamShift法による領域追跡を実行する cvCamShift( m_backproject, m_track_window, cvTermCriteria( CV_TERMCRIT_EPS | CV_TERMCRIT_ITER, 10, 1 ), &m_track_comp, &m_track_box ); m_track_window = m_track_comp.rect; if( m_backproject_mode ) cvCvtColor( m_backproject, g_image, CV_GRAY2BGR ); if( g_image->origin ) m_track_box.angle = -m_track_box.angle; cvEllipseBox( g_image, m_track_box, CV_RGB(255,0,0), 3, CV_AA, 0 ); // マウスで選択された領域の中心座標を保存 if (g_selected_flag) { g_selected_x = m_track_box.center.x; g_selected_y = m_track_box.center.y; g_selected_flag = 0; } // マウスで選択された位置からの移動量をOutPortから出力 m_displacement.data[0] = m_track_box.center.x - g_selected_x; m_displacement.data[1] = -(m_track_box.center.y - g_selected_y); m_displacementOut.write(); } // マウスで選択中の初期追跡領域の色を反転させる if( g_select_object && g_selection.width > 0 && g_selection.height > 0 ) { cvSetImageROI( g_image, g_selection ); cvXorS( g_image, cvScalarAll(255), g_image, 0 ); cvResetImageROI( g_image ); } // 画像を表示する cvShowImage( "ObjectTracking", g_image ); // 画像をOutPortから出力する int len = g_image->nChannels * g_image->width * g_image->height; m_tracking_image.data.length(len); memcpy((void *)&(m_tracking_image.data[0]),g_image->imageData,len); m_tracking_imageOut.write(); // キー入力を待ち、押されたキーによって処理を分岐させる int c = cvWaitKey(10); // while無限ループから脱出(プログラムを終了) if( (char) c == 27 ) { this->exit(); } } return RTC::RTC_OK; } /*! * 全てのイメージ用メモリーの確保 */ void ObjectTracking::allocateBuffers() { g_image = cvCreateImage( cvSize(m_img_width,m_img_height),8, 3 ); m_hsv = cvCreateImage( cvSize(m_img_width,m_img_height),8, 3 ); m_hue = cvCreateImage( cvSize(m_img_width,m_img_height),8, 1 ); m_mask = cvCreateImage( cvSize(m_img_width,m_img_height),8, 1 ); m_backproject = cvCreateImage( cvSize(m_img_width,m_img_height),8, 1 ); m_hist = cvCreateHist( 1, &m_hdims, CV_HIST_ARRAY, &m_hranges, 1 ); m_init_flag = 1; } /*! * ヒストグラムの計算 */ void ObjectTracking::calcHistogram() { float max_val = 0.f; cvSetImageROI( m_hue, g_selection ); cvSetImageROI( m_mask, g_selection ); // ヒストグラムを計算し、最大値を求める cvCalcHist( &m_hue, m_hist, 0, m_mask ); cvGetMinMaxHistValue( m_hist, 0, &max_val, 0, 0 ); // ヒストグラムの縦軸(頻度)を0-255のダイナミックレンジに正規化 cvConvertScale( m_hist->bins, m_hist->bins, max_val ? 255. / max_val : 0., 0 ); // hue,mask画像に設定された ROI をリセット cvResetImageROI( m_hue ); cvResetImageROI( m_mask ); m_track_window = g_selection; // track_object をトラッキング中にする g_track_object = 1; } extern "C" { void ObjectTrackingInit(RTC::Manager* manager) { RTC::Properties profile(objecttracking_spec); manager->registerFactory(profile, RTC::Create<ObjectTracking>, RTC::Delete<ObjectTracking>); } // // マウスドラッグによって初期追跡領域を指定する // // 引数: // event : マウス左ボタンの状態 // x : マウスが現在ポイントしているx座標 // y : マウスが現在ポイントしているy座標 // flags : 本プログラムでは未使用 // param : 本プログラムでは未使用 // void on_mouse( int event, int x, int y, int flags, void* param ) { // 画像が取得されていなければ、処理を行わない if( !g_image ) return; // 原点の位置に応じてyの値を反転(画像の反転ではない) if( g_image->origin ) y = g_image->height - y; // マウスの左ボタンが押されていれば以下の処理を行う if( g_select_object ) { g_selection.x = MIN(x,g_origin.x); g_selection.y = MIN(y,g_origin.y); g_selection.width = g_selection.x + CV_IABS(x - g_origin.x); g_selection.height = g_selection.y + CV_IABS(y - g_origin.y); g_selection.x = MAX( g_selection.x, 0 ); g_selection.y = MAX( g_selection.y, 0 ); g_selection.width = MIN( g_selection.width, g_image->width ); g_selection.height = MIN( g_selection.height, g_image->height ); g_selection.width -= g_selection.x; g_selection.height -= g_selection.y; } // マウスの左ボタンの状態によって処理を分岐 switch( event ) { case CV_EVENT_LBUTTONDOWN: // マウスの左ボタンが押されたのであれば、 // 原点および選択された領域を設定 g_origin = cvPoint(x,y); g_selection = cvRect(x,y,0,0); g_select_object = 1; break; case CV_EVENT_LBUTTONUP: // マウスの左ボタンが離されたとき、width と height がどちらも正であれば、 // g_track_objectフラグを開始フラグにする g_select_object = 0; if( g_selection.width > 0 && g_selection.height > 0 ) { g_track_object = -1; g_selected_flag = 1; } break; } } };
// -*- C++ -*- /*! * @file ObjectTracking.h * @brief Object tracking component * @date $Date$ * * $Id$ */ #ifndef OBJECTTRACKING_H #define OBJECTTRACKING_H #include <rtm/Manager.h> #include <rtm/DataFlowComponentBase.h> #include <rtm/CorbaPort.h> #include <rtm/DataInPort.h> #include <rtm/DataOutPort.h> #include <rtm/idl/BasicDataTypeSkel.h> #include <cv.h> #include <highgui.h> #include <stdio.h> #include <ctype.h> using namespace RTC; /*! * @class ObjectTracking * @brief Object tracking component * */ class ObjectTracking : public RTC::DataFlowComponentBase { public: /*! * @brief constructor * @param manager Maneger Object */ ObjectTracking(RTC::Manager* manager); /*! * @brief destructor */ ~ObjectTracking(); /*! * * The initialize action (on CREATED->ALIVE transition) * formaer rtc_init_entry() * * @return RTC::ReturnCode_t * * */ virtual RTC::ReturnCode_t onInitialize(); /*** * * The activated action (Active state entry action) * former rtc_active_entry() * * @param ec_id target ExecutionContext Id * * @return RTC::ReturnCode_t * * */ virtual RTC::ReturnCode_t onActivated(RTC::UniqueId ec_id); /*** * * The deactivated action (Active state exit action) * former rtc_active_exit() * * @param ec_id target ExecutionContext Id * * @return RTC::ReturnCode_t * * */ virtual RTC::ReturnCode_t onDeactivated(RTC::UniqueId ec_id); /*** * * The execution action that is invoked periodically * former rtc_active_do() * * @param ec_id target ExecutionContext Id * * @return RTC::ReturnCode_t * * */ virtual RTC::ReturnCode_t onExecute(RTC::UniqueId ec_id); /*! * 入力された1つの色相値を RGB に変換する * * 引数: * hue : HSV表色系における色相値H * 戻り値: * CvScalar: RGB の色情報が BGR の順で格納されたコンテナ */ CvScalar hsv2rgb( float hue ) { int rgb[3], p, sector; static const int sector_data[][3]= {{0,2,1}, {1,2,0}, {1,0,2}, {2,0,1}, {2,1,0}, {0,1,2}}; hue *= 0.033333333333333333333333333333333f; sector = cvFloor(hue); p = cvRound(255*(hue - sector)); p ^= sector & 1 ? 255 : 0; rgb[sector_data[sector][0]] = 255; rgb[sector_data[sector][1]] = 0; rgb[sector_data[sector][2]] = p; return cvScalar(rgb[2], rgb[1], rgb[0],0); } /*! * 全てのイメージ用バッファの確保 */ void allocateBuffers(); /*! * ヒストグラムの計算 */ void calcHistogram(); protected: // Configuration variable declaration /*! * * - Name: b_max * - DefaultValue: 256 * - 明度の最大値 */ int m_b_max; /*! * * - Name: b_min * - DefaultValue: 10 * - 明度の最小値 */ int m_b_min; /*! * * - Name: s_min * - DefaultValue: 30 * - 彩度の最小値 */ int m_s_min; /*! * * - Name: m_img_height * - DefaultValue: 240 * - 画像の高さ */ int m_img_height; /*! * * - Name: m_img_width * - DefaultValue: 320 * - 画像の幅 */ int m_img_width; // DataInPort declaration TimedOctetSeq m_in_image; InPort<TimedOctetSeq> m_in_imageIn; // DataOutPort declaration TimedOctetSeq m_tracking_image; OutPort<TimedOctetSeq> m_tracking_imageOut; TimedFloatSeq m_displacement; OutPort<TimedFloatSeq> m_displacementOut; private: int dummy; IplImage* m_hsv; // HSV 表色系用 IplImage IplImage* m_hue; // HSV 表色系のHチャンネル用 IplImage IplImage* m_mask; // マスク画像用 IplImage IplImage* m_backproject; // バックプロジェクション画像用 IplImage CvHistogram * m_hist; // ヒストグラム処理用構造体 // 処理モード選択用フラグ int m_backproject_mode; // バックプロジェクション画像の表示/非表示用フラグ値 (0: 非表示, 1: 表示) // CamShiftトラッキング用変数 CvRect m_track_window; CvBox2D m_track_box; CvConnectedComp m_track_comp; // ヒストグラム用変数 int m_hdims; // ヒストグラムの次元数 float m_hranges_arr[2]; // ヒストグラムのレンジ float* m_hranges; // 初期化判定フラグ int m_init_flag; }; extern "C" { void ObjectTrackingInit(RTC::Manager* manager); void on_mouse( int event, int x, int y, int flags, void* param ); }; #endif // OBJECTTRACKING_H
図25は、USBCameraAcquire,Flip,ObjectTracking,SeqIn コンポーネントの接続例です。
まず、USBCameraAcquire コンポーネントにて USBカメラの画像を取得します。
次に、Flip コンポーネントにて左右を反転させます。
反転させている理由は、物体追跡コンポーネントの出力をジョイスティックとして使用する場合に、画像が鏡のように表示されていた方が操作しやすいためです。
次に、ObjectTracking コンポーネントで、あらかじめ選択された追跡対象物の移動量を OutPort (displacement) から出力し、SeqIn コンポーネントで移動量を表示します。
ビルド済みパッケージを下記からダウンロードできます。
拡張子を"zip_"としてますので、"zip"にリネームしてから解凍して下さい。
$cd c:\cygwin\nexttool $./NeXTTool.exe /COM=usb -firmware=lms_arm_nbcnxc_107.rfw
//============================================================================= // Main Task TASK(TaskMain) { // establish blutooth connection with a PC to use a PC HID GamePad controller BTConnection btConnection(bt, lcd, nxt); (void)btConnection.connect(BT_PASS_KEY); for (U32 i = 5; i <= Lcd::MAX_CURSOR_Y; i++) lcd.clearRow(i); lcd.cursor(0,5); lcd.putf("snsns", "TOUCH:START/STOP", "STAND IT UP AND", "WAIT FOR A BEEP."); lcd.disp(); SetRelAlarm(Alarm4msec, 1, 4); // Set 4msec periodical Alarm for the drive event while(1) { sonarDriver.checkObstacles(sonar); clock.wait(40); // 40msec wait } }
(void)btConnection.connect(BT_PASS_KEY, "alias")
$cd c:\cygwin\nxtOSEK\samples_c++\cpp\NXTway_GS++
$make all ... Generating binary image file: nxtway_gs++_rom.bin Generating binary image file: nxtway_gs++_ram.bin Generating binary image file: nxtway_gs++_.rxe $
$sh ./rxeflash.sh Executing NeXTTool to upload nxtway_gs++.rxe... nxtway_gs++.rxe=34240 NeXTTool is terminated. $
LEGO Mindstorm NXT (以下NXT) は3つのモーターと4種類のセンサー、これらを制御するためのインテリジェントブロックNXTから構成される、ロボットを作ることができる LEGOブロックです。 NXT は USB または Bluetooth で PC と接続して、インテリジェントブロックをプログラミングしたり、直接制御したりすることができます。 また、自作のプログラムをインテリジェントブロックにロードしてスタンドアロンで動作させることもできます。
ここでは、インテリジェントブロックを PC上で RTコンポーネント化し、他のコンポーネントからモーターを動かしたり、センサーデータを読み出したりできるようにします。 NXT を RTコンポーネント化することで、既存の RTコンポーネント化された入力デバイスで NXT を制御したり、センサーデータを読み出したりすることが簡単にできるようになります。
NXT を RTC化するにあたり、まずは NXT と PC の設定を行います。 PC と NXT は USB または Bluetooth で接続することができるますが、せっかく電池で動く NXT を紐付きでは使いたくないのでBluetoothで接続します。
ここでは、写真に示すような構成を例にとり、NXT を RTC化します。
これは、Tribot と呼ばれる構成の移動ベースの部分に、目玉のような超音波センサーを前面に取り付けただけの簡単な構成です。 移動ベースの組み立て方法は、Mindstorm NXT の箱に入っている「Start Here」と書かれた小箱の中の小冊子に詳しく記載されているので参照してください。 この小冊子で解説されている移動ベースに、いくつかの LEGOブロックを追加して、超音波センサーを取り付ければ完成です。
NXT のインテリジェントブロックには最初から Bluetooth が内蔵されています。 一方、PC に Bluetoothデバイスが内臓されていない場合は、写真のような Bluetoothデバイスを PC に取り付けることで、NXT と通信することができるようになります。
まずは、これらのデバイスを取り付けて、必要ならデバイスドライバをインストールするなどして使えるようにします。 Windows-XP などではたいていの市販の Bluetooth デバイスなら、ドライバを改めてインストールすることなくデフォルトのドライバで動作するようです。
Bluetooth が適切にインストールされていれば、コントロールパネルに Bluetooth のアイコンが現れます。 これをクリックすると図のような Bluetooth設定ダイアログが現れます。
次は、NXT と PC を接続するので、このダイアログはとりあえずそのままにしておきます。
PC と NXT を Bluetoothで接続する手順はおおよそ以下のとおりです。
NXT のインテリジェントブロックの中央のオレンジ色のボタンを押して電源を入れます。 電源が入ると「ピロリロリ♪」と音が鳴ってブロックが起動します。(音量の設定を0にしている場合には音は出ない。) 電源を入れると、図のような画面「My files」モードになります。
このような画面にならない場合は、オレンジ色のボタンのしたの四角いボタンを何度か押すことで、「My files」モードにすることができます。
「My files」モードの状態で、オレンジ色ボタンの左右にある、灰色の三角ボタンを押し「Bluetooth」モードにカーソルを合わせ、オレンジボタンを押します。
さらに、左右の三角ボタンを押し、「Search」モードにカーソルを合わせます。
この状態で、オレンジ色のボタンを押し、実際に接続先を検索します。 検索状態の画面を下に示します。
PC の Bluetooth が有効で NXT から PC が見えれば、図のように PC の名前が表れるはずです。 ここで PC の名前とは Windows における「コンピューター名」です。
近くに Bluetooth搭載 PC があれば、何台かの PC が見えるかもしれません。 三角ボタンを押して自分の PC にカーソルを合わせ、オレンジ色の確認ボタンを押します。
次に Bluetooth のチャネル選択画面になるので、そのままオレンジ色の確認ボタンを押します。 Connecting と表示された後、パスキー入力画面になるので、そのままオレンジボタンを押します。
PC側で図のようなバルーンが表示されるのでこのバルーンをクリックします。
パスキーの入力を求められるので、先ほど NXT に表示されていたパスキーを入力します。 接続が完了すると、図のようなダイアログが現れます。 他のデバイスを認識しないように「発見機能を無効にする」をチェックし「完了」を押して終了します。
Bluetooth設定ダイアログの「デバイス」タブで見ると、図のように NXT が接続されていることが確認できます。
上記リンクからWindows用 のインストーラをダウンロードします。 ダウンロードした実行ファイルを実行すればインストールは完了です。
setup.py install
c:\Python24\python setup.py install
Microsoft Windows XP [Version 5.1.2600] (C) Copyright 1985-2001 Microsoft Corp. C:\tmp\nxt_python-0.7>setup.py install running install running build running build_py ... copying build\scripts-2.4\nxt_filer -> c:\python24\Scripts copying build\scripts-2.4\nxt_push -> c:\python24\Scripts copying build\scripts-2.4\nxt_test -> c:\python24\Scripts C:\tmp\nxt_python-0.7>
NXT を ON にしてPCに接続されている状態で、example 以下のサンプルを実行し、NXT Python から NXT が制御できるかどうか確認します。
example ディレクトリーには以下のサンプルがあります。
これらのテストを実行する際には、それぞれのサンプルが使用するモーター、センサーなどが接続された状態で実行しなければなりません。
以上で、PC から Python を使用して NXT を制御する準備が整いました。
RtcTemplate などを使用して、NXT のコンポーネントの雛形を生成してコーディングに取り掛かりたいところです。 NXT Python はきれいにまとまっている Python モジュールですが、NXT Python のコードを直接 RTC の雛形に書き込むのはちょっと待ってください。
サンプルを見てもわかるように、NXT Python はロケータ、モーター、センサー等の複数のモジュールから構成されていて、モーターやセンサーへの細かな機能も制御できるため、アクセス方法も若干煩雑です。
ここは、NXT Python の複数のモジュールをまとめてひとつのインターフェースからアクセスするためのクラスを作ることにします。 これはソフトウエアパターンで言うところの Façade パターンになります。
Facade パターンあるいは Facade パターン(ファサード・パターン)とは、GoF(Gang of Four; 4人のギャングたち)によって定義された、コンピュータソフトウェアのデザインパターンの1つです。 Facade とは「窓口」を意味します。関連するクラス群を使用するための手続きを、窓口となる一つのクラスに集約することにより、プログラムの冗長性を無くすことを目的とします。(出典: フリー百科事典『ウィキペディア(Wikipedia)』Facade パターン)
こうした便利クラスを作成すると以下の利点があります。
これは、RTC を作成する際全般に言えることです。 対象とするロボットやデバイスなどを、うまくクラス化することで、デバイスの変更や、RTC 自体の変更・バージョンアップにも柔軟に対応でき、変更に強いソフトウエアを作ることができます。
決して、onExecute() などで
ioctl(xxxx, xxxx); // UNIX デバイスへの直アクセス inb(xxx); // I/O への直アクセス outb(xxx); // I/O への直アクセス
のようなプリミティブな関数呼び出しをしてはいけません。 理想的には、
onExecute(ec_id) { // 擬似コード if (m_inport.isNew()) { retval = m_myrobot.set_actuator(m_inport.read()); if (retval == fatal_error) return RTC::RTC_ERROR; } if (myrobot.get_sensor(sensor_data) == true) { m_outport.wrtie(sensor_data); } else {// リードエラーの場合の処理 if (fatal_error) return RTC::RTC_ERROR; } return RTC::RTC_OK; }
NXT Python 自体は NXTインテリジェントブロックのほとんどすべての機能を使用することができますが、それらの機能すべてを直接使用するのは煩雑になるため、利用したい機能のみをインターフェースする Facade クラスを作ります。
NXT の主な機能を列挙します。
これらすべてを一度にサポートするクラスを作るのでは Façade クラスを作る意味がなくなってしまいます。 必要な機能が出てきたらそのつど Façade クラスを更新すればよいのです。 したがって、ここで作成する Façade クラスでは、今回作成するロボットに合わせて、以下の機能のみをサポートすることにします。
関数名はおおよそのイメージです。 この考え方で作成したクラス (NXTBrick.py) を以下に示します。
#!/usr/bin/env python # @file NXTBrick.py # -*- coding:shift_jis -*- import nxt.locator from nxt.sensor import * from nxt.motor import * class NXTBrick: def __init__(self, bsock=None): コンストラクタ NXT ブロックへのコネクションを行い、モーターや、センサーオブジェクトを 作成する。モーターのエンコーダのリセットを行う。 """ if bsock: self.sock = bsock else: self.sock = nxt.locator.find_one_brick().connect() self.motors = [Motor(self.sock, PORT_A), Motor(self.sock, PORT_B), Motor(self.sock, PORT_C)] self.sensors = [TouchSensor(self.sock, PORT_1), SoundSensor(self.sock, PORT_2), LightSensor(self.sock, PORT_3), UltrasonicSensor(self.sock, PORT_4)] self.resetPosition() def close(self): """ NXT ブロックとの接続を終了する """ self.sock.close() def resetPosition(self, relative = 0): """ NXT のモーターのエンコーダをリセットする """ for m in self.motors: m.reset_position(relative) def setMotors(self, vels): """ 配列を受け取り、モーターのパワーとしてセットする。 vels の数とモーターの数が一致しない場合、両者の要素数のうち小さい方でループを回す。 """ for i, v in enumerate(vels[:min(len(vels),len(self.motors))]): self.motors[i].power = max(min(v,127),-127) self.motors[i].mode = MODE_MOTOR_ON | MODE_REGULATED self.motors[i].regulation_mode = REGULATION_MOTOR_SYNC self.motors[i].run_state = RUN_STATE_RUNNING self.motors[i].tacho_limit = 0 self.motors[i].set_output_state() def getMotors(self): """ モーターの位置(角度)を取得する """ state = [] for m in self.motors: state.append(m.get_output_state()) return state def getSensors(self): """ センサの値を取得する。得られたデータは配列で返される。 """ state = [] for s in self.sensors: state.append(s.get_sample()) return state """ テストプログラム モーターに適当な出力を与え、角度を読み表示する。 センサから値を読み込み表示する。 """ if __name__ == "__main__": import time nxt = NXTBrick() print "connected" # モーターのテスト for i in range(100): nxt.setMotors([80,-80,80]) print "Motor: " mstat = nxt.getMotors() for i, m in enumerate(mstat): print "(" , i, "): ", m time.sleep(0.1) nxt.setMotors([0,0,0]) # センサーのテスト for i in range(100): sensors = ["Touch", "Sound", "Light", "USonic"] sval = nxt.getSensors() for s in sensors: print s + ": " + sval print "" time.speel(0.1)
最後の if name == "main":から始まる部分はテストプログラムです。このモジュールを単体で動かしたときに実行されます。 まずは、このモジュールが完全に動くまでテストをすることが重要です。
以上で、NXT の Façade クラスができました。 非常に簡単なクラスですが、モーターに速度を与え、ポジションを読み、センサーの値を読むことのできるクラスができました。 はじめから何でもできるようにしようとすると、結局何をするためのクラスなのかよくわからないクラスができてしまいます。 バージョンアップはいつでもできるので、まずは最低限の機能でもちゃんと動くクラスを作ることが重要です。
では、上で実装した NXTBrick クラスを組み込むための RTC を作成します。 作成する RTC の仕様は以下のとおりです。
RtcTemplateで雛形を作成します。 雛型を作成する方法には、コマンドライン版の rtc-template を使う方法と、Eclipse版の RtcTemplate を使う方法があります。
rtc-template を実行するために、以下のようなバッチファイルを作成します。
python "C:\Program Files\OpenRTM-aist\0.4\utils\rtc-template\rtc-template.py" -bpython^ --module-name=NXTRTC --module-desc="NXT sample component"^ --module-version=0.1 --module-vendor=AIST --module-category=example^ --module-comp-type=DataFlowComponent --module-act-type=SPORADIC^ --module-max-inst=10^ --inport=vel:TimedFloatSeq^ --outport=pos:TimedFloatSeq --outport=sens:TimedFloatSeq^ --config="map:string:A,B"
rtc-template(gen.bat)の実行
> gen.bat python "C:\Program Files\OpenRTM-aist\0.4\utils\rtc-template\rtc-template.py" -bpython --module-name=NXTRTC --module-desc="NXT sample component" --module-version=0.1 --module-vendor=AIST --module-category=example --module-comp-type=DataFlowComponent --module-act-type=SPORADIC --module-max-inst=10 --inport=vel:TimedFloatSeq --outport=pos:TimedFloatSeq --outport=sens:TimedFloatSeq --config="map:string:A,B" File "NXTRTC.py" was generated. File "README.NXTRTC" was generated. File "NXTRTC.yaml" was generated.
以上のように、NXTRTC.py などのひな型ファイルができました。
ここから、NXTRTC.py に先ほど作成した NXTBrick.py の機能を組み込んでいきます。
import NXTBrick
# create NXTBrick object try: self._nxtbrick = NXTBrick.NXTBrick() except: print "NXTBrick create failed.""" return RTC.RTC_ERROR
self._nxtbrick.resetPosition()
上記をまとめたサンプルコードを以下に示す。
#!/usr/bin/env python # -*- coding:shift_jis -*- # -*- Python -*- import sys import time sys.path.append(".") # Import RTM module import OpenRTM import RTC # import NXTBrick class import NXTBrick # This module's spesification # <rtc-template block="module_spec"> nxtrtc_spec = ["implementation_id", "NXTRTC", "type_name", "NXTRTC", "description", "NXT sample component", "version", "0.1", "vendor", "AIST", "category", "example", "activity_type", "DataFlowComponent", "max_instance", "10", "language", "Python", "lang_type", "SCRIPT", "conf.default.map", "A,B", ""] # </rtc-template> class NXTRTC(OpenRTM.DataFlowComponentBase): def __init__(self, manager): OpenRTM.DataFlowComponentBase.__init__(self, manager) # DataPorts initialization # <rtc-template block="data_ports"> self._d_vel = RTC.TimedFloatSeq(RTC.Time(0,0),[]) self._velIn = OpenRTM.InPort("vel", self._d_vel, OpenRTM.RingBuffer(8)) self.registerInPort("vel",self._velIn) self._d_pos = RTC.TimedFloatSeq(RTC.Time(0,0),[]) self._posOut = OpenRTM.OutPort("pos", self._d_pos, OpenRTM.RingBuffer(8)) self.registerOutPort("pos",self._posOut) self._d_sens = RTC.TimedFloatSeq(RTC.Time(0,0),[]) self._sensOut = OpenRTM.OutPort("sens", self._d_sens, OpenRTM.RingBuffer(8)) self.registerOutPort("sens",self._sensOut) # initialize of configuration-data. # <rtc-template block="configurations"> self._map = [['A', 'B']] self._nxtbrick = None self._mapping = {'A':0,'B':1,'C':2} def onInitialize(self): # Bind variables and configuration variable # <rtc-template block="bind_config"> self.bindParameter("map", self._map, "A,B") # create NXTBrick object try: print "Connecting to NXT brick ...." self._nxtbrick = NXTBrick.NXTBrick() print "Connection established." except: print "NXTBrick connection failed." return RTC.RTC_ERROR return RTC.RTC_OK def onFinalize(self): self._nxtbrick.close() def onActivated(self, ec_id): # reset NXTBrick's position. self._nxtbrick.resetPosition() return RTC.RTC_OK def onDeactivated(self, ec_id): # reset NXTBrick's position. self._nxtbrick.resetPosition() return RTC.RTC_OK def onExecute(self, ec_id): cnt = 0 # check new data. if self._velIn.isNew(): # read velocity data from inport. self._d_vel = self._velIn.read() vel_ = [0,0,0] vel_[self._mapping[self._map[0][0]]] = self._d_vel.data[0] vel_[self._mapping[self._map[0][1]]] = self._d_vel.data[1] # set velocity self._nxtbrick.setMotors(vel_) # get sensor data. sensor_ = self._nxtbrick.getSensors() if sensor_: self._d_sens.data = sensor_ self._sensOut.write() # get position data. position_ = self._nxtbrick.getMotors() if position_: self._d_pos.data = [position_[self._mapping[self._map[0][0]]][9], position_[self._mapping[self._map[0][1]]][9]] # write position data to outport. self._posOut.write() return RTC.RTC_OK def MyModuleInit(manager): profile = OpenRTM.Properties(defaults_str=nxtrtc_spec) manager.registerFactory(profile, NXTRTC, OpenRTM.Delete) # Create a component comp = manager.createComponent("NXTRTC") def main(): mgr = OpenRTM.Manager.init(len(sys.argv), sys.argv) #mgr = OpenRTM.Manager.init(sys.argv) mgr.setModuleInitProc(MyModuleInit) mgr.activateManager() mgr.runManager() if __name__ == "__main__": main()
このサンプルでは上記のサンプルにコールバック OnWrite を追加し、InPort のバッファへデータが書き込まれた際にモーターへの速度出力を行うように拡張します。
# @class CallBackClass # @brief callback class # # when data is written in the buffer of InPort, # it is called. class CallBackClass: def __init__(self, nxtbrick_, map_): self._nxtbrick = nxtbrick_ self._map = map_ self._mapping = {'A':0,'B':1,'C':2} def __call__(self, pData): vel_ = [0,0,0] vel_[self._mapping[self._map[0][0]]] = pData.data[0] vel_[self._mapping[self._map[0][1]]] = pData.data[1] # set velocity self._nxtbrick.setMotors(vel_)
このクラスでは、NTXBrick クラスのオブジェクトとコンフィギュレーション変数 map を引数にとります。
# set callback class self._velIn.setOnWrite(CallBackClass(self._ntxbrick,self._map))
setOnWrite() により、InPort へデータが書き込まれた際に、CallBackClassのcall() メソッドが呼ばれるようになります。
上記をまとめたサンプルコードを以下に示す。
#!/usr/bin/env python # -*- coding:shift_jis -*- # -*- Python -*- import sys import time sys.path.append(".") # Import RTM module import OpenRTM import RTC # import NXTBrick class import NXTBrick # This module's spesification # <rtc-template block="module_spec"> nxtrtc_spec = ["implementation_id", "NXTRTC", "type_name", "NXTRTC", "description", "NXT sample component", "version", "0.1", "vendor", "AIST", "category", "example", "activity_type", "DataFlowComponent", "max_instance", "10", "language", "Python", "lang_type", "SCRIPT", "conf.default.map", "A,B", ""] # </rtc-template> # @class CallBackClass # @brief callback class # # when data is written in the buffer of InPort, # it is called. class CallBackClass: def __init__(self, nxtbrick_, map_): self._nxtbrick = nxtbrick_ self._map = map_ self._mapping = {'A':0,'B':1,'C':2} def __call__(self, pData): vel_ = [0,0,0] vel_[self._mapping[self._map[0][0]]] = pData.data[0] vel_[self._mapping[self._map[0][1]]] = pData.data[1] # set velocity self._nxtbrick.setMotors(vel_) class NXTRTC(OpenRTM.DataFlowComponentBase): def __init__(self, manager): OpenRTM.DataFlowComponentBase.__init__(self, manager) # DataPorts initialization # <rtc-template block="data_ports"> self._d_vel = RTC.TimedFloatSeq(RTC.Time(0,0),[]) self._velIn = OpenRTM.InPort("vel", self._d_vel, OpenRTM.RingBuffer(8)) self.registerInPort("vel",self._velIn) self._d_pos = RTC.TimedFloatSeq(RTC.Time(0,0),[]) self._posOut = OpenRTM.OutPort("pos", self._d_pos, OpenRTM.RingBuffer(8)) self.registerOutPort("pos",self._posOut) self._d_sens = RTC.TimedFloatSeq(RTC.Time(0,0),[]) self._sensOut = OpenRTM.OutPort("sens", self._d_sens, OpenRTM.RingBuffer(8)) self.registerOutPort("sens",self._sensOut) # initialize of configuration-data. # <rtc-template block="configurations"> self._map = [['A', 'B']] self._nxtbrick = None self._mapping = {'A':0,'B':1,'C':2} def onInitialize(self): # Bind variables and configuration variable # <rtc-template block="bind_config"> self.bindParameter("map", self._map, "A,B") # create NXTBrick object try: print "Connecting to NXT brick ...." self._nxtbrick = NXTBrick.NXTBrick() print "Connection established." except: print "NXTBrick connection failed." return RTC.RTC_ERROR # set callback class self._velIn.setOnWrite(CallBackClass(self._ntxbrick,self._map)) return RTC.RTC_OK def onFinalize(self): self._nxtbrick.close() def onActivated(self, ec_id): # reset NXTBrick's position. self._nxtbrick.resetPosition() return RTC.RTC_OK def onDeactivated(self, ec_id): # reset NXTBrick's position. self._nxtbrick.resetPosition() return RTC.RTC_OK def onExecute(self, ec_id): # get sensor data. sensor_ = self._nxtbrick.getSensors() if sensor_: self._d_sens.data = [sensor_[3]] # write sensor data to outport. self._sensOut.write() # get position data. position_ = self._nxtbrick.getMotors() if position_: self._d_pos.data = [position_[self._mapping[self._map[0][0]]][9],position_[self._mapping[self._map[0][1]]][9]] # write position data to outport. self._posOut.write() return RTC.RTC_OK def MyModuleInit(manager): profile = OpenRTM.Properties(defaults_str=nxtrtc_spec) manager.registerFactory(profile, NXTRTC, OpenRTM.Delete) # Create a component comp = manager.createComponent("NXTRTC") def main(): mgr = OpenRTM.Manager.init(len(sys.argv), sys.argv) #mgr = OpenRTM.Manager.init(sys.argv) mgr.setModuleInitProc(MyModuleInit) mgr.activateManager() mgr.runManager() if __name__ == "__main__": main()
はじめの例では、モーターへの出力、センサー値の読み込み、モーターエンコーダの読み込みをすべて同一ループで同期的に行っていましたが、コールバックを使用することで、データが来たらすぐにモーターへ値を出力できるようになります。
では、作成した NXTRTC を実際に動作させてみます。
ネームサーバーを起動します。 Windows用の OpenRTM-aist(C++版) をインストールしている場合は、スタートメニューの「OpenRTM-aist」>「C++」>「tools」>「Start Naming Service」から起動できます。
rtc.conf を作成します。
corba.nameservers: localhost naming.formats: %n.rtc manager.shutdown_auto: NO
corna.nameservers の項目は使用したいネームサーバーのアドレスに合わせて設定します。 ここでは、ローカルのネームサーバーを使用しますので、localhostとします。
起動したいコンポーネントが入っているそれぞれのディレクトリーにコピーしておきます。 今回作成した NXTRTC のほかにRTSystemEditor を起動します。 RTSystemEditor を起動後、ネームサーバー(ここでは localhost)に接続します。 また、SystemDiagram エディタを開くために、ツールバーの SystemDiagram アイコンをクリックします。
TkJoyStickComp は GUI上でジョイスティックを模擬するためのコンポーネントです。 図に示すような GUI があらわれ、中央の丸をドラッグすることでジョイスティックのように、X-Yの値を OutPort(上) から出力するコンポーネントです。 TkJoyStickComp にはもうひとつ OutPort(下) が付いていて、こちらは作動駆動型移動ロボットを操作するのに便利な左右の車輪の速度を出力するためのデータポートです。 NXTRTC の入力ポートに直接接続すれば、Tribot を制御することができます。
TkMotorComp は GUI上で車輪に見立てた丸いアイコンが、InPort へ入力されたデータ(角度)に従って回転する、車輪の回転の様子をモニタリングするためのコンポーネントです。 NXTRTC の車輪の角度を出力する OutPort に接続することで、NXT の車輪の回転の様子を見ることができます。 NXT の車輪を手で回しても、その様子をモニタすることができます。
TkSliderMonitorComp は、InPort へ入力されたデータの値をGUIのスライダで表示するためのコンポーネントです。 NXTRTC のセンサー出力ポートに接続することで、NXT のセンサーデータのモニタリングをすることができます。
すべてのコンポーネントが起動したら、コンポーネントを接続します。 NameService ビューから SystemDiagram エディタへ各コンポーネントをドラッグアンドドロップします。 接続したいポートからポートへドラッグアンドドロップすることでポートを接続することができます。 下に、幾つかのコンポーネントを接続した例を示します。
NXTに超音波センサーが取り付けられていない場合、NXTRTC の Activate 後にエラー状態(赤色)になります。
これらのコンポーネントを使って、NXTRTC が正しく動いているかどうかを確認しましょう。