前言:运动对象常用在视频监控领域,目的是从序列图像中将变化区域从背景图像中提取出来,运动区域的有效检测对目标分类、跟踪、行为理解等后期处理非常重要。根据摄像机与运动目标之间的关系可分为静态背景下的运动目标检(摄像机静止)和动态背景下的运动目标检测(摄像机也同时运动)。项目中我用到的是静态背景下的运动目标检测,需通过固定摄像机检测运动物体,并完成抓取动作。
内容:
运动目标检测常用的方法一般分为两大类,一种是基于特征的方法,另一种是基于灰度的方法。基于特征的方法是依据图像的特征来检测运动目标,多用于目标较大、特征容易提取的场合。基于灰度的方法一般是依据图像中灰度的变化来检测运动目标。目前基于视频的检测方法主要有基于帧间差分的方法、基于光流场的方法、基于背景差的方法等。
帧间差分法是基于运动图像序列中相邻两帧图像具有较强的相关性而提出的检测方法,具有很强的自适应性。但是如果物体灰度分布均匀,这种方法会造成目标重叠部分形成较大空洞,严重时造成目标分割不连通,从而检测不到目标。
光流场法是基于对光流的估算进行检测分割的方法,光流中既包括被观察物体的运动信息,也包括有关的结构信息。光流场的不连续性可以用来将图像分割成对应于不同运动物体的区域。但多数光流法的计算复杂、耗时,难以满足实时监测的需求。
背景差法是运动检测中最常用的一种方法,它将输入图像与背景图像进行比较,直接根据灰度变化等统计信息的变化来分割运动目标。差分法一般计算量小实用价值大,但受光线、天气等外界条件影响较大。其基本思想是将当前图像与背景相减,若像素差值大于某一阈值,则判断此像素为运动目标上的点。其最重要的一步就是背景建模,需要估计出一个不带有运动目标的背景模型,通过计算当前帧与该背景模型的差来确定运动目标的位置。
项目中,我准备使用帧间差分法来实现运动目标检测。
帧间差分法通过对序列图像中相邻帧做差分或相减运算,利用序列图像中相邻帧的强相关性做变化检测,从而检测出运动目标。它通过直接比较相邻帧对应像素点灰度值的不同,然后通过选取阈值来提取序列图像中的运动区域。在序列图像中,第k帧图像fk(x,y)和第k+1帧图像fk+1(x,y)之间的变化可用二值差分图像D(x,y)表示,如下:
式中,T为差分图像二值化的阈值。二值图像中为“1”的部分由前后两帧对应像素灰度值发生变化的部分组成,通常包括运动目标和噪声;为“0”的部分由前后两帧对应像素灰度值不发生变化的部分组成。
算法流程:读取视频文件->图像预处理(包括将彩色图像转换为灰度图像、滤波降噪--中值滤波)->帧间差分->运动目标检测(形态学滤波--膨胀、腐蚀)。
具体实现:采用C#+Emgu完成,具体代码如下:
1 using System;
2 using System.Collections.Generic;
3 using System.Windows;
4 using System.Windows.Threading;
5 using Emgu.CV;
6 using Emgu.CV.Structure;
7 using Emgu.CV.WPF;
8 using Rectangle = System.Drawing.Rectangle;
9
10 namespace ImageDetect_ATAW
11 {
12 /// <summary>
13 /// MainWindow.xaml 的交互逻辑
14 /// </summary>
15 public partial class MainWindow : Window
16 {
17 private Capture _capture;
18 private Image<Bgr, byte> _currentFrame;
19 private Image<Bgr, byte> _previousFrame;
20 private int Threshold = 40;
21 private DispatcherTimer _timer = new DispatcherTimer();
22 //private int[] _rectPosition = {130, 240, 120, 150};
23 public MainWindow()
24 {
25 InitializeComponent();
26 _capture = new Capture();
27 }
28
29 private void buttonOpen_Click(object sender, RoutedEventArgs e)
30 {
31 _capture.QueryFrame();
32 _previousFrame = _capture.QueryFrame();
33 _timer.Interval = new TimeSpan(0, 0, 0, 0, 40);
34 _timer.Tick += timer_Tick;
35 _timer.Start();
36 }
37
38 void timer_Tick(object sender, EventArgs e)
39 {
40 _currentFrame = _capture.QueryFrame().Clone();
41 var _currentFrameCopy = _currentFrame.Clone();
42 //_currentFrameCopy.Draw(new Rectangle(_rectPosition[0],_rectPosition[1],_rectPosition[2],_rectPosition[3]),new Bgr(0,0,255), 2);
43 imageBox1.Source = BitmapSourceConvert.ToBitmapSource(_currentFrameCopy);
44 FrameDiff(_previousFrame,_currentFrameCopy);
45 }
46
47 private void FrameDiff(Image<Bgr, byte> preFrame, Image<Bgr, byte> curFrame)
48 {
49 //var position = new int[4];
50 var curGrayImage = curFrame.Convert<Gray, byte>();
51 var preGrayImage = preFrame.Convert<Gray, byte>();
52 var diffImage = curGrayImage.AbsDiff(preGrayImage);
53 var imageB = diffImage.ThresholdBinary(new Gray(Threshold), new Gray(255));
54 var imageBin = imageB.Erode(2).Dilate(2);
55 var contour = imageBin.FindContours();
56 var list = new List<Rectangle>();
57
58 while (contour != null)
59 {
60 if (contour.Area >150)
61 {
62 var xValue = contour.BoundingRectangle.X;
63 var yValue = contour.BoundingRectangle.Y;
64 var wValue = contour.BoundingRectangle.Width;
65 var hValue = contour.BoundingRectangle.Height;
66
67 list.Add(contour.BoundingRectangle);
68 imageBin.Draw(new Rectangle(xValue,yValue,wValue,hValue),new Gray(255), 2);
69 }
70 contour = contour.HNext;
71 }
72 imageBox2.Source = BitmapSourceConvert.ToBitmapSource(imageBin);
73 _previousFrame = curFrame.Clone();
74 //return list.ToArray();
75 }
76
77 private void buttonClose_Click(object sender, RoutedEventArgs e)
78 {
79 _capture.Dispose();
80 _timer.Stop();
81 }
82
83
84 }
85 }
View Code
Keep moving.