立体視できる図を 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:左目と右目の距離(瞳孔間距離)以上に離すと、平行法が得意な人でも難しくなります。