我们在之前的 Azure Kinect 编程实战系列文章中写的都是 C++ 下的使用,用到的都是 C++ 下的第三方库,比如 OpenCVOpenGLPCLOpen3D等。

后面的几篇文章,我将简述如何在 Unity 下调用 Azure Kinect,并显示 RGB + Depth + Point Cloud 的程序。

在 Unity 下调用 Azure Kinect

对于熟悉 Unity 编程开发的同学来说,一个典型的 Unity C# 源码文件有如下形式。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Program : MonoBehaviour
{
    void Start()
    {
    }

    void Update()
    {
    }
}

Start() 函数用来初始化环境。Update() 函数无限循环调用直到程序退出。

那么我们回顾一下之前在 C++ 环境下如何调用 Azure Kinect。

C++ 下如何调用 Azure Kinect

int main(int argc, char **argv)
{
    //1. device capture

    //2.  

    //3. while loop
    while (1)
    {
        //4. capture =>  
        
        //5. show  
        
        //6. waitKey
        
    }
    return 0;
}

在 C++ 的写法中,

1. device capture + 2. 应该类似的写在 Unity C# 脚本中的 Start() 函数。

3. while loop + 4. capture => + 5. show 则类似的写在 Update() 函数中。

那么我们重新写一下 Unity C# 脚本。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Microsoft.Azure.Kinect.Sensor;

public class Program : MonoBehaviour
{
    private Device device = null;
    private Capture capture = null;

    void Start()
    {
        device = Device.Open(0);

        device.StartCameras(new DeviceConfiguration
        {
            ColorFormat = ImageFormat.ColorBGRA32,
            ColorResolution = ColorResolution.R720p,
            DepthMode = DepthMode.NFOV_2x2Binned,
            SynchronizedImagesOnly = true,
            CameraFPS = FPS.FPS30
        });
    }

    void Update()
    {
        capture = device.GetCapture();
    }

    private void OnDestroy()
    {
        device.StopCameras();
    }
}

首先引入命名空间,

using Microsoft.Azure.Kinect.Sensor;

然后在 Program 类中声明两个私有变量,并初始化为 null

private Device device = null;private Capture capture = null;

Start() 函数中启动设备,并设置深度摄像头模式,关于模式设置的讲解请参看我之前发表的文章。

Azure Kinect 深度摄像头参数设置 KinectAzureDK编程实战_OpenCV 1

void Start()
{
    device = Device.Open(0);
    device.StartCameras(new DeviceConfiguration
    {
        ColorFormat = ImageFormat.ColorBGRA32,
        ColorResolution = ColorResolution.R720p,
        DepthMode = DepthMode.NFOV_2x2Binned,
        SynchronizedImagesOnly = true,
        CameraFPS = FPS.FPS30
    });
}

Update() 函数中,类似于在 C++ 中的写法,为获取实时帧,需要实时循环获取当前设备的 Capture。

void Update()
{
    capture = device.GetCapture();
}

我们引入了新的函数 onDestroy(),当 Unity 程序退出时,要关闭摄像头。

private void OnDestroy()
{
    device.StopCameras();
}

在 Unity 下显示 RGB 帧

我们以上面的代码为基础显示实时 RGB 帧。

我个人的习惯是,在 Unity 下新建一个 3D Game => Plane

然后将实时 RGB 帧作为这个 Plane 的纹理来显示。后面的 Depth 图的显示也如此。

那么我们需要再声明几个变量来辅助显示 RGB 帧。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Microsoft.Azure.Kinect.Sensor;

public class Program : MonoBehaviour
{
    public Game  colorImagePlane;

    private Device device = null;
    private Capture capture = null;

    protected byte[] colorImageBytes;

    private int colorImageWidth;
    private int colorImageHeight;
    
    void Start()
    {
        //...
    }

    void Update()
    {
        //...
    }

    private void OnDestroy()
    {
        //...
    }
}

Azure Kinect 输出的 RGB 帧的内存格式是 uint8 型的数组。那么在 C# 脚本中,我们就要写成相应的 byte[]

Start() 函数中,初始化这些变量。

void Start()
{
    colorImagePlane = Game .Find("ColorImagePlane");

    device = Device.Open(0);

    device.StartCameras(new DeviceConfiguration
    {
        ColorFormat = ImageFormat.ColorBGRA32,
        ColorResolution = ColorResolution.R720p,
        DepthMode = DepthMode.NFOV_2x2Binned,
        SynchronizedImagesOnly = true,
        CameraFPS = FPS.FPS30
    });

    colorImageWidth = device.GetCalibration().ColorCameraCalibration.ResolutionWidth;
    colorImageHeight = device.GetCalibration().ColorCameraCalibration.ResolutionHeight;

    colorImageBytes = new byte[colorImageWidth * colorImageHeight * 4];

    colorImagePlane.transform.localScale = 
        new Vector3((float)colorImageWidth / (float)colorImageHeight, 1.0f, 1.0f);
}

RGB 帧的每个像素保存了 B G R A,共 4uint8。那么我们初始化的 byte[] 数组大小为 colorImageWidth * colorImageHeight * 4

在 Update() 函数中,需要从 capture 中获取实时 RGB 帧,并将 RGB 帧转为 Unity 下纹理格式 Texture2D

void Update()
{
    capture = device.GetCapture();

    Image colorImage = capture.Color.Reference();
    colorImageBytes = colorImage.Memory.ToArray();

    Texture2D colorImageTex2D =
        new Texture2D(colorImageWidth, colorImageHeight, TextureFormat.BGRA32, false);

    colorImageTex2D.wrapMode = TextureWrapMode.Clamp;
    colorImageTex2D.filterMode = FilterMode.Point;
    colorImageTex2D.LoadRawTextureData(colorImageBytes);
    colorImageTex2D.Apply();
    
    colorImagePlane.GetComponent<Renderer>().material.mainTexture = colorImageTex2D;
}

根据 Azure Kinect 在 Github 开源的官方 SDK 中给出的 C# 代码。

Azure Kinect 官方开源 C# SDK: WPF 示例 https://github.com/microsoft/Azure-Kinect-Sensor-SDK/blob/develop/src/csharp/Examples/WPF/MainWindow.xaml.cs

官方推荐的获取图片的形式是获取其 引用

Image colorImage = capture.Color.Reference();

作为 Azure Kinect SDK 的 C# SDK代码中,Image 类有一个可以直接返回 byte 数组的函数。

public Memory<byte> Memory { get; }

那么我们可以直接获取 Image 所含像素的 byte 数组。

colorImageBytes = colorImage.Memory.ToArray();

然后,我们转换该 byte 数组为 Unity 下的纹理格式 Texure2D

Texture2D colorImageTex2D =
    new Texture2D(colorImageWidth, colorImageHeight, 
              TextureFormat.BGRA32, false);

colorImageTex2D.wrapMode = TextureWrapMode.Clamp;
colorImageTex2D.filterMode = FilterMode.Point;
colorImageTex2D.LoadRawTextureData(colorImageBytes);
colorImageTex2D.Apply();

注意 Azure Kinect 返回的 RGB 的分辨率都不是严格的2次方幂

那么 Texture2D 的格式还需要特别设置一下。

colorImageTex2D.wrapMode = TextureWrapMode.Clamp;
colorImageTex2D.filterMode = FilterMode.Point;

其实这个和 C++ OpenGL 面对非2次方幂分辨率纹理的处理方式一样。

glTexParameterf(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameterf(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

设置 wrap 模式和过滤方式。

下面一篇文章,我将讲述如何在 Unity 彩色化的显示 Depth Map。

商业合作请联系:

E-mail:forestsen@vip.qq.com

收藏 打印