このケーススタディでは、簡単な画像処理をコンポーネント化する方法を紹介します。既存のカメラコンポーネントと画像表示コンポーネントを利用し、カメラからの画像を左右(または上下)に反転させる処理部分をコンポーネントとして作成してカメラの画像を反転させ表示するシステムを作成します。
画像を反転する処理は簡単に実装することができますが、ここではより簡単に実装するために OpenCV ライブラリを利用し、汎用性の高い RTコンポーネントを作成します。
OpenCV (オープンシーブイ) とはかつてインテルが、現在はItseezが開発・公開しているオープンソースのコンピュータービジョン向けライブラリです。
Wikipediaより抜粋。
入力された画像を左右または上下に反転し出力する RTコンポーネントを、OpenCV ライブラリの cv::flip 関数を利用して作成します。作成および実行環境は Windows上 の Visual Studio を想定しています。対象 OpenRTM-aist のバージョンは 1.1.2 です。
作成手順はおおよそ以下のようになります。
cv::flip 関数は、OpenCV で標準的に用いられている cv::Mat 型の画像データを垂直軸 (左右反転)、水平軸 (上下反転)、または両軸 (上下左右反転)に対して反転させます。関数プロトタイプと入出力の引数の意味は以下の通りです。
void flip(const Mat& src, Mat& dst, int flipMode)
src | 入力配列 |
dst | 出力配 |
flipMode | 配列の反転方法の指定内容: flipMode = 0: X軸周りでの反転(上下反転) flipMode > 0: Y軸周りでの反転(左右反転) flipMode < 0: 両軸周りでの反転(上下左右反転) |
これから作成するコンポーネントを Flip コンポーネントと呼ぶことにします。
このコンポーネントは画像データ型の入力ポート (InPort) と、反転処理した画像を出力するための出力ポート (OutPort) を持ちます。それぞれのポートの名前を 入力ポート(InPort)名: originalImage、出力ポート(OutPort)名: flippedImage とします。
OpenRTM-aist には OpenCV を使用したビジョン関連のコンポーネントがサンプルとして付属しています。これらのコンポーネントのデータポートは画像の入出力に以下のような CameraImage 型を使用しています。
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; };
このFlipコンポーネントではこれらのサンプルコンポーネントとデータのやり取りができるよう同じく CameraImage型 を InPort と OutPort に使用することにします。
CameraImage型 は InterfaceDataTypes.idl で定義されており、C++であれば、InterfaceDataTypesSkel.h をインクルードすると使えるようになります。
また、画像を反転させる方向は、左右反転、上下反転、上下左右反転の3通りがあります。これを実行時に指定できるように、RTコンポーネントのコンフィギュレーション機能を使用して指定できるようにします。パラメーター名は flipMode という名前にします。
flipMode は cv::flip 関数の仕様に合わせて、型は int 型とし 上下反転、左右反転、上下左右反転それぞれに 0、1、-1 を割り当てることにします。
flipMode の各値での画像処理のイメージを下図に示します。
以上から Flip コンポーネントの仕様をまとめます。
コンポーネント名称 | Flip |
InPort | |
ポート名 | originalImage |
型 | RTC::CameraImage |
説明 | 入力画像 |
OutPort | |
ポート名 | flippedImage |
型 | RTC::CameraImage |
説明 | 反転された画像 |
Configuration | |
パラメーター名 | flipMode |
型 | int |
デフォルト値 | 0 |
制約 | (0,-1, 1) |
Widget | radio |
説明 | 反転モード 上下反転: 0 左右反転: 1 上下左右反転: -1 |
ここで動作環境および開発環境を確認しておきます。
OpenRTM-aist-1.1 以降では、コンポーネントのビルドに CMake を使用します。
Flipコンポーネントの雛型の生成は、RTCBuilder を用いて行います。
Eclipse では、各種作業を行うフォルダーを「ワークスペース」(Work Space)とよび、原則としてすべての生成物はこのフォルダの下に保存されます。 ワークスペースはアクセスできるフォルダーであれば、どこに作っても構いませんが、このチュートリアルでは以下のワークスペースを仮定します。
まずは Eclipse を起動します。 Windows 10の場合はデスクトップの以下のショートカットをダブルクリックして起動します。
最初にワークスペースの場所を尋ねられますので、上記のワークスペースを指定して [OK] をクリックしてください。
すると、以下のようなWelcomeページが表示されます。
Welcomeページは必要ないので左上の [×] ボタンをクリックして閉じてください。
右上の [Open Perspective] ボタンをクリックしてください。
「RTC Builder」を選択し、[OK] ボタンをクリックします。
RTCBuilderが起動します。
Flipコンポーネントを作成するために、RTCBuilder で新規プロジェクトを作成する必要があります。
左上の [Open New RTCBuilder Editor] のアイコンをクリックしてください。
「プロジェクト名」欄に作成するプロジェクト名 (ここでは Flip) を入力して [終了] をクリックします。
指定した名称のプロジェクトが生成され、パッケージエクスプローラ内に追加されます。
生成したプロジェクト内には、デフォルト値が設定された RTC プロファイル XML(RTC.xml) が自動的に生成されます。
RTC.xmlが生成された時点で、このプロジェクトに関連付けられているワークスペースとして RTCBuilder のエディタが開くはずです。
まず、いちばん左の「基本」タブを選択し、基本情報を入力します。先ほど決めた Flip コンポーネントの仕様(名前)の他に、概要やバージョン等を入力してください。ラベルが赤字の項目は必須項目です。その他はデフォルトで構いません。
次に、「アクティビティ」タブを選択し、使用するアクションコールバックを指定します。
Flipコンポーネントでは、onActivated()、onDeactivated()、onExecute()コールバックを使用します。下図のように①の onAtivated をクリック後に ②のラジオボタンにて [ON] にチェックを入れます。onDeactivated、onExecute についても同様の操作を行います。
さらに、「データポート」タブを選択し、データポートの情報を入力します。 先ほど決めた仕様を元に以下のように入力します。なお、変数名や表示位置はオプションなので、変更しなくて結構です。
次に、「コンフィギュレーション」タブを選択し、先ほど決めた仕様を元に、Configuration の情報を入力します。制約条件および Widget とは、RTSystemEditor でコンポーネントのコンフィギュレーションパラメーターを表示する際に、スライダー、スピンボタン、ラジオボタンなど、GUIで値の変更を行うためのものです。
ここでは、flipMode が取りうる値は先ほど仕様を決めたときに、-1、0、1 の3つの値のみ取ることにしたので、ラジオボタンを使用することにします。
次にプログラミング言語の選択とコードの生成を行いますが、OpenRTM-aist 2.0と1.2でツールの仕様が変わっています。
まずは1.2.2以前のバージョンの手順について説明します。
「言語・環境」タブを選択し、プログラミング言語を選択します。ここでは、C++(言語)を選択します。なお、言語・環境はデフォルト等が設定されておらず、指定し忘れるとコード生成時にエラーになりますので、必ず言語の指定を行うようにしてください。
最後に、「基本」タブにある"コード生成"ボタンをクリックし、コンポーネントの雛型を生成します。
次に2.0以降のバージョンの手順について説明します。
「基本」タブを選択して、下にスクロールすると見える「言語」の項目でC++を選択します。
最後にコード生成ボタンをクリックし、コンポーネントの雛型を生成します。
コード生成が完了したら、以下のようにFlipプロジェクトを右クリックして、「表示方法」->「システム・エクスプローラー」をクリックすることで、エクスプローラーでワークスペースのフォルダを開いてください。 Flipフォルダに各種ファイルが生成されているか確認してください。
RTC Builder で生成したコードの中には CMake でビルドに必要な各種ファイルを生成するための CMakeLists.txt が含まれています。 CMake を利用することにより CMakeLists.txt から Visual Studio のプロジェクトファイル、ソリューションファイル、もしくは Makefile 等を自動生成できます。
Flipフォルダのsrc/CMakeLists.txtをメモ帳などで開いて編集します。
このコンポーネントでは OpenCV を利用していますので、OpenCV のヘッダのインクルードパス、ライブラリやライブラリサーチパスを設定する必要があります。OpenCV は CMake に対応しており、以下の2行を追加・変更するだけで OpenCV のライブラリがリンクされ使えるようになります。
set(comp_srcs Flip.cpp ) set(standalone_srcs FlipComp.cpp) find_package(OpenCV REQUIRED) # <- この行を追加 :中略 add_dependencies(${PROJECT_NAME} ALL_IDL_TGT) target_link_libraries(${PROJECT_NAME} ${OPENRTM_LIBRARIES} ${OpenCV_LIBS}) # <- OepnCV_LIBSを追加 :中略 add_executable(${PROJECT_NAME}Comp ${standalone_srcs} ${comp_srcs} ${comp_headers} ${ALL_IDL_SRCS}) add_dependencies(${PROJECT_NAME}Comp ALL_IDL_TGT) target_link_libraries(${PROJECT_NAME}Comp ${OPENRTM_LIBRARIES} ${OpenCV_LIBS}) # <- OepnCV_LIBSを追加
CMakeを利用してビルド環境のConfigureを行います。 まずはCMake(cmake-gui)を起動してください。Windows 10の場合は画面左下の「ここに入力して検索」にCMakeと入力して検索してください。
画面上部に以下のようなテキストボックスがありますので、それぞれソースコードの場所( CMakeList.txt がある場所) と、ビルドディレクトリーを指定します。
ソースコードの場所は Flip コンポーネントのソースが生成された場所で CMakeList.txt が存在するディレクトリーです。デフォルトでは <ワークスペースディレクトリー>/Flip になります。
FlipフォルダのCMakeLists.txtをcmake-guiにドラッグアンドドロップすると、Flipフォルダのパスが自動的に入力されます。
ビルドディレクトリーとは、ビルドするためのプロジェクトファイルやオブジェクトファイル、バイナリを格納する場所のことです。場所は任意ですが、この場合 <ワークスペースディレクトリー>/Flip/build のように分かりやすい名前をつけたFlipのサブディレクトリーを指定することをお勧めします。
Where is the soruce code | C:\workspace\Flip |
Where to build the binaries | C:\workspace\Flip\build |
指定したら、下のConfigureボタンを押します。Create Directoryの画面が出たらYESをクリックしてください。
すると下図のようなダイアログが表示されますので、生成したいプロジェクトの種類を指定します。 今回は Visual Studio 16 2019 とします。VS2017やVS2022 を利用している方はそれぞれ読み替えてください。項目にVisual Studio 2022が無い場合は、cmake-guiのバージョンが古いため最新のバージョンをインストールし直してください。
ダイアログで [Finish] をクリックすると Configure が始まります。問題がなければ下部のログウインドウに「Configuring done」と表示されますので、続けて [Generate] ボタンをクリックします。「Generating done」と表示されればプロジェクトファイル・ソリューションファイル等の出力が完了します。
「Open Project」ボタンを押すと、Visual Studio 2019が起動して先ほど指定した build ディレクトリーの中の Flip.sln を 開きます。
なお、CMake は Configure の段階でキャッシュファイルを生成しますので、トラブルなどで設定を変更したり環境を変更した場合は [File] > [Delete Cache] でキャッシュを削除して Configure からやり直してください。
ヘッダ (include/Flip/Flip.h) およびソースコード (src/Flip.cpp) をそれぞれ編集します。 Visual Studio のソリューションエクスプローラから Flip.h、Flip.cpp をクリックすることで編集画面が開きます。
Flip コンポーネントでは、InPort から受け取った画像を画像保存用バッファに保存し、その保存した画像を OpenCVのcv::flip() 関数にて変換します。その後、変換された画像を OutPort から送信します。
onActivated()、onExecute()、onDeactivated()での処理内容を下図に示します。
onExecute() での処理を下図に示します。
OpenCV のライブラリを使用するため、OpenCV のインクルードファイルをインクルードします。
#include <rtm/DataInPort.h> #include <rtm/DataOutPort.h> #include <opencv2/opencv.hpp> #この行を追加 // <rtc-template block="component_description">
反転した画像の保存用にメンバー変数を追加します。
private: // <rtc-template block="private_attribute"> // </rtc-template> // <rtc-template block="private_operation"> // </rtc-template> cv::Mat m_imageBuff; #この行を追加 cv::Mat m_flipImageBuff; #この行を追加
下記のように、onActivated()、onDeactivated()、onExecute() を実装します。
RTC::ReturnCode_t Flip::onActivated(RTC::UniqueId ec_id) { // OutPortの画面サイズを0に設定 m_flippedImage.width = 0; m_flippedImage.height = 0; return RTC::RTC_OK; }
RTC::ReturnCode_t Flip::onDeactivated(RTC::UniqueId ec_id) { if (!m_imageBuff.empty()) { // 画像用メモリの解放 m_imageBuff.release(); m_flipImageBuff.release(); } 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; m_imageBuff.create(cv::Size(m_originalImage.width, m_originalImage.height), CV_8UC3); m_flipImageBuff.create(cv::Size(m_originalImage.width, m_originalImage.height), CV_8UC3); } // InPortの画像データをm_imageBuffにコピー memcpy(m_imageBuff.data, (void *)&(m_originalImage.pixels[0]), m_originalImage.pixels.length()); // InPortからの画像データを反転する。 m_flipMode 0: X軸周り、1: Y軸周り、-1: 両方の軸周り cv::flip(m_imageBuff, m_flipImageBuff, m_flipMode); // 画像データのサイズ取得 int len = m_flipImageBuff.channels() * m_flipImageBuff.cols * m_flipImageBuff.rows; m_flippedImage.pixels.length(len); // 反転した画像データをOutPortにコピー memcpy((void *)&(m_flippedImage.pixels[0]), m_flipImageBuff.data, len); // 反転した画像データをOutPortから出力する。 m_flippedImageOut.write(); } return RTC::RTC_OK; }
Visual Studioの「ビルド」→「ソリューションのビルド」を選択してビルドを行います。
ここでは、OpenRTM-aist-1.1 以降で同梱されるようになったカメラコンポーネント (OpenCVCameraComp)とビューアコンポーネント (CameraViewerComp)を接続し動作確認を行います。
まずはRTSystemEditorを起動します。 OpenRTPのパースペクティブを開く画面からRTSystemEditorを選択して開いてください。
コンポーネントの参照を登録するためのネームサービスを起動します。
RTSystemEditorのネームサービスビュー上部のネームサービス起動ボタンを押してください。
Flip コンポーネントを起動します。
build/src/Debug (もしくはbuild/src/Release)フォルダの FlipComp.exe ファイルを実行して下さい。
USBカメラのキャプチャ画像を OutPort から出力する OpenCVCameraComp と、InPort で受け取った画像を画面に表示する CameraViewerComp を起動します。
これら2つのコンポーネントは、下記の手順にて起動できます。
コンポーネントが起動すると、以下のようにネームサービスビューにFlip、CameraViewer、OpenCVCameraコンポーネントが表示されるため、左上の「Open New System Editor」ボタン (ONのボタン) をクリックしてエディタを起動後に、エディタにコンポーネントをドラッグアンドドロップします。
下図のように、RTSystemEditorにて OpenCVCameraComp と Flip、CameraviewerComp コンポーネントを接続します。
ポートの接続は、片側のポートからもう片側のポートにドラッグアンドドロップすることでコネクタが生成されます。
RTSystemEditor の上部にあります「All Activate」というアイコンをクリックし、全てのコンポーネントをアクティブ化します。正常にアクティベートされた場合、下図のように黄緑色でコンポーネントが表示されます。
下図のようにコンフィギュレーションビューにてコンフィギュレーションを変更することができます。
エディタ上のFlipコンポーネントをクリックして編集ボタンを押してください。
Flip コンポーネントのコンフィギュレーションパラメーター「flipMode」を「0」や「-1」などに変更し、画像の反転が行われるかを確認してください。
コンポーネントを終了する場合は、「All Exit」ボタンを押すとエディタ上のコンポーネントが全て終了します。