前言:運動對象常用在視頻監控領域,目的是從序列圖像中將變化區域從背景圖像中提取出來,運動區域的有效檢測對目標分類、跟蹤、行為理解等后期處理非常重要。根據攝像機與運動目標之間的關系可分為靜態背景下的運動目標檢(攝像機靜止)和動態背景下的運動目標檢測(攝像機也同時運動)。項目中我用到的是靜態背景下的運動目標檢測,需通過固定攝像機檢測運動物體,並完成抓取動作。
內容:
運動目標檢測常用的方法一般分為兩大類,一種是基於特征的方法,另一種是基於灰度的方法。基於特征的方法是依據圖像的特征來檢測運動目標,多用於目標較大、特征容易提取的場合。基於灰度的方法一般是依據圖像中灰度的變化來檢測運動目標。目前基於視頻的檢測方法主要有基於幀間差分的方法、基於光流場的方法、基於背景差的方法等。
幀間差分法是基於運動圖像序列中相鄰兩幀圖像具有較強的相關性而提出的檢測方法,具有很強的自適應性。但是如果物體灰度分布均勻,這種方法會造成目標重疊部分形成較大空洞,嚴重時造成目標分割不連通,從而檢測不到目標。
光流場法是基於對光流的估算進行檢測分割的方法,光流中既包括被觀察物體的運動信息,也包括有關的結構信息。光流場的不連續性可以用來將圖像分割成對應於不同運動物體的區域。但多數光流法的計算復雜、耗時,難以滿足實時監測的需求。
背景差法是運動檢測中最常用的一種方法,它將輸入圖像與背景圖像進行比較,直接根據灰度變化等統計信息的變化來分割運動目標。差分法一般計算量小實用價值大,但受光線、天氣等外界條件影響較大。其基本思想是將當前圖像與背景相減,若像素差值大於某一閾值,則判斷此像素為運動目標上的點。其最重要的一步就是背景建模,需要估計出一個不帶有運動目標的背景模型,通過計算當前幀與該背景模型的差來確定運動目標的位置。
項目中,我准備使用幀間差分法來實現運動目標檢測。
幀間差分法通過對序列圖像中相鄰幀做差分或相減運算,利用序列圖像中相鄰幀的強相關性做變化檢測,從而檢測出運動目標。它通過直接比較相鄰幀對應像素點灰度值的不同,然后通過選取閾值來提取序列圖像中的運動區域。在序列圖像中,第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 }
