おもちゃラボ

Unityで遊びを作っていきます

【ARFoundation】カメラ映像を取得する

ARKitを使っていると、ARとは別にカメラからの映像を取得したい場合があります。そこで、この記事ではARFoundationを使ってカメラ映像を取得する方法を紹介したいと思います。目次は次のとおりです。

ARFoundationのセットアップ

まずはARFoundationを使うため、ARSessionとARSessionOriginをインスペクタから追加します。ヒエラルキーウインドウから「Create→XR→ARSession」と「Create→XR→AR Session Origin」を追加します。

f:id:nn_hokuson:20200129213752p:plain:w230

ヒエラルキーウインドウから「Create→3D Object→Plane」を選択して、カメラ(AR Camera)の映像を映すための平面を作ります。常にカメラに映るように、カメラに正対するように配置して、カメラの子要素にしておきます。

f:id:nn_hokuson:20200129214111p:plain

また、このPlaneがライティングの影響を受けないように、プロジェクトウィンドウで右クリックしてCreate→Materialを選択してマテリアルを作ります。マテリアルのShaderをUnlit→Textureに設定してからPlaneにドラッグしておきましょう。

f:id:nn_hokuson:20200129214741j:plain

カメラ映像をフックするスクリプトを作る

続いて、ARFoundationで使用しているカメラ映像をフックするスクリプトを作成します。ARFounationでカメラ映像を取得する流れは次のようになります。

  1. カメラの更新イベントを登録
  2. カメラフレームが更新されたタイミングでフレーム取得
  3. 必要に応じて回転・フリップを行う

プロジェクトウインドウで右クリックして、Create→C# Scriptでスクリプトを作成し、CamerImageControllerという名前で保存してください。保存できたら先程のPlaneにアタッチしておきましょう。

アタッチできたら、CameraImageControllerに次のスクリプトを入力します。

using System;
using Unity.Collections.LowLevel.Unsafe;
using UnityEngine;
using UnityEngine.XR.ARFoundation;
using UnityEngine.XR.ARSubsystems;

public class CamerImageController : MonoBehaviour
{
    public ARCameraManager cameraManager;

    private Texture2D mTexture;
    private MeshRenderer mRenderer;

    private void Start()
    {
        mRenderer = GetComponent<MeshRenderer>();
    }

    void OnEnable()
    {
        cameraManager.frameReceived += OnCameraFrameReceived;
    }

    void OnDisable()
    {
        cameraManager.frameReceived -= OnCameraFrameReceived;
    }

    unsafe void OnCameraFrameReceived(ARCameraFrameEventArgs eventArgs)
    {
        XRCameraImage image;
        if (!cameraManager.TryGetLatestImage(out image))
            return;

        var conversionParams = new XRCameraImageConversionParams
        (
            image,
            TextureFormat.RGBA32,
            CameraImageTransformation.None
        );
      
        if (mTexture == null || mTexture.width != image.width || mTexture.height != image.height)
        {            
            mTexture = new Texture2D(conversionParams.outputDimensions.x,
                                     conversionParams.outputDimensions.y,
                                     conversionParams.outputFormat, false);
        }

        var buffer = mTexture.GetRawTextureData<byte>();
        image.Convert(conversionParams, new IntPtr(buffer.GetUnsafePtr()), buffer.Length);
        
        mTexture.Apply();
        mRenderer.material.mainTexture = mTexture;

        buffer.Dispose();
        image.Dispose();
    }
}

このスクリプトではframeReceivedイベントにOnCameraFrameReceivedメソッドを追加しています。これにより、カメラのフレームが更新されるたびにOnCameraFrameReceivedメソッドが呼び出されるようになります。OnCameraFrameReceivedメソッドではTryGetLatestImageメソッドを使ってカメラ映像をXRCameraImage型のimage変数に代入しています。

続いて、XRCameraImageクラスのConvertメソッドを使って取得したカメラ映像をTexture2D型に変換しています。変換後の画像フォーマットはXRCameraImageConversionParamsで指定します。ここではカラーフォーマットはRGBA32、回転フォーマットは無しで映像を取得しています。カラーフォーマットには次のような値を指定できます。

フォーマット名 意味
TextureFormat.R8 1チャネルで8bitの画像
TextureFormat.Alpha8 アルファチャネルのみ
TextureFormat.RGB24 RGBの3チャネルで各8bitの画像
TextureFormat.RGBA32 RGBAの4チャネルで各8bit画像
TextureFormat.ARGBA32 ARGBの4チャネルで各8bit画像
TextureFormat.BGRA32 BGRAの4チャネルで各8bit画像

また、回転フォーマットには次の値を指定できます。

フォーマット 意味
MirrorX X軸でフリップ
MirrorY Y軸でフリップ
None 回転しない

最後にTexture2DをApplyして変更を反映した後に、PlaneのMesh Rendererに設定してPlaneにカメラ映像を表示しています。

unsafeコードの設定

上記のスクリプトではunsafeコードを使っているため、そのままではコンパイルエラーになってしまいます。メニューバーからEdit→Project Settings→Playerを選択して、Allow unsafe codeにチェックを入れます。

f:id:nn_hokuson:20200129215259p:plain

アウトレット接続

スクリプト中で宣言したARCameraManagerに値を代入します。UnityのインスペクタにCamerImageControllerを表示し、ARCameraManagerの欄にヒエラルキーウインドウのAR Cameraをドラッグ&ドロップしてください。

f:id:nn_hokuson:20200129215637j:plain

これでカメラ映像がPlaneにも映るようになっているはずです。ビルドして確かめてみてください。