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

カメラへのアクセス

機能の有効化

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

機能の設定

alt text

カメラフレームへのアクセス機能には4つの設定があります。

  • Direct Memory Access Conversion
    • CPUによるフレーム変換の前に、アプリケーションがカメラメモリへのアクセス方法を決定するために使用します。デバイスによっては性能が向上する場合もありますが、性能が低下する場合もあります。
  • CPU Frame Cache Size
    • XRCpuImage.ConvertAsyncのキャッシュフレームのサイズで、非同期変換要求をキューに入れることができる数を定義します。上限に達すると、古いリクエストは期限切れになります。
  • High Priority Async Conversion
    • 変換スレッドを優先度の高いスレッドとして扱うかどうかを決定するために、アプリケーションによって使用されます。
  • Cache Frame Before Async Conversion
    • CPUのキャッシュフレームに依存することなく、要求時にフレームをキャッシュすることで非同期変換を保証します。Direct Memory Access Conversionが有効な場合は無視されます。

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

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

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

Snapdragon Spacesは、XRCpuImage.ConvertAsyncを通じてフレームの非同期変換もサポートしています。
この場合、返されたAsyncConversionオブジェクトをポーリングしてAsyncConversion.GetData<byte>でデータを取得するか、NativeArray<byte>引数を持つコールバックを提供して変換が完了した際に実行します。

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

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

IMPORTANT

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

IMPORTANT

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

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

IMPORTANT

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

XRCpuImage.ConvertAsyncを使用してコールバック関数を提供する場合、提供されたNativeArray<byte>のデータバッファを破棄する必要はありません。変換データは、コールバックが終了した際にカメラのサブシステムによって破棄されます。

frameReceivedTryAcquireLatestCpuImageXRCpuImage.ConvertXRCpuImage.ConvertAsyncの使用方法の詳細については、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イベントに関連付けられたカメラのポーズを取得します。提供されるポーズは、XR Originの座標系からの相対的なポーズです。

以下のサンプルコードでは、選択したセンサーの外部パラメーターを取得し、アプリの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で画像を処理する場合は、データバッファを共通の係数でサブサンプリングすることを検討してください。
  • XRCpuImage.ConvertAsyncを使用して画像を処理することを検討してください。変換中にメインアプリケーションのスレッドがブロックされるのを避けることができます。

Snapdragon Spacesには、XRCpuImage.ConvertXRCpuImage.ConvertAsyncによるデータの読み書き方法を変更する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 > 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デバイスを区別し、カメラ画像を適切に処理するために関連する可能性があります。