NRSDK(Nreal SDK) for Unity の新バージョン 1.4.8 の新機能を試す

AR グラス Nreal Light のアプリを開発するための NRSDK(Nreal SDK) for Unity のバージョン 1.4.8 がこの9月に出た。1.3.0 の次が 1.4.8 なのがちょっと謎。
Release Note のうち、機能追加にあたるのは以下の通り。

  • Added running state tips (temperature, battery, lost tracking notifications)
  • Added dynamic switch of 6DoF/3DoF/0DoF
  • Supported Nreal Dimmer
  • Added "IsTouching" API in NRInput
  • Adapted to smartphone screen resolution change when NRSDK is running
  • Adapted to Unity 2019.4 LTS

Unity 2019.4 LTS への対応を明示してくれるようになったのが地味に嬉しい。
Unity の特定のバージョン以外で NRSDK を使うと、アプリを起動してしばらくしたらメモリリークして落ちたりするので、Unity ガチャで NRSDK がちゃんと使えるバージョンを引くまで結構面倒だった。
2019.4 LTS と NRSDK 1.4.8 の組み合わせでは今のところかなり安定して使えている。

それ以外も地味な機能ばかり……。
Nreal のサイトに、まるですでに対応しているかのように書かれているハンドトラッキングは、いったいいつになったらサポートされるんだ。
AR グラスのインターフェースの本命はやっぱりハンドトラッキングだと思うので、今の画像タグや平面認識と同レベルでもいいから早いところ公開してほしい。

気を取り直して。
地味とはいえ、せっかくの新機能なので使い方をチェックしておきたいところ。
しかし Nreal のドキュメントは1日で読んで試し終わるくらいの分量で、新機能についての記述はなく。API Reference はカラッポ。
せめて NRSDK の github があればよかったのだが、いつまでたっても Coming Soon。

というわけで 1.3.0 と 1.4.8 の diff 見比べて、ソースを読み、新機能の使い方を洗い出してみた。
Nreal Dimmer はなんのことかわからず、IsTouching in NRInput はさすがに想像がつく(そして想像通り)。smartphone screen resolution change は純正 computing unit には多分関係ない?*1
そこで残りの running state tips と dynamic switch of 6DoF/3DoF/0DoF についてまとめる。

running state tips (temperature, battery, lost tracking notifications)

バッテリーの残量やグラスの温度について2段階の警告(Middle/High Level)を表示できる。トラッキング(SLAM State)は失っているかどうかだけで段階はない。

これを使うには、まず UI Canvas をシーンに追加して、そこに UI 部品の Text を2個(title, message), Image, Button を適当に配置する。Button は無くてもいいが、追加する場合は Nreal のドキュメント Controller の Building a Project with User Input にしたがって Canvas の構成をいじる。
この Canvas に "NR Notification Window" というコンポーネントを追加し、Icon/Title/Message/Confirm Btn にさきほどの Image, Text×2, Button をひもづける。*2

f:id:n_shuyo:20200928160829p:plain

NR Notification Window の Middle Level Info と High Level Info を開いて、Sprite/Title/Message に適当なアイコン画像やテキストを設定する。アイコン画像は NRSDK にそれっぽいのが含まれているので、そのまま使ってもいい。

f:id:n_shuyo:20200928161751p:plain

High と Middle の違いは、ソースやアイコン画像名(苦笑)を信じれば以下の通り。tracking では Middle Level Info しかつかわない。

Middle High
電池残量 40% 以下 30%以下
グラスの温度 45度以上 55度以上
ラッキング Lost ---

次に空のゲームオブジェクトをシーンに追加して(紛れなければ既存のオブジェクトでもOK)、Component に "NR Notification Listener" を追加する。
Low Power と SLAM State と High Temp のうち、使いたいものを選んで Enable にチェック、Notification Prefab に先ほどの NR Notification Window を追加したオブジェクト(今の場合は Canvas)を設定する。
ここで注意しないといけないのは、使わない通知項目も含めて3か所ともすべてに NR Notification Window を設定しないといけないこと。これを怠ると、NRNotificationListener.Awake() が NullReferenceException を吐いて通知機能が働かない(Nreal ぇ……)。

f:id:n_shuyo:20200928192318p:plain

これで完成。上述の条件を満たすと UI Canvas が表示され、各レベルに設定したアイコンやテキストが表示される。ボタンは High Level 時のみ表示され、これをクリックするとアプリケーションが終了する。
電池や温度は確認に時間かかるが、トラッキングはグラス前面のカメラをふさぐだけでロストしてくれるので、動作確認が楽。

f:id:n_shuyo:20200928193719j:plain

dynamic switch of 6DoF/3DoF/0DoF

サンプルの NRCameraRig をインスペクタで開いて、 "NRHMD Pose Tracker" コンポーネントの項目 "Tracking Type" から "Tracking 6/3/0 Dof" を選ぶことで、もともと静的にトラッキングモードを変更することはできていた。
バージョン 1.4.8 では この NRHMDPoseTracker に ChangeTo6Dof() などが生えて、これをアプリ実行中に叩くことでトラッキングモードを動的に変更できるようになった。使い道は……思いつかない(笑)。

この機能を使う最小サンプル。
以下のコンポーネントを作って、NRCameraRig に追加。

 public class ChangeTrackingMode : MonoBehaviour {
     private NRHMDPoseTracker tracker;
     void Start() {
         tracker = GetComponent<NRHMDPoseTracker>();
     }
     public void OnChangeTo3Dof() {
         tracker.ChangeTo3Dof();
     }
     public void OnChangeTo6Dof() {
         tracker.ChangeTo6Dof();
     }
 }

あとは UI Canvas に Change 6 DoF などのボタンを配置し、上で作った OnChangeTo6Dof() などに紐づければ OK。
3DoF にしたときの position は (0,0,0) になるみたいで、6DoF の状態で移動した後切り替えても初期位置に戻される。

*1:もし関係あっても、scrcpy の解像度が変わって嬉しいこと何もないだろうから、パスで。

*2:"NR Notification Window" を追加するのは別の GameObject でもよい。構成上、Canvas と "NR Notification Window" が一対一対応するので、Canvas に追加しておけばいいだろう。

AR グラス Nreal Light ファーストインプレッション

AR グラス Nreal Light の開発キットを1月に注文。
3月に届くはずが、コロナ禍のせい(だけかどうかわからないが)で遅れに遅れて8月にようやく到着。

f:id:n_shuyo:20200908163801j:plain

AR と言えば Microsoft の HoloLens が代表的な製品で*1、他に Magic Leap One などもあるけど、「電脳コイル*2を実現するには、メガネ型で、日常的にずっとかけていられて、子供が学校に持っていける価格でないといけない。

後述するが、Nreal Light(以降 "Nreal") もまだまだ全然ずっとかけていられる代物ではないけど、現行機(開発版含む)で唯一のメガネ型であり、グラス単体なら $500 程度と、ぎりぎり小学校に持っていけなくもない(例:牛革のランドセルは4~5万円)、という理想に一番近い(一応)、夢を見させてくれるデバイスである。
まあまだ夢なんだけど。

というわけで、まずはハードウェア周りのファーストインプレッションから。

かけてみて真っ先に気づくのが、ツルの幅が広すぎてずり落ちる。調整機能も見当たらない。
しょうがないので、ダイソーで「メガネストッパー」なる、メガネのツルに通すシリコン製の柔らかい爪を買ってきて Nreal のツルに頑張って付けた。
これが大正解。ずり落ちなくなって、しかも Nreal の重さを鼻だけではなく両耳の後ろにも分散できるようになって、掛け心地もかなり良くなった。電源を入れてなければ、1時間以上かけていてもしんどくない。
ただし、Nreal の左のツルからは本体であるコンピューティングユニットと接続するための約 1メートルの USB ケーブルが伸びていて、そのコネクタもふくめてメガネストッパーを通す必要がある。かなり時間のかかる作業で、ケーブルやその接続部分に負荷をかける可能性もあるので、ご興味のある方は自己責任で。

f:id:n_shuyo:20200908171014j:plain

映像の見え具合は、高精細で十分きれい。立体感もちゃんとある。
Nreal の映像の視野角は 52度あり、実際の見える範囲としては60センチ先の27インチディスプレイといったところ。それをはみ出した映像はぶった切られる。
現行機最大クラスの視野角だが、「拡張現実」と言い張るにはまだまだやっぱり狭い。現行 VR ヘッドセットで標準の 100~110度にはなんとか早いところ追いついてほしい。
またハーフミラー方式(実視界と CG をハーフミラーで合成)なためしかたないのだが、仮想の映像を透かしてその向こうの「現実」がうっすら見えちゃっている。いわゆるホログラム(特に映画やゲームにおける表現*3)を思い浮かべてもらうとわかりやすいだろう。

電脳コイル」では、電脳物質(仮想の物体やペット)かどうかをメガネをずらして確かめるというシーンがちょいちょいあるのだが、Nreal では仮想の映像と現実の区別がつかない心配はない。
視野角はともかく、「ホログラムっぽさ」はハーフミラー式ではどうしようもないので、それ以外の表示方式が安価かつ軽量で実現するのを期待するしかないだろう(ビデオパススルーか、網膜投影か、はたまたブレインマシンインターフェースか……って、そこまでいったらメガネいらん)。

f:id:n_shuyo:20200908173100j:plain

さらに 6DoF トラッキングの精度がまだ低いのか、体を動かすと映像がちょっとついてきてしまう。それどころか、自分が静止していても表示が微妙に動く。
Oculus Quest など現行の VR ヘッドセットでは、静止している仮想オブジェクトは自分がいくら移動してもその場に静止して見えるおかげで、物理的に質量を持って存在しているかのように感じさせてくれる。XR におけるトラッキングの重要性を改めて実感。
このあたりはソフトウェアアップデートによる向上を期待したいところだけど、Nreal のカメラは前面の前方向にしかついていないので、果たして。
とはいえ Qualcommチップセットにトラッキング技術を積んでくる気満々なので、こうした心配は今だけになりそう。

クアルコムVR/ARデバイスの新型リファレンスモデル スマホ接続と一体型モードを切り替え | Mogura VR
https://www.moguravr.com/qualcomm-vr-ar-reference-model/

多くの VR ヘッドセットと同様に近視の人が裸眼で見ることはできないので、視力矯正の必要がある。
形状的にリアルメガネと重ねるわけにもいかないので、視力矯正用レンズを付けられるようになっている。
標準で矯正用フレームのサンプルが付いていて、これをメガネ屋さんに持って行って自分用の矯正レンズを入れてもらうか、開発キットと一緒に売っている Lens Box($499) を購入する必要がある。Lens Box は度数 1.0 から 8.0 まで 0.5 刻みの 15×2 枚のレンズが入ってて、Nreal の内側にマグネットで簡単につけたり外したりできる。
周りのいろんな人に AR グラスを体験してもらうべく Lens Box もゲットしたのだが、コロナ禍のせいでその機会は当分難しそうだ。無念。

f:id:n_shuyo:20200908183535j:plain

この写真でレンズの上に少し平たい部分があるが、ここが主な放熱を担っているようで、稼働しているとかなり熱くなる。温度を測る機器を持っていないので正確な数字を出すことができないのだが、とても触ってはいられないほどである。
その状態でこのメガネを掛けると額のすぐそばに高熱源がある状態になり、連続利用は我慢して30分が限界だった(個人の感想です)。
メガネ側の仕事は映像のデコードとカメラのエンコードあたりか。そのため、ある程度熱を持つのは仕方ないが、顔に近い側で触れないほどの熱を持つのはさすがに困る。せめて顔から遠い側ならよかったのだが(Oculus Go のように)、前面にはカメラとレンズがあるから今の場所しかなかったということだろう。

連続使用を阻むのはこの熱問題だが、日常使用を妨げる問題がもう1つある。
Nreal を前から見ると、レンズの上半分にはカメラがある。その後ろには実は液晶パネルが下向きに入っており、ハーフミラーで実視界と映像が合成されるとてもシンプルな構造をしている。そのおかげで Nreal は他製品に比べて安価だが、レンズ部分にどうしても厚みが出てしまっている。

f:id:n_shuyo:20200820151923j:plain
問題は、このように上半分にいろんなモノが入っているため、実視界は下半分しかないという点である。Nreal をかけて道を歩くと、信号を見るために顔を上に向ける必要がある。車の運転はありえないだろう。
またハーフミラー式ならある程度は仕方ないものの、おそらくコストダウンか発熱を少しでも抑えるためあたりの事情で、実視界がかなり暗くなっている(その方が液晶パネルの輝度を上げなくても良い)。Nreal を掛けながら PC を操作するには、ディスプレイの輝度を最大近くしないと文字が読めないほどの暗さである。
視界の暗さは最初からサングラスかけてるつもりになればなんとかなる(かなあ……)としても、実視界の狭さは絶対に越えないといけないハードルなので、頑張ってほしいところ。
とはいえ、HoloLens がまさにその視界の広さ・明るさ(そしてレンズ部の薄さ)に技術をぶっこむことであの価格になっている様子を見ると、すぐに解決できる問題ではないんだろうなあ。

HoloLens光学系の謎に迫る
https://www.slideshare.net/AmadeusSVX/hololens-85758620

あとは、ここまでに書いたことと比べると些細な話だが、Nreal は公称 88g の「軽さ」をめちゃめちゃウリにしている。しかし実際に持って、掛けてみた感じはもうちょっと重く感じる。
また本記事ではほとんど触れなかった Nreal の本体であるコンピューティングユニット(こいつもめっちゃ熱くなる)は公称 140g なはずが、持った瞬間にわかる、それより絶対重い。
というわけで測ってみた。

f:id:n_shuyo:20200908191842j:plain

グラスの重さはケーブルを手で支えながら計測している。メガネストッパーも付けちゃった後なので、もう少し数字を割り引かれる可能性はあるが(鼻当ても外せるが、めんどくさくてやってない)、それでも 88g まで減るだろうか。
コンピューティングユニットはこれ以上外せるものは何もないので、140g は何かの間違いだろう(電池を除外した重量? 外せないが)。
それにしてもディスプレイもカメラもないのに重すぎ。Galaxy Note20 Ultra でやっと 208g なのに。

とここまで期待の裏返しで文句ばかり書いてしまったが、いろいろ割り引いた後でも十分楽しいデバイスである。
ソフトウェア面でもハードウェア面でも、VR における Oculus DK1 に相当すると言えば雰囲気は伝わるだろうか。Oculus Go/Quest のように人を選ばないレベルになるにはまだ数年かかるだろう。
そして「電脳コイル」のように日常的に使えるようになるのは何年後か。そういう夢を実物のデバイスを手にしながら語れるようになったのがすごいよね。

Nreal のソフトウェアや開発周りはまた別の記事で。

*1:単に AR というとポケモン GO なども含まれてしまうので、この場合 MR と言った方がいいのかも。

*2:電脳コイル」は、メガネ型のウェアラブルコンピュータが普及して、仮想の電脳世界が現実世界に交じり合った拡張現実が当たり前になった近未来(202X年)の地方都市を舞台に、主人公の小学生たちが日常の遊びの延長で電脳世界をハッキングしたり、破棄された電脳世界に隠された秘密を探したり……という名作アニメ。一言で説明するの、難しいな。https://www6.nhk.or.jp/anime/program/detail.html?i=coil

*3:スターウォーズのホログラムや、「ホライゾン ゼロ ドーン」のフォーカスでみるホログラフィックなどなど

立体視できる図を R で描く

拙著「わけがわかる機械学習」では、2次の標準正規分布のグラフを平行法で立体視できる図で掲載しています。
平行法を知らない人、知ってても苦手な人には申し訳ないですが、ちょっとした遊び心ということで許してください。

f:id:n_shuyo:20190924122321p:plain

人間は右目からと左目からの視差(見え方の違い)によって立体を認識する能力があります。
上の図は一見同じ絵が2枚並んでいるように見えますが、実はこの右側は右目用の、左側は左目用の絵となっており、重ねてみるとちょっとだけ違っていることがわかります。*1

f:id:n_shuyo:20190924122337p:plain

平行法とは、左の図を左目で、右の図を右目で見ることで立体を認識する方法です。
簡易なやり方は、遠くを見て、目の状態を維持しながらその視線に図を割り込ませて、図が3つに見えるように中央の図に焦点を合わせます。

上の図をうまく平行法で見ることができると、標準正規分布が存在感のある立体図形として見えてきます。
少々コツと練習がいるため、誰でもできるわけではありません。立体に見えなかったらすいません……。

PC などの画面上でも平行法による立体視は可能ですが、平行法をやりやすい2枚の図の距離は人によって違います*2
ブラウザの表示倍率を変えて2枚の距離を調整すると、平行法が苦手な人でもうまくいく可能性がでてくるかもしれません。

VR のヘッドセットは両眼それぞれ用のディスプレイを備えて、レンズを使って焦点が自然に合う位置にそれらをみせることで、平行法のような特別なスキルを身に着けなくても立体視することができるようになっています。


さて、「わけがわかる機械学習」の図はごく一部の例外を除いてほぼ全て R で、しかも ggplot2 などのリッチなパッケージではなく plot や lattice などの標準的な機能で描いています。
上の立体視用の図もやはり lattice パッケージの wireframe で描いています。
誰の役に立つノウハウかよくわかりませんが、この立体視できる図を R で描く方法をメモしておきます。

ポイントは lattice パッケージの screen パラメータで視点の方向を指定するときに、少し角度を変えて右目用と左目用の画像を描画することです。
例えば wireframe なら

wireframe(z, screen=list(z=26,x=-60)) # 左目用
wireframe(z, screen=list(z=24,x=-60)) # 右目用

のように z に与える値を 1~3 ほど変えることで、ちょうど両眼の視差に対応する図を描画できます。

wireframe が自動的に描く軸のラベルや方向を表す矢印、外枠は立体視にはジャマですし、余白も広すぎるので、そこらへんを調整しつつ、1枚の png ファイルに両眼分を出力するコードは以下になります。

x <- seq(-3, 3, length=30)
z <- outer(x, x, function(x,y) exp(-(x^2+y^2)/2))

library(lattice)
par.set <- list(axis.line=list(col="transparent"), clip=list(panel="off"))
wf <- function(a) wireframe(z, screen=list(z=a,x=-60), 
                par.settings=par.set, xlab="", ylab="", zlab="", zoom=1.2)

png("normal-dist-3d.png", width=1100, height=600, res=200)
print(wf(26), split = c(1,1,2,1), more = TRUE)
print(wf(24), split = c(2,1,2,1))
dev.off()

*1:立体視の方法は様々あります。アナグリフ(左右に赤青のフィルムを貼ったメガネで見る)は古典的な方法ですが有名でしょう。この図はアナグリフをイメージして赤青にしてみましたが、手元に赤青メガネがないので、実際に立体に見えるかはわかりません……。

*2:左目と右目の距離(瞳孔間距離)以上に離すと、平行法が得意な人でも難しくなります。

深層学習やプログラミングについては書かれていない「わけがわかる機械学習」

引き続き、確率の話が 1/3 もある入門本「わけがわかる機械学習」の宣伝エントリです。

2012年に深層学習が大規模画像認識コンペ(ILSVRC)で圧勝して以来、「機械学習をやりたい」という人より「深層学習(ディープラーニング)をやりたい」という人のほうが年々増えているように実感しています。
しかし「わけがわかる機械学習」という本には、深層学習に関する記述をかき集めても精々3ページほどしかありません。

また、ここ数年に限っても機械学習や深層学習の入門本は数え切れないほど出版されています。そうした入門本の多くは、本に書いてある通りにプログラミングするだけで、性能がそれなりに高い予測器や分類器を実装して動かすことができる優れものです。
強力で便利な機械学習や深層学習のライブラリを使うため、難易度もそれほど高くありません。

しかし「わけがわかる機械学習」に書かれているプログラミングの話題は、オーバーフローに気をつけましょうという短いコラムが唯一あるのみです。

他にも、紹介されているモデルはとてもベーシックなものに限られますし、データの集め方(前処理含む)や機械学習の裏付けとなる理論といった、実践に必要になることの多くも書かれていません。
もし「機械学習(深層学習)を使ってみたいから、何か1冊だけ読もうかな」というなら、この本はおすすめできません。

では、流行りのことが何も書かれていない「わけがわかる機械学習」には一体何が書かれているでしょう。

例えば1.2節では、機械学習で最も重要な概念である「モデル」とは何かを説明しています。
数式も出てこない長くない説明ですから、その部分まるっと引用してしまいましょう。

先ほど、機械学習ではモデルが重要であるという話をしました(※注:直前の節にて、機械学習とは「モデルを決めて、データに合うパラメータを探す」枠組みであるという話をして、続いてモデルを説明しています)。しかし、モデルとはいったいなんでしょう。「解きたい問題を数値で扱えるようにしたもの」では、わかるようでわかりません。

モデルとは、直訳の模型という意味のとおり、「何かの偽物」であり、本物そのものではありません。しかし偽物ならなんでもよいわけではありません。

例えば人体模型は人間の体の偽物です。本物の人間の体と見間違う心配がまったくないくらい似ていません。人体模型はパーツを取り外して手にとって見るなど、人体の内部構造がどうなっているか観察できるという点で役に立ちます。本物の人間の体でそんなことは軽々しくできません。

このように本物ではないが、特定の目的において(ときには本物より)役に立つ偽物がモデルと呼ばれます。ほとんどのモデルは何かひとつふたつの目的に特化しており、それ以外の部分を似せることは最初からあきらめています。

本物が持ついろいろな側面のうち、どれをどのように似せるかによってさまざまなモデルが作られます。つまり、モデルを設計したときに、そのモデルがなんの役に立つかはおおよそ決まります。そのため、モデルが役に立たないと感じる場合は、モデルが悪いのではなく、間違ったモデルを選んでしまったためであることがほとんどです。人体模型に「こいつ、歩かない!」と文句言ってもしかたないですよね。歩いてほしかったら、例えば2足歩行ロボット(これも人体のモデルのひとつ)を選ぶべきだったのです。

ちなみに、機械学習の「モデルを決めて、データに合うパラメータを探す」という枠組みも、人間の『学習』のうち、結果を見て行動を修正するという一面を真似したモデルです。この枠組みはとても役に立ちますが、人間の『学習』の「できないことをできるようにする」という機能を期待するのは、人体模型に歩くことを期待するくらい間違っています。


モデルは、ありとあらゆる機械学習の本に登場するだろう最重要概念の1つです。
ところがほとんど機械学習の教科書や入門書においては、モデルという言葉をちゃんと定義してくれればまだ良い方、天下りに名前だけ登場することもあまり珍しくないでしょう(例:モデルが何かの説明がないまま、モデル選択の話が始まる)。*1

そこで、いい本が他にいっぱいある深層学習やプログラミングはまるごと他の本に任せてしまって、「機械学習に出てくる○○はなぜそうなっているのか・そうしてもいいのか」という他の本にあまり(ほとんど)書かれていないことに集中したのがこの「わけがわかる機械学習」です。

他にも例えば、3.4節と続くコラムでは、集合や分布の「散らばり具合」を表す値として「中心に平均を、離れ具合に差の2乗を使った」分散がよく使われる理由を説明しています。

4.2節では、最小二乗法(や線形回帰)がどのような仮定のもとに成立しているかを明らかにしています。

4.5節と4.6節では、正則化が「何を正則に(正しく)しているか」を紹介し、それによって過学習が抑えられる(と考えて良い)理屈を解説しています。

6.1節に続くコラムでは、「ノイズは正規分布に従う」とよく仮定しますが、なぜその仮定が許されるか、その考え方の1つを述べています。

いずれも機械学習で基本的な知識や技術であり、「こういうときにはこうする」のベストプラクティスだけでも使えなくはないです。
しかし学んだことを応用して新しくモデルを拡張したいとき(あるいは簡易化したいとき)には、「なぜそうなっているのか」の理解は重要なポイントになるかもしれません。

深層学習ブームの中で、機械学習を一切学ばずに深層学習のライブラリでモデルを作って実験や研究、サービス開発が行われることも増えてきているように感じています。
しかし、すべてのモデルはその前提となる仮定を持ち、そしてすべての仮定には必ず都合と不都合があります。
それは深層学習だって例外ではありません。強力で使いやすいライブラリとわかりやすい(試しやすい)本という高速道路ができても、その部分の必要性はまったく変わっていないはずなんです。

とまあ、ちょっと急に使命感っぽいものを振りかざしたりしてみましたが、単純に「納得しながら次に進む本」として「わけがわかる機械学習」を一度手にとって見てもらえると嬉しいですね。

*1:教科書は限られた紙幅の中で知識を網羅的・体系的にまとめることが優先ミッションなので、言葉や定義が天下りに登場するのはある程度仕方ない面はあります(識別子として機能していればOK)。

機械学習の本なのに、なぜか確率の話が 1/3 を占める「わけがわかる機械学習」

「わけがわかる機械学習」という本を書きました。
一言でいうと、「機械学習はなぜそんなことをしたいか・してもいいか」を解説する入門本です。

わけがわかる機械学習 ── 現実の問題を解くために、しくみを理解する

わけがわかる機械学習 ── 現実の問題を解くために、しくみを理解する

目次を見るとわかりますが、機械学習の本をうたっていながら、なぜか確率の章が 3 個もあります。ページ数にして約80ページ。全体の 1/3 が確率の話です。

- 0章: はじめに
- 1章: 機械学習ことはじめ
- 2章: 確率
- 3章: 連続確率と正規分布
- 4章: 線形回帰
- 5章: ベイズ確率
- 6章: ベイズ線形回帰
- 7章: 分類問題
- 8章: 最適化
- 9章: モデル選択
- 10章: おわりに
- 付録A: 本書で用いる数学

たしかに確率は、統計や機械学習でとても重要です。
しかし同じく重要な線形代数や解析(微積分)は付録の章(わずか8ページ)にまとめられており、確率へのエコヒイキが目に余ります。

この扱いの偏りの理由は「線形代数や解析はいい本がいっぱいあるから」です。

いや、確率論も本いっぱいありますよ?
でも確率論を学ぼうと思って教科書を開くと、最初のページでいきなりσ加法族なる謎の概念に困惑し、続けて極めて抽象的な確率空間が定義された瞬間に思わず本を閉じ、「何も見なかったことにしよう」とラノベくらいでしか見たことのないセリフをリアルでつぶやくことができます*1

正直、近代確率論は抽象化しすぎなんですよね。群や位相空間で数学の抽象的議論に慣れている人でもちょっと厳しめですから、機械学習や統計をやりたいだけの人にはぶっちゃけオーバースペック過ぎです*2

かといって、易しい本を探すと今度は易しくなりすぎて、「サイコロを振って 1 の目が出る確率は 1/6」「同様に確からしい」といった高校確率(古典確率)の延長から急にモンティ・ホール問題*3に飛んだりして、「これがベイズだ!」とか言われたってさすがにそれでは何もわかりません。

ベイズ確率や連続確率をちゃんとわかって使うには、確率を「枠組みとしての確率」(公理的な確率)と「モデルとしての確率」(古典・頻度・ベイズ)に分離することがどうしても必要になります。
その「統計や機械学習を使う人にとって最低限必要な確率の抽象化」をボトムアップに導くことがこの「わけがわかる機械学習」の確率編のテーマになっており、そのため紙幅が費やされることになりました。

また実は機械学習も「目的関数を最適化する枠組み」と「線形回帰や分類問題などの個別のモデル」という構成を持っており、枠組みに対する理論や手法が複数のモデルで共通に使われるという点で、確率と機械学習は相似形をなしています。
確率という分野は線形代数や解析とはその点で本質的に異なっており、本書での扱いの手厚さの違いはここから来ていると言うこともできます。

って、機械学習の本なのに確率パートの話ばかりになっちゃってますので、機械学習パートの特徴についてはまた別途紹介します。

*1:そこをかろうじて乗り越えた人の多くも、確率変数の定義で心折られるでしょう……。

*2:機械学習や統計でも、理論屋さんは測度論や確率論を避けられませんけどね!

*3:モンティ・ホール問題は有名な「勘違いしやすい問題」。ベイズ確率で説明できることからベイズの利点の1つとしてよくあげられますが、ベイズを知らない人には説明に納得感がなく、問題自体もベイズを使わないで説明したほうがよっぽどわかりやすいという、もはや存在が地雷となりつつある問題……。

ワイドアパーチャ写真から 3D モデルを生成

VR180 という規格がある。端的に言うと、「180度の視野がある 3D (両眼立体視)」と解釈できるためのメタデータ作法。
QooCam と Insta360 EVO という VR180 カメラを手に入れて、身近な出来事いろいろを VR180 写真や動画で撮りまくっている。

f:id:n_shuyo:20190808185306j:plain
QooCam と Insta360 EVO (とオマケの THETA V)

で、使えば使うほど VR180 は過渡期の技術だなあと実感している。
今は VR/AR 向けの手軽な映像記録手段に VR180 と 360度カメラの他にないから、しばらく使い続けるけどね。

最大の問題は、視点が固定されてしまうので 6DoF VR や AR(MR含む) で価値を発揮できないこと。
もう一つの問題は、(少なくとも今ある全ての VR180 カメラでは)瞳孔間距離に相当する2眼のレンズの距離が固定で、近い距離(50cm以内)の被写体をうまく立体視できるように撮影しづらいこと。

ただでさえ立体視が意味のある距離は狭いのに(遠いと視差が発生しない)、近い方もうまく撮れないのはかなり痛い。手の届く範囲こそ立体視したいのに…。
ほとんどの VR180 カメラはおのおののギミックで 360度カメラに変形できるんだけど、撮り比べたらどうせ THETA の圧勝(スティッチ精度とかジャギー解消とか、拡大してみたらほぼ一瞬でわかる)なんで、そっちは360度専門のカメラに任せて、近距離撮影用にレンズ間距離を調整できる機能とかの方がよっぽど欲しい。後処理の都合で固定せざるをえないんだろうけど……。

これらの問題を根本的に解消するには、やはり 3D モデルとして撮影できるようになる必要がある。簡易でもいいので。
具体的には、深度センサを含むマルチカメラで撮影したらその場で glb などの可搬性が高くてテクスチャも包含する 3D モデルデータが生成される手法に VR180 は置き換えられると予想している(ちなみに 360度カメラは、空気遠近法が表現できるほど高解像度になる方向で生き残ると睨んでいる)。
その点 Huawei P30 Pro(3光学+1TOFカメラ)には大いに期待しまくってたんだけど、技術の場外戦で先行き不透明……。

と、ここまでが前置き。

iPhone 8 以降のインカメラや Huawei のここ数年の機種など、いくつかのスマホは深度情報を含んだ写真を撮影できる。
そしたらその深度情報から 3D モデルを生成できるよね! やってみた! という話がこの記事の本題。

f:id:n_shuyo:20190816201209j:plain:w400

対象は Huawei のワイドアパーチャ(撮影後に焦点距離を変更できる)で撮影した写真。
ワイドアパーチャ写真は、一見は最近流行りのポートレート写真(人物や対象を強調するため、それ以外をぼかした写真)に見えるが、先頭以外のフレームにパンフォーカス(手前から奥まで焦点があった)写真と深度情報が格納されていて、編集時には該当する深度以外をぼかすことで焦点距離の変更をエミュレートしている。
既存の Huawei スマホは TOF センサなど積んでおらず、深度の情報はマルチカメラによる僅かな視差(1cmくらい……)と機械学習で推定していると思われる。単色無地の壁とか奥行き均一(z方向に垂直な面)になっちゃうし。

深度情報の取り出しは次のブログやコードを参考に。

Huawei Mate 10 liteで撮った画像から深度情報(Depth of Field・Depth Map)を取得する方法 - Qiita
https://qiita.com/kotauchisunsun/items/d5c2b73ec3d76d159449
jpbarraca/dual-camera-edof
Extract the EDOF from photos obtained from Dual Camera smartphones:https://github.com/jpbarraca/dual-camera-edof

実際のコードがこちら。

撮影したワイドアパーチャ写真データ(Google Photos とか通すと必要なフレームやメタデータが落ちるんで、元のファイルをそのまま持ってこないとダメ)を edof.py に食わせると、model ディレクトリに obj と mtl とテクスチャ画像を出力する。
動作確認したのは手元にある nova lite 2 だけだが、深度情報取り出しロジックは上のブログの記事と同じなので、たぶん Mate シリーズや P20 とかで撮ったワイドアパーチャでも動く、はず。
深度情報は、nova lite 2 の場合、612x816 の unsigned char で格納されており、各値が元の写真と縮尺を合わせた場合の各領域の深度(0~255)を表している。
写真画像は重力の方向が下になるように勝手に回転してくれるが、深度情報はなぜか回転してくれないので、向きにあわせて自前で回転する必要がある。


出力された obj には法線ベクトルを計算するのをサボって付けていないため、Blender などで一度読み込んで法線ベクトルを付加しないと Unity や WebGL などで正常に表示されない。
Blender なら obj をインポートし、オブジェクトを選択(右クリック)、編集モードに移行(TABキー)、タブの「シェーディング/UV」を開いて、法線「再計算」ボタンをクリック、オブジェクトモードに戻って(TABキー)、obj なり好きな形式でエクスポートすれば OK。
裏表が逆になることもちょいちょいあるので、そのときは編集モードで「メッシュ>面>面を反転」して、エクスポートし直し。

奥行きは 0~255 の離散値なので、適当にスケールした程度で z 軸の値に使ったらガタガタのレゴブロックにようになってしまう。
そこで畳込みでぼかしを与えている*1。与え具合は --nconv と --range というオプションで指定できる。--range はもとの値からどれだけの移動を許可するか、--nconv はぼかし処理を何回行うかの指定で、思いっきりなめらかにしたければ --nconv 20 --range 10 くらいを指定しておけばいいだろう。
レゴブロックを見たかったら --nconv 0 で。


f:id:n_shuyo:20190816171617j:plain:w400

nova lite 2 で撮影したワイドアパーチャに対する作例をいくつか示してみる。
近距離の対象+背景(いわゆるポートレート写真)あたりだとそれなりなモデルを生成する。わずかな視差が多少有効かつ、奥行き推定モデルもそういうデータを一番たくさん食べてるんだろう。
f:id:n_shuyo:20190816201128j:plain:w300f:id:n_shuyo:20190816201149j:plain:w300

そこから離れるほど、まあ言葉飾らず言えば、悲惨になっていく。
近距離背景+オブジェクト(静物画みたいなやつ)はまだまし。
f:id:n_shuyo:20190816201110j:plain:w400f:id:n_shuyo:20190816201116j:plain:w300

中距離になるとかなりつらい。
このあたりを結果が書き割りのようになっているのを見ると、基本的には写真を領域に分割し、それぞれの奥行きを個別に推定しているんだろうなあという処理が見える。
壁がおかしいことになっているのは、単色無地で奥行き推定に失敗しているからだろう。
f:id:n_shuyo:20190816201047j:plain:w300f:id:n_shuyo:20190816201058j:plain:w300

奥行きのある廊下とかもダメ。
f:id:n_shuyo:20190816201014j:plain:w300f:id:n_shuyo:20190816201026j:plain:w300

遠距離もダメダメ。
f:id:n_shuyo:20190816200741j:plain:w300f:id:n_shuyo:20190816200759j:plain:w300


最後のはともかく、廊下くらいまでは TOF カメラを積めばもうちょいマシなものがでてくるんじゃないかと期待している。
P30 Pro (ないし Mate 30 Pro) は日本でもちゃんと出るんだろうか……。出初めは高すぎて手出なさそうだけど(苦笑

*1:ガウシアンフィルタによるぼかしも一応実装してみたが、あんまり良い感じにならなかった。試してみたければ --gfilter オプションで。

A-Frame で glb を表示すると暗くなる件

Oculus Go が出てから WebVR ( A-Frame ) でいろいろこっそり作ってみている。
ノウハウもそこそこ溜まってきて、アウトプットしようかなと思いつつもサボりっぱなし(苦笑

いろんな角度から撮った写真から 3D モデルを生成する photogrammetry ツールの 3DF Zephyr が楽しくて、3D モデルにしたらおもしろそうと思うようなものを写真取りまくって試している。
たとえばお寿司。

 


Photogrammetry(3DF Zephyr)でお寿司回してみた

 

こうした 3D モデルはなにがしかのフォーマットで保存するわけだが、とりあえず最初に使った obj+mtl 形式をずっと使ってきた。
後で調べると、まあずいぶんわんさかとある( Template:3Dファイル形式 - Wikipedia )。どれがいいのか比べて検討してみたが、obj+mtl をサポートしていないツールって基本無くて、交換用フォーマットとしてはなんだかんだ一番ポータビリティ高いのかも、と思ってたりする。

glTF は次の標準を狙っているということと OpenGL に最適化しているということで気になったけど、Blender がエクスポートできるのにインポートできない(要別途プラグイン)など、意外とサポートしてないツールがちょいちょいあるのが難点。
そして、A-Frame はちゃんと glTF をサポートしているのだけど、これで gltf/glb を表示するとなぜか暗くなるという現象があって、使うのをやめていた。


同じ 3DF Zepher からエクスポートした obj と glb(glTF のバイナリ形式) を A-Frame で表示すると見た目がこれくらい変わる。
モデルはサイボウズのエントランスにいるキリンのぬいぐるみ(「こきりんとーん」という名前)を 3DF Zephyr で 3D モデル化したもの。
(真ん中に浮いてるキューブは、後の対策で色がどう変わってしまうか見るために入れている)

 
f:id:n_shuyo:20181128161746j:plain

 

ライティングをいろいろ工夫したがなんともならない。directional light をあてても暗いままなので、どうも「暗い」と言うより「黒い」らしい。
3DF Zephyr で出力したものが悪いのかと、obj を Blender や MS 3D Builder で読んで glb にしても症状変わらず。
glb を A-Frame で表示したいなんてすごくよくある話だと思うんだが、ネットで探してもそれらしい情報はなぜか出てこない。手詰まり。

というわけで glb の使用はあきらめていたのだが、obj と glb の Oculus Go でのロード時間を比べると、大きいときは glb が 300ms くらいのときに obj+mtl が 3000ms と10倍から違う。*1

このモデルのロード(正確にはおそらくグラフィックメモリへの転送)中は完全に止まってしまう。初回表示時の 1回だけとはいえ 3秒待ちは体験を大きく損ねる。

そうなると「 glb 暗くなる問題」をなんとか解消して glb を使いたいところ。本腰を入れて探して、 Three.js の issue でようやくそれらしい情報に突き当たった。

GLTFLoader display model is darker · Issue #12554 · mrdoob/three.js · GitHub

長いんでテキトーに要約すると、まさに gltf だと暗くなるよ問題で、同様に glb を作るツールによらず起きるので データの問題ではないと報告。にもかかわらず、これ Three.js のせいじゃないよと close されたあと、問題を再認識して、"So... what's going on here?" で終了。えー困るー。

ただここには renderer.gammaOutput = true; したらいいよ、という情報も書かれていた。Three.js の renderer は、A-Frame では <a-scene> からアクセスできる。具体的には、

document.querySelector("a-scene").renderer.gammaOutput=true;

を </body> の後ろとか、ページの表示完了イベントとかで実行すればいい。

上のキリンで実行すると次のようになり、 glb モデルが期待する明るさになった!

 

f:id:n_shuyo:20181128161752j:plain

ただし見てわかる通り、確かに glb キリンは元の obj キリンと同じ明るさになったが、glb 以外のオブジェクトが明るすぎる……。

まだこの新しい問題は解決できていないが、「どうやっても暗い(黒い)」と違って対処のしようはあるので、一歩前進?

 

*1:同時表示可能オブジェクト数でも glb のほうが若干 obj より有利。といっても本当に「若干」で、巨大なテクスチャのモデルについて、obj だと12個表示したらブラウザが落ちるか VR モードが強制解除されるところ、glb だと13個表示できる「ことがある」くらいの差……。