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

壁に優しいHTC Viveのセットアップ (+メイキングもあるよ)

ベースステーションが落ちてくる!

(記事の最後の方DMM.makeのリンクがあるので欲しい人はそこから買ってください)

半月ほど前にHTC Vive買いました。Oculus Touchが発売されてない現状、Viveは普通に買える最もハイエンドなVRデバイスなわけですが、コントローラーやHMDをトラッキングするのにベースステーション(300gぐらい)を天井近くに取り付ける必要があります。

問題は方法で、壁にネジ止めするための器具が付属するのですが、賃貸住宅に住んでると穴を開けるのはちょっと…という人も多いと思います(私もそうです)。たまたまタンスとか置いてあればいいものの、そうでない場合2m近いポール+クランプ(しかも2個ずつ)を買って取り付けるというのは結構大掛かりで見た目も悪いです。

indiegame-japan.com

ところで世の中には剥がせる両面テープというのがあって、私の家では電子機器の配線だけでなく、電源タップやACアダプター自体も壁に貼り付けています。結構強度も高く、1年ぐらい経っても剥がせるのを確認済みです。

3M コマンド タブ(はがせる両面粘着) お買い得パック M 60枚 CMR3-60

3M コマンド タブ(はがせる両面粘着) お買い得パック M 60枚 CMR3-60

 

付属の固定具の裏に貼ってみたのですが、接地面積が小さい+重心が壁から遠いで明らかに失敗フラグが立っていたので、ベースステーションを支えるスポンジと、バックアップ用のフックを着けていたのですが、2日ぐらいで落ちてきました。

f:id:xanxys:20160720193710j:plain  f:id:xanxys:20160722193925j:plain

 

とはいえシールの強力さを考えると、上手く接地面積さえ稼げれば外れないはずだし、これはもう作るしかない!ということで作りました。

つくりかた

必要なのが何か考えてみると:

  1. ベースステーションを固定できること
    • ネジ止め無しで固定したい (爪が曲がってカチッとなる感じのやつ)
    • ラッキング範囲が広くなるように傾けたい
    • 貼付け後もケーブルとボタンにアクセスできること
  2. シールが使いやすい感じで壁・天井と接すること
    • 3面で接してほしい
  3. 他の人に配布できること (必須ではないけどせっかくなので)

あたりを満たすために、DMM.makeで3Dプリントすることにしました。

Tilt Brushでスケッチ

3Dで物を作るときは、2次元の図面だけで考えてると(少なくとも私の頭では)必ず見落としが出るので、VR環境を活かしてTilt Brushでスケッチしていきます。大きさと位置関係が直感的に分かるのはかなりいいです。

f:id:xanxys:20160724233234g:plain

↑壁との接触面を考えてる様子

f:id:xanxys:20160724233742g:plain

↑ケーブルの取り回しとスナップフィット(カチッとなるやつ)でかかる力を考えてる様子

これでだいたいの目処がついたので、CADでモデルを起こしていきます。 

Fusion 360モデリング

昔にDesignSpark Mechanicalは使ったことがあったのですが、もうちょっと強力そうなCADを探していたら、ちょうどFusion 360が無料で1年使える・なんかUIが良さそう(小並感)らしいので使ってみることにしました。

www.autodesk.co.jp

まず、ベースステーションの実物をノギスで測って地道にモデリングします。実際かなり地味な作業で、測って入力しようとすると「あれ、どの辺の長さだったっけ?」と忘れてしまって測りなおしたりするのが頻発した+CADに慣れてなかったので数時間かかりました。(ノギスから自動でAR上のモデルに制約が反映とかされて欲しい)

f:id:xanxys:20160725000218g:plain f:id:xanxys:20160724235442p:plain

(ちなみに作業履歴は自動で保存されててて、こういうアニメーションを再生するモードがある)

一番難しそうなのはベースステーションを保持する部分なので、さっき作ったモデルを押し出して枠を作っていきます。それっぽい形を作るのは簡単ですが、実際にちゃんと動くかどうかは別です。たとえば、スナップフィットが硬すぎて入らないとか、枠がふにゃふにゃとか、いろいろな失敗例が考えられます。

Fusion360にはFEMで歪をシミュレートする機能があって、適当にボタンを押だけで1分ほどで↓みたいな結果が見れるので、歪みがいい感じなるように梁を足したり厚みを変えたりして調整しました。

f:id:xanxys:20160725212233g:plain f:id:xanxys:20160725212412p:plain

そのあと壁に接する部分を作って(最初のバージョンが)完成!レンダリングも完全に透過的にローカルレンダリングクラウドレンダリングが切り替えれたり、web UIにいつの間にか綺麗なサムネイルが生成されたりします。この調子でマルチドメインなシミュレーションとか、冒頭で言ったようなAR対応とかがあると未来って感じがします。

f:id:xanxys:20160725213154p:plain f:id:xanxys:20160725213201p:plain

DMM.make

最初はABSライクで作る予定だったのですが、見積もりが4万円ぐらいしたので一番安いナイロンに変更(それでも1万円ぐらいかかる)しました。今回の形状は空白が多いので、DMM.makeの価格モデルでいうところの空間費が高く付いてます。

エクスプレスサービスで数日待つと…

f:id:xanxys:20160725215218j:plain

かなり丁寧に梱包されてきました。早速試してみると、なんと一発で動きました。スナップフィットが初設計で動いたのはシミュレーションのおかげだし、ケーブル取り回しの問題が起きなかったのはVR内でスケッチしたおかげ(多分)だし、今時の文明の恩恵を感じます。


HTC Viveベースステーションホルダー スナップフィット (音がいい感じ)

 

DMM.make購入リンク & まとめ

買うもの

ベースステーション一個あたりシール三枚必要ですが、貼り付けに失敗する可能性もあるので、多めに買っておいたほうがいいと思います(安いですし)。

3M コマンド タブ(はがせる両面粘着) M 12枚 CMR3

3M コマンド タブ(はがせる両面粘着) M 12枚 CMR3

 

 

本体

2個セット: 14,800円 (7,400円/個)

make.dmm.com

1個だけバージョン: 10,800円

Viveベースステーション固定具(v1.0) 1個 - DMM.make クリエイターズマーケット 

 

何百円かづつ利益乗せてますが*1、なんだかんだで設計に10時間以上かかってるのと、試作で1個余分に買ってるのでその分ぐらいは儲けたいです。

 

使い方

f:id:xanxys:20160723131858j:plain

こんな感じでシール3枚貼って部屋の隅に押し付けるだけですが、雑に貼ると落ちて来るので(実際一回落ちた *2 )、オススメな手順は:

  1. シールを貼る (ベースステーションはまだはめ込まない)
  2. 壁面の保護シートは剥がさずに、位置決めしてみる
  3. 壁面のシートを剥がして、決めた位置に押し付ける
    • 特に天井は念入りに押し付けると良いです
    • 接触が悪いと感じたら、剥がして新しいシールでやり直すほうがいいです
  4. 電源ケーブルを後ろの穴から通して、ベースステーションをはめ込む

f:id:xanxys:20160723134021j:plain f:id:xanxys:20160730164928j:plain

(←:隅に3面で固定する例 →:L字型に2面で固定する例)

これで1週間程度使っていますが、落ちて来る気配は無いです。

免責事項

お約束ですが、もしこれを使ってベースステーションが落ちて壊れたり怪我をしても、私は一切の責任を負いません。あと、完全に非公式の物なので、HTCにゴネたりするのもやめてください。(とはいえ、落ちたら写真付きで知らせてもらえると嬉しいです。注意書きを追加するか、モデルを修正するか、公開停止するかのいずれかで対応するので。)

最後に

壁にネジ止めして退去時に原状回復費払うほうが安い可能性が微レ存

EC2(g2.2xlarge)でOpenGLを使う方法

EC2上でGPUを使って何かをさせるというのを比較的よく見かけるようになりました。

CUDA/OpenCLを使う例は多いですが、OpenGL(のオフスクリーンレンダリング)を使いたい場合もあると思います。例えば、WebGLを使う3Dコンテンツのサムネイル画像をサーバー側で生成したいとか、本当はCUDAのほうが適しているけどGLSLで書かれている資産を活用したいなどのケースです。

今回、私もOpenGLを使う必要が発生して、わりと苦労したのでその手順と注意点などを書いておきます。

より具体的にはg2.2xlargeで次のライブラリを使うアプリケーションを動作させます。

AMIの選択

まずAMIには仮想化の方式によってpravirtualとHVMの2種類があって、GPUを使うにはここでHVMの方を選ぶ必要があります。

この時点で大まかに2つの選択があって、

  1. Amazon Linux AMI with NVIDIA GRID GPU Driver on AWS Marketplace

  2. 「普通の」Linux (GPUドライバーなし)

1はドライバーはインストールされているのですが、3rd partyのライブラリはほとんどリポジトリになく、自前でmakeする必要があります。

一方2はライブラリ等は比較的簡単に入れられるものの、肝心のGPUのドライバー周りがなかなか動作しないという問題があります。

最初は2でubuntu/images/hvm/ubuntu-trusty-14.04-amd64-server-20140416.1をベースにやってたんですが、ドライバーはなんとかインストールできたもののmodprobe nvidiaが動かない(drm_openが見つからないとか言われる)ので断念しました。

1は面倒ですが、特に問題はないはずです。

GPUの動作確認とX11の起動

nvidia-smi -q -a

とすると以下のように表示されました。X11が動作していなくてもこれは表示されるはずです。

ここで

sudo xinit &

してもモニターがない的なことを言われるので、仮想スクリーンを設定する必要があります。

nvidia-xconfig --virtual=1280x1024 --use-display-device=none

とするとそれっぽい設定が/etc/X11/xorg.confに生成されるのですが、そのままでは動作せず、実際に動いてるファイルは次のような感じです(どの違いが重要なのかは特定していない)。

xorg.confを変更してsudo xinit &とするとX11は終了しないはずなので、

env DISPLAY=:0 glxinfo

とすると色々表示されて、特に下の2行が含まれていれば問題ないでしょう。

direct rendering: Yes

server glx vendor string: NVIDIA Corporation

OpenGLX11に依存しているので、他のアプリケーションでもDISPLAY=:0が必要です。

GLFWのバージョン

ここまでは順調なのですが、GLFWでwindowを作成しようと*1すると

X Error of failed request:  BadRRCrtc (invalid Crtc parameter)
  Major opcode of failed request:  140 (RANDR)
  Minor opcode of failed request:  22 (RRGetCrtcGammaSize)
  Crtc id in failed request: 0x8a6b90
  Serial number of failed request:  57
  Current serial number in output stream:  57

と言ってX11が落ちてしまいます。

どうもこれはglfw-3.0.4の問題のようで、https://github.com/glfw/glfw/issues/36 の後ろの方でもEC2で動かそうとして失敗したという人がいます。幸いなことにこの記事を書いている時点でのmasterでは正常に動作するようです(動作しました)。 

あとは好きなことが出来るはずです。

 

*1:フレームバッファーに描画する場合もOpenGLコンテキストを得るためにwindowは作る必要があります。

PNaClでOpenCVを使う方法

2014年の初めぐらいにOpenCVPNaCl(つまりPPAPIを使うLLVM IR)用にコンパイルできるようになったという話題がありました。たしかにOpenCV公式サイトの1/9のニュースが存在しているのだけど、実際に動かしてみたという例は2ヶ月ほど経った今でも見かけていません。そこで、本当に使えるのかな?という検証も兼ねてキャリブレーション用のちょっとしたツール(Webcam Calibrator)を書いてみた話をします。

PNaCl

PNaClが何であるかについては、http://d.hatena.ne.jp/hdk_embedded/20131128/1385669904とかを読むといいと思います。基本的にはブラウザ内で安全にネイティブコードを実行するための技術です(まだChromeにしか実装されてませんが)。何が嬉しいかというと、

  1. 実行速度が速い
  2. 既存資産の活用

があると思いますが、1についてはjavascriptと数倍しか変わらないわけで、わざわざサポートが薄いPNaClを使うほどの理由になることは少ないと思います。むしろ2のC/C++の資産活用のほうが重要で、画像処理・音声処理・暗号・ゲームエンジンみたいな、別の言語で再実装したくないし、重い/低いレイテンシ要求のある処理をクライアント側でやらせたいと言う理由が大きいと思います。

それで、今回はOpenCVで画像処理をやってみます。

NaCl SDK / NaCl Ports

とりあえずDownload the Native Client SDKあたりを見つつNaCl SDKを入れてチュートリアルは動いたものの、OpenCVをどうやったらPNaCl向けにコンパイルできるのか一向にわかりません。実はCMakeのオプションなどがあるのではなくNaCl Portsというところから持ってこないといけないのでした。

一見すると謎クライアント(gclient)が必要っぽいですが、実はひっそりとgitのミラーがあるのでそこから適当なところにcloneして、あとはREADMEとかに従ってmakeすれば自動的にNaCl SDKの中にライブラリが生成されます。*1

PNaCl使う上での注意点

まず、PNaClは現時点では例外をサポートしていなくて*2、例外が出ると"NaCl module crashed"とかそういう感じです。OpenCVの場合stderrにもエラーを吐いてくれるので、これを見ると良いです。PNaClモジュールからstdout/stderrに吐かれたメッセージは、デフォルトではブラウザ自体のstdout/stderrに出るようです(つまりブラウザをターミナルで開くとそこに出る)。

あと、build方法に癖があります。基本的にはpnacl-clang++で.cpp→.bcして、pnacl-finalizeで.bc→.pexeというフローなんですが、例えばpnacl-clang++で各.cppを.oにコンパイルした後、pnacl-clang++で.oを.bcにリンクするとバイナリは出来るのですが、なぜか実行時にクラッシュしたりします。それならばということで、チュートリアルMakefileを真似して.cppから.bcに直接するコマンドを生成しようとしたりするもののなぜかundefined referenceが出たり*3するので、今のところはMakefileをそのまま使うほうが無難そうです。

例: Webcam Calibrator

ただblurしたりedge検出するぐらいだとそれjsfeatでできるよ!とか言われそうなので、チェッカーボードの検出とキャリブレーションというわりと実装が面倒そうな関数を使う例を作ってみました。それにキャリブレーションはCVとかARでは基本になる操作なので、実用性もそこそこあると思います。

まだ解像度が320x240しか選べないのでパラメーターの精度は低いですが、一応(Chromeでは)動くはずです。

*1:ここが動かないとどうするのかは謎

*2:そもそもGoogleでは歴史的事情によりC++の例外は使っていないらしい。とはいえPNaClでは「すごく近い将来に」サポートする予定とのこと https://developers.google.com/native-client/dev/reference/pnacl-c-cpp-language-support?hl=ja#exception-handling

*3:どうもpnacl-clang++はpythonで書かれたwrapperで、テンポラリファイルをいくつも使ってstripとかしているようなので、そのあたりがSConsと相性が悪い(かもしれない)?

SublimeのPackage Controlへ自作packageを登録する方法+α

さてSublime TextというとEmacserやvimmerも黙る(?)モダンな大人気エディタですが、その人気の結構な部分はPackage Controlにあるでしょう。
というのも、だいたいの紹介記事では「まずPackage Controlを入れましょう」と書いてありますし、見かけたpackageのREADMEに「まずgit cloneして、hoge.tmPreferencesをふがふがにコピーして・・・」とか書いてあると大抵は「まぁ今すぐには要らないか・・・」といってそのまま忘れてしまうことが多いです(特にWindows使用時)。

というわけで、自分で何かpackageを書いた場合、package controlからインストールできるようになるとユーザーも増えるしいろいろ捗りそうです。この記事ではpackage controlへThriftSyntaxというちっちゃなpackageを登録してみたときに調べたことを書いておきます。

Packageのつくり方

・・・については説明しないので、適当に既存の似たようなpackageを見るなり、チュートリアルを探すなりしてみてください。

Package Controlとは?

wbondさん(http://wbond.net/)が提供しているパッケージと運営しているサービスの対で、こんな感じ↓で動作します。

Package ControlがSTにインストールされると、デフォルトのchannelとしてhttps://sublime.wbond.net/channel.jsonが登録された状態になります。
Package Controlでpackgeのインストールをしようとすると一覧が表示されますが、このときにchannel.jsonを読んで全packageのメタデータを得ます。このメタデータには各packageのURLが含まれているので、Package Control(クライアント)がそれを持ってきてインストールします。

Package Control公式のchannelでは、登録するpackageの管理にgithubのリポジトリを持っていて、ここには全てのpacakgeのリポジトリへのリンクが含まれています。Package Control(サーバー)はこのリポジトリをだいたい1時間に一回ぐらい調べて、全pacakgeの各バージョンに対するメタデータを作成してpackage.jsonを更新します。

まぁ毎日80GBのjsonをserveしてる(https://sublime.wbond.net/stats)とか、そういうどえらいシステムです。package作者にとっては、2点ほど役に立つかもしれないことが分かります。

Channelは自分で作れる

クライアント側サーバー側ソースコードは公開されていますし、クライアント側でchannel追加は簡単に(Ctrl+Shift+P > Add Channel)できます。

これを使うと、組織内で(非公開の)packageのリポジトリを作ってPackage Controlから便利に使うということが比較的簡単にできると思います。

Package Control(公式)へのpackage登録方法

packageを登録してもらうには https://github.com/wbond/package_control_channel にpull requestを送るといいということが分かります。これについて以下で詳しく書きます。

Package Controlへの登録の仕方

Package Controlにもとから入っているchannelでは、packageの受け入れ用のリポジトリもあって(https://github.com/wbond/package_control_channel/tree/master/repository)、基本的には

  1. packageを作る (github or bitbucket)
  2. https://github.com/wbond/package_control_channelをforkして、自分のpackageを追記
  3. pull requestを送る
  4. mergeを待つ (なんか問題を指摘されたら修正する)

という感じになります。

一旦mergeされると、新しいバージョンをリリースするときは自分のリポジトリにtagを付ければ自動的に拾ってくれるので楽です。

より詳細な手順やpackage名に関するガイドラインなどは https://sublime.wbond.net/docs/submitting_a_package に書いてあるので、目を通しておきましょう。

今回私はThriftSyntaxというのを作ったんですが、mergeされるまでに1日もかかりませんでした。受け入れ時のチェックは既存のpackageと機能が重複していないか、jsonが壊れていないか、などを見ているようです。*1

まとめ

というわけで、わりと簡単にpackageを登録できるということと、channel自体を作るのもそこそこ簡単かもしれないので、みなさんも機会があれば是非やってみてください。

*1:とはいえ、Pull Requestを見てmergeしてるのはどこかのボランティアの人なので、身内ネタとかのpackageをどんどん送りつけるのはやめましょう。そういうことがしたい場合channelを自分で作ると良さそうです。

Ricoh THETAのEXIF (回転を読むためのPythonコード付き)

追記 (2014/2/12)

2014/1/30のアップデート(https://theta360.com/ja/info/news/2014-01-30-2/)で、Sphere XMPフォーマットでも回転情報が埋め込まれるようになったようです。

Photo Sphere XMPAdobeメタデータ形式であるXMPGoogleが拡張したもの(https://developers.google.com/photo-sphere/metadata/?hl=ja)で、ちゃんとドキュメントもあるので新しいデータについてはこちらを使うほうがよいでしょう。

はじめに

(主にVR界隈で?)最近人気のthetaですが、

公式のviewerでみるとちゃんと向きが自動で調整されるんですが、中に入ってるjpegはこんな感じで向きの補正はされてません。

もちろん、撮った写真を見るだけなら公式サイトにアップロードすれば(ちょっと重いのを除いて)特に問題はないんですが、Oculusでみるぞーとか思うと、当然向きの情報にアクセスしたいところです。

で、公式のWindows用viewerだとこのjpegだけを開いて向きを調整できるので、jpegのどこかに入ってるはずだ、ということになります。

EXIFを見る

試しに一つ開いてExifReaderでみてみるとこんなかんじです。(GPS情報がある場合)


ファイル名 : R0010004.JPG
Exif : Exif
▼メイン情報
タイトル :
メーカー名 : RICOH
機種 : RICOH THETA
画像方向 : 左上
幅の解像度 : 72/1
高さの解像度 : 72/1
解像度単位 : インチ
ソフトウェア : RICOH THETA Ver 1.02
変更日時 : 2013:11:09 17:40:42
YCbCrPositioning : 一致
著作権 :
Exif情報オフセット : 434
GPS情報オフセット : 904
▼サブ情報
露出時間 : 1/30秒
レンズF値 : F2.1
露出制御モード : プログラムAE
ISO感度 : 800
Unknown (8830)3,1 : 1
Exifバージョン : 0230
オリジナル撮影日時 : 2013:11:09 17:40:42
デジタル化日時 : 2013:11:09 17:40:42
コンポーネントの意味 : YCbCr
画像圧縮率 : 320/100 (bit/pixel)
レンズ絞り値 : F2.1
対象物の明るさ : EV-1.5
露光補正量 : EV0.0
開放F値 : F2.1
自動露出測光モード : 分割測光
光源 : 不明
フラッシュ : オフ
レンズの焦点距離 : 0.75(mm)
カメラの内部情報 : RIOCH Format [.............]
ユーザーコメント :
FlashPixのバージョン : 0100
色空間情報 : sRGB
画像幅 : 3584
画像高さ : 1792
ExifR98拡張情報 : 58224
撮影モード : オート
ホワイトバランスモード : オート
レンズの焦点距離(35mm) : 6(mm)
シーン撮影タイプ : 標準
シャープネス : 標準
GPS情報
GPSタグバージョン : 2,3,0,0
緯度(N/S) : N
緯度(数値) : 34゚ ****.** [DMS]
経度(E/W) : E
経度(数値) : 135゚ ****.** [DMS]
高度基準 : 海抜基準
高度(数値) : 2148/100 メートル
GPS時間(UTC) : 08:40:37
撮影した画像の方向基準 : 真方位
撮影した画像の方向 : 22.50°
測地系 : WGS84
タイムスタンプ : 2013:11:09
TOKYO測地系換算緯度 : 34/**/**.*** [DMS]
TOKYO測地系換算経度 : 135/**/**.*** [DMS]
▼ExifR98情報
互換性識別子 : R98
バージョン : 0100
▼サムネイル情報
圧縮の種類 : OLDJPEG
幅の解像度 : 72/1
高さの解像度 : 72/1
解像度単位 : インチ
JPEGInterchangeFormat : 58356
JPEGInterchangeFormatLength : 3225
で、"撮影した画像の方向"(GPSImgDirection)はあるんですが、加速度の情報はありません。これは"カメラの内部情報"、いわゆるmakernoteに謎のフォーマットで入っているに違いありません。*1

Makernote

写真を沢山とって変化する部分とか調べてたんですが、さすがに効率が悪いのでちょっと方針を変えてWindows版のアプリを調べて見ることにしました。よく見るとAdobe Airでできていたので、早速SphericalViewer.swf(1.5MB)を逆コンパイルしてみました*2

するとjp.co.ricoh.exif.RicohIFDEntryとjp.co.ricoh.receptor.entities.EquirectangularImageというのがすぐ見つかったので、この辺を調べてみます。すると

  • ZenithEs (TagId=0x0003)
  • Zenith (TagId=0x0006)
  • CompassEs (TagId=0x0004)
  • Compass (TagId=0x0007)

というタグがあるのが分かります。Jpegの標準にもIFD(Image File Directory)というのはあるらしく、それほど変な形式でも無いようですが違いはよくわからないのでこの辺のタグ付近の形式だけ書いておきます。

基本的にはbig endianで、


Entry
= TagId(uint16) TypeId(uint16) NumData(uint32) Offset(uint32) (Dataが大きい時)
| TagId(uint16) TypeId(uint16) NumData(uint32) Data{NumData} (そうでないとき)

TypeId
= 0x0005 (unsigned rational)
| 0x000a (signed rational)

Data
= a(uint32) b(uint32) (unsigned rational, a/b)
| a(int32) b(int32) (signed rational, a/b)

となっています。データ本体は小さい場合(データの合計が4B以下の時)はそのままインラインに入っていて、大きい時はファイルのオフセットが入っています。で、なぜかこのオフセットに+12したところから実際のデータは始まります。

で、私がみたファイルの中にはZenithEsとCompassEsが定義されていて、

  • ZenithEs: signed ratioanl, NumData=2
  • CompassEs: unsigned rational, NumData=1

となっていました。エラーチェックのコードから値の範囲が分かり、全てdegreeで

  • 0 <= ZenithEs[0] <= 360
  • -90 <= ZenithEs[1] <= 90
  • 0 <= CompassEs <= 360

となっているようです。

で、この辺の値がTilt3Dというクラスに入り、ZenithX(ZenithEs[0]),ZenithY(ZenithEs[1]),ZenithZ(0),Compass(CompassEs)と呼ばれます。座標系等はまだ調べていませんが、このうちZenithX,ZenithYだけを使って、このような回転行列を作っているようです。


m =
cos(zY) -sin(zY)*cos(zX) -sin(zY)*sin(zX)
sin(zY) cos(zY)*cos(zX) cos(zY)*sin(zX)
0 sin(zX) cos(zX)

取り出し方

本当はIFDをちゃんとパースするといいのでしょうが、面倒そうなので簡易的に取り出せそうな方法を書いておきます。さっきあげたタグはバイナリの中で比較的ユニークなシグネチャになるので、それを検索して値を取り出すと良さそうです。

#!/bin/python2

import os
import subprocess
import struct

def find_data(s, tag):
	ix = s.find(tag)
	if ix < 0:
		raise Exception('Cannot find tag')
	return ix + len(tag)

def parse_u_rational(s):
	a, b = struct.unpack('>II', s)
	return float(a) / float(b)

def parse_s_rational(s):
	a, b = struct.unpack('>ii', s)
	return float(a) / float(b)

def get_angles(path):
	f = open(path, 'rb')
	head = f.read(10 * 1000)  # take long enough header

	# Find CompassEs
	ix = find_data(head, '\x00\x04\x00\x05\x00\x00\x00\x01')  # search CompassEs,UnsignedRational,1
	offset = struct.unpack('>I', head[ix : ix + 4])[0] + 12
	compass = parse_u_rational(head[offset : offset + 8])

	# Find ZenithEs
	ix = find_data(head, '\x00\x03\x00\x0a\x00\x00\x00\x02')  # search ZenithEs,SignedRational,2
	offset = struct.unpack('>I', head[ix : ix + 4])[0] + 12
	zenith_x = parse_s_rational(head[offset : offset + 8])
	zenith_y = parse_s_rational(head[offset + 8 : offset + 16])
	
	return {
		'zenith_x': zenith_x,
		'zenith_y': zenith_y,
		'compass': compass
	}	

で、手元のファイルに対してはそれっぽい値がでてきています。本当にこれらの値を使うためには座標系をもうちょっと調べたりする必要があるでしょうが、そこは比較的楽だと思います。

雑感

他にもおもしろそうなタグがいくつか定義されているようなので、気になる人は調べてみると良いと思います。

  • HDRType
  • HDRData
  • AbnormalAcc

*1:ここで多少のプロプライエタリな形式に対応しているexiftoolも使ってみますが、thetaは新しすぎて対応してませんでした

*2:JPEXS Free Flash Decompiler http://www.free-decompiler.com/flash/ が便利でした

三次元回転の確率分布

唐突ですが、三次元空間内での回転の確率分布というのを考えてみます。回転の距離、回転の表現、超球面上の分布、回転の分布、という順番に見ていきます。

回転の距離の定義

まず、単一の回転Rの角度f(R)、というのを考えてみましょう。不変な場合は0になって欲しいのでf(I)=0となりますが、それ以外ではどうでしょう。
ちょっと考えてみると、Iじゃない回転は回転軸との正の角度に分解できるので、その角度のうち最小のものを使って、f(R)=\thetaとしてやれば良さそうです。このとき0 \le a \le \piとなります。

ちなみに、この角度aは、全ての三次元単位ベクトルvについてRvvの角度の最大値をとったものに等しいです。

このfを使うと、二つの回転の距離d(R_1,R_2)d(R_1,R_2)=f(R_1^{-1}R_2)と書けます。で、これが以下の距離の公理を満たしているか順番に見てみます。

  • 非負性: d\ge 0
  • 対称性: d(R_1,R_2)=d(R_2,R_1)
  • 三角不等式:  d(R_1,R_2) + d(R_2,R_3) \ge d(R_1,R_3)
  • 非退化性: d(R_1,R_2)=0 \Leftrightarrow R_1=R_2

まず、fの値域から非負性は満たされます。
d(R_1,R_2)=f(R_1^{-1}R_2)=f((R_1^{-1}R_2)^{-1})=f(R_2^{-1}R_1)=d(R_2,R_1)なので対称性もあります。

三角不等式はちょっとややこしいですが、d(R_1,R_3)R_1からR_3まで最短で回転させる角度にもなっている(ような気がする)ので、その経路上にR_2が乗ってれば d(R_1,R_2)+d(R_2,R_3) = d(R_1,R_3)になって、それ以外では遠回りになると考えると成り立ちそうです。

で、最後の非退化性ですが、d(R,R)=0はすぐにわかって、d(R_1,R_2)=0ならR_1^{-1}R_2=Iで、R_1=R_2なので問題なく成り立ちます。これでdが距離であると言えるでしょう。

単位クオータニオン

さて、みなさんご存知の通り(?)、三次元の回転は単位クオータニオンで表すこともできます。クオータニオンスカラーと三次元ベクトルの組み合わせq=s+vとして書くことができます。*1そのうちノルムが1のものを単位クオータニオンと呼んで、うまいことベクトルの回転の規則を選んでやると、角度がs=\cos (\theta/2)|v|=|\sin (\theta/2)|を満たし、軸がa=\hat{v}な回転を表します。

回転の合成はクオータニオンの積q_1 q_2 = (s_1s_2 - v_1\cdot v_2) + (s_1 v_2 + s_2 v_1 + v_1 \times v_2)で表せます。他にも、共役q^*=s-vqの逆の回転になります。

3自由度を表すのに4つの実数しか使ってなくて、オイラー角みたいな汚い問題(ジンバルロックとか)も発生しないのでよく用いられているらしいです。この素晴らしい表現を維持したまま、確率分布を考えたいわけです。

von Mises-Fisher分布

任意の次元の超球面上の分布のひとつにvon Mises-Fisher分布というのがあるらしく、pdfは次のようになります。
f(x|\mu, \kappa) = C_p(\kappa) \exp(\kappa \mu \cdot x) 但しC_p(\kappa) = \kappa^{p/2-1} / (2\pi)^{p/2} I_{p/2-1}(\kappa)

よくみるとexpの中には\mu \cdot xが入っていて、これで\muとの距離を計っているのが分かります。

回転の確率分布

さて、クオータニオンの要素の順番には意味があるので、これをさっきのvMF分布に直接突っ込んでしまっても果たして意味のある分布が得られるのかが問題になります。vMFでは\mux内積を考えていたので、クオータニオン内積が序盤で定義した回転の距離とどう関係するのか考えてみます。

回転R_1,R_2を単位クオータニオンq_1,q_2で表すこととすると、d(q_1,q_2)=f(q_1^*q_2)となります。そこで、q_1^*q_2を計算してみると次のようになります。
q_1^*q_2 = (s_1 s_2 + v_1 \cdot v_2) + (s_1 v_2 - s_2 v_1 - v_1 \times v_2)

この結果のスカラーの部分(\cos (\theta/2)相当)を見てみると、これはq_1,q_2を単純に四次元のベクトルとみなして内積をとったものに等しくなっています。というわけで、四次元空間でのクオータニオンふたつの角度の半分が、それらのクオータニオンが表す回転の角度になっていることがわかります。正直この辺のイメージはあまり鮮明ではないですが、一応めでたくvMF分布を使えそうです。

*1:複素数の虚部がベクトルになってるような感じです

しょぼいネットワークのレイテンシーについて

さて、oculus riftみたいなデバイスが手元にあると、いろんな場所から部屋の様子を(深度)カメラで撮りたいなーと思ったりするわけです。順当に考えると有線LANを使うのがよい気がしますが、気になるのは遅延です。そこで今回は1日ぐらいpingしつづけた結果を書こうと思います。

条件

私はネットワークは趣味じゃないので、使ってるスイッチングハブとかはcoregaとかIO DATAの1000円ぐらいのやつです。今回関係しそうな部分の接続を書くと以下のような感じになります。

これでpingを次の二つに送ってRTTを調べます。期間は1日ちょっと、負荷は特にかけずに「普通に」使用します。

  1. malkuth → daath
  2. malkuth → keter

今回気になるのは、ハードウェアに起因する遅延と言うよりも、特に何も考えずに設定したネットワークでDHCPの何かとかOSにかかる負荷みたいなソフトウェアに起因する突発的な要因がどれだけ悪影響を及ぼすかです。

結果

さて、結果ですが、先ほど挙げた順番通りに以下のようになりました。ちなみにketerの方は間の半日ぐらいスリープにしてたのでデータが抜けています。

まず気がつくのは、hop数が小さい(-keter)ほうがなぜか2倍ぐらい遅いということです。で、どちらも正規分布ではないです。今回の目的とは関係ないですが、遅延のプロファイルをすごく精密に調べると内部状態がちょっとぐらい分かるのかもしれません。

最大のRTTはどちらも2ms以下に収まってるので、全体の遅延の中ではかなり小さい部分しか占め無さそうです。

あとがき

SW起因の遅延を調べると言いつつ、pingはもしかするとわりと優先度の高いプロセスが処理してるんじゃないかと思い当たったので、実際はだいぶ違うかもしれません。