Kinect 开发 —— 近距离探测

如何将Kinect设备作为一个近距离探测传感器。为了演示这一点,我们处理的场景可能在以前看到过。就是某一个人是否站在Kinect前面,在Kinect前面移动的是人还是什么其他的物体。当我们设置的触发器超过一定的阈值,我们就发起另一个处理线程。类似的触发器如当用户走进房间时,我们打开房间里面的灯。对于商业广告系统,当展示牌前面没有人时,可以以“attract”模式展现内容,而当人靠近展示牌时,则展现一些更多的可供交互的内容。和仅仅编写交互性强的应用程序不同,我们可以编写一些能够感知周围环境的应用程序。

Kinect甚至可以被我们改造成一个安全的摄像头,当某些重要的特征发生时,我们可以记录下Kinect看到的景象。在晚上,我在门前放了一些食物,住在哪儿的猫可以吃到。最近我开始怀疑有其他的动物在偷吃我们家猫的食物。我将Kinect作为一个运动探测和视频录像机放在门口,这样就可以知道真实发生的情况了。如果你喜欢一些自然景像,你可以通过简单的设置来实现长时间的录像来获取其他动物的出现情况。虽然在探测到有动物靠近时开启视频影像录制可以节省磁盘空间,但是识别有动物靠近可能需要花费很长的时间。如果像我这样,你可以打开影像录制功能,这样你能够看到长时间的景象变化,比如风吹叶落的声音。Kinect作为一种显示增强的工具,其不仅仅可以作为应用程序的输入设备,一些新的Kinect的可能应用正在迅速发掘出来。


简单的近距离探测

建立一个近距离探测应用,当有人站在Kinect设备前面时,打开视频影像录制。自然,当有用户进入到Kinect的视野范围时需要触发一些列的操作。最简单的实现近距离探测的方法是使用KinectSDK中的骨骼探测功能。

    首先创建一个名为KinectProximityDetectionUsingSkeleton的WPF应用程序,添加Microsoft.Kinect.dll和对System.Drawing命名空间的引用。将ImageExtensions.cs类文件拷贝到项目中,并添加对ImageManipulationExtensionMethods命名空间的引用。主界面元素非常简单,我们只是添加了一个名为rgbImage的Image对象来从Kinect影像数据中获取并显示数据。

<Grid >
    <Image   Name="rgbImage" Stretch="Fill"/>
</Grid>

下面的代码显示了一些初始化代码。大部分的代码都是在为Image提供数据源。在MainWindows的构造函数中,对_kinectSensor对象进行了初始化,并注册影像数据流和骨骼数据流响应事件。这部分代码和以前我们写的代码类似。所不同的是,我们添加了一个布尔型的_isTracking来表示是否我们的近距离探测算法识别到了有人进入视野。如果有,则更新影像数据流,更新image对象。如果没有,我们略过影像数据流,给Image控件的Source属性赋null值。

Microsoft.Kinect.KinectSensor _kinectSensor;
bool _isTracking = false;

public MainWindow()
{
    InitializeComponent();
          
    this.Unloaded += delegate{
        _kinectSensor.ColorStream.Disable();
        _kinectSensor.SkeletonStream.Disable();
    };

    this.Loaded += delegate
    {
        _kinectSensor = Microsoft.Kinect.KinectSensor.KinectSensors[0];
        _kinectSensor.ColorFrameReady += ColorFrameReady;
        _kinectSensor.ColorStream.Enable();
             
        _kinectSensor.Start();
    };
}

void ColorFrameReady(object sender, ColorImageFrameReadyEventArgs e)
{
            
    if (_isTracking)
    {
        using (var frame = e.OpenColorImageFrame())
        {
            if (frame != null)
                rgbImage.Source = frame.ToBitmapSource();
        };
    }
    else
        rgbImage.Source = null;
}

private void OnDetection()
{
    if (!_isTracking)
        _isTracking = true;
}

private void OnDetectionStopped()
{
    _isTracking = false;
}

为了能够处理_isTracking标签,我们需要注册KienctSensor.SkeletonFrameReady事件。SkeletonFrameReady事件类似心脏跳动一样驱动程序的运行。只要有物体在Kinect前面,SkeletonFrameReady事件就会触发。在我们的代码中,我们需要做的是检查骨骼数据数组,判断数组中是否有骨骼数据处在追踪状态中。代码如下。

    有时候我们不需要抛出事件。我们有一个内建的机制能够通知我们有人体进入到了Kinect视野范围内,但是,我们没有一个机制能够告诉在什么时候人走出了视野或者不在追踪状态。为了实现这一功能,不管是否探测到了用户,我们开启一个计时器,这个计时器的功能是存储最后一次追踪到的事件的时间,我们检查当前时间和这一时间的时间差,如果时差超过某一个阈值,就认为我们失去了对物体的追踪,我们应该结束当前的近距探测。

int _threshold = 100;
DateTime _lastSkeletonTrackTime;
DispatcherTimer _timer = new DispatcherTimer();
public MainWindow()
{
    InitializeComponent();
          
    this.Unloaded += delegate{
        _kinectSensor.ColorStream.Disable();
        _kinectSensor.SkeletonStream.Disable();
    };

    this.Loaded += delegate
    {
        _kinectSensor = Microsoft.Kinect.KinectSensor.KinectSensors[0];
        _kinectSensor.ColorFrameReady += ColorFrameReady;
        _kinectSensor.ColorStream.Enable();
             

        _kinectSensor.SkeletonFrameReady += Pulse;
        _kinectSensor.SkeletonStream.Enable();
        _timer.Interval = new TimeSpan(0, 0, 1);
        _timer.Tick += new EventHandler(_timer_Tick);

        _kinectSensor.Start();
    };
}
void _timer_Tick(object sender, EventArgs e)
{

    if (DateTime.Now.Subtract(_lastSkeletonTrackTime).TotalMilliseconds > _threshold)
    {
        _timer.Stop();
        OnDetectionStopped();
    }
}

private void Pulse(object sender, SkeletonFrameReadyEventArgs e)
{
    using (var skeletonFrame = e.OpenSkeletonFrame())
    {
        if (skeletonFrame == null || skeletonFrame.SkeletonArrayLength == 0)
            return; 

        Skeleton[] skeletons = new Skeleton[skeletonFrame.SkeletonArrayLength];
        skeletonFrame.CopySkeletonDataTo(skeletons);

        for (int s = 0; s < skeletons.Length; s++)
        {
            if (skeletons[s].TrackingState == SkeletonTrackingState.Tracked)
            {
                OnDetection();

                _lastSkeletonTrackTime = DateTime.Now;

                if (!_timer.IsEnabled)
                {
                    _timer.Start();
                }
                break;
            }
        }
    }
}

使用景深数据进行近距离探测

使用骨骼追踪进行近距离探测是近距离探测的基础,当没有一个人进入到视野中,并进行交互时,电子广告牌进入“StandBy”模式,在这种情况下,我们只是简单的播放一些视频。不幸的是,骨骼追踪不能很好的捕捉类似在我家后面的门廊上的偷食物的浣熊或者是在旷野中的大脚野人的图像。这是因为骨骼追踪的算法是针对人类的关键特征以及特定的人体类型进行设计的。超出人体的范围,在Kinect镜头前骨骼追踪会失败或者是追踪会变的时断时续。

    为了处理这一情况,我们可以使用Kinect的深度影像数据,而不能依靠骨骼追踪。深度影像数据也是近距离探测的一种基本类型。如下代码所示,程序运行中必须配置或者获取彩色影像和深度影像数据流,而不是骨骼数据流。

相比骨骼追踪数据,使用景深数据作为近距探测算法的基础数据有一些优点。首先只要传感器在运行,那么深度影像数据就是连续的。这避免了需要另外设置一个计时器来监控在探测过程是否意外终止。另外,我们可以对我们要探测的对象离Kinect的距离设置一个最小和最大的距离阈值。当物体离Kinect的距离比这个最小的阈值还要小,或者超过最大阈值的范围时,将_isTracking设置为false。下面的代码中,我们探测距离Kinect 1米至1.2米的对象。通过分析深度影像数据的每一个像素来判断是否有像元落在该距离范围内。如果有一个像元落在该范围内,那么停止对影像的继续分析,将isTracking设置为true。ColorFrameReady事件处理探测到物体的事件,然后使用彩色影像数据来更新image对象。

void DepthFrameReady(object sender, DepthImageFrameReadyEventArgs e)
{
    bool isInRange = false;
    using (var imageData = e.OpenDepthImageFrame())
    {
        if (imageData == null || imageData.PixelDataLength == 0)
            return;
        short[] bits = new short[imageData.PixelDataLength];
        imageData.CopyPixelDataTo(bits);
        int minThreshold = 1000;
        int maxThreshold = 1200;

        for (int i = 0; i < bits.Length; i += imageData.BytesPerPixel)
        {
            var depth = bits[i] >> DepthImageFrame.PlayerIndexBitmaskWidth;

            if (depth > minThreshold && depth < maxThreshold)
            {
                isInRange = true;
                OnDetection();
                break;
            }
        }
    }

    if (!isInRange)
        OnDetectionStopped();
}

相比骨骼数据,使用深度影像数据进行近距离探测的最后一个好处是速度较快。即使在比我们对景深数据处理更低的级别上进行骨骼追踪,骨骼追踪仍需要有完整的人体出现在Kinect视野中。同时Kinect SDK需要利用决策树分析整个人体影像数据,并将识别出来的结果和骨骼识别预设的一些特征参数进行匹配,以判断是否是人体而不是其他物体。使用景深影像数据算法,我们只需要查找是否有一个像元点落在指定的深度值范围内,而不用分析整个人体。和之前的骨骼追中算法不同,代码中深度值探测算法只要有物体落在Kinect传感器的视野范围内,都会触发OnDetection方法。


对近距离探测的改进

当然,使用景深数据进行近距离探测也有一些缺点。最小和最大深度阈值必须明确定义,这样才能避免_isTracking永远为true的情况。深度影像允许我们放松只能对人体进行近距离探测的这一限制,但是这一放松有点过了,使得即使一些静止不动的物体可能也会触发近距离探测。在实现一个运动测试来解决这一问题之前,我们可以实现一个探测条件不紧也不松的近距离探测。

    下面的代码展示了如何结合深度影像数据中的深度值数据和游戏者索引位数据来实现一个近距离探测器。如果骨骼追踪算法符合你的需求,同时你又想将探测的对象限定在距离传感器的最大最小距离阈值范围内,这种方法是最好的选择。这在露天的广告牌中也很有用,比如可以在广告牌前设置一个区域范围。当有人进入到这一范围时触发交互。当人进入到距离装有Kinect的广告牌1米至1.5时触发另一种交互,当人离广告牌够近以至于可以触摸到时,触发另外一种交互。要建立这种类型的近距离探测,需要在MainWindows的构造函数中开启骨骼追踪功能,使得能够使用景深影像的深度数据和游戏者索引位数据。这些都做好了之后,可以改写之前例子中的DepthFrameReady事件,来对距离阈值进行判断,并检查是否有游戏者索引位数据存在。代码如下:

void DepthFrameReady(object sender, DepthImageFrameReadyEventArgs e)
{
    bool isInRange = false;
    using (var imageData = e.OpenDepthImageFrame())
    {
        if (imageData == null || imageData.PixelDataLength == 0)
            return;
        short[] bits = new short[imageData.PixelDataLength];
        imageData.CopyPixelDataTo(bits);
        int minThreshold = 1700;
        int maxThreshold = 2000;

        for (int i = 0; i < bits.Length; i += imageData.BytesPerPixel)
        {
            var depth = bits[i] >> DepthImageFrame.PlayerIndexBitmaskWidth;
            var player = bits[i] & DepthImageFrame.PlayerIndexBitmask;

            if (player > 0 && depth > minThreshold && depth < maxThreshold)
            {
                isInRange = true;
                OnDetection();
                break;
            }
        }
    }

    if(!isInRange)
        OnDetectionStopped();
}
原文地址:https://www.cnblogs.com/sprint1989/p/3859137.html