カメラフレームへのアクセス (実験的)
はじめに
カメラフレームへのアクセス機能は、全てのAR Foundation API
を完全にサポートしておらず、パッケージとSnapdragon Spaces Service側の最適化により、現時点ではリリース間の後方互換性が失われているため、この機能は実験的とされています。
このサンプルでは、画像処理のために RGBのカメラフレームと内部のプロパティを取得する方法を紹介します。
カメラフレームへのアクセスとAR FoundationのAR Camera Manager
のコンポーネントの基本的な機能については、Unityのドキュメントを参照してください。
機能の有効化
この機能を使用するには、以下のOpenXRのプロジェクト設定でCamera Accessにチェックする必要があります。
Project Settings > XR Plug-in Management > OpenXR (Androidタブ)
サンプルのインポート
まだサンプルをインポートしていない場合は、以下の手順でインポートすることができます。
サンプルシーンは以下の場所に存在します。
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:デバイスのカメラの様々なプロパティを列挙するカメラ情報のパネル
CPU上のデバイスカメラ画像の取得
新しいカメラフレームがサブシステムで利用可能になるたびに、AR Camera Manager
はframeReceived
イベントを発行します。このイベントをサブスクライブすることで、他のスクリプトができるだけ早いタイミングで最新のカメラフレームを取得し、そのフレームに対して処理を実行できるようになります。
カメラフレームが利用可能になると、カメラマネージャのTryAcquireLatestCpuImage
関 数は、選択されたデバイスカメラからの単一のRaw画像を表すXRCpuImage
オブジェクトを返します。この画像のRawピクセルデータは、NativeArray<byte>
を返すXRCpuImageのConvert
関数で抽出できます。
Snapdragon SpacesはXRCpuImage.ConvertAsync
によるフレームの非同期変換をサポートしていません。
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
オブジェクトは変換後に明示的に破棄する必要があります。これにはXRCpuImage
のDispose
関数を使用します。XRCpuImage
オブジェクトを廃棄しないと、カメラアクセスのサブシステムが破壊されるまでメモリリークする可能性があります。
変換前にNativeArray<byte>
を確保する場合、このバッファもコピーまたは操作後に破棄する必要があります。これを行うには、NativeArray<T>
のDispose
関数を使用します。NativeArray<byte>
を破棄しないと、カメラアクセスのサブシステムが破棄されるまでメモリがリークします。
frameReceived
、TryAcquireLatestCpuImage
、XRCpuImage
の使用方法の詳細については、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");
}