第5回から引き続き、測域センサを用いたコンテンツの制作を進めていきます。今回はプロジェクタによる投影位置をより使いやすいよう調整します。
TEXT_高田稔則 / Toshinori Takata(Codelight)
EDIT_小村仁美 / Hitomi Komura(CGWORLD)
<1>プロジェクタの投影サイズとUnity上の表示サイズを合わせる
こんにちは、高田です。番外編を1回挟んでしまいましたが、今回は引き続き北陽電機の測域センサを活用したコンテンツ制作を進めていきます。3月初めに事務所を引っ越し、作業スペースが大きく取れるようになったので、プロジェクタなどを利用した場所を使うコンテンツがかなり作りやすくなりました。
前回のソースで基本的な位置検出はできました。しかし、実際に試してみるといくつか使いにくい部分があるため、それを修正していきます。今回説明するコードもGitHubにプッシュしてあります。
●今回のコードhttps://github.com/toshinoritakata/URG
測域センサを使うコンテンツで一番多いのは、壁に手をかざすとそこに何かの反応が起こるパターンです。これを実現させるためには以下の3つの要素を合わせる必要があります。
1.プロジェクタの投影範囲
2.センサの取り付け位置
3.実空間でタッチされた場所
まず、1つめのプロジェクタの投影範囲から考えてみます。Unityの画面が投影された場合の実際の大きさを調べる必要があります。今回は1メートルのオブジェクトが1メートルの大きさで画面に投影されるようにします。
まずは壁に画像がゆがみなく投影されるようにプロジェクタの位置を調整します。今回は床置きしたプロジェクタを壁に投影させました。プロジェクタのテストパターンを投影してできるだけ歪みが出ないように調整します。このときレーザー水準器があれば確認が楽になります。
テストパターンを投影
確認に使用したミニレーザー水準器
プロジェクタの位置が決まれば、続いてUnity側の設定を行います。UnityでQuadオブジェクトを作成します。Unityの単位はメートルなので、実空間で考えると1メートルの板が作成されたことになります。次にカメラを正投影に設定し、Quadが適当な大きさになるようカメラのサイズを調整します。正投影にしたのは設定が簡単になること、今回のコンテンツではパースペクティブが不要だからです。
これをプロジェクタに投影して、1辺のサイズをメジャーで測ります。
測ってみると1辺の長さが620mmでした。これを1,000mm(1メートル)の大きさに投影させるためには以下のように設定します。現在のカメラのサイズが1.16と設定されているので、以下のように変更します。
ここで計算された0.719をカメラのサイズに設定します。Unity内で1メートルのオブジェクトが実際に1,000mmの大きさで投影されるようになりました。
なぜmm(ミリ)を使うのか不思議に思うかもしれません。実際の現場では長さを表すのにmmを使うことがほとんどです。図面の表記や打ち合わせでもmmを使われるので、慣れておくと良いと思います。
次にセンサーの様子をプロジェクタで投影してみます。
このようになりました。センサの取り付け位置とプロジェクタが投影する画像がずれているので合わせていきます。黄色い部分がセンサの検出結果です。扇形の中心が測域センサの中心を示しています。この位置が設置されている測域センサの位置とどのくらい離れているかメジャーを使って測ります。今回は縦方向に760mm、横方向に60mmずれているようです。
先ほど行なったカメラ設定によって、投影されている画像は実空間の縮尺と一致しているので、Unity内のセンサの位置をずれ分移動します。URGSensorViewerスクリプトにオフセットを設定できるようにしたので、計測した値をミリ単位でそのまま設定します。回転は見た目で調整します。
これで設定完了です。目印としてセンサから1,000mm離れたところに障害物をつけてあります。このように、センシング結果を合わせてゆくことをキャリブレーションといいます。キャリブレーション結果を見ると大体合っていると思います。
キャリブレーション済み結果
[[SplitPage]]<2>ノイズ除去
しかし、触っていないのに環境にある凹凸を認識して物体があると認識してしまっているようです(Sceneエディタ上で確認しました)。
センサ周りの障害物を全てなくすことができれば一番ですが、状況によってはそれができないことがあります。今回は背景差分を取って回避しました。起動後にスペースバーを押すとその時点でのセンサ情報が保存され、以後認識された情報との差を取ります。こうすることで変化があったところだけがオブジェクトの認識候補になります。
さらにノイズ除去の処理としてメディアンフィルタを追加しました(URGSensor.cs 156行目から168行目)。メディアンフィルタはデータをなめすことなく細かなノイズを除去することができます。以下のコードは3つの距離データの中央値を使うメディアンフィルタの実装例です。filterSizeに指定する数を大きくすることでフィルタのかかり具合を変更できますが、数が大きくなるに従って処理は重くなります。
int filterSize = 3;
var tmp = new long[filterSize];
int mid = filterSize / 2;
for (int i = 0; i < this.Steps; i++)
{
for (int n = 0; n < filterSize; n++)
{
var m = Math.Min(Math.Max(0, i + n - mid), this.Steps - 1);
tmp[n] = Math.Abs(_distance[m] - _calib_distance[m]);
}
Array.Sort(tmp);
_filtered_distance[i] = tmp[mid];
}
171行目からはボックスフィルタ、190行目からはフィルタなしの実装がコメントアウトされています。物体の認識が安定したところで、手を認識した場所に花を表示してみます。
動作の様子
花の素材はAdobe Stockを利用しています、花のアニメーションはDOTweenアセットでつけています。このアセットはスクリプトからアニメーションを付けるときにとても便利なものです。
花を咲かせ5秒後に閉じるアニメーションの部分は以下のように書けます。プログラムだけで簡単に書くことができるので、Timelineよりも便利な場面もあります(URGSensorView.cs 124行目から)。
void Bloom(int n, Vector3 p)
{
_spawn[n] = GameObject.Instantiate(_flowers[n % _flowers.Length], p, Quaternion.identity);
_spawn[n].transform.localScale = Vector3.zero;
var seq = DOTween.Sequence();
seq.Append(_spawn[n].transform.DOScale(Vector3.one * 0.25f, 0.5f));
seq.Join(_spawn[n].transform.DOShakeRotation(0.5f, 30f));
seq.AppendInterval(5f);
seq.Append(_spawn[n].transform.DOScale(Vector3.zero, 1f));
seq.Join(_spawn[n].transform.DOShakeRotation(1f, -30f));
seq.OnComplete(() => {
GameObject.Destroy(_spawn[n]);
_spawn[n] = null;
});
}
コンテンツでの測域センサの使い方はこのような感じです。実際のコンテンツでは死角をなくすため複数の測域センサを利用するケースがあります。その場合はもう少し工夫が必要になってきますが、基本的な考え方は同じです。
次回はゲーム筐体でよく使われる押しボタンをUnityで使ってみます。できることはキーボードやマウスのボタンと同じですが、何らかの形でPCにボタンが押されたことを通知する必要があります。その方法を考えてみたいと思います。
Profile.
高田稔則/Toshinori Takata(Codelight)Codelight株式会社 代表取締役・インタラクションエンジニア
フリーランス、株式会社TBSテレビ等で映画CG制作、株式会社ソニー・コンピュータエンタテインメント(現 ソニー・インタラクティブエンタテインメント)でPS4のOSD開発などを経て2006年にCodelight株式会社を設立。インタラクティブコンテンツの制作を中核として、製造業向けのプロトタイプ開発なども行う
www.codelight.co.jp