【AzureKinect】【Unity】RGBカメラと深度センサの情報を画像として表示する
AzureKinectのRGBカメラと深度センサの情報は、最大30fpsの速さで取得することができます。 以下記事では、これらの情報からメッシュを生成しました。
今回は、各種情報をTexture2Dに変換して表示します。 AzureKinectをUnity上で開発する際に、基本となる実装になるでしょう。
基本実装
AzureKinectの起動や終了等、基盤となるスクリプトです。
using Microsoft.Azure.Kinect.Sensor; using System.Threading.Tasks; using UnityEngine; public class KinectImageViewer : MonoBehaviour { private Device _kinectDevice = null; private void Start() { Init(); StartLoop(); } private void OnDestroy() { _kinectDevice.StopCameras(); // Kinectの終了処理 } private void Init() { _kinectDevice = Device.Open(0); _kinectDevice.StartCameras(new DeviceConfiguration { ColorFormat = ImageFormat.ColorBGRA32, ColorResolution = ColorResolution.R1080p, DepthMode = DepthMode.NFOV_2x2Binned, SynchronizedImagesOnly = true, CameraFPS = FPS.FPS30 }); // Kinectの開始処理。設定はお好みで。 } private async void StartLoop() { while (true) { using (Capture capture = await Task.Run(() => _kinectDevice.GetCapture()).ConfigureAwait(true)) { // ここで画像の生成、更新処理を行う } } }
ループ部分のcapture
に、今後の実装に必要なことが入っています。
Unity向けに改変して表示していきます。
なお、Unity上でAzureKinectを初めて使用する方は、以下記事を参考にセットアップしてください。 tks-yoshinaga.hatenablog.com
RGB画像の表示
capture.
capture.Color
から1ピクセルずつの色情報を表すBGRA配列を取得し、テクスチャーを生成します。
そのままの順番で表示すると、上下左右が反転される点に注意しましょう。
UnityEngine.UI
と Microsoft.Azure.Kinect.Sensor
のImageが被るのでちょっとだけ厄介...。
using Microsoft.Azure.Kinect.Sensor; using System.Threading.Tasks; using UnityEngine; public class KinectImageViewer : MonoBehaviour { [SerializeField] private UnityEngine.UI.RawImage _viewerRawImage = null; // --- 省略 --- private async void StartLoop() { while (true) { using (Capture capture = await Task.Run(() => _kinectDevice.GetCapture()).ConfigureAwait(true)) { // 必要な情報を用意 Image colorImage = capture.Color; int pixelWidth = colorImage.WidthPixels; int pixelHeight = colorImage.HeightPixels; BGRA[] bgraArr = colorImage.GetPixels<BGRA>().ToArray(); Color32[] colorArr = new Color32[bgraArr.Length]; // BGRA配列 => Color32配列 for (int i = 0; i < colorArr.Length; i++) { int index = colorArr.Length - 1 - i; colorArr[i] = new Color32( bgraArr[index].R, bgraArr[index].G, bgraArr[index].B, bgraArr[index].A ); } // Texture2Dの作成 Texture2D resultTex = new Texture2D(pixelWidth , pixelHeight ); resultTex.SetPixels32(colorArr); resultTex.Apply(); // RawImageの更新 _viewerRawImage.texture = GetTexture2D(pixelWidth, pixelHeight, colorArr); _viewerRawImage.rectTransform.sizeDelta = new Vector2(width, height); // rectTransformのサイズ変更 } } }
その辺にあったガムやルアーを配置して撮影してみました
深度画像の表示
capture.Depth
から震度情報のushort配列を取得し、テクスチャーを生成します。
深度の範囲を指定できるようにしておくと、いい感じに画像を調整できます。
using Microsoft.Azure.Kinect.Sensor; using System.Threading.Tasks; using UnityEngine; public class KinectImageViewer : MonoBehaviour { [SerializeField] private UnityEngine.UI.RawImage _viewerRawImage = null; [SerializeField] private int _depthDistanceMin = 200; [SerializeField] private int _depthDistanceMax = 3000; // --- 省略 --- private async void StartLoop() { while (true) { using (Capture capture = await Task.Run(() => _kinectDevice.GetCapture()).ConfigureAwait(true)) { // 必要な情報を用意 Image depthImage = capture.Depth; int pixelWidth = depthImage.WidthPixels; int pixelHeight = depthImage.HeightPixels; ushort[] depthByteArr = depthImage.GetPixels<ushort>().ToArray(); Color32[] colorArr = new Color32[depthByteArr.Length]; // ushort配列 => Color32配列 for (int i = 0; i < colorArr.Length; i++) { int index = colorArr.Length - 1 - i; int depthVal = 255 - (255 * (depthByteArr[index] - _depthDistanceMin) / _depthDistanceMax); // 近いほど値が大きくなるよう計算 if (depthVal < 0) { depthVal = 0; } else if (depthVal > 255) { depthVal = 255; } colorArr[i] = new Color32( (byte)depthVal, (byte)depthVal, (byte)depthVal, 255 ); } // Texture2Dの作成 Texture2D resultTex = new Texture2D(pixelWidth , pixelHeight ); resultTex.SetPixels32(colorArr); resultTex.Apply(); // RawImageの更新 _viewerRawImage.texture = GetTexture2D(pixelWidth, pixelHeight, colorArr); _viewerRawImage.rectTransform.sizeDelta = new Vector2(width, height); // rectTransformのサイズ変更 } } }
結果はこんな感じ。RGBとDepthでは画角が異なります。
完成版スクリプト
Inspector上で以下を変更できるようにしてみました。
- 表示する画像を選択(RGB or Depth)
- 深度の範囲
using Microsoft.Azure.Kinect.Sensor; using System.Threading.Tasks; using UnityEngine; public enum KinectImageType { RGB = 0, Depth = 1, } public class KinectImageViewer : MonoBehaviour { [SerializeField] private KinectImageType _imageType = KinectImageType.RGB; [SerializeField] private UnityEngine.UI.RawImage _viewerRawImage = null; [SerializeField] private int _depthDistanceMin = 500; [SerializeField] private int _depthDistanceMax = 5000; private Device _kinectDevice = null; private void Start() { InitKinect(); StartLoop(); } private void OnDestroy() { _kinectDevice.StopCameras(); } private void InitKinect() { _kinectDevice = Device.Open(0); _kinectDevice.StartCameras(new DeviceConfiguration { ColorFormat = ImageFormat.ColorBGRA32, ColorResolution = ColorResolution.R1080p, DepthMode = DepthMode.NFOV_2x2Binned, SynchronizedImagesOnly = true, CameraFPS = FPS.FPS30 }); } private async void StartLoop() { while (true) { using (Capture capture = await Task.Run(() => _kinectDevice.GetCapture()).ConfigureAwait(true)) { if (_imageType == 0) { ViewColorImage(capture); } else { ViewDepthImage(capture); } } } } // RGB情報をRawImageに表示 private void ViewColorImage(Capture capture) { Image colorImage = capture.Color; int pixelWidth = colorImage.WidthPixels; int pixelHeight = colorImage.HeightPixels; BGRA[] bgraArr = colorImage.GetPixels<BGRA>().ToArray(); Color32[] colorArr = new Color32[bgraArr.Length]; for (int i = 0; i < colorArr.Length; i++) { int index = colorArr.Length - 1 - i; colorArr[i] = new Color32( bgraArr[index].R, bgraArr[index].G, bgraArr[index].B, bgraArr[index].A ); } _viewerRawImage.texture = GetTexture2D(pixelWidth, pixelHeight, colorArr); } // 深度情報をRawImageに表示 private void ViewDepthImage(Capture capture) { Image colorImage = capture.Depth; int pixelWidth = colorImage.WidthPixels; int pixelHeight = colorImage.HeightPixels; ushort[] depthByteArr = colorImage.GetPixels<ushort>().ToArray(); Color32[] colorArr = new Color32[depthByteArr.Length]; for (int i = 0; i < colorArr.Length; i++) { int index = colorArr.Length - 1 - i; int depthVal = 255 - (255 * (depthByteArr[index] - _depthDistanceMin) / _depthDistanceMax); if (depthVal < 0) { depthVal = 0; } else if (depthVal > 255) { depthVal = 255; } colorArr[i] = new Color32( (byte)depthVal, (byte)depthVal, (byte)depthVal, 255 ); } _viewerRawImage.texture = GetTexture2D(pixelWidth, pixelHeight, colorArr); } // カラー配列 -> Texture2D private Texture2D GetTexture2D(int width, int height, Color32[] colorArr) { _viewerRawImage.rectTransform.sizeDelta = new Vector2(width, height); Texture2D resultTex = new Texture2D(width, height); resultTex.SetPixels32(colorArr); resultTex.Apply(); return resultTex; } }
参考記事
以下の公式サンプル github.com
Unityを使用せずに画像を表示している記事 tks-yoshinaga.hatenablog.com tks-yoshinaga.hatenablog.com
を参考にさせていただきました。