hiroykyの個人日記

@hiroyky IT系エンジニアのまったりブログ

Hololensでワールド座標系とOpenCVの画像座標系の変換を確認

f:id:hiroyky:20170321014208p:plain

やったこと(概要)

Hololensでスクリーンショットを撮って,OpenCVで画像処理,その結果を空間に反映ということは,今後よくあるのではないかと思います. ここで重要なのが座標系の変換です.Hololensの空間であるUnityのワールド座標系とOpenCVの画像座標系を適切に変換する必要があります.

Unityはこうした座標系変換を行うAPIを提供しています.今回はそれらのAPIが適切に機能するかを検証・確認してみました.

OpenCVの画像座標としていますが,一般的な画像座標と等価です.

(何事もおそらくできるだろう という発想が危険です!)

さらに進化した画像処理ライブラリの定番 OpenCV 3基本プログラミング

さらに進化した画像処理ライブラリの定番 OpenCV 3基本プログラミング

詳解 OpenCV ―コンピュータビジョンライブラリを使った画像処理・認識

詳解 OpenCV ―コンピュータビジョンライブラリを使った画像処理・認識

OpenCVによる画像処理入門 (KS情報科学専門書)

OpenCVによる画像処理入門 (KS情報科学専門書)

方法

AirTapしたときに,次の動作を行うプログラムを作成します.

  1. スクリーンショット撮影.
  2. HololensのCursorのワールド座標を取得し,OpenCV画像座標に変換.
  3. OpenCVスクリーンショットに点を描画.
  4. その描画座標を再度,ワールド座標に変換し,空間にCubeを設置.

つまり,ワールド座標→画像座標→ワールド座標の変換を行い,スクリーンショットの点とワールド座標のCubeが同じ場所に設置されればOKです.

コード

今回作成したコードはこちらにあります.

github.com

なお,HololensToolkit for UnityOpenCVForUnity(有償)が必要です.

ポイント

変換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;
}

結果

う~ん,微妙にずれてるけど仕方ないのかぁ~.

f:id:hiroyky:20170321014346j:plain f:id:hiroyky:20170321014208p:plain

その他

※ 生活感ありありの画像で申し訳ありませんm_m