CrossRoad

XRを中心とした技術ブログ。 Check also "English" category.

【Unity】任意のパスにARCore Point Cloudの値をcsvで書き出す

ARCoreのPoint Cloudを調べています。UnityでAndroidアプリのデータ書き出し処理を作るのに意外に手間取ったので、まずはPoint Cloudデータのファイル 書き出しの手順をまとめました。

ARCoreを例に書いていますが、UnityでAndroidスマートフォンの任意の場所にファイル 書き出しをしたい、という場合にも使えると思います。

以下の環境で動作確認しています。

  • Unity2017.4.4f1
  • Android8.1.0
  • jdk1.8.0_171.jdk




1. 今回の検討をした背景

今回、PointCloudのデータを取り出して調べるのに、データをcsvで書き出してPC経由で取り出したいと思いました。アプリ領域内だとcom/limes/appname/<任意のデータ>、のように保存できますが、階層が深くなります。

そこで、浅い階層でも取り出せることを優先し、PC接続時にすぐ取り出せるように、アプリ領域外で保存できる仕組みを整理しました。

なお、ここでの「アプリ領域外」とは、「Download」、「DCIM」など、PCとAndroidを接続した時、エクスプローラ上ですぐ見えるフォルダを指します。


2. 書き出しの手順

2-1. 保存先のパスを取得する

「Unity ファイル パス」のように調べると、Application.dataPath、Application.persistentDataPathなどを使う方法が出てきます。しかし、これらは今回のようなアプリ領域外書き出しには使えません。

一方、Androidのnativeコードには、書き出し先のパスを取得するメソッド「getExternalStorageDirectory」があります。少し前の記事ですが、こちらがわかりやすく、参考になりました。

Android におけるストレージまとめ - Unmotivated

(注)
Android8.1では、getExternalStorageDirectoryメソッドで取得したパスは、「/storage/sdcard0」ではなく、「/storage/sdcard/0」でした。

パスの取得方法を作ろうと思ったら、シンプルに実現する方法が書かれているサイトがありました。
Unityで画像をAndroid本体に保存する | Narumium Blog

こちらを使わせていただいて、パスを取得するコードを書いたのが以下です。

gist2a7003d67c7b9d973291d74275cc32bf

ここに記載のAndroidJavaClassは、UnityからAndroid nativeの関数を呼ぶための方法です。だいぶ前になりますが、下記の記事にUnityでAndroidのnative関数を使う方法を色々とまとめたので、よかったらご参考ください。

Android StudioでUnity向けmoduleを作る方法 - CrossRoad

Android Studioで作ったmoduleをUnityから呼ぶ方法 - CrossRoad

【2017/7更新】Android StudioでUnity向けmodule開発時のTips - CrossRoad

【2016/11/27 更新】UnityでBing Speech to Text API(Android用)を使う方法(Android Studio側の構築手順) - CrossRoad

【2017/3/1更新】UnityでBing Speech to Text API(Android用)を使う方法(Unity側の構築手順) - CrossRoad


2-2. 取得するデータを決める

ARCoreのUnitySDKには、PointCloudVisualizer.csというソースがあります。その一部を使って、point cloudのindexとx,y,z座標を取得します。


gist532fe9f658eba341d45a6a88ac34d9cd

実際は、下記の2つを使います。

private int[] indices = new int[k_MaxPointCount];
private Vector3[] m_Points = new Vector3[k_MaxPointCount];




2-3. ファイル に書き出す

C#にはFileというクラスがあります。しかし、このような記述があるため、今回のように随時書き込む可能性がある場合は、C#のStreamWriterを使う必要があります。

Fileクラスのメソッドは言わばユーティリティメソッドで、ファイル内容すべてを読み書きするというごく基本的な読み込み・書き込み操作しか行えません。 そのため、ファイルの一部分のみを読み込んだり、データ構造やフォーマットが定められたファイルを扱う目的には不向きです。

引用:ファイル 入出力, smdn:総武ソフトウェア推進所, http://smdn.jp/programming/netfx/filesystem/2_filereadwrite/

今回は、先ほど選んだ、indicesとm_Pointsを書き込みます。書き込むための処理は下記の通りです。


gist5aa5c451857e9024b3cabdef13297fbb


ソースコードの通りですが、書き込み場所のフルパスを指定して、StreamWriterクラスのインスタンスを作成し、StreamWriter.WriteLineで1行ずつ書きこむだけです。

sw.Flush();
sw.Close();

を使って最後にクローズ処理をしています。下記の記事より、closeを入れないとうまくいかないようです。

https://qiita.com/HisatakaSuzuki/items/a97f4ebdd4b89afe3dc4


2-4. ファイル 書き出し用のソースコードをPoint Cloudオブジェクトにアタッチする

ARCoreのUnity SDKをインポートし、Assets/GoogleARCore/Example/HelloAR/HellowAR.sceneを開きます。

Hierarchy ViewにあるPoint CloudオブジェクトにあるPoint Cloud Visualizerコンポーネントを削除し、下記のSavePointCloudDataをアタッチします。


gistcfd8aa376a79cbdca3a935c1a375580a

途中のendLoopCountですが、この処理を入れないと、point cloudの数がいくつであっても、indices.Length = k_MaxPointCount = 61440 行の値が書き込まれます。

実際、表面検出に数分かけた程度ではpoint cloudは200個程度でしたので、毎回61440行書き込むのは無駄になります。

そこで、全ての要素が0であることが2回続いたら書き込みを終了させました。

あとは、uGUIのボタンなどで下記を呼び出します。

public void savePointCloudInfo()

すると、

/storage/sdcard/0/pointcloud/yyyyMMddHHmmss.csvというファイル が生成され、point cloudデータが書き込まれます。



2-5. ビルド設定、ビルド

PlayerSettingsを以下のように修正します。

Company Name、Product Name :
任意の名前に変更

Other Settings
Multi Thread Rendering :チェックを外す
Package Name : com.<開発者名>.<アプリ名> のようにする。"."で区切って入れば任意の文字列でよい。たとえば、com.Limes.SampleApp
 Minimum API Level:Android 7.0以上
Target API Level:Android 7.0以上で、ビルド対象のスマートフォンのバージョンに合わせる
 
XR Settings
AR Core Supported :チェックをつける

あとは、BuildまたはBuild and Runを実行します。(Build and Runの場合、Buildと実機デプロイを実行します)

3. 動作確認結果

実行すると、このようにindicesとm_Points(x,y,z)の値を取得できます。

PointCloudのデータ取得結果の例


4. 終わりに

今回はARCore特有の話が少なかったので、次回はPoint Cloudデータをもう少し細かく見てみようと思います。