蓬莱の備忘録

VRとか画像処理とか

Raspberry Piのセットアップ

はじめに

 今回は完全に趣味のためです.最近はTRPGにはまってて,「どどんとふ」っていうオンセツールを使ったりするんですけど,自分で用意したサーバーに導入してみたいな~って思ってたんですよ.
 そしたら,ラズパイでもどどんとふを稼働させることができるらしいので,頑張って導入してみたいと思います.

OSをインストールするまで

Raspberry Piと周辺機器の準備

 Raspberry Pi3 Model Bでは,仕様として無線LAN,Bluetooth4.1を搭載しています.そのため,Wi-Fiや周辺機器への接続は無線で容易にやり取りできます.

  • Raspberry Pi3 Model B(本体)
  • microSDカード(4GB以上)
  • microUSBカード
  • USB充電器
  • LANケーブル(もしくはWi-Fiルーター)
  • HDMIケーブル
  • モニター
  • キーボード

OSイメージをダウンロード

 Raspberry PiのOSイメージをダウンロードします.
 Raspberry PiのOSは非公式のものも数えると,現時点(2019/03)で13種類のOSが公開されています.今回は公式が用意している基本OSであるRaspbianをインストールしたいと思います.
www.raspberrypi.org
 ダウンロードページにアクセスして,イメージをダウンロードしました.
f:id:yuyusama_myon:20190306014336p:plain

SDカードにOSデータを配置

 SDカードのフォーマット要領やRaspberry Pi起動ディスクの作成要領については,非常にわかりやすく記事にされている方々がいらっしゃるので,そちらの記事を参照してください.
Raspberry Pi 3(RASPBIAN JESSIE)OSインストールから初期設定【セットアップ前編 】 - 環境構築メモ
Raspberry Pi SDカードに関するノウハウ一覧 | アラコキからの Raspberry Pi 電子工作

OSの起動と初期設定

 SDカードをRaspberry Piに差し込みます.microUSBケーブルで電源を供給すると,そのまま起動します.初回起動時には初期設定のウィザードが出てくるので,指示に従って設定を行います.
 まず,【Set Country】で言語設定をします.

Country: Japan
Language: Japanese
Timezone: Tokyo

次に,【Change Password】パスワードの設定をします.

Enter new password: xxxxxx
Confirm new password: xxxxxx

 また,必要があれば【Select WiFi Network】で使用するWi-Fiの設定もします.
 その後,【Check For Updates】でNextを選択します.“sudo apt-get update”や“sudo apt-get upgrade”をしていたようなことを,こちらでアップデートできるようです.
 セットアップの詳細ついては,以下の記事を見るとわかりやすく解説されていました.
最近のRaspberry Piイメージ(Raspbian)をインストールするメモ – 1ft-seabass.jp.MEMO

piユーザ名の変更

各アカウントにパスワードを設定

 LXTerminalを開き,「root」「pi」にそれぞれパスワードを設定する.
 特に,rootは権限が非常に強いため,複雑かつ文字数の長いパスワードを設定すること.

自動ログインを解除する

 [設定]→[Raspberry Piの設定]を選択して,[システム]タブを開く.そのうち,[自動ログイン]の「現在のユーザとしてログインする」のチェックを外します.
 そうすることで,再起動時にpi以外のアカウントを選択できるようになります.

rootでログインする

 その他を選択して,ユーザ名に「root」を選択して,自身が設定したパスワードを入力します.
 また,rootでのログイン時には,LXTerminalでの命令にsudoを使用する必要はありません.

ユーザ名を変更する

 以下のコマンドでpiユーザ名を変更します(例:newPi)

usermod -l newPi pi

 homeフォルダ直下にあるpiユーザのディレクトリを自身が付けた名前(ここではnewPi)に移します.

usermod -d /home/newPi -m newPi
groupmod -n newPi pi

自動ログインユーザの変更

 自動ログイン関連のファイルを,nanoエディタを用いて直接変更します.
 編集するのは,/etc/systemd/system/autologin@.serviceというファイルです.

nano /etc/systemd/system/autologin@.service

 変更が終わったら,ラズパイを再起動します.

reboot

 piユーザ名の変更については,以下の記事が参考になりました.RaspberryPiでpiユーザー名をSSHなしで変更する - Qiita

おわりに

 ラズパイ用に毎回ディスプレイを繋いだりするのが面倒だったりするので,メイン機であるWindows PCから操作できるようにしたい...
 次回は,その辺りを含めたインストールすべきパッケージについて纏めます.

【Direct2D】メモリ上からBitmapへとコピーする

はじめに

カメラでキャプチャしたイメージに何らかの画像処理を施して,その結果をリアルタイムでウィンドウに描画するプログラムを作りたいと思います.
基本的に扱うのは2D画像のみなので,Direct3Dと相互運用性のある高速かつ高精細な2Dグラフィックスを提供するAPIとしてDirect2Dを使用します.

大まかな流れとしては,
1. ID2D1RenderTarget::CreateBitmap() メソッドでID2D1Bitmapを作成する.
2. ID2D1HwndRenderTarget::BeginDraw() メソッドでレンダーターゲットの描画操作を開始.
3. カメラでキャプチャしたイメージ(もしくは画像処理を施したイメージ)を作成したID2D1Bitmapに反映させる.
4. ID2D1HwndRenderTarget::EndDraw() メソッドでレンダーターゲットの描画操作を終了.
5. 以後,2~4までを繰り返す.
となります.

しかしながら,毎フレーム更新の度にID2D1RenderTarget::CreateBitmapを行い,画像データを流し込むのは(要求するフレームレートや解像度にもよるが)大幅なパフォーマンスの低下を招きます.基本的にリアルタイムグラフィックスの分野においては,フレームごとのリソース再確保は避けるべきとのことらしいです.
そのため,事前に作成しておいたID2D1Bitmapを再利用し,同一の幅と高さを持つ画像データをCopyFromMemory メソッドを用いてメモリ上でやり取りを行いたいと思います.

開発環境

OS Windows10
CPU Intel Core i5-6600K
GPU GeForce 750 Ti

CopyFromMemoryの使い方

Microsoft社のドキュメントを見てみると,以下のような構文となっています.
ID2D1Bitmap::CopyFromMemory メソッド (Windows)
第2パラメータでコピーするデータを指定,第3パラメータでコピーするデータのストライド(ピッチ)を指定します.

virtual HRESULT CopyFromMemory(
  [in, optional]  const D2D1_RECT_U *dstRect,
  [in]            const void *srcData,
                  UINT32 pitch
) = 0;

dstRectはnullptrを指定,pitchについては”ピクセル幅×ピクセル当たりのバイト数+メモリパディング”という数式を使用して計算できます.
しかし,肝心のsrcDataについてはどのようなデータを用意すればいいのか,ドキュメントの記述では理解できませんでしたが,同じくMicrosoft社のフォーラムに同様の疑問と対処が記載されていました.
How to use ID2D1Bitmap::CopyFromMemory
これによると,事前にID2D1BitmapをID2D1RenderTarget::CreateBitmap() メソッドで作成する際に,指定するパラメータの一つにpixelFormatがあります.
DXGI_FORMAT_B8G8R8A8_UNORMやDXGI_FORMAT_R8G8B8A8_UNORMのように,DXGI_FORMATでピクセル形式を指定します.srcDataの画像データも,この形式に準じるようです.

m_pRenderTarget->CreateBitmap(
         D2D1::SizeU(width, height), 
         D2D1::BitmapProperties(D2D1::PixelFormat(DXGI_FORMAT_B8G8R8A8_UNORM, D2D1_ALPHA_MODE_IGNORE)), 
         &pBitmap);

サンプルコードのようにDXGI_FORMAT_B8G8R8A8_UNORMで指定した場合,データ配列は32bitの符号なし整数の配列となります.青色成分が最初の8bit,緑色成分が次の8bit,赤色成分がその次の8bit,アルファが最後の8bitです.

OpenCVのcv::Mat型画像データをメモリ上に配置

というわけで,画像処理ライブラリのOpenCVを扱うことが多いので,cv::Mat型からID2D1Bitmapへのコピーを行っていきます.
サンプルコードについては適宜置き換えて下さい.

cv::Mat image;  // カメラからキャプチャしたイメージを格納
byte* memory = new byte[image.rows*image.cols*4];  // CopyFromMemory() メソッドの第2パラメータで指定
・・・・・
for (int row = 0; row < image.rows; row++) {
	cv::Vec3b *src = image.ptr<cv::Vec3b>(row);
	for (int col = 0; col < image.cols; col++) {
		cv::Vec3b bgr = src[col];
		int pointBGR = col * 4 + row * image.rows * 4;
		memory[pointBGR + 0] = bgr[0];
		memory[pointBGR + 1] = bgr[1];
		memory[pointBGR + 2] = bgr[2];
	}
}
・・・・・
/* メモリ上のピクセルデータをBitmapにコピー */
pBitmap->CopyFromMemory(nullptr, memory, image.rows*image.cols*4);
/* Bitmapの描画 */
m_pRenderTarget->DrawBitmap(pBitmap, //the bitmap to draw [a portion of],
	D2D1::RectF(0.0f, 0.0f, image.rows, image.cols), //destination rectangle,
	1.0f, //alpha blending multiplier,
	D2D1_BITMAP_INTERPOLATION_MODE_NEAREST_NEIGHBOR, //interpolation mode,
	D2D1::RectF(0.0f, 0.0f, image.rows, image.cols)); //source rectangle

おわりに

当初の目的は達成できましたが,如何せんDirect2Dの理解が足りていない...
次は,Webカメラでキャプチャしたイメージと,OpenCVで画像処理を施したイメージを同時に表示するサンプルプログラムを試作してみたいと思います.ソースコードについては,載せる予定ではあります.

【Direct2D】規定値でパラメータを初期化したら垂直同期に合わせられて困った話

はじめに

OpenCVで画像処理をした結果をDirect2Dで描画しようとプログラムを作ってみたら予想以上にフレームレートが落ちていたので,それについて記事にしてみました.

実装と計測

HWNDを対象とするレンダーターゲットであるID2D1HwndRenderTargetインターフェースに関しては,以下の通りに生成しました.

D2D1_SIZE_U PixelSize = { rect.Width(), rect.Height() };
D2D1_RENDER_TARGET_PROPERTIES RenderTargetProperties = D2D1::RenderTargetProperties();
D2D1_HWND_RENDER_TARGET_PROPERTIES HwndRenderTargetProperties = D2D1::HwndRenderTargetProperties(m_hWindow, PixelSize);
HRESULT hr= m_pD2d1Factory->CreateHwndRenderTarget(
		RenderTargetProperties,
		HwndRenderTargetProperties,
		&m_pRenderTarget);

また,今回は処理が無事に実行されることを確認するだけだったので,ウィンドウの背景をクリアするのみとなっています.

/* 開始 */
m_pRenderTarget->BeginDraw();
/* 背景のクリア */
m_pRenderTarget->Clear(D2D1::ColorF(D2D1::ColorF::Blue));
/* 終了 */
m_pRenderTarget->EndDraw();

処理自体は非常にシンプルであり,レンダリングの開始・背景のクリア・終了しかしていません.
しかし,このコードで処理を実行した結果,約60fpsしか出ませんでした...

フレームレートが重くなった原因

ID2D1HwndRenderTarget インターフェイスインスタンスを生成する上で,CreateHwndRenderTarget() 関数を使っていました.
このメソッドの第1・第2パラメータには,それぞれ適切に初期化されたD2D1_RENDER_TARGET_PROPERTIES 構造体,D2D1_HWND_RENDER_TARGET_PROPERTIES 構造体が必要となります.
しかし,先のプログラムでは初期化を簡単にするため,以下の関数を使用していました.

RenderTargetProperties(
        D2D1_RENDER_TARGET_TYPE type =  D2D1_RENDER_TARGET_TYPE_DEFAULT,
        _In_ CONST D2D1_PIXEL_FORMAT &pixelFormat = D2D1::PixelFormat(),
        FLOAT dpiX = 0.0,
        FLOAT dpiY = 0.0,
        D2D1_RENDER_TARGET_USAGE usage = D2D1_RENDER_TARGET_USAGE_NONE,
        D2D1_FEATURE_LEVEL  minLevel = D2D1_FEATURE_LEVEL_DEFAULT
        )
HwndRenderTargetProperties(
        _In_ HWND hwnd,
        _In_ D2D1_SIZE_U pixelSize = D2D1::Size(static_cast<UINT32>(0), static_cast<UINT32>(0)),
        _In_ D2D1_PRESENT_OPTIONS presentOptions = D2D1_PRESENT_OPTIONS_NONE
        )

このうち,HwndRenderTargetProperties() 関数の第3パラメータに指定するD2D1_PRESENT_OPTIONSがフレームレートを下げていた原因でした.このパラメータは規定値だと,レンダリングをディスプレイのリフレッシュレートに合わせる(垂直同期)ようです.
...うん,綺麗に60fpsなんて数字になったのでなんかおかしいとは思ったんですけど,パラメータの指定をさぼった自分のミスでしたね(-_-;)
このパラメータをD2D1_PRESENT_OPTIONS_IMMEDIATELY メンバに設定すれば垂直同期をしなくなるようなので,処理の実行速度なんかを計測したい場合はこのパラメータを設定するといいでしょう.

おわりに

元々は,OpenCVで画像処理をした結果をリアルタイムで表示するプログラムを作っていたんですが,OpenCVの機能でウィンドウに表示しようとしたらフレームレートがかなり落ちてしまったので,描画部分の処理をDirectXで実装しようとしていました.
その辺りについては,今作っているプログラムがひと段落したらまた記事にしたいと思います.

Unityで始めるOculus GOアプリ開発環境の構築手順(Windows)

はじめに

本ページは,筆者がOculus GOの開発環境を整える際に調べたりしたメモとなっています.

開発環境

  • Unity 2018.1.6f1
  • Oculus GO 32 bit
  • WIndows 10 10.0.17134

Oculus GOのセットアップ

developer登録

まずは,Oculus GOのdeveloper登録を行います.
以下のリンクから新しい団体を任意の名称で登録を行ってください.
Oculus開発者ダッシュボード

Oculus GOを開発者モードにする

既にOculus GOアプリを購入したりで遊んだ方は,自身のモバイル端末にOculus GOアプリが入っていると思います.そのアプリから,以下のフローで開発者モードをONに切り替えます.

設定 > Oculus GO端末情報メニュー > その他の設定 > 開発者モード > ON

これで,Oculus GO側の設定はひとまずは終了です.

Unityのセットアップ

次に,Unity側のセットアップをしていきます.

Unityで3Dプロジェクトを作成

まずは,Unityで3Dのプロジェクトを新規に作成します.プロジェクト名や保存先はなんでもいいです.
f:id:yuyusama_myon:20180803135041p:plain

File > Build Settingsを開きます.
PlatformでAndroidを選択し,Switch Platformボタンを押して確定させます.
f:id:yuyusama_myon:20180803140642p:plain

この時,Consoleビューにエラーが出ていないかを確認します.
Oculus GOのOSはAndroid (Version 7.1.2)ベースで動作しているため,Androdアプリ開発と同様にJDK (Java Development Kit)とAndroid SDKのインストールが必要となります.そのため,JDKSDKのパスが設定されていない,あるいはインストールがされていない場合は,以下のようなエラーが表示されます.

f:id:yuyusama_myon:20180803140645p:plain
詳しくは,こちらを参照してエラーハンドリングしてください.
          
Unity - Manual: Android environment setup

Oculus Go用のAndroid開発環境 (JDK/Android SDK)のパス設定

Unity側で,
Edit > Preferences.. > External Tools > Android
にあるSDKJDKのパスを設定します.とはいえ,インストール終了後にUnityを再起動すると,それらのパスが自動で設定されるようです.

XR Settings

次は,Oculus GO用のVRアプリ開発が行えるように設定をします.
Edit > Project Settings > Player > XR Settings
にあるVirtual Reality Supportedにチェックを入れ,Virtual Reality SDKsよりOculusを選択します.
f:id:yuyusama_myon:20180803161723p:plain

デフォルトのままBuildをするとおそらく,以下のエラーが出ると思います.

Oculus Requires a Minimum API Level of 19.
You have selected 16

API LebelはAndroidのバージョンごとに提供されているAPIを識別するためのものとのことでした.
https://developer.android.com/guide/topics/manifest/uses-sdk-element?hl=ja

先にも述べたように,Oculus GOのOSはAndroid (Version 7.1.2)ベースで動作しています.そのため,エラーによって指示されたAPI Lebelである19~25であれば,問題なくBuildに成功すると思います(因みに,筆者はAPI Lebelを19, 25のどちらにしてもBuildに成功しました).

Bundle Identifierの設定

同じく,"Other Setting"内にあるBundle Identifierにはデフォルトで,

com.Company.ProductName

と記述されていると思います.このままでは,Buildの際にエラーが出てしまうので,任意の独自ドメインに書き換えてください.
(アプリをBuildするだけならば,デフォルト値を変更するだけでエラーを回避することができますが,作成したアプリを公開などをする際には注意が必要です.基本的に,Bundle Identifierは(iOSではiPhoneBundleIdentifier)は独自のものである必要があるためです)

Build and Runの実行

最後に,UnityでBuild and Runを実行して,Oculus GOを装着すると,作成したアプリケーションが無事に動作していると思います.
f:id:yuyusama_myon:20180803164425p:plain

おわりに

以上で,ひとまずはOculus GOの開発環境を構築することができました.
個人的には,床?の縁のジャギーが結構目立つと感じました.この辺りはVR空間の作り方次第なのでしょうか...?


次回は,Oculus GOのコントローラなどを試してみたいと思います.