クワマイでもできる

クワマイでもわかる

回転行列の右手系左手系変換

f:id:kuwamai:20210130052241p:plain ROSの座標系は右手系、Unityの座標系は左手系と異なるためそれぞれ連携させるためには右手系左手系の変換が必要だった。要は対応する方向の要素を入れ替えればいいわけだけど、調べてもあまり出てこないのでメモ。

座標系について

f:id:kuwamai:20210131230801p:plain それぞれの座標系はこんな感じ。歴史的経緯で異なる座標系を使っている。ROSはx-y平面上を移動するロボットを基準に考えてるし、Unityはx-y平面上のカメラ画像を基準に考えてる。

右手(ROS)→左手系(Unity)変換

右手系であるROSの(x, y, z)は、左手系であるUnityの(-y, z, x)に対応している。一気に入れ替えるとややこしいので、行、列の2段階に分けて入れ替えてみる。


\begin{bmatrix} a & b & c \\ d & e & f \\ g & h & i \end{bmatrix}
\rightarrow
\begin{bmatrix} -b & c & a \\ -e & f & d \\ -h & i & g \end{bmatrix}
\rightarrow
\begin{bmatrix} e & -f & -d \\ -h & i & g \\ -b & c & a \end{bmatrix}

左手(Unity)→右手系(ROS)変換

左手系であるUnityの(x, y, z)は、右手系であるROSの(z, -x, y)に対応している。一気に入れ替えるとややこしいので、行、列の2段階に分けて入れ替えてみる。


\begin{bmatrix} a & b & c \\ d & e & f \\ g & h & i \end{bmatrix}
\rightarrow
\begin{bmatrix} c & -a & b \\ f & -d & e \\ i & -g & h \end{bmatrix}
\rightarrow
\begin{bmatrix} i & -g & h \\ -c & a & -b \\ f & -d & e \end{bmatrix}

参考

気づいたらお友達がほぼ同じ記事を書いてたのだけど僕も書いちゃった。こちらはクォータニオン

思うところ

本当は回転行列の各要素について説明して、回転行列の数値を見ただけで姿勢が想像できるから便利だよって話をしたかったんだけどそれはまた今度ね。

ソーシャルVRに点群を持ち込みたい[2] モバイル対応編

f:id:kuwamai:20201121174738p:plain

VRChatやCluster、Stylyなど、VRやARの空間を簡単に共有できるツールが増えてきた。多くはスクリプトが使用できない制限があるため、点群を表示するにはshaderで頑張る必要がある。最近ではOculus Questやスマホなど、使用できるデバイスが多様になっているのでこれらにも対応したい。今回のお話はPhi16さんからいただいた点群表示shader、PointCloud shaderの紹介とモバイル対応させた記録。

今回はコードを書いた思い出話。肝心の点群を持ち込む方法は前回の記事を見て。

元になったもの

Phi16さんからshaderをいただいた経緯はこちらの記事。

似たような仕組みのFuwaParticleについてPhi16さん自身が解説されている記事があります。

つくったもの

作ったshaderは全部公開している。これで君も点群ワールドをアップしよう。

  • 点群表示用shader

  • 恒星表示用shader

制作の記録

点群をテクスチャに書き込む

点群は点の位置と色がセットになったデータなので、それぞれのデータをテクスチャに書き込む。これは元々Phi16さんからいただいたshaderの方式と同じ。すでにいくつかの作品がこのフォーマットを用いてたので互換性を保つようにした。

色はテクスチャの1ピクセルが点群の1点に対応しているので(r, g, b)がそのまま書き込まれている。同様に点の位置(x, y, z)もテクスチャの色の(r, g, b)に対応して書き込まれているけど、色と比べて扱う数値が大きいので工夫が必要。今回のフォーマットでは単精度浮動小数点数*1を色に変換する処理がされている。単精度浮動小数点数は32 bit、色は8 bitで表現されているので32 bitを8 bitずつ切り出すとちょうど4ピクセルに書き込める。それっぽい図を下に書いてみた。VRChatのshader芸として、ワールドの状態保存にこの方式がたびたび使われてるみたい*2

f:id:kuwamai:20201205194157p:plain

コードだとこの部分。uvが4ピクセルのちょうど真ん中なので、半ピクセル分動いてそれぞれの値を取得してる感じ。

float3 unpack(float2 uv) {
    float texWidth = _Pos_TexelSize.z;
    float2 e = float2(-1.0/texWidth/2, 1.0/texWidth/2);
    uint3 v0 = uint3(DecodeHDR(tex2Dlod(_Pos, float4(uv + e.xy,0,0)), _Pos_HDR).xyz * 255.) << 0;
    uint3 v1 = uint3(DecodeHDR(tex2Dlod(_Pos, float4(uv + e.yy,0,0)), _Pos_HDR).xyz * 255.) << 8;
    uint3 v2 = uint3(DecodeHDR(tex2Dlod(_Pos, float4(uv + e.xx,0,0)), _Pos_HDR).xyz * 255.) << 16;
    uint3 v3 = uint3(DecodeHDR(tex2Dlod(_Pos, float4(uv + e.yx,0,0)), _Pos_HDR).xyz * 255.) << 24;
    uint3 v = v0 + v1 + v2 + v3;    return asfloat(v);
}

Geometry shaderが使えない環境について

Geometry shaderでパーティクル表現することを一部ではGPUパーティクルと呼ばれている。いただいたshaderもGeometry shaderを使っていたけど、大半のモバイル環境ではGeometry shaderが使えない。代わりにVertex shaderでパーティクル表現をすることにした。

Geometry shaderは1ポリゴンずつ処理できるので繋がったメッシュをバラバラにしたりできる*3けど、Vertex shaderは1頂点ずつ処理されるので下図左側みたいにポリゴンをたくさん並べたメッシュを作った。

f:id:kuwamai:20201210023902j:plain

この三角ポリゴン1つが点群の1点に対応する。z軸(図の青矢印)の位置で何番目のポリゴンか判別できるので、下記のコードでどのポリゴンがテクスチャのどのピクセル(uv)に対応するか計算できる。

float2 uv = float2((floor(v.vertex.z / texWidth) + 0.5) / texWidth, (fmod(v.vertex.z, texWidth) + 0.5) / texWidth);

また、y軸(緑矢印)の位置で三角形のどの頂点なのか判別できるので、下記のコードで点を表示する正三角形のどの頂点に対応するか計算できる。triVertに(ほぼ)正三角形の各頂点の位置を保存してるので、pを中心にそれぞれ頂点をずらして上図右側みたいに変形してる。xAxisyAxisは描画した点が常にカメラの方を向くようにポリゴンを回転してくれてる。

float3x2 triVert = float3x2(
    float2(0, 1),
    float2(-0.9, -0.5),
    float2(0.9, -0.5));

o.uv = triVert[round(v.vertex.y)];
offset = o.uv * sz;
o.vertex = UnityWorldToClipPos(worldPos + xAxis * offset.x + yAxis * offset.y);

その後のclipで三角形を丸くくり抜いてあげることで点を描画している。clipがないと三角形がそのまま描画されるし、lに応じて透明度を変更すれば円の端をぼかすようなこともできる。

float l = length(i.uv);
clip(0.5-l);
return float4(i.color,1);

iOS対応

上記の変更でQuest等のAndroid端末で表示ができるようになった。ただ下記TweetのようにiOS端末で表示が崩れてしまった。

この件に関しては勉強不足で詳しく説明できないけど、iOS端末はネイティブで浮動小数点テクスチャをサポートしておらず、DecodeHDR関数でデコードしてあげる必要があるとのこと。例えばこんな感じ。

DecodeHDR(tex2Dlod(_Pos, float4(uv + e.xy,0,0)), _Pos_HDR).xyz

デコードしてあげるとこんな感じでキレイに点が表示された。

えっじゃあTex2Dlod使っただけじゃ正しい色が取れてないの?と気になるところだけど全然情報がない。闇を感じた。

感想

これでPC、Oculus Quest、その他Android端末、iOS端末の主要なデバイスで点群表示ができるようになった。iPhone 12 Proが発売されてかなりお手軽に点群が撮れるようになってきたのでそのうち点群展でも開きたいな。

ソーシャルVRに点群を持ち込みたい

f:id:kuwamai:20201213200032p:plain VRChat、Cluster、StylyなどのソーシャルVRに点群を持ち込みたい。なぜなら点群がかわいいから。今回はスマホで点群を撮影、パソコンで編集、ソーシャルVR向けの素材に変換するとこまでご紹介。

本記事はBoze Advent Calendar 2020 - Qiitaに登録されています。VRとかVRに関わるものの記事が多いみたい。

動いている様子

渋谷の点群データをClusterに表示した例。とりあえず表示しただけでまだワールドとして体裁は整えてない。

点群ではないけどVRChatに恒星データを表示した例。PCVR、Oculus Quest両方に対応。同様のワールドをClusterにもアップし、こちらもPC、モバイル(スマホなど)両方に対応できた。

モバイル対応したのでStyly ARにもアップロードするだけで簡単にAR表示できた。

スマホで点群撮影

最近は点群が撮れるスマホが増えてきた。iPhone X以降ならTrue depthが使えるし、iPhone 12 ProならLiDARが使える。Androidは使ってないからわからない*1けど、iPhone向けアプリをいくつか紹介。

ちなみに撮影した点群はあとでCloudCompareに読み込んで編集するので、形式は問わず何かしらの点群データが取れればOK。スキャンしなくとも自分で点の位置と色を用意するのも楽しそう。例えば星とか*2

EveryPoint

EveryPoint

EveryPoint

  • URC Ventures Inc
  • ユーティリティ
  • 無料
今回はこのAppを使った。取得できる点群数が無制限みたいで描画負荷の限界まで撮れる。プレビュー機能が無いのが残念。今後追加されたらいいな。

SiteScape

SiteScape - LiDAR Scanner 3D

SiteScape - LiDAR Scanner 3D

  • SiteScape Inc.
  • 写真/ビデオ
  • 無料
点群数の限界はあるけどアップデートでちょっとずつ増えてる。プレビュー機能もあるので狭い範囲ならちょうど良さそう。

3d Scanner App

3d Scanner App™

3d Scanner App™

  • Laan Labs
  • ユーティリティ
  • 無料
メッシュ生成が主体だけど点群も書き出せる。僕は点群スキャン用には使ってないので詳細はよくわかってない。

Polycam

Polycam - LiDAR 3D スキャナー

Polycam - LiDAR 3D スキャナー

  • Christopher Heinrich
  • 写真/ビデオ
  • 無料
同じくメッシュ生成が主体だけど点群も書き出せる。こちらも僕は点群スキャン用には使ってないので詳細はよくわかってない。

Capture

Capture: 3D Scan Anything

Capture: 3D Scan Anything

  • Standard Cyborg
  • 写真/ビデオ
  • 無料
True depthを使うタイプなのでiPhone X以降のデバイスで使える。残念なことにアップデートが来なくてLiDARには対応してない。プレビュー機能も美しいしクラウドにアップできるしで使いやすい。

点群を編集する

撮った点群を切り抜いたりノイズ除去したりしたい。今回CloudCompareを使ってみたけど長くなったので別記事にした。下記記事を参照。

点群をテクスチャに書き込む

現状点群がそのままアップできるソーシャルVRは多分無いので、点群をテクスチャに書き込んでshaderで展開する方式を用いる。まずはテクスチャへの書き込み。みんなが簡単に使えるようにGoogle Colaboratoryを使ってみた。使い方は下記リンク先に書いてるのでそっちを見て欲しい。

Unityに表示する

無事テクスチャができたはずなのでUnityに表示する。下記リンクから必要なパッケージをUnityにimportする。

生成したテクスチャもimport。こんな感じの設定にする。

f:id:kuwamai:20201214015854p:plain f:id:kuwamai:20201214015901p:plain

新しくマテリアルを作ってPointCloud prefabにセットすると完成。あとは個々のプラットフォームにアップロードするだけ。

f:id:kuwamai:20201214020208p:plain

点群展とかしてみたい

せっかくアップロードできるようになったのでみんなで点群を持ち寄ってみたい。お友達どうしでできないかな。持ち寄れる人は教えて。

関連記事

モバイル環境に対応させる際に気をつけたところをメモした。よかったら見て。

*1:Tangoはちょっと遊んでたよ

*2:こんなん作りました→ VRChatに銀河を作る[2] - クワマイでもできる

CloudCompareで点群編集

f:id:kuwamai:20201214025328p:plain

最近はスマホで気軽に点群が撮れるようになった。撮った点群を共有したいけどその前に切り抜いたりノイズを除去したりしたい。CloudCompareを使って点群のノイズ除去、間引き、切り分け、書き出し等簡単な作業をまとめてみた。

関連記事

元々VRで点群が見たくてCloudCompareを使い始めたので、今回の使い方記事は下記記事の副産物。点群撮影ができるスマホアプリの紹介もあるのでよかったら見て。

CloudCompareのインストール

点群の編集にCloudCompareを使用する。下記サイトのdownloadからダウンロードできる。今回はv2.11.1を使用した。

点群の読み込み

画面左上のフォルダマークをクリックして読み込みたいファイルを選択。点群データによっては位置と色だけでなく反射強度とか法線とか色々登録されてたりする。左右の項目が合ってるか確認できたらApply。

f:id:kuwamai:20201207160826p:plain

ノイズ除去

センサーノイズや動くものの映り込みによって物がない場所にも点が入ってしまうことがある。邪魔なのでSOR filterで除去する。

画面上のSOR filterをクリック。Standard deviationでどれくらい外れた位置にある点を許容するか決められる。つまり値が大きいほど除去される点は少なくなる。

f:id:kuwamai:20201210201015p:plain

除去前

f:id:kuwamai:20201210201036p:plain

除去後

f:id:kuwamai:20201210201101p:plain お店の奥にあった点が消えている。画像じゃわかりにくいけど道の上にもポツポツあった点が消えてていい感じ。あまり減らしすぎると点数が少なくなっちゃうので多めに残しといた。

DB Treeの見方

編集した点群が左のDB Treeに追加されていく仕組み。編集や保存もここで選択したデータを対象に行われるので何を選択しているか、チェックを入れているか気をつけながら作業するのがおすすめ。

f:id:kuwamai:20201207161348p:plain

点群の間引き

画面左上の赤と青のSub Samplingマークをクリック。methodをspaceにしてmin. space between pointsで指定した距離内に密集してる点が削除される。点群の密度も均一になって見栄えもよくなるはず。

f:id:kuwamai:20201207161318p:plain

視点移動

左側の立方体マークをクリックすると真上や真下などから見れる。次に紹介する点群の切り分け作業で便利。

f:id:kuwamai:20201207161409p:plain

点群の切り分け

画面上のはさみのSegmentマークをクリック。Rectangular selectionで四角く選択できる。赤い五角形のSegment inをクリックすることで、選択した範囲の内側が残る。チェックマークのconfirmをクリックして切り分け完了。

f:id:kuwamai:20201207161456p:plain

点の数と中心位置

画面左側のPropertiesで色々情報が見れる。Shifted box centerが点群の中心位置。Pointsが点数。点群はスキャンし始めた位置を基準に位置が決まるので、中心が0というわけじゃない。

f:id:kuwamai:20201207161545p:plain

点の位置

点群をダブルクリックすると点の位置が左下に表示される。決まったある場所を原点にしたい場合この数値を使うといいかも。

f:id:kuwamai:20201207161525p:plain

点群の書き出し

左上のフロッピーマークで保存。あとで自分で書いたスクリプトで処理したいので今回はASCII cloudを選択。

f:id:kuwamai:20201207161620p:plain

設定はよくわからないけどこんな感じ。orderはPTS。OKを押して保存。

f:id:kuwamai:20201207161635p:plain

メッシュ化

当たり判定用とかに点群からメッシュを作りたい場合もあるかも。こちらの記事の手順でできました。

おしまい

他にも点群を結合したりできて色々楽しそう。いつか写真くらいカジュアルに3Dデータをみんなで共有できる日が来るのかな。

参考

in3Dでリアルアバターを作ってもらう

f:id:kuwamai:20201122195634p:plain in3Dというスマホアプリで体をスキャンし、1220円でVRChatのアバターに変換してもらった。あんまり情報が無かったのでスキャンからアバターの受け取りまでをメモ。結論としてはUnityでアバターのセットアップができる人は課金せずFBX出力して自分でセットアップしたほうが良さそう。

使ったアプリ

TrueDepthを用いるのでiPhoneX以降が必要。VRChatのアバター制作サービスはv1.1.7(2020/11/17)から始まった。

in3D: 3D body scanning

in3D: 3D body scanning

  • GOODSIZE INC.
  • 写真/ビデオ
  • 無料

アバター制作の流れ

スキャン

顔と体を2ステップでスキャンする感じ。スキャンを始めるとビデオが流れるのでその指示に従うだけ。顔は上下左右に動かすだけ。体はスマホを机に置いてインカメラの前でぐるっとターンしておしまい。1分くらいで終わっちゃった。その後はデータがサーバに送られて、7分くらいでモデルのできあがり。

f:id:kuwamai:20201123004633g:plain

アプリ内でもAR表示したりアニメーションで踊らせたりできて楽しい。

アバターの購入

f:id:kuwamai:20201123004729j:plain

Actionsの右側にVRChatの項目がある。

f:id:kuwamai:20201123004907p:plain

1220円で購入。2営業日以内でできるとのこと。

アバターの受け取り

翌日こんなメールが来た。

Hi! My name is Vadim, I am from the in3D team. Thank you for your purchase of the VRChat avatar from our in3D app! We are still working on the interaction with VRChat, so we have two ways to deliver your avatar to you. First, I can import your model under my profile as a public avatar, then we met inside the game, and you clone the avatar. The problem is we can't guarantee somebody else wouldn't clone it. The other way is to simply send you the rigged model or the unity project with the model, so you can upload the avatar yourself under your own account, and make it private from the start. This approach requires you to have unity installed and to be ranked above visitor in VRChat. Which way is preferable?

VRChat上で会ってアバターを受け取るか、プロジェクトファイルを受け取るかどっちがいいかとのこと。VRChatは始めてすぐにアバターアップロードができないのでそれに配慮してる感じ。今思えば会ったほうが面白かったなと後悔してるけど、僕の場合アップロードできるので後者を希望した。

さらに翌日Google driveのリンクが送られてきたのでUnityで開いてアップロードできた。

届いたアバター

f:id:kuwamai:20201122200549p:plain

見た目はアプリで見た通り。

f:id:kuwamai:20201122201037p:plain

Unityで見たらボーンがmixamorig*だった。MixamoとはWebでお手軽にリギングできるツール。つまりin3D側がやってくれるのはMixamoでリギングしてUnityでアバターセットアップをしてくれるところまで。僕はてっきりもっといい感じにリギングしてくれるのかと思ってたのでちょっぴりがっかり。始まったばかりだからこれから充実したりするかな。

f:id:kuwamai:20201122200605p:plain

指のリギングがちょっと変な感じ。

f:id:kuwamai:20201122200614p:plain

僕の場合特に薬指がちゃんと閉じなかった。

感想

f:id:kuwamai:20201123010151j:plain

  • 顔のテクスチャが写真に近い状態ですごくキレイ
  • 人型に特化してるので鼻のシルエットもしっかり出る
  • UVがアジの開きみたいにキレイなので修正が簡単そう
  • スタジオのスキャンと違って好きな格好で何度も撮り直せる
  • Unityに親しみのある人はFBX出力して自力でセットアップしたほうが早そう

モバイルバッテリーからUSBハブに給電したい

f:id:kuwamai:20200926185657j:plain

RealSense D435iとT265をバスパワーUSBハブ経由で使おうとしたところ、電流が足りないみたいで起動しなかった。セルフパワーUSBハブを使えば解決するが、持ち運びながら使用したいためACアダプタでなくモバイルバッテリーから給電したい。今回USB給電タイプのセルフパワーUSBハブを買って無事動作確認できたので動いたよレポ。

買ったもの

4ポート繋げられ、Micro-Bコネクタで給電することができる。安いUSBハブはコスト削減のためにACアダプタを同梱せず、このようにUSB給電式になっているものが多いらしい。

動作確認

f:id:kuwamai:20200926185510p:plain

何故かバスパワーの時点でD435iが3台動作した。モバイルバッテリーで給電しながら使用するとD435i 3台に加えてT265を1台動作させることができた。今回はD435iとT265が1台ずつ動けば問題ないので、結局給電せずに使うことになりそう。

感想

モバイルバッテリーからUSBハブに給電する話が調べてもあまりなかったから動くか心配だったけど、買ってみたら普通に動いたのでブログにも書いておいた。思えばUSB→DCジャック変換ケーブルがあればUSBハブの選択肢も増えるのかな。

groupタグを使って複数のロボットを同じROSスクリプトで動かす

f:id:kuwamai:20200828183007p:plain 以前2台のマニピュレータをそれぞれ右手用、左手用に分担させて動かした。制御自体はほぼ同じだけど手先位置のtopic名だけ違う名前にする必要がある。topic名だけ異なるスクリプトを2つ書いてもいいけど、roslaunch実行時の引数で切り替えができるようにgroupタグのnamespaceを設定してみた。

動作環境

  • Ubuntu 18.04
  • ROS Melodic Morenia

以前やったこと

group tag

下記のように記述することでnodeやrosparamにまとめてnamespaceが設定できた。今思えば左右さえ識別できればいいのでparamとnamespaceの両方を指定する必要は無かったけど気にしない。

<?xml version="1.0"?>
<launch>
    <arg name="arm_name" default="raspigibbon"/>
    <arg name="arm_lr" default="r"/>

    <group ns="$(arg arm_name)">
        <param name="arm_lr" value="$(arg arm_lr)"/> 
        <node pkg="raspigibbon_vr" name="vr_controller" type="vr_controller.py" />
    </group>
</launch>

下記コマンドでroslaunchを実行すればnodeがpublishするtopicの前に/raspigibbon_r/raspigibbon_lがつくようになる。

$ roslaunch raspigibbon_vr arm_controller.launch arm_name:=raspigibbon_r arm_lr:=r
$ roslaunch raspigibbon_vr arm_controller.launch arm_name:=raspigibbon_l arm_lr:=l

namespaceを分けるイメージ

そもそもnamespaceって何という人向けに簡単に絵に書いた。

f:id:kuwamai:20200829022940j:plain

同じnodeやtopicを異なるロボットが共有した場合、同じ命令を受け取って動くので動きも同じになってしまう。

f:id:kuwamai:20200829023002j:plain

groupタグのnamespaceを設定することでnodeやtopicの先頭に異なる名前をつけることができ、同じスクリプトでもそれぞれのロボットに命令を送ることができる。

参考

namespaceとrosparamの取得

rospyだとこんなかんじで取得できた。roscppにもgetNamespace()があるみたい。

ns = rospy.get_namespace()

ちなみにrosparamの取得はこんな感じ。

param = rospy.get_param("param_name")

実際に使用したスクリプトはこちら。

参考

感想

久しぶりに書いたからかなんだか日本語がめちゃくちゃになっている気がするけど許して欲しい。