Hololensでワールド座標系とOpenCVの画像座標系の変換を確認
やったこと(概要)
Hololensでスクリーンショットを撮って,OpenCVで画像処理,その結果を空間に反映ということは,今後よくあるのではないかと思います. ここで重要なのが座標系の変換です.Hololensの空間であるUnityのワールド座標系とOpenCVの画像座標系を適切に変換する必要があります.
Unityはこうした座標系変換を行うAPIを提供しています.今回はそれらのAPIが適切に機能するかを検証・確認してみました.
※OpenCVの画像座標としていますが,一般的な画像座標と等価です.
(何事もおそらくできるだろう という発想が危険です!)
さらに進化した画像処理ライブラリの定番 OpenCV 3基本プログラミング
- 作者: 北山洋幸
- 出版社/メーカー: カットシステム
- 発売日: 2016/04
- メディア: 単行本
- この商品を含むブログ (1件) を見る
詳解 OpenCV ―コンピュータビジョンライブラリを使った画像処理・認識
- 作者: Gary Bradski,Adrian Kaehler,松田晃一
- 出版社/メーカー: オライリージャパン
- 発売日: 2009/08/24
- メディア: 単行本(ソフトカバー)
- 購入: 17人 クリック: 272回
- この商品を含むブログ (35件) を見る
- 作者: 小枝正直,上田悦子,中村恭之
- 出版社/メーカー: 講談社
- 発売日: 2014/07/18
- メディア: 単行本(ソフトカバー)
- この商品を含むブログを見る
方法
AirTapしたときに,次の動作を行うプログラムを作成します.
- スクリーンショット撮影.
- HololensのCursorのワールド座標を取得し,OpenCV画像座標に変換.
- OpenCVでスクリーンショットに点を描画.
- その描画座標を再度,ワールド座標に変換し,空間にCubeを設置.
つまり,ワールド座標→画像座標→ワールド座標の変換を行い,スクリーンショットの点とワールド座標のCubeが同じ場所に設置されればOKです.
コード
今回作成したコードはこちらにあります.
なお,HololensToolkit for UnityとOpenCVForUnity(有償)が必要です.
ポイント
変換API
ワールド座標系から画像座標系へ
画像座標系すなわちビューポート座標系の変換にはCamera.WorldToViewportPointを使います.この関数は正規化された値が返ってくること,Y軸の向きが逆(上方向)であることに注意しましょう. 従って正規化された値を画像サイズで乗算し,Y軸の向きを反転させる必要があります.
OpenCVForUnity.Point worldPointToMatPoint(Vector3 worldPosition, Mat image) { Vector3 viewportPoint = Camera.main.WorldToViewportPoint(worldPosition); OpenCVForUnity.Point drawPoint = new OpenCVForUnity.Point(viewportPoint.x * image.width(), (1.0 - viewportPoint.y) * image.height()); return drawPoint; }
画像座標系からワールド座標系へ
画像座標系からワールド座標系への変換には,まずビューポート座標系は正規化された値であるため,画像座標を正規化し,Camera.ViewportPointToRayでRayを取得し,ワールド座標系を取得します. また,ビューポート座標を求めるときはY軸の向きを反転させる必要があります.
bool matPointToWorldPoint(OpenCVForUnity.Point point, Mat image, out Vector3 worldPoint) { // 画像座標を正規化,Y軸の向きを反転し,ビューポート座標を求める. float viewportPointX = (float)(point.x / (double)image.width()); float viewportPointY = (float)(1.0 - point.y / (double)image.height()); Vector3 viewportPoint = new Vector3(viewportPointX, viewportPointY); // Rayを使って,Z軸も含んだワールド座標を求める. Ray ray = Camera.main.ViewportPointToRay(viewportPoint); RaycastHit hitInfo; if (Physics.Raycast(ray, out hitInfo)) { worldPoint = hitInfo.point; return true; } worldPoint = new Vector3(); return false; }
結果
う~ん,微妙にずれてるけど仕方ないのかぁ~.
その他
※ 生活感ありありの画像で申し訳ありませんm_m