読者です 読者をやめる 読者になる 読者になる

自習室

こもります

Kinect, OpenCV, openFrameworks のカラー画像を相互に変換する

.Kinect .OpenCV .openFrameworks .C++

はじめに

以前の記事で KinectColorFrameReader から得た画像を ofImage 形式に変換して描画したりしましたが、いろいろ調べてみると、Kinect の画像形式から直接 ofImage に変換している例は意外と少ないようでした。そりゃまぁカラー画像をそのまま取得するだけだったらわざわざKinect使わんわい、という話だと思います。

それで、OpenCV も一緒に利用するケースを考えてみました。

f:id:AMANE:20150209231053p:plain

この三者間を画像データが行き来出来れば、柔軟にプログラムが出来そうです。

oF 内で OpenCV を利用する方法について

先日の記事ではoFでKinect for Windows SDK 2.0 を使う手法について調査検討しましたが、OpenCVについても同様に調査します。

大まかには、以下の方法があると思います。

  • 公式の ofxOpenCV を使う
  • kylemcdonald/ofxCv を使う
  • OpenCV を直接叩く
公式の ofxOpenCV を使う

OpenCVのバージョンは2.3.1です。ofxCvColorimage など oF での編集や描画がしやすいようアレンジされた画像クラスが利用できるのは便利です。公式のアドオンですが、IplImageを使っている箇所があったりするあたりはイケてません。そしてちょっと古い。

kylemcdonald/ofxCv を使う

内部で ofxOpenCV のアドレスを見ているので、利用しているのは ofxOpenCV と同様に 2.3.1 です。toCv() toOf() という多目的の関数があり、以下の様な変換が可能です。これは非常に便利です。変換可能な組を一部抜粋します。

- ofVec2(3)f --- Point2(3)f
- ofImage --- cv::Mat
- ofRectangle --- cv::Rect

詳細はコードをご参考 ofxCv/Utilities.h at master · kylemcdonald/ofxCv · GitHub

OpenCVはちょっと古いのですが、一方でコード中で ofDraw** という openFrameworks 0.9 系で採用される予定の記述があったりもし、いくつかのサンプルがビルドできませんでした。これの修正も結構大変そうです。

OpenCV を直接叩く

ofxAddons では無いので導入は若干面倒ですが、最新の機能を全部使える様になります。ただし、画像のデータなど、自分で変換する必要があります。

どうするか

ofxCv が素直に使えれば良かったのですが、先述の通りの問題があったりするのが玉に瑕で、Kinect for Windows SDK 2.0 を直接叩くのと同様の理屈で OpenCV も直接使えば良いじゃん、という結論になりました。OpenCV の生コードを書くのが何だかんだ楽です。

ところで、ofxCv はその目標に以下の様な物を掲げています。

Provide clean implementations of all functions in order to provide a stepping stone to direct OpenCV use.

と言う風に素のOpenCVも書けるような仕様になっているらしいので、ちゃんと ofxCv がまとまり oF も 0.9 に上がった暁には、 ofxCv を利用するのも良いかもしれません。

環境

openFrameworksv0.8.4 を Visual Studio Community 2013 で使う方法については、先日の記事をご参照ください

また、Visual StudioOpenCV を用いて開発する方法については、下記ブログなどをご参照ください

完成品はあげてあります

解説

ウィンドウが複数開いて大量に絵が出てきますが、下図の様な変換を行っています。

f:id:AMANE:20150209231104p:plain

以下ポイントだけ説明いたします ofImage と cv::Mat の相互変換については、先述の Kyle先生の ofxCv から toCv toOf 関数の実装を参考にさせていただいております。

ofImage -> cv::Mat

cv::Matコンストラクタに、配列の中身になる実データを指定出来るものがあるので、ofImageから実データを引っこ抜いて利用します。

// ofImage ofColor; ... ofVideoGrabber からデータを引っこ抜いたモノ
ofColor.allocate(CAM_WIDTH, CAM_HEIGHT, OF_IMAGE_COLOR);
cv::Mat cvColor = cv::Mat(CAM_HEIGHT, CAM_WIDTH, CV_8UC3, ofColor.getPixels());

cv::Mat のドキュメントによると

data – Pointer to the user data. Matrix constructors that take data and step parameters do not allocate matrix data. Instead, they just initialize the matrix header that points to the specified data, which means that no data is copied. This operation is very efficient and can be used to process external data using OpenCV functions. The external data is not automatically deallocated, so you should take care of it.

ということで、cv::Mat は実際は ofImage の確保しているデータを直接参照します。メモリの無駄がない、と言うことです。

また、ofImage は標準で RGB 配列ですが、 OpenCV では BGR 配列が標準ですので、変換が必要です

// #include <opencv2/imgproc/imgproc.hpp>
cvtColor(cvColor, cvColor, CV_RGB2BGR);

cv::Mat -> ofImage

// ofImage ofCropped;
ofCropped.allocate(CROPPED_WIDTH, CROPPED_HEIGHT, OF_IMAGE_COLOR); 
ofCropped.setFromPixels(cvCropped.ptr(), cvCropped.cols, cvCropped.rows, OF_IMAGE_COLOR, false);

cv 側でROIを決めてクロップした画像の生データ、のポインタを教えて sestFromPixels 関数で ofImage を作ることが出来ます。 四つ目の引数を false とすることで、BGR配列のデータでも直接 ofImage にRGB配列で突っ込むことが出来ます。

(Kinect) ColorFrame -> cv::Mat

// unsigned char* bufForColorFromKinect;
int nBufferSize = KINECT_CAM_WIDTH * KINECT_CAM_HEIGHT * 4;
bufForColorFromKinect = new unsigned char[nBufferSize];
hr = colorFrame->CopyConvertedFrameDataToArray(nBufferSize, bufForColorFromKinect, ColorImageFormat_Bgra);
cv::Mat cvColorFromK = cv::Mat(KINECT_CAM_HEIGHT, KINECT_CAM_WIDTH, CV_8UC4, bufForColorFromKinect);

Kinect画像の生データをバッファにいったんコピーして、それを使って先ほどと同様に cv::Mat() を作成します。 CopyCOnvertedFrameDataToArray() 関数で、ターゲットとなるフォーマットを指定するのがポイントです。cv::Mat のアルファ付きカラー画像は BGRAが標準です。

(Kinect) ColorFrame -> ofImage

// ofImage ofColorFromK;
ofColorFromK.allocate(KINECT_CAM_WIDTH, KINECT_CAM_HEIGHT, OF_IMAGE_COLOR_ALPHA);
int nBufferSize = KINECT_CAM_WIDTH * KINECT_CAM_HEIGHT * 4;
unsigned char* data = ofColorFromK.getPixels();
hr = colorFrame->CopyConvertedFrameDataToArray(nBufferSize, data, ColorImageFormat_Rgba);
ofColorFromK.update();

先ほどと同様に ColorFrame からデータを変換しつつバッファにコピーしますが、今回は ofImage ofColorFromK を事前に確保しているので、書き込む先の実データのポインタを直接指定できます。また、ofImage では色のフォーマットは RGBA になります。

最後の update() を忘れずに。

cv::Mat、ofImageから(Kinect)ColorFrameに戻す

Kinectの画像形式に戻すことは無い気がするので、ここでは割愛いたします。

さいごに

グレーの画像だったり、Depthなどビット深度の異なる画像も、CVの方で CV_8UC3 等と指定している画像のデータ形式を合わせることで対応出来ると思います。

少々面倒なところもありますが、ofxCv を使う場合より現時点では汎用性が高いと思います。活用していきます。