Scaffold開発日記1: 万能機械に向けて ~ モジュラーロボットの現状と失敗例

万能機械?

ターミネーターの液体金属的なロボットとか、いろんなSFにでてくるナノマシン霧みたいなものに昔から憧れています。

これらの機械の万能性は以下の特徴をもってある程度定義できると思います。

  1. 任意の形状に自力で変形できる
  2. 各部分の機能(力の発生・化学反応の誘導・センシングetc)を任意に変えられる

任意といっても(フィクションの中ですら)技術的な限界がありますが、とりあえず何10kgか万能機械を部屋に放り込んでおけば普通の人間ができそうなこと(道具を使ったり、モノを運んだり)は何でもリモートでできる、という水準にすら達成していないのが現代の人類です。

実在するモジュラーロボット

形状の変化と、それを使った移動についてはモジュラーロボットという分野でそこそこの研究が行われています。

個人的に一番感動したのがMITのM-Blocksで、立方体が飛び跳ねたりくっついたりします。

www.youtube.com

問題がいくつかあって、全てのユニットがモーター・バッテリー・通信機能を備えたアクティブな素子なのでコストが高い(ひとつ数万円)という問題と、立方体がすべての空間を埋めてしまうので付加機能をつける余裕が無いという問題があります。

こういうのは最低でも1000個ぐらいないと面白い形が作れなさそうなのでシンプルな構造で安いのは重要です。

再構成の要素

モジュラーロボットをいくつか見ていると、その実装は以下の二つの機能に抽象化できます。

  1. 1ユニット長以上離れた場所から近づく
  2. 近づいたら解除可能な結合(数ユニット重を支えられる)を形成する

これらの機能をいつアクティブにするかを制御すると形状の再構成ができます。

 

M-Blocksの場合、1は内部のフライホイール+ブレーキ+表面の磁石で、2はElectro-Permanent Magnetで行っています。フライホイールを回してそれに急ブレーキをかけて跳躍、表面の磁石で向きがそろったタイミングを見計らって結合を生成するという動作の繰り返しが基本になります。

Electro Permanent Magnetというのは電磁石の一種ですが、普通の電磁石が常時通電しないと磁力を発生しないのに対して、パルス状の電力をかけて磁化を変化させることで磁力を継続的に発生させるものです(構造上、理想的にはネオジム磁石の半分ぐらいの吸着力がでます)。これは可動部が全くなく切り替えも高速なので(たとえば機械的なラッチ機構に対して)優位な場合もあります。

 

↓の人がモジュラーな構造について博論を書いていて、そのなかに結合方式のおもしろい比較があります。

f:id:xanxys:20171230100024p:plain

http://cba.mit.edu/docs/theses/10.06.knaian.pdf Table 7.1の引用 

歴史: Simplex (2016/9~2017/1)

ここからは私がやったことの紹介になります。

 

とにかく安くしないといけない、ということでアクティブな(通信とかできる複雑な)要素を減らして、パッシブな要素を増やしていこうということで、最初に思い付いたのがこの図のようなアーキテクチャです。

f:id:xanxys:20171230110938p:plain

 

非通電状態のユニットがいっぱい(理想的には砂のように) 入っている容器があって、そこに専用の杖型デバイス(電力と情報を供給する)を入れて引き上げると、引き上げる段階で結合を行って形状を構成するというものです。

引き上げというユニット長に比べてはるかに大きい動作を人力で行うことで、要素1:離れた場所から近づく が0.5ユニット長ぐらいの引き寄せ(電磁石でできそう)に簡略化できて、2:結合 は低融点合金の融解でできる(電気的なコネクタとしても使える)という目論見でした。

単純な形状のほうが向きの組み合わせが少なくなると思い、正三角形が1ユニット、正三角形4つと8つでできる正四面体と正八面体を立体の構成要素とするのが最初の実装です。

f:id:xanxys:20171230111029j:plain

(ネオジム磁石で結合して手で組み立てた例)

各エッジに円筒型のネオジム磁石(半円側がN、反対の半円がS)が回転可能な状態で入っていて着脱可能です。

f:id:xanxys:20171230111127j:plain

(磁石のおもちゃで様子を見ている図)

四面体と八面体以外は作れないと思っていましたが、正三角形だけでできる立体は

デルタ多面体 - Wikipediaと呼ばれているらしく、いろいろな形が実現できるというのはおもしろい発見でした。

 

正三角形のユニットはミニマルで美しいし、筋はよいと思ったのですが、

  • 直交しない要素が多いとCADが使いにくい
  • 平面を基礎要素とするとエッジの部分にすべての機構を詰め込む必要がある

ということで、立方体を基本要素にしようと方針転換しました。

f:id:xanxys:20171230111745j:plain

(2cm角の電磁石入り立方体)

そろそろ要素1:離れた場所から近づく ができるかどうか試してみようと思ったのですが、電磁石が思ったより圧倒的に弱いという致命的な問題が発生しました。

電磁石-電磁石間なら数cm離れていても大丈夫なんですが、片側は通電されてない状態で、永久磁石を使うわけにもいかないので(永久磁石どうしでくっついて外れなくなる)、電磁石-磁性体で吸着する必要があるのですがこれがすごい難しい。

f:id:xanxys:20171230104855p:plain

 (2mm×7mmの磁石と鉄の磁界シミュレーション)

磁力は磁界の勾配に比例するのですが、見ての通り磁力線は距離に対して4~5乗(精密な値は忘れた)で減衰するので2cmも離れると全く動きません。使える体積が限定されているので、磁石を大きくすれば解決するというものでもありません。

最終的には1cmの電磁石に50Wかけてみてわずかに震える程度だったのでこのアプローチ(電磁石で引き寄せる)は諦めました。電磁石は0.5ユニット長以上では役に立たないというのが収穫です。

 

歴史: Scaffold (2017/2~現在)

遠隔力ではなく、機械的に接触しつつ移動するのが確実だろうと思い、三次元のモジュラーな格子とその中を移動して格子を組み立てるロボットというアーキテクチャに方針転換しました。*1

f:id:xanxys:20171230111802j:plainf:id:xanxys:20170312235930j:plain

立方格子と立方体型ロボット (2017/2~2017/4)

試行錯誤の結果、エッジとエッジ2つを結合するコネクターへの分割ができそうということは分かった(↓)のですが、

f:id:xanxys:20170314235436j:plain

  • 自重を常に支えられて
  • 三軸に移動できて
  • ロッドとコネクターを持ち運ぶ/取付できて
  • 単位格子に収まる

f:id:xanxys:20171230121019p:plain

(部品をCAD上で並べて体積の無さに頭を抱える様子)

磁石の体積と磁力のような物理的なトレードオフの壁に突き当たったというわけではないので、アクチュエーターが10個程度、パーツ数が100を超えるような高度な機械であれば可能だと思うのですが、その当時の私には設計/製作スキルがなかったのと、あとすべての軸に強い力を発生させるのは無駄に感じました。

三次元レール  (2017/4~現在)

1軸にだけ強い推進力を持っていて、他の動作は補助的なアクチュエーターで行うという電車のようなシステムが実績もあってよいのではないかということで、三次元空間を自由に移動できるモジュラーなレールをいろいろと3Dプリントして検討してみました。

f:id:xanxys:20170401172009j:plain

 

最終的に落ち着いたのが以下の三要素です。

f:id:xanxys:20170331224117j:plain

  • 直進 *2
  • ロール方向90°回転
  • ヨー方向90°×N回転 / 4-分岐

この3要素があれば次のような複雑な三次元構造でも生成可能です。

f:id:xanxys:20171230123537p:plain

あとはレールを持ち運びながらこの上を移動できて固定と取り外しができるロボットを作るだけ(簡単とは言っていない)です。

 

2017年の進捗を2分弱の動画にまとめたので見ると分かりやすいと思います。

www.youtube.com

今のところの感想・展望

理論的には動きそうな機構を考えるだけでも結構大変ですが、実際に安定動作させるには大きさ・信頼性・複雑性の色々なトレードオフがあります。動画を見るとわかりますが、現状ではとにかく全ての動作の信頼性が低いです(レールに引っかかる、ねじ穴の位置合わせに失敗する、etc)。

普通の機械だと大きく重くすることで剛性と精度に関する問題は解決すると思うのですが、レールとロボットが相互に制約を持つので悪循環に陥るケースが多いです。

例えば、ロール方向の角度安定性を上げようと思って、レールを太くする→レール持ち上げ機構が大きくなる→トルクが足りない→モーターを大型化する→重心が偏って角度安定性は改善されない。のようなパターンが頻発します。

 

とはいえ最近はFDMプリンタ(up mini2)とSLAプリンタ(Form2)に加えてCNCフライス(KitMill Qt100)も買ったので、材質の改善でどうにかなるかもしれません*3

今はついにヨー方向の回転(ターンテーブルを自力で回す機構)とロール方向の回転を互いに干渉せずに行う機構を思いついた(↓)ので、地道にフォールトモードを潰していけば動くはずです。

gyazo.com

 

*1:プロジェクト名(Scaffold)は足場という意味ですが、これは工事現場でよく見かける鉄パイプ製の足場をイメージしてつけています

*2:実は"ヨー方向90°×N回転"のサブセットですがシンプルなので残してあります。

*3:Form2は造形精度は非常によく、剛性も許容レベルですが、耐クリープ性がかなり悪いです

壁に優しい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:複素数の虚部がベクトルになってるような感じです