見出し画像

[FemtoMega] openFrameworksでネットワーク接続してみる

内容

2023年に主に深度情報によるポイントクラウドの活用で実用的であったToFカメラAzure Kinectが生産停止になりました。
その後継機にあたるORBBEC社のFemtoMegaについてあまり記事がないので触ってみた内容をまとめます。
特にLANでの制御ができる点が、特徴的なのでそこを中心にまとめました。

基本的には以下の内容を補足してまとめていく感じになります。

使用したもの

PC:Windows11
PC:Ubuntu
ソフト:openFrameworks(v.0.12.0), TouchDesigner(v.2023)

FemtoMega

Azure Kinectとの違い

FemtoMegaは扱える情報はAzure Kinectとほぼ同じようです。
内部にJetson Nanoを搭載しているため、USB-CでもLANでも制御ができるのが大きな違いかと思います。

ORBBEC製品には他にも処理を外部化しているToFカメラのFemto BoltやLiDER製品などがあったので機会があれば、この辺も触って見れたらいいなと思います。


接続

基本的に公式のドキュメントに従って進めていきます。
https://www.orbbec.com/femto-mega-document/

PCドライバーのインストール

まずは以下よりドライバーをインストールしますhttps://www.orbbec.com/developers/orbbec-sdk/ 

USB-C接続

今回はLANでの接続メインですが、より簡単だったのでまずはUSB-Cの接続についてまとめます。

k4aviewerを使用
SDK内のソフトで接続のチェックが可能です。まんまAzureKinectのビューワーです。
https://github.com/orbbec/OrbbecSDK-K4A-Wrapper/releases

TouchDesigner
TouchDesignerを最新版に更新する必要があります。 
Azure Kinectのノードを使用して認識可能です
ボディートラッキングに関してはそのままでは使用できずでした

LAN接続

今回メインのLANでの接続です。
PoEでの給電と制御ができるようです。今回PoEは試していません。
デフォルトで本体は192.168.1.10にIPがふられています。
PC側のIPを適当に以下のように設定してください。
ターミナルでping 192.168.1.10を飛ばすと応答がとれます。

IP:192.168.1.100
サブネットマスク:255.255.255.0
ルーター:192.168.1.1

orbbecviwer
https://github.com/orbbec/OrbbecSDK/releases
SDK内のソフトで接続のチェックが可能です。

openFrameworks
ネットワーク接続用のSDKがC++のライブラリなのでopenFrameworksで接続してみます。

ファームウェアの更新
この後、SDKを実行した際にSDKのバージョンとファームウエアのバージョンが異なるとPC側が接続できませんでした。
そのためファームウェアの更新を行う必要があります。
このファームウェアの更新にIlnuxPCが必要になります。今回はUbuntuのPCでファームウェアのアップデートを行いましたがIlnuxPCが必要になるのがなかなか面倒だなと思いました。ラズパイでもできそうですが、更新時の時間的にラズパイだと30分くらいかかりそうだなと思いました。

ファームウェアのアップデートについては公式のここに書いてあります。
ファームウェアはここ

ざっくりした手順

  • 先のorbbecviwerを管理者権限で実行(管理者で実行しない場合エラーが出ます)

  • 本体の上部の穴を細めのピン(クリップなど)で押しながら電源とUSB-a-microを接続

  • 画面上で認識されたらファームウェアのファイルFemtoMega_20240103153450_v1.2.8_2b5db3b-489cbcd.tbz2を指定して書き込み(書き込み後に自動で再起動されます)

  • orbbecviwerで書き込み後のバージョン確認
    (記事の時点ではv.1.2.8でした)

SDKの導入

VisualStudioで開発します
必要になるのは以下のSDKになります(今回はビルド済みのものを使用します)
OrbbecSDK_K4A_Wrapper_v1.9.3_arm_64_release_2024_03_22.tar.gz

ダウンロードしたファイルフォルダを展開し、名前を適当なもの、今回はOrbbecSDK_K4A_Wrapper_v1.9.3に変更します。

openFrameworksでprojectGeneraterから新規プロジェクトを作成してください。今回はfemtomegaTestで作成しました。

インクルードファイル(k4a.hファイル)のパスの指定
VisualStudio内の画面左、ソリューションファイル(今回はfemtomegaTest)を右クリックからプロパティを開き、c++以下の全般の設定項目を開きます。
追加のインクルードファイルの欄を編集します。

%(SolutionDir)addons/OrbbecSDK_K4A_Warraper_v1.9.3/include

を追記してください。

静的ライブラリ(k4a.libファイル)のパスの指定
VisualStudio内の画面左、ソリューションファイル(今回はfemtomegaTest)を右クリックからプロパティを開き、リンカー以下の全般の設定項目を開きます。
追加のライブラリの欄を編集します。

%(SolutionDir)addons/OrbbecSDK_K4A_Warraper_v1.9.3/lib

を追記してください。
さらにリンカー以下の入力の設定項目を開きます。

k4a.lib

を追記してください。

動的ライブラリ(k4a.dll, .ObbecSDK.dllファイル)の追加
k4a.dll, .ObbecSDK.dllをSDK内のbinフォルダからコピーして
プロジェクトファイル以下のbinフォルダ内にペーストで追加してください。

以上の設定でSDKをビルドして実行できるようになります
openFrameworksでコードを書いていきます。

#pragma once
#include "ofMain.h"

#pragma comment(lib, "k4a.lib")
#include <k4a/k4a.h>

class ofApp : public ofBaseApp{

	public:
		void setup();
		void update();
		void draw();
		void exit();

		k4a_device_t device;
		ofTexture colorTexture;
		ofTexture depthTexture;
     ofTexture setColorToTex(k4a_image_t img);
		ofTexture setDepthToTex(k4a_image_t img);
		
};
#include "ofApp.h"

//--------------------------------------------------------------
void ofApp::setup(){
	ofSetVerticalSync(false);

	uint32_t count = k4a_device_get_installed_count();
	ofLogNotice("device count") << count;

	if (count == 0)
	{
		ofLogNotice() << "No k4a devices attached!";
	}

	// Open the first plugged in Kinect device
	device = NULL;
	if (K4A_FAILED(k4a_device_open(K4A_DEVICE_DEFAULT, &device)))
	{
		ofLogNotice() << "Failed to open k4a device!";
	}

	// Get the size of the serial number
	size_t serial_size = 0;
	k4a_device_get_serialnum(device, NULL, &serial_size);
	// Allocate memory for the serial, then acquire it
	char* serial = (char*)(malloc(serial_size));
	k4a_device_get_serialnum(device, serial, &serial_size);
	ofLogNotice("Open device") << serial;
	free(serial);

	k4a_device_configuration_t config = K4A_DEVICE_CONFIG_INIT_DISABLE_ALL;
	config.depth_mode = K4A_DEPTH_MODE_NFOV_UNBINNED;
	config.camera_fps = K4A_FRAMES_PER_SECOND_30;
	config.color_format = K4A_IMAGE_FORMAT_COLOR_BGRA32;
	config.color_resolution = K4A_COLOR_RESOLUTION_1080P;
	k4a_device_start_cameras(device, &config);

}

//--------------------------------------------------------------
void ofApp::exit() {
	ofLogNotice() << "Close device";
	k4a_device_stop_cameras(device);
	k4a_device_close(device);
}

//--------------------------------------------------------------
void ofApp::update(){
	k4a_capture_t capture;
	//k4a_wait_result_t result = k4a_device_get_capture(device, &capture, K4A_WAIT_INFINITE);

	if (k4a_device_get_capture(device, &capture, K4A_WAIT_INFINITE) == K4A_WAIT_RESULT_SUCCEEDED){
		ofLogNotice() << "get result";
		// キャプチャからカラー画像を取得
		k4a_image_t color_image = k4a_capture_get_color_image(capture);
		// キャプチャから深度画像を取得
		k4a_image_t depth_image = k4a_capture_get_depth_image(capture);
		// キャプチャからIR画像を取得
		k4a_image_t ir_image = k4a_capture_get_ir_image(capture);

		// 画像を使用した処理(表示、保存など)
		if (color_image != NULL)
		{
			colorTexture = setColorToTex(color_image);
		}

		if (depth_image != NULL) {
			depthTexture = setDepthToTex(depth_image);
		}

		// 画像のメモリ解放
		k4a_image_release(color_image);
		k4a_image_release(depth_image);
		k4a_image_release(ir_image);
	}
	k4a_capture_release(capture);

}

//--------------------------------------------------------------
void ofApp::draw(){
	ofSetColor(ofColor::white);
	// カラー画像を表示
	int winW = ofGetWidth();
	int winH = ofGetHeight();
	colorTexture.draw(0, 0, winW/2, winH/2);
	depthTexture.draw(winW / 2, 0, winW / 2, winH / 2);

	int fps = ofGetFrameRate();
	ofDrawBitmapStringHighlight(ofToString(fps), 10, 20);
}

//--------------------------------------------------------------
ofTexture ofApp:: setColorToTex(k4a_image_t img) {
	int width = k4a_image_get_width_pixels(img);
	int height = k4a_image_get_height_pixels(img);
	//int stride = k4a_image_get_stride_bytes(img);
	uint8_t* buffer = k4a_image_get_buffer(img);
	ofTexture tex;
	if (buffer == nullptr) {
		ofLogError("buffer") << "Image buffer is null!";
		return tex;
	}
	ofPixels pixels;
	pixels.setFromExternalPixels(buffer, width, height, OF_PIXELS_BGRA);  // ofPixelsにBGRAフォーマットでデータをセット
	ofLogNotice("BGR pixel size") << pixels.getWidth();
	pixels.swapRgb();  // BGRAからRGBAへスワップ(ofPixelsは内部でRGBとBGRの変換をswapRgbでサポート)
	tex.allocate(pixels);
	return tex;
}

ofTexture ofApp::setDepthToTex(k4a_image_t img) {
	int width = k4a_image_get_width_pixels(img);
	int height = k4a_image_get_height_pixels(img);
	//int stride = k4a_image_get_stride_bytes(img);
	uint8_t* buffer = k4a_image_get_buffer(img);
	ofTexture tex;

	if (buffer == nullptr) {
		ofLogError("buffer") << "Image buffer is null!";
		return tex;
	}
	ofShortPixels depthPixels;
	depthPixels.setFromExternalPixels((unsigned short*)k4a_image_get_buffer(img), width, height, 1);

	// 深度画像を正規化して描画可能な形式に変換
	unsigned char* depthPix = new unsigned char[width * height];
	for (int i = 0; i < width * height; i++) {
		depthPix[i] = ofMap(depthPixels[i], 500, 4500, 0, 255, true);  // 適切な値に調整
	}
	
	tex.loadData(depthPix, width, height, GL_LUMINANCE);
	delete[] depthPix;
	return tex;
}

とりあえず適当に画像表示まで簡単にやりました。
orbbecviwerに比べて遅延が大きいので書き方を考える必要がありそうです。


まとめ

LANの接続は一本で長距離接続できるので便利ですが、接続の簡単さを考えるとUSB-cで接続できる環境にあるならそっちの方が使い勝手良さそうだなと思いました。
とはいえ最近はToFカメラつかっているシーンにあまり遭遇しなくなったので今後どれほど活用されていくのかわかりません。
今回は以上になります。

この記事が気に入ったらサポートをしてみませんか?