Kinect 开发 —— 进阶指引 (下)
运动识别
利用运动识别(motion detection)来进行近景识别是最有意思的一种方式。实现运动识别的基本原理是设置一个起始的基准RGB图像,然后将从摄像头获取的每一帧影像和这个基准图像进行比较。如果发现了差异,我们可以认为有东西进入到了摄像头的视野范围。
不难看出这种策略是有缺陷的。在现实生活中,物体是运动的。在一个房间里,某个人可能会轻微移动家具。在户外,一辆汽车可能会启动,风可能会将一些小树吹的摇摇晃晃。在这些场景中,尽然没有连续的移动动作,但是物体的状态还是发生了变化,依据之前的策略,系统会判断错误。因此,在这些情况下,我们需要间歇性的更改基准图像才能解决这一问题。
EmguCV项目的官方网站为http://www.emgu.com/wiki/index.php/Main_Page 实际的源代码和安装包放在SourceForge(http://sourceforge.net/projects/emgucv/files/ )上。本文使用的Emgu版本为2.3.0。Emgu的安装过程很简单直观,只需要点击下载好的可执行文件即可。不过有一点需要注意的是EmguCV似乎在x86架构的计算机上运行的最好。如果在64位的机器上开发,最好为Emgu库的目标平台指定为x86,如下图所示(你也可以在官网上下载源码然后自己在x64平台上编译)。
要使用Emgu库,需要添加对下面三个dll的引用:Emgu.CV、Emgu.CV.UI以及Emgu.Util。
因为Emgu是对C++类库的一个.Net包装,所以需要在dll所在的目录放一些额外的非托管的dll,使得Emgu能够找到这些dll进行处理。Emgu在应用程序的执行目录查找这些dll。如果在debug模式下面,则在bin/Debug目录下面查找。在release模式下,则在bin/Release目录下面。共有11个非托管的C++ dll需要放置在相应目录下面,他们是opencv_calib3d231.dll, opencv_conrib231.dll, opencv_core231.dll,opencv_features2d231.dll, opencv_ffmpeg.dll, opencv_highgui231.dll, opencv_imgproc231.dll,opencv_legacy231.dll, opencv_ml231.dll, opencv_objectdetect231.dll, and opencv_video231.dll。这些dll可以在Emgu的安装目录下面找到。为了方便,可以拷贝所有以opencv_开头的dll。
在我们的扩展方法库中,我们需要一些额外的扩展帮助方法。上一篇文章讨论过,每一种类库都有其自己能够理解的核心图像类型。在Emgu中,这个核心的图像类型是泛型的Image<TColor,TDepth>类型,它实现了Emgu.CV.IImage接口。下面的代码展现了一些我们熟悉的影像数据格式和Emgu特定的影像格式之间转换的扩展方法。新建一个名为EmguExtensions.cs的静态类,并将其命名空间改为ImageManipulationMethods,和我们之前ImageExtensions类的命名空间相同。我们可以将所有的的扩展方法放到同一个命名空间中。这个类负责三种不同影像数据类型之间的转换:从Microsoft.Kinect.ColorFrameImage到Emgu.CV.Image<TColor,TDepth>,从System.Drawing.Bitmap到Emgu.CV.Image<TColor,TDepth>以及Emgu.CV.Image<TColor,TDepth>到System.Windows.Media.Imaging.BitmapSource之间的转换。
使用Emgu类库来实现运动识别,我们将用到在之前文章中讲到的“拉数据”(polling)模型而不是基于事件的机制来获取数据。这是因为图像处理非常消耗系统计算和内存资源,我们希望能够调节处理的频率,而这只能通过“拉数据”这种模式来实现。需要指出的是本例子只是演示如何进行运动识别,所以注重的是代码的可读性,而不是性能,大家看了理解了之后可以对其进行改进。
因为彩色影像数据流用来更新Image控件数据源,我们使用深度影像数据流来进行运动识别。需要指出的是,我们所有用于运动追踪的数据都是通过深度影像数据流提供的。如前面文章讨论,CompositionTarget.Rendering事件通常是用来进行从彩色影像数据流中“拉”数据。但是对于深度影像数据流,我们将会创建一个BackgroundWorker对象来对深度影像数据流进行处理。BackgroundWorker对象将会调用Pulse方法来“拉”取深度影像数据,并执行一些消耗计算资源的处理。当BackgroundWorker完成了一个循环,接着从深度影像数据流中“拉”取下一幅影像继续处理。代码中声明了两个名为MotionHistory和IBGFGDetector的Emgu成员变量。这两个变量一起使用,通过相互比较来不断更新基准影像来探测运动。
KinectSensor _kinectSensor;
private MotionHistory _motionHistory;
private IBGFGDetector<Bgr> _forgroundDetector;
bool _isTracking = false; public MainWindow()
{
InitializeComponent(); this.Unloaded += delegate
{
_kinectSensor.ColorStream.Disable();
}; this.Loaded += delegate
{ _motionHistory = new MotionHistory(
1.0, //in seconds, the duration of motion history you wants to keep
0.05, //in seconds, parameter for cvCalcMotionGradient
0.5); //in seconds, parameter for cvCalcMotionGradient _kinectSensor = KinectSensor.KinectSensors[]; _kinectSensor.ColorStream.Enable();
_kinectSensor.Start(); BackgroundWorker bw = new BackgroundWorker();
bw.DoWork += (a, b) => Pulse();
bw.RunWorkerCompleted += (c, d) => bw.RunWorkerAsync();
bw.RunWorkerAsync();
};
}
下面的代码是执行图象处理来进行运动识别的关键部分。代码在Emgu的示例代码的基础上进行了一些修改。Pluse方法中的第一个任务是将彩色影像数据流产生的ColorImageFrame对象转换到Emgu中能处理的图象数据类型。_forgroundDetector对象被用来更新_motionHistory对象,他是持续更新的基准影像的容器。_forgroundDetector还被用来与基准影像进行比较,以判断是否发生变化。当从当前彩色影像数据流中获取到的影像和基准影像有不同时,创建一个影像来反映这两张图片之间的差异。然后将这张影像转换为一系列更小的图片,然后对运动识别进行分解。我们遍历这一些列运动的图像来看他们是否超过我们设定的运动识别的阈值。如果这些运动很明显,我们就在界面上显示视频影像,否则什么都不显示。
private void Pulse()
{
using (ColorImageFrame imageFrame = _kinectSensor.ColorStream.OpenNextFrame())
{
if (imageFrame == null)
return; using (Image<Bgr, byte> image = imageFrame.ToOpenCVImage<Bgr, byte>())
using (MemStorage storage = new MemStorage()) //create storage for motion components
{
if (_forgroundDetector == null)
{
_forgroundDetector = new BGStatModel<Bgr>(image
, Emgu.CV.CvEnum.BG_STAT_TYPE.GAUSSIAN_BG_MODEL);
} _forgroundDetector.Update(image); //update the motion history
_motionHistory.Update(_forgroundDetector.ForgroundMask); //get a copy of the motion mask and enhance its color
double[] minValues, maxValues;
System.Drawing.Point[] minLoc, maxLoc;
_motionHistory.Mask.MinMax(out minValues, out maxValues
, out minLoc, out maxLoc);
Image<Gray, Byte> motionMask = _motionHistory.Mask
.Mul(255.0 / maxValues[]); //create the motion image
Image<Bgr, Byte> motionImage = new Image<Bgr, byte>(motionMask.Size);
motionImage[] = motionMask; //Threshold to define a motion area
//reduce the value to detect smaller motion
double minArea = ; storage.Clear(); //clear the storage
Seq<MCvConnectedComp> motionComponents = _motionHistory.GetMotionComponents(storage);
bool isMotionDetected = false;
//iterate through each of the motion component
for (int c = ; c < motionComponents.Count(); c++)
{
MCvConnectedComp comp = motionComponents[c];
//reject the components that have small area;
if (comp.area < minArea) continue; OnDetection();
isMotionDetected = true;
break;
}
if (isMotionDetected == false)
{
OnDetectionStopped();
this.Dispatcher.Invoke(new Action(() => rgbImage.Source = null));
return;
} this.Dispatcher.Invoke(
new Action(() => rgbImage.Source = imageFrame.ToBitmapSource())
);
}
}
} private void OnDetection()
{
if (!_isTracking)
_isTracking = true;
} private void OnDetectionStopped()
{
_isTracking = false;
}
运动模板 —— 运动检测(只用到RGB信息)
<Window x:Class="KinectMovementDetection.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="400" Width="525">
<Grid >
<Image Name="rgbImage" Stretch="Fill"/>
</Grid>
</Window>using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
using Microsoft.Kinect;
using System.Drawing.Imaging;
using System.Runtime.InteropServices;
using Emgu.CV;
using Emgu.CV.Structure;
using System.Windows;
using System.IO; namespace ImageManipulationExtensionMethods
{
public static class EmguImageExtensions
{
public static Image<TColor, TDepth> ToOpenCVImage<TColor, TDepth>(this ColorImageFrame image)
where TColor : struct, IColor
where TDepth : new()
{
var bitmap = image.ToBitmap();
return new Image<TColor, TDepth>(bitmap);
} public static Image<TColor, TDepth> ToOpenCVImage<TColor, TDepth>(this Bitmap bitmap)
where TColor : struct, IColor
where TDepth : new()
{
return new Image<TColor, TDepth>(bitmap);
} public static System.Windows.Media.Imaging.BitmapSource ToBitmapSource(this IImage image)
{
var source = image.Bitmap.ToBitmapSource();
return source;
}
}
}using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
using Microsoft.Kinect;
using System.Drawing.Imaging;
using System.Runtime.InteropServices;
using Emgu.CV;
using Emgu.CV.Structure;
using System.Windows;
using System.IO; namespace ImageManipulationExtensionMethods
{
public static class EmguImageExtensions
{
public static Image<TColor, TDepth> ToOpenCVImage<TColor, TDepth>(this ColorImageFrame image)
where TColor : struct, IColor
where TDepth : new()
{
var bitmap = image.ToBitmap();
return new Image<TColor, TDepth>(bitmap);
} public static Image<TColor, TDepth> ToOpenCVImage<TColor, TDepth>(this Bitmap bitmap)
where TColor : struct, IColor
where TDepth : new()
{
return new Image<TColor, TDepth>(bitmap);
} public static System.Windows.Media.Imaging.BitmapSource ToBitmapSource(this IImage image)
{
var source = image.Bitmap.ToBitmapSource();
return source;
}
}
}using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using Emgu.CV;
using Emgu.CV.Structure;
using Emgu.CV.VideoSurveillance;
using Microsoft.Kinect;
using System.ComponentModel;
using ImageManipulationExtensionMethods; namespace KinectMovementDetection
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
KinectSensor _kinectSensor;
private MotionHistory _motionHistory; // 历史运动模板
private IBGFGDetector<Bgr> _forgroundDetector;
bool _isTracking = false; public MainWindow()
{
InitializeComponent(); this.Unloaded += delegate
{
_kinectSensor.ColorStream.Disable();
}; this.Loaded += delegate
{ _motionHistory = new MotionHistory(
1.0, //in seconds, the duration of motion history you wants to keep
0.05, //in seconds, parameter for cvCalcMotionGradient
0.5); //in seconds, parameter for cvCalcMotionGradient _kinectSensor = KinectSensor.KinectSensors[]; _kinectSensor.ColorStream.Enable();
_kinectSensor.Start(); BackgroundWorker bw = new BackgroundWorker(); // 单独线程上执行操作
bw.DoWork += (a, b) => Pulse();
bw.RunWorkerCompleted += (c, d) => bw.RunWorkerAsync();
bw.RunWorkerAsync();
};
} private void Pulse()
{
using (ColorImageFrame imageFrame = _kinectSensor.ColorStream.OpenNextFrame())
{
if (imageFrame == null)
return; using (Image<Bgr, byte> image = imageFrame.ToOpenCVImage<Bgr, byte>())
using (MemStorage storage = new MemStorage()) //create storage for motion components
{
if (_forgroundDetector == null)
{
_forgroundDetector = new BGStatModel<Bgr>(image
, Emgu.CV.CvEnum.BG_STAT_TYPE.GAUSSIAN_BG_MODEL);
} _forgroundDetector.Update(image); //update the motion history
_motionHistory.Update(_forgroundDetector.ForegroundMask); //get a copy of the motion mask and enhance its color
double[] minValues, maxValues;
System.Drawing.Point[] minLoc, maxLoc;
_motionHistory.Mask.MinMax(out minValues, out maxValues
, out minLoc, out maxLoc);
Image<Gray, Byte> motionMask = _motionHistory.Mask
.Mul(255.0 / maxValues[]); //create the motion image
Image<Bgr, Byte> motionImage = new Image<Bgr, byte>(motionMask.Size);
motionImage[] = motionMask; //Threshold to define a motion area
//reduce the value to detect smaller motion
double minArea = ; storage.Clear(); //clear the storage
Seq<MCvConnectedComp> motionComponents = _motionHistory.GetMotionComponents(storage);
bool isMotionDetected = false;
//iterate through each of the motion component
for (int c = ; c < motionComponents.Count(); c++)
{
MCvConnectedComp comp = motionComponents[c];
//reject the components that have small area;
if (comp.area < minArea) continue; OnDetection();
isMotionDetected = true;
break;
}
if (isMotionDetected == false)
{
OnDetectionStopped();
this.Dispatcher.Invoke(new Action(() => rgbImage.Source = null));
return;
} this.Dispatcher.Invoke(
new Action(() => rgbImage.Source = imageFrame.ToBitmapSource())
);
}
}
} private void OnDetection()
{
if (!_isTracking)
_isTracking = true;
} private void OnDetectionStopped()
{
_isTracking = false;
} }
}
Kinect 开发 —— 进阶指引 (下)的更多相关文章
- Kinect 开发 —— 进阶指引(上)
本文将会介绍一些第三方类库如何来帮助处理Kinect传感器提供的数据.使用不同的技术进行Kinect开发,可以发掘出Kinect应用的强大功能.另一方面如果不使用这些为了特定处理目的而开发的一些类库, ...
- Kinect for Windows SDK开发入门(15):进阶指引 下
Kinect for Windows SDK开发入门(十五):进阶指引 下 上一篇文章介绍了Kinect for Windows SDK进阶开发需要了解的一些内容,包括影像处理Coding4Fun K ...
- Kinect 开发 —— 手势识别(下)
基本手势追踪 手部追踪在技术上和手势识别不同,但是它和手势识别中用到的一些基本方法是一样的.在开发一个具体的手势控件之前,我们先建立一个可重用的追踪手部运动的类库以方便我们后续开发.这个手部追踪类库包 ...
- Kinect 开发 —— 语音识别(下)
使用定向麦克风进行波束追踪 (Beam Tracking for a Directional Microphone) 可以使用这4个麦克风来模拟定向麦克风产生的效果,这个过程称之为波束追踪(beam ...
- Kinect开发文章目录
整理了一下去年为止到现在写的和翻译的Kinect的相关文章,方便大家查看.另外,最近京东上微软在搞活动, 微软 Kinect for Windows 京东十周年专供礼包 ,如果您想从事Kinect开发 ...
- Kinect开发笔记之三Kinect开发环境配置具体解释
0.前言: 首先说一下我的开发环境,Visual Studio是2013的,系统是win8的64位版本号,SDK是Kinect for windows SDK 1.8版本 ...
- iOS开发进阶
<iOS开发进阶>基本信息作者: 唐巧 出版社:电子工业出版社ISBN:9787121247453上架时间:2014-12-26出版日期:2015 年1月开本:16开页码:268版次:1- ...
- HTML5游戏开发进阶指南(亚马逊5星畅销书,教你用HTML5和JavaScript构建游戏!)
HTML5游戏开发进阶指南(亚马逊星畅销书,教你用HTML5和JavaScript构建游戏!) [印]香卡(Shankar,A.R.)著 谢光磊译 ISBN 978-7-121-21226-0 201 ...
- Kinect开发学习笔记之(一)Kinect介绍和应用
Kinect开发学习笔记之(一)Kinect介绍和应用 zouxy09@qq.com http://blog.csdn.net/zouxy09 一.Kinect简单介绍 Kinectfor Xbox ...
随机推荐
- IHttpHandler的学习(0-2)
这个IHttpHandler,要想到asp.net生命周期 ,想到哪个从你发起请求开始,这个请求通过HttpModule------>IHttpHandler的: 执行HttpModule的一系 ...
- PostgreSQL Replication之第二章 理解PostgreSQL的事务日志(3)
2.3 理解一致性和数据丢失 挖掘PostgreSQL事务日志而不考虑一致性是不可能的.在本章的第一部分,我们已经大体上解释了事务日志的基本思想.您已经知道,无需事先的日志改变的能力,使数据处于一种好 ...
- 515Nod 1126 求递推序列的第n项【矩阵快速幂】
有一个序列是这样定义的:f(1) = 1, f(2) = 1, f(n) = (A * f(n - 1) + B * f(n - 2)) mod 7. 给出A,B和N,求f(n)的值. Input 输 ...
- CMD和AMD的区别
CMD和AMD俩者之间的区别 AMD和CMD最大的区别是对依赖模块的执行时机处理不同 CMD和AMD都是CommonJS延伸而来的,CommonJS是随着node的出现而出现的,它是一个规范,用于定义 ...
- session 存入 redis
<?php header('content-type:text/html;charset=utf-8'); /* * 更改 session 存储位置及存储方式. */ ini_set('sess ...
- yes---重复输出指定的字符串
yes命令在命令行中输出指定的字符串,直到yes进程被杀死.不带任何参数输入yes命令默认的字符串就是y. 语法 yes(参数) 参数 字符串:指定要重复打印的字符串. 实例 [root@localh ...
- head---显示文件的开头的内容
head命令用于显示文件的开头的内容.在默认情况下,head命令显示文件的头10行内容. 语法 head(选项)(参数) 选项 -n<数字>:指定显示头部内容的行数: -c<字符数& ...
- 20180929 北京大学 人工智能实践:Tensorflow笔记03
更改的程序部分如下: 另: 难?????????????见链接: https://www.bilibili.com/video/av22530538/?p=17 + (完)
- 【Uva 11400】Lighting System Design
[Link]: [Description] 你要构建一个供电系统; 给你n种灯泡来构建这么一个系统; 每种灯泡有4个参数 1.灯泡的工作电压 2.灯泡的所需的电源的花费(只要买一个电源就能供这种灯泡的 ...
- POJ——T 1182 食物链
http://poj.org/problem?id=1182 Time Limit: 1000MS Memory Limit: 10000K Total Submissions: 77012 ...