カメラへのアクセス
機能の有効化
この機能を使用するには、以下のOpenXRのプロジェクト設定でCamera Accessにチェックする必要があります。
Project Settings > XR Plug-in Management > OpenXR (Androidタブ)
機能の設定
カメラフレームへのアクセス機能には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 Manager
はframeReceived
イベントを発行します。このイベントをサブスクライブすることで、他のスクリプトができるだけ早いタイミングで最新のカメラフレームを取得し、そのフレームに対して処理を実行できるようになります。
カメラフレームが利用可能になると、カメラマネージャーのTryAcquireLatestCpuImage
関数は、選択されたデバイスカメラからの単一のRaw画像を表すXRCpuImage
オブジェクトを返します。この画像のRawピクセルデータは、NativeArray<byte>
を返すXRCpuImageのConvert
関数で抽出できます。
Snapdragon Spacesは、XRCpuImage.ConvertAsync
を通じてフレームの非同期変換もサポートしています。
こ の場合、返されたAsyncConversion
オブジェクトをポーリングしてAsyncConversion.GetData<byte>
でデータを取得するか、NativeArray<byte>
引数を持つコールバックを提供して変換が完了した際に実行します。
AR Camera Managerのコンポーネントは、XR Originの作成時に自動的にシーンのHierarchyに追加されます。AR Camera Managerのコンポーネントが有効になっている間は、OpenXRのランタイムアプリケーションからカメラフレームが要求されます。シーンでカメラフレームへのアクセスを必要としない場合は、このコンポーネントを無効にすることを検討してください。
XR Originを作成すると、AR Camera Backgroundのコンポーネントが自動的にシーンのHierarchyに追加されますが、Snapdragon Spacesはバージョン0.20.0以降これをサポートしていません。レンダリングの問題を避けるには、このコンポーネントを無効にする必要があります。
XRCpuImage
オブジェクトは変換後に明示的に破棄する必要があります。これにはXRCpuImage
のDispose
関数を使用します。XRCpuImage
オブジェクトを廃棄しないと、カメラアクセスのサブシステムが破壊されるまでメモリリークする可能性があります。
変換前にNativeArray<byte>
を確保する場合、このバッファもコピーまたは操作後に破棄する必要があります。これを行うには、NativeArray<T>
のDispose
関数を使用します。NativeArray<byte>
を破棄しないと、カメラアクセスのサブシステムが破棄さ れるまでメモリがリークします。
AsyncConversion
オブジェクトは、データの使用後に明示的に破棄する必要があります。これにはAsyncConversion
の Dispose
関数を使用します。AsyncConversion
オブジェクトを破棄しないと、カメラアクセスのサブシステムが破棄されるまでメモリリークする可能性があります。
XRCpuImage.ConvertAsync
を使用してコールバック関数を提供する場合、提供されたNativeArray<byte>
のデータバッファを破棄する必要はありません。変換データは、コールバックが終了した際にカメラのサブシステムによって破棄されます。
frameReceived
、TryAcquireLatestCpuImage
、XRCpuImage.Convert
、XRCpuImage.ConvertAsync
の使用方法の詳細については、Unityのドキュメントを参照してください。
カメラフレームへのアクセスは、使用するデバイスによっては初期化に数秒かかる場合があります。サブシステムが正常に初期化される前にフレームにアクセスしようとしないでください。エラーを避けるため、AR Foundation APIからframeReceived
イベントを使用することを強く推奨します。
以下のサンプルコードは、frameReceived
イベントが発生したときに、AR Camera ManagerにCPU上のカメラ画像を要求します。成功すれば、XRCpuImage
のRawピクセルデータを直接管理されているTexture2D
のGetRawTextureData<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′UV420sp
とYUY2
フォーマットをサポートしています。
Y'UV420sp
- Yバッファとインターリーブされた2x2サブサンプリングされたU/Vバッファで構成されています。
YUY2
- YとU/VのサンプルをY-U-Y-Vの「マクロピクセル」という形でインターリーブした1つのバッファで構成され、各サンプルはクロミナンスが等しい2つの水平ピクセルを表し、垂直方向のサブサンプリングはありません。
YUVカラーモデルの詳細については、Wikipediaの「YCbCr」の記事のYUVの項目を参照してください。
RGB変換が不要な場合は、XRCpuImage
のGetPlane
関数で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は、ColorCameraPositionとColorCameraRotationの入力アクションを提供します。これらの入力アクションは、Position InputおよびRotation Inputとバインドすることで、Tracked Pose Driver (Input System)で使用できます。 このTracked Pose Driver (Input System)をゲームオブジェクトに追加することで、ゲームオ ブジェクトのポーズがカメラの外部パラメーターのポーズと一致します。
Tracked Pose Driver (Input System)コンポーネントは、基礎となるInput Systemの更新タイミングに依存しているため、関連するゲームオブジェクトのTransformが要求されたときに、1フレーム分古くなっている可能性があります。
最小限の待ち時間が必要な場合や、システムのエラー耐性が低い場合は、代わりに位置と回転をそれぞれTrackedInputDriver.positionAction.ReadValue<Vector3>()
とTrackedInputDriver.rotationAction.ReadValue<Quaternion>()
で取得することができます。
次の図は、これらの入力バインディングの使い方を示しています。
方法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.Convert
とXRCpuImage.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
この設定を有効にすると、特定のデバイスではパフォーマンスが向上する可能性がありますが、他のアーキテクチャではパフォーマンスに悪影響を及ぼす可能性があります。
以下は、Direct Memory Access Conversion設定が推奨されるデバイスを示した表です。
デバイス | 推奨されるか |
---|---|
Lenovo ThinkReality A3 | パフォーマンスを向上させるため、推奨。 |
Lenovo ThinkReality VRX | パフォーマンスを低下させるため、非推奨。 |
Spaces AR Camera Manager ConfigのMaximumVerticalResolution
とDownsamplingStride
プロパティはバージョン0.21.0で廃止されました。
追加センサーデータの取得
Spaces AR Camera Manager Configは、GetActiveCameraCount
メソッドを使用してデバイスのカメラセンサー数を公開します。この情報は、デュアルデバイスとVRデバイスを区別し、カメラ画像を適切に処理するために関連する可能性があります。