メインコンテンツまでスキップ
バージョン: 0.23.2

カメラフレームへのアクセス (実験的)

はじめに

WARNING

カメラフレームへのアクセス機能は、全てのAR Foundation APIを完全にサポートしておらず、パッケージとSnapdragon Spaces Service側の最適化により、現時点ではリリース間の後方互換性が失われているため、この機能は実験的とされています。

このサンプルでは、画像処理のために RGBのカメラフレームと内部のプロパティを取得する方法を紹介します。

カメラフレームへのアクセスとAR FoundationのAR Camera Managerのコンポーネントの基本的な機能については、Unityのドキュメントを参照してください。

機能の有効化

この機能を使用するには、以下のOpenXRのプロジェクト設定でCamera Accessにチェックする必要があります。
Project Settings > XR Plug-in Management > OpenXR (Androidタブ) alt text

サンプルのインポート

まだサンプルをインポートしていない場合は、以下の手順でインポートすることができます。

  1. 基本パッケージのインポート
  2. 基本サンプルのインポート

サンプルシーンは以下の場所に存在します。
Assets/Samples/Snapdragon Spaces/0.XX.0/Core Samples/Scenes/Camera Frame Access Sample/Camera Frame Access Sample.unity

サンプルの仕組み

AR Camera ManagerコンポーネントをAR Session Origin > AR Cameraのゲームオブジェクトに追加すると、カメラアクセスのサブシステムが有効になります。

起動すると、サブシステムはデバイスから有効なセンサー情報を取得します。有効なY'UV420spまたはYUY2センサー設定が見つかった場合、サブシステムはCPUカメラ画像のプロバイダとしてこの設定を選択します。

概念的に、AR Camera Managerは単一のカメラを表し、複数のセンサを同時に管理することはありません。

サンプルシーンは2つのパネルで構成されています。

  • Camera View:デバイスのカメラからの最新のCPU上のカメラ画像を表示するパネル
  • Camera Info:デバイスのカメラの様々なプロパティを列挙するカメラ情報のパネル alt text

CPU上のデバイスカメラ画像の取得

新しいカメラフレームがサブシステムで利用可能になるたびに、AR Camera ManagerframeReceivedイベントを発行します。このイベントをサブスクライブすることで、他のスクリプトができるだけ早いタイミングで最新のカメラフレームを取得し、そのフレームに対して処理を実行できるようになります。

カメラフレームが利用可能になると、カメラマネージャのTryAcquireLatestCpuImage関数は、選択されたデバイスカメラからの単一のRaw画像を表すXRCpuImageオブジェクトを返します。この画像のRawピクセルデータは、NativeArray<byte>を返すXRCpuImageのConvert関数で抽出できます。

Snapdragon SpacesはXRCpuImage.ConvertAsyncによるフレームの非同期変換をサポートしていません。

パフォーマンスに関するTIP

AR Camera Managerのコンポーネントは、AR Session Originの作成時に自動的にシーンのHierarchyに追加されます。AR Camera Managerのコンポーネントが有効になっている間、Snapdragon Spaces Servicesからカメラフレームが要求されます。シーンでカメラフレームアクセスを必要としない場合は、このコンポーネントを無効にすることを検討してください。

重要

AR Session Originを作成すると、AR Camera Backgroundのコンポーネントが自動的にシーンのHierarchyに追加されますが、Snapdragon Spacesはバージョン0.20.0以降これをサポートしていません。レンダリングの問題を避けるには、このコンポーネントを無効にする必要があります。

重要

XRCpuImageオブジェクトは変換後に明示的に破棄する必要があります。これにはXRCpuImageDispose関数を使用します。XRCpuImageオブジェクトを廃棄しないと、カメラアクセスのサブシステムが破壊されるまでメモリリークする可能性があります。

変換前にNativeArray<byte>を確保する場合、このバッファもコピーまたは操作後に破棄する必要があります。これを行うには、NativeArray<T>Dispose関数を使用します。NativeArray<byte>を破棄しないと、カメラアクセスのサブシステムが破棄されるまでメモリがリークします。

frameReceivedTryAcquireLatestCpuImageXRCpuImageの使用方法の詳細については、Unityのドキュメントを参照してください。

WARNING

カメラフレームへのアクセスは、使用するデバイスによっては初期化に数秒かかる場合があります。サブシステムが正常に初期化される前にフレームにアクセスしようとしないでください。エラーを避けるため、AR Foundation APIからframeReceivedイベントを使用することを強く推奨します。

以下のサンプルコードは、frameReceivedイベントが発生したときに、AR Camera ManagerにCPU上のカメラ画像を要求します。成功すれば、XRCpuImageのRawピクセルデータを直接管理されているTexture2DGetRawTextureData<byte>バッファに抽出し、その後Apply関数でテクスチャバッファを適用します。最後に、ターゲットのRawImageのテクスチャを更新し、アプリのUIで新しいフレームが見えるようにします。

public RawImage CameraRawImage;

private ARCameraManager _cameraManager;
private Texture2D _cameraTexture;
private XRCpuImage _lastCpuImage;

public void Start()
{
_cameraManager.frameReceived += OnFrameReceived;
}

private void OnFrameReceived(ARCameraFrameEventArgs args)
{
_lastCpuImage = new XRCpuImage();
if (!_cameraManager.TryAcquireLatestCpuImage(out _lastCpuImage))
{
return;
}

UpdateCameraTexture(_lastCpuImage);
}

private unsafe void UpdateCameraTexture(XRCpuImage image)
{
var format = TextureFormat.RGBA32;

if (_cameraTexture == null || _cameraTexture.width != image.width || _cameraTexture.height != image.height)
{
_cameraTexture = new Texture2D(image.width, image.height, format, false);
}

var conversionParams = new XRCpuImage.ConversionParams(image, format);
var rawTextureData = _cameraTexture.GetRawTextureData<byte>();

try
{
image.Convert(conversionParams, new IntPtr(rawTextureData.GetUnsafePtr()), rawTextureData.Length);
}
finally
{
image.Dispose();
}

_cameraTexture.Apply();
CameraRawImage.texture = _cameraTexture;
}

以下のテクスチャフォーマットがAR Camera Managerでサポートされています。

  • RGB24
  • RGBA32
  • BGRA32

YUVプレーンデータの取得

Snapdragon Spacesは現在、Y′UV420spYUY2フォーマットをサポートしています。

  • Y'UV420sp
    • Yバッファとインターリーブされた2x2サブサンプリングされたU/Vバッファで構成されています。
  • YUY2
    • YとU/VのサンプルをY-U-Y-Vの「マクロピクセル」という形でインターリーブした1つのバッファで構成され、各サンプルはクロミナンスが等しい2つの水平ピクセルを表し、垂直方向のサブサンプリングはありません。

YUVカラーモデルの詳細については、Wikipediaの「YCbCr」の記事のYUVの項目を参照してください。

RGB変換が不要な場合は、XRCpuImageGetPlane関数でRawのYUVプレーンデータを取得できます。これはXRCpuImage.Planeオブジェクトを返し、そこからプレーンデータを読み取ることができます。フォーマットはXRCpuImage.planeCountで区別でき、フレームを表すプレーンの量を示します。

Y'UV420sp

  • Yプレーンのデータはインデックス0で、GetPlane(0)でアクセスできます。
  • UVプレーンのデータはインデックス1で、GetPlane(1)でアクセスできます。

YUY2

  • YUYVプレーンのデータはインデックス0で、GetPlane(0)でアクセスできます。
カメラへ同時にアクセスする場合

複数のアプリが同じセンサーに同時にアクセスする場合、通常はY'UV420spフレームフォーマットが表示されるところ、YUY2フレームフォーマットが表示されることがあります。

そのため、アプリで両方のケースを処理することをお勧めします。そうしないと、ビデオキャプチャやストリーミングサービスを使用する際に問題が発生する可能性があります。

XRCpuImage.GetPlaneの詳細については、Unityのドキュメントを参照してください。

XRCpuImage.Planeの詳細については、Unityのドキュメントを参照してください。

以下のサンプルコードは、frameReceivedイベントが発生した時にAR Camera ManagerにCPU上のカメラ画像を要求します。成功した場合、XRCpuImageのRawのプレーンデータを取得し、YUVフォーマットに応じて異なる画像処理を適用します。

private ARCameraManager _cameraManager;
private XRCpuImage _lastCpuImage;

public void Start()
{
_cameraManager.frameReceived += OnFrameReceived;
}

private void OnFrameReceived(ARCameraFrameEventArgs args)
{
_lastCpuImage = new XRCpuImage();
if (!_cameraManager.TryAcquireLatestCpuImage(out _lastCpuImage))
{
return;
}

switch(_lastCpuImage.planeCount)
{
case 1:
ProcessYuy2Image(_lastCpuImage);
break;
case 2:
ProcessYuv420Image(_lastCpuImage);
break;
}
}

private void ProcessYuv420Image(XRCpuImage image)
{
var yPlane = image.GetPlane(0);
var uvPlane = image.GetPlane(1);

for (int row = 0; row < image.height; row++)
{
for (int col = 0; col < image.width; col++)
{
// Perform image processing here...
}
}
}

センサーの内部パラメーターの取得

カメラマネージャーのTryGetIntrinsics関数は、選択したセンサーの物理的特性を記述したXRCameraIntrinsicsオブジェクトを返します。XRCameraIntrinsicsの詳細については、Unityのドキュメントを参照してください。

以下のサンプルコードでは、選択したセンサーの内部パラメーターを取得し、アプリのUIに表示しています。

public Text[] ResolutionTexts;
public Text[] FocalLengthTexts;
public Text[] PrincipalPointTexts;

private ARCameraManager _cameraManager;
private XRCameraIntrinsics _intrinsics;

private void UpdateCameraIntrinsics()
{
if (!_cameraManager.TryGetIntrinsics(out _intrinsics))
{
Debug.LogWarning("Failed to acquire camera intrinsics.");
return;
}

ResolutionTexts[0].text = _intrinsics.resolution.x.ToString();
ResolutionTexts[1].text = _intrinsics.resolution.y.ToString();
FocalLengthTexts[0].text = _intrinsics.focalLength.x.ToString("#0.00");
FocalLengthTexts[1].text = _intrinsics.focalLength.y.ToString("#0.00");
PrincipalPointTexts[0].text = _intrinsics.principalPoint.x.ToString("#0.00");
PrincipalPointTexts[1].text = _intrinsics.principalPoint.y.ToString("#0.00");
}

センサーの外部パラメーターの取得

AR Foundation APIはセンサーの外部パラメーターを公開していません。その代わりに、2つのメソッドが提供されています。

方法1:Snapdragon Spacesの入力バインディング

Snapdragon Spacesは、ColorCameraPositionColorCameraRotationの入力アクションを提供します。これらの入力アクションは、Position InputおよびRotation Inputとバインドすることで、Tracked Pose Driver (Input System)で使用できます。 このTracked Pose Driver (Input System)をゲームオブジェクトに追加することで、ゲームオブジェクトのポーズがカメラの外部パラメーターのポーズと一致します。

Tracked Pose DriverのTransformの更新

Tracked Pose Driver (Input System)コンポーネントは、基礎となるInput Systemの更新タイミングに依存しているため、関連するゲームオブジェクトのTransformが要求されたときに、1フレーム分古くなっている可能性があります。

最小限の待ち時間が必要な場合や、システムのエラー耐性が低い場合は、代わりに位置と回転をそれぞれTrackedInputDriver.positionAction.ReadValue<Vector3>()TrackedInputDriver.rotationAction.ReadValue<Quaternion>()で取得することができます。

次の図は、これらの入力バインディングの使い方を示しています。 alt text

方法2:Spaces Camera Pose Provider

Spaces Camera Pose Providerコンポーネントをシーンに追加し、GetPoseFromProvider関数を使用してAR Camera Managerの最新のframeReceivedイベントに関連付けられたカメラのポーズを取得します。提供されるポーズは、AR Sessionの座標系からの相対的なポーズです。

以下のサンプルコードでは、選択したセンサーの外部パラメーターを取得し、アプリのUIに表示します。

public SpacesCameraPoseProvider PoseProvider;
public Text[] ExtrinsicPositionTexts;
public Text[] ExtrinsicOrientationTexts;

private Pose _extrinsics;

private void UpdateCameraExtrinsics()
{
if (PoseProvider.GetPoseFromProvider(out _extrinsics) == PoseDataFlags.NoData)
{
Debug.LogWarning("Failed to acquire camera extrinsics.");
return;
}

var position = _extrinsics.position;
var orientation = _extrinsics.rotation.eulerAngles;

ExtrinsicPositionTexts[0].text = position.x.ToString();
ExtrinsicPositionTexts[1].text = position.y.ToString();
ExtrinsicPositionTexts[2].text = position.z.ToString();
ExtrinsicOrientationTexts[0].text = orientation.x.ToString();
ExtrinsicOrientationTexts[1].text = orientation.y.ToString();
ExtrinsicOrientationTexts[2].text = orientation.z.ToString();
}

Spaces Camera Pose Provider は、Tracked Pose Driver のポーズプロバイダとしても使用できます。

パフォーマンス向上のヒント

カメラへのアクセスは、画像の解像度が大きい場合や、使用されるアルゴリズムの性質により、計算コストが高くなる可能性があります。

  • XRCpuImage.Convertで画像を変換する場合は、XRCpuImage.ConversionParamsに小さいoutputDimensionsを指定します。
  • XRCpuImage.GetPlaneで画像を処理する場合は、データバッファを共通の係数でサブサンプリングすることを検討してください。

Snapdragon Spacesには、XRCpuImage.Convertによるデータの読み書き方法を変更するDirect Memory Access Conversionの設定があります。デフォルトでは、Snapdragon SpacesはMarshal.Copyを使用してフレームデータを移動します。この設定を有効にすると、SpacesはソースバッファとターゲットバッファのNativeArray<byte>直接表現を使用できるようになります。

この設定は、以下の設定項目で確認できます。
Project Settings > XR Plug-in Management > OpenXR (> Android Tab) > Snapdragon Spaces > Camera Frame Access (Experimental) > Direct Memory Access Conversion alt text

この設定を有効にすると、特定のデバイスではパフォーマンスが向上する可能性がありますが、他のアーキテクチャではパフォーマンスに悪影響を及ぼす可能性があります。

以下は、Direct Memory Access Conversion設定が推奨されるデバイスを示した表です。

デバイス推奨されるか
Lenovo ThinkReality A3パフォーマンスを向上させるため、推奨。
Lenovo ThinkReality VRXパフォーマンスを低下させるため、非推奨。

Spaces AR Camera Manager ConfigMaximumVerticalResolutionDownsamplingStrideプロパティはバージョン0.21.0で廃止されました。

追加センサーデータの取得

Spaces AR Camera Manager Configは、GetActiveCameraCountメソッドを使用してデバイスのカメラセンサー数を公開します。この情報は、デュアルデバイスとVRデバイスを区別し、カメラ画像を適切に処理するために関連する可能性があります。