MRが楽しい

MRやVRについて学習したことを書き残す

HoloLensでカメラ画像を取得する その1(Locatable camera in Unity)

本日は Unity の技術調査枠です。
HoloLens でカメラ画像を取得する方法を試したら取っ散らかりました。
以下の公式ドキュメントに目を通し、週を通してじっくりやります。
・Locatable camera in Unity
 https://developer.microsoft.com/en-us/windows/mixed-reality/locatable_camera_in_unity

フォトビデオカメラの機能を有効にする

アプリがカメラを使用するためには"WebCam"のcapabilityを宣言する必要があります。
1. Unity エディタのメニューから Edit -> Project Settings -> Player を開く。
2. WindowsStoreタブを開く。
3. PublishingSettings -> Capabilities から"WebCam"と"Microphone"を有効にする。

カメラでは一度に1つの操作だけ実行できます。
カメラが現在使用しているモード(フォト、ビデオ、または無し)を確認するには、UnityEngine.XR.WSA.WebCam.Modeをチェックします。

フォトキャプチャ

名前空間: UnityEngine.XR.WSA.WebCam
モード: PhotoCapture

PhotoCapture モードでは、カメラで写真を撮ることができます。
PhotoCapture を使用して写真を撮る一般的なパターンは次のとおりです。

1. PhotoCapture オブジェクトを作成する。
2. 必要な設定で CameraParameters オブジェクトを作成する。
3. StartPhotoModeAsync 経由でフォトモードを開始する。
4. 目的の写真を撮る。
(オプション)その写真を加工する。
5. フォトモードを停止してリソースをクリーンアップする。

PhotoCaptureの一般的なセットアップ

キャプチャする写真の用途は以下の3つがあります。
・ファイルに写真を取り込む
・テクスチャ2Dへの写真を反映する
・写真を撮影し、バイトデータとして利用する

全ての用途について、以下の同じ手順から始めます
1. まず、PhotoCaptureオブジェクトを作成します。

PhotoCapture photoCaptureObject = null;
   void Start()
   {
       PhotoCapture.CreateAsync(false、OnPhotoCaptureCreated);
   }

2. 次に、オブジェクトを保存し、パラメータを設定してフォトモードを開始します。

void OnPhotoCaptureCreated(PhotoCapture captureObject)
   {
       photoCaptureObject = captureObject;

       Resolution cameraResolution = PhotoCapture.SupportedResolutions.OrderByDescending((res) => res.width * res.height).First();

       CameraParameters c = new CameraParameters();
       c.hologramOpacity = 0.0f;
       c.cameraResolutionWidth = cameraResolution.width;
       c.cameraResolutionHeight = cameraResolution.height;
       c.pixelFormat = CapturePixelFormat.BGRA32;

       captureObject.StartPhotoModeAsync(c, false, OnPhotoModeStarted);
   }

3. 最後に使用するクリーンアップコードを作成します。

void OnStoppedPhotoMode(PhotoCapture.PhotoCaptureResult result)
   {
       photoCaptureObject.Dispose();
       photoCaptureObject = null;
   }

これらの手順を実行すると、キャプチャする写真のモードを選択できます。

ファイルに写真を取り込む

最も簡単な操作は、写真を直接ファイルに取り込むことです。写真はJPGまたはPNG形式で保存できます。
フォトモードを正常に開始した後、写真を撮ってディスクに保存します。

private void OnPhotoModeStarted(PhotoCapture.PhotoCaptureResult result)
   {
       if (result.success)
       {
           string filename = string.Format(@"CapturedImage{0}_n.jpg", Time.time);
           string filePath = System.IO.Path.Combine(Application.persistentDataPath, filename);

           photoCaptureObject.TakePhotoAsync(filePath, PhotoCaptureFileOutputFormat.JPG, OnCapturedPhotoToDisk);
       }
       else
       {
           Debug.LogError("Unable to start photo mode!");
       }
   }

写真をディスクに保存した後、フォトモードを終了してオブジェクトをクリーンアップします

void OnCapturedPhotoToDisk(PhotoCapture.PhotoCaptureResult result)
   {
       if (result.success)
       {
           Debug.Log("Saved Photo to disk!");
           photoCaptureObject.StopPhotoModeAsync(OnStoppedPhotoMode);
       }
       else
       {
           Debug.Log("Failed to save Photo to disk");
       }
   }
テクスチャ2Dへの写真を反映する

Texture2Dにデータを反映する場合、プロセスはディスクに保存するのと非常に似ています。
セットアッププロセスを実施し、OnPhotoModeStarted では、フレームをメモリに保存します。

private void OnPhotoModeStarted(PhotoCapture.PhotoCaptureResult result)
   {
       if (result.success)
       {
           photoCaptureObject.TakePhotoAsync(OnCapturedPhotoToMemory);
       }
       else
       {
           Debug.LogError("Unable to start photo mode!");
       }
   }

次に、結果をテクスチャに適用し、一般的なクリーンアップコードを使用します。

void OnCapturedPhotoToMemory(PhotoCapture.PhotoCaptureResult result, PhotoCaptureFrame photoCaptureFrame)
   {
       if (result.success)
       {
           // 使用するTexture2Dを作成し、正しい解像度を設定する
           // Create our Texture2D for use and set the correct resolution
           Resolution cameraResolution = PhotoCapture.SupportedResolutions.OrderByDescending((res) => res.width * res.height).First();
           Texture2D targetTexture = new Texture2D(cameraResolution.width, cameraResolution.height);
           // 画像データをターゲットテクスチャにコピーする
           // Copy the raw image data into our target texture
           photoCaptureFrame.UploadImageDataToTexture(targetTexture);
           // マテリアルに適用するなど、テクスチャで行いたい操作を実施します
           // Do as we wish with the texture such as apply it to a material, etc.
       }
       // クリーンアップ
       // Clean up
       photoCaptureObject.StopPhotoModeAsync(OnStoppedPhotoMode);
   }
写真を撮影し、バイトデータとして利用する

フレームのバイトデータとやりとりするための手順です。
セットアッププロセスを実施し、OnPhotoModeStarted を使って写真をTexture2Dに反映するのと同じ手順を実行します。
違いは OnCapturedPhotoToMemory にあり、バイトデータを取得して利用することができます。

この例では、更に処理したり、SetPixels()でテクスチャに適用できる List を作成します。

void OnCapturedPhotoToMemory(PhotoCapture.PhotoCaptureResult result, PhotoCaptureFrame photoCaptureFrame)
   {
       if (result.success)
       {
           List<byte> imageBufferList = new List<byte>();
           // IMFMediaBuffer のデータを空のバイトリストにコピーします。
           // Copy the raw IMFMediaBuffer data into our empty byte list.
           photoCaptureFrame.CopyRawImageDataIntoBuffer(imageBufferList);

           // この例では、BGRA32形式を使用してイメージをキャプチャしました。
           // だから私たちのストライドは、各RGBAチャンネルのバイトがあるので4になります。
           // 画像データも反転され、ピクセルデータに逆の順序でアクセスします。
           // In this example, we captured the image using the BGRA32 format.
           // So our stride will be 4 since we have a byte for each rgba channel.
           // The raw image data will also be flipped so we access our pixel data
           // in the reverse order.
           int stride = 4;
           float denominator = 1.0f / 255.0f;
           List<Color> colorArray = new List<Color>();
           for (int i = imageBufferList.Count - 1; i >= 0; i -= stride)
           {
               float a = (int)(imageBufferList[i - 0]) * denominator;
               float r = (int)(imageBufferList[i - 1]) * denominator;
               float g = (int)(imageBufferList[i - 2]) * denominator;
               float b = (int)(imageBufferList[i - 3]) * denominator;

               colorArray.Add(new Color(r, g, b, a));
           }
           // これで、texture.SetPixels()などの配列を使って何かできるようになりました。
           // Now we could do something with the array such as texture.SetPixels() or run image processing on the list
       }
       photoCaptureObject.StopPhotoModeAsync(OnStoppedPhotoMode);
   }


ビデオキャプチャは一旦見送り。
以上の情報を元に HoloLens でのカメラ画像処理にトライします。