基於C#的機器學習--面部和動態檢測-圖像過濾器

在本章中,咱們將展現兩個獨立的例子,一個用於人臉檢測,另外一個用於動態檢測,以及如何快速地將這些功能添加到應用程序中。node

       在這一章中,咱們將討論:算法

  1. 面部檢測
  2. 動態檢測
  3. 將檢測添加到應用程序中

面部檢測

       人臉檢測,是人臉識別的第一部分。若是你不能從屏幕上的全部東西中識別出一個或多我的臉,那麼你將永遠沒法識別那是誰的臉。框架

       首先讓咱們看一張咱們的應用程序截圖:機器學習

 

 

       上圖中,經過攝像頭咱們已經捕獲到一張圖像,接下來啓用面部跟蹤,看看會發生什麼:ide

 

 

       物體面部特徵正在被追蹤。咱們在物體周圍看到的是面部追蹤器(白色線框),它告訴咱們咱們這裏有一張臉;以及咱們的角度探測器(紅線),它提供了一些關於咱們臉所處水平方向的參考。學習

       當咱們移動物體時,面部追蹤器和角度探測器會追蹤他。這一切都很好,可是若是咱們在真實的人臉上啓用面部跟蹤會發生什麼呢?測試

以下圖,面部追蹤器和角度探測器正在追蹤人的面部。this

 

 

       當咱們把頭從一邊移到另外一邊時,面部追蹤器會跟蹤這個動做,能夠看到角度探測器會根據它所識別的面部水平角度進行調整。spa

能夠看到,在這裏咱們的顏色是黑白的,而不是彩色的。由於這是一個直方圖的反向投影,並且它是一個能夠更改的選項。3d

 

 

即便咱們遠離攝像機,讓其餘物體也進入視野中,面部追蹤器也能在諸多噪音中跟蹤咱們的臉,以下圖所示。這正是咱們在電影中看到的面部識別系統的工做原理,儘管它更爲先進。

 

 

如今讓咱們深刻程序內部,看看它究竟是如何工做的。

首先,咱們須要問本身一個問題,咱們想要解決的問題究竟是什麼。究竟是人臉識別仍是人臉檢測。這裏不得不提到Viola-Jones算法,由於,首先它有很高的檢出率和很低的誤報率,而後它很是擅長對數據的實時處理,最終要的一點是,它很是善於從非人臉中分別出人臉。

要永遠記住,人臉檢測只是人臉識別的第一步!

這個算法要求輸入一個完整的正面,垂直的臉。臉部須要直接指向採集設備,頭部儘可能不要歪,不要昂頭或低頭。

這裏有必要在強調一次,咱們要作的只是在圖像中檢測出人臉便可。

咱們的算法須要通過四個步驟來完成這件事:

  1. Haar 特徵選擇
  2. 建立一個完整的圖像
  3. AdaBoost算法(經過迭代弱分類器而產生最終的強分類器的算法) 訓練分類器
  4. 級聯分類器

在正式開始以前,讓咱們先捋一捋面部檢測究竟是若是工做的。全部的臉,不管是人的,動物的仍是其餘的,都有一些類似的特徵。例如,都有一個鼻子,兩個鼻孔,一張嘴巴,兩個眼睛,兩個耳朵等等。咱們的算法經過Haar特徵來匹配這些內容,咱們能夠經過其中任一項找到其餘的特徵。

       可是,咱們這裏會遇到一個問題。在一個24x24像素的窗口中,一共有162336個可能的特徵。若是這個計算結果是正確的,那麼計算他們的時間和成本將很是之高。所以,咱們將會使用一種被稱爲adaptive boosting(自適應提高法)的算法,或者更爲常見的AdaBoost算法。若是你研究過機器學習,我相信你據說過一種叫作boosting(提高)的技術。咱們的學習算法將使用AdaBoost來選擇最好的特徵並訓練分類器來使用它們。

       AdaBoost能夠與許多類型的學習算法一塊兒使用,而且被業界認爲是許多須要加強的任務的最佳開箱即用算法。一般在切換到另外一種算法並對其進行基準測試以前,您不會注意到它有多好和多快。實際上這種區別是很是明顯的。

       在繼續以前,咱們先來了解一下什麼是boosting(提高)技術。

Boosting從其餘弱學習算法中獲取輸出,並將其與weighted sum(加權和)結合,加權和是boost分類器的最終輸出。AdaBoost的自適應部分來自於這樣一個事實,即後續的學習者被調整,以支持那些被之前的分類器錯誤分類的實例。

       與其餘算法相比,該算法更傾向於對數據進行過擬合,因此AdaBoost對噪聲數據和異常值很敏感。所以咱們在準備數據的時候,須要格外注意這一點。

       如今,讓咱們來看看示例中的程序究竟是如何工做的。對於這個示例,咱們將再次使用Accord框架。

       首先建立一個FaceHaarCascade對象。該對象包含一系列 Haarlike 的特徵的弱分類階段的集合。每一個階段都包含一組分類器樹, 這些分類器樹將在決策過程當中使用。FaceHaarCascade自動爲咱們建立了全部這些階段和樹,而不須要咱們去關心具體實現的細節。

       首先,須要在底層構建一個決策樹,它將爲每一個階段提供節點,併爲每一個特性提供數值。如下是Accord的部分源碼。

List<HaarCascadeStage> stages = new List<HaarCascadeStage>();
List<HaarFeatureNode[]> nodes;
HaarCascadeStage stage;
stage = new HaarCascadeStage(0.822689414024353);
nodes = new List<HaarFeatureNode[]>();
nodes.Add(
    new[] {
        new HaarFeatureNode(
            0.004014195874333382,0.0337941907346249, 
            0.8378106951713562,
            new int[] { 3, 7, 14, 4, -1 },
            new int[] { 3, 9, 14, 2, 2 }
        ) 
    }
);
nodes.Add(
    new[] { 
        new HaarFeatureNode(
            0.0151513395830989,
            0.1514132022857666,
            0.7488812208175659,
            new int[] { 1, 2, 18, 4, -1 },
            new int[] { 7, 2, 6, 4, 3 }
        ) 
    }
);
nodes.Add(
    new[] { 
        new HaarFeatureNode(
            0.004210993181914091,
            0.0900492817163467, 
            0.6374819874763489,
            new int[] { 1, 7, 15, 9, -1 },
            new int[] { 1, 10, 15, 3, 3 }
        )
    }
);

一旦構建完成,咱們就可使用cascade對象來建立HaarObjectDetector,這就是咱們將用於檢測的對象。

接下來咱們須要提供:

  1. 咱們的面部級聯對象
  2. 搜索對象時使用的最小窗口大小
  3. 咱們的搜索模式,假設咱們只搜索一個對象
  4. 在搜索期間從新縮放搜索窗口時要使用的從新縮放因子
HaarCascade cascade = new FaceHaarCascade();
detector = new HaarObjectDetector(
  cascade,
  25,   ObjectDetectorSearchMode.Single,
  1.2f,   ObjectDetectorScalingMode.GreaterToSmaller
);

如今,咱們須要準備數據,在本示例中,咱們將使用筆記本電腦上的攝像頭捕獲全部圖像。然而,Accord.NET framework 使得使用其餘源進行數據採集變得很容易。例如 avi文件,jpg文件等等。

接下來,鏈接攝像頭,選擇分辨率:

// 建立視頻源
VideoCaptureDevice videoSource = new VideoCaptureDevice(form.VideoDevice);
// 設置幀的大小
videoSource.VideoResolution = selectResolution(videoSource);

/// <summary>
/// 獲取幀的大小
/// </summary>
/// <param name="videoSource">視頻源</param>
/// <returns>幀的大小</returns>
private VideoCapabilities selectResolution(VideoCaptureDevice videoSource)
{
        foreach (var cap in videoSource?.VideoCapabilities)
        {
            if (cap.FrameSize.Height == 240)
                    return cap;
            if (cap.FrameSize.Width == 320)
                    return cap;
        }
         return videoSource?.VideoCapabilities.Last();
} 

 

在這個演示中,你會注意到檢測物體正對着攝像機,在背景中,還有一些其餘的東西,那就是所謂的隨機噪聲。這樣作是爲了展現人臉檢測算法是如何區分出臉的。若是咱們的探測器不能處理這些,它就會在噪聲中消失,從而沒法檢測到臉。

隨着視頻源的加入,咱們須要在接收到新的視頻幀時獲得通知,以便處理它、應用標記,等等。咱們經過頻源播放器的NewFrameReceived事件來實現這一點。\

在咱們已經有了一個視頻源和一個視頻,讓咱們看看每當咱們被通知有一個新的視頻幀可用時發生了什麼。

咱們須要作的第一件事是對圖像進行採樣,以使它更容易工做:

ResizeNearestNeighbor resize = new ResizeNearestNeighbor(160, 120);

UnmanagedImage downsample = resize.Apply(im);

若是咱們沒有找到一張臉,咱們將保持跟蹤模式,等待一個具備可檢測面部的幀。一旦咱們找到了面部區域,咱們須要重置跟蹤器,定位臉部,減少它的大小,以儘量的剔除背景噪聲,而後初始化跟蹤器,並將在圖像上進行標記。代碼以下:

Rectangle[] regions = detector?.ProcessFrame(downsample);
if (regions != null && regions.Length > 0)
{
     tracker?.Reset();
    // 跟蹤第一張臉
    Rectangle face = regions[0];
    // 減少人臉檢測的大小,避免跟蹤背景上的其餘內容
    Rectangle window = new Rectangle(
      (int)((regions[0].X + regions[0].Width / 2f) * xscale),
      (int)((regions[0].Y + regions[0].Height / 2f) * yscale),
      1,
      1
  ); window.Inflate((int)(0.2f * regions[0].Width * xscale), (int)(0.4f * regions[0].Height * yscale)); if (tracker != null) { tracker.SearchWindow = window; tracker.ProcessFrame(im); } marker = new RectanglesMarker(window); marker.ApplyInPlace(im); eventArgs.Frame = im.ToManagedImage(); tracking = true; } else { detecting = true; }

一旦檢測到臉,咱們的圖像幀是這樣的:

 

 

若是把頭偏向一邊,咱們如今的形象應該是這樣的:

 

 

動態檢測

能夠看到,在上一個例子中,咱們不只實現了面部檢測,還實現了動態檢測。如今,讓咱們把目光轉向更大的範圍,檢測任何物體的運動,而不只僅是面部。咱們將繼續使用Accord.NET來實現。

在動態檢測中,咱們會用紅色高亮顯示屏幕上的任何運動。移動的數量由任何一個區域的紅色濃度表示。因此,以下圖所示,咱們能夠看到手指在移動可是其餘的都是靜止的。

 

 

以下圖所示,能夠看到整個手的移動範圍在增長。

 

 

以下圖所示,一旦整隻手開始移動,你不只能夠看到更多的紅色,並且紅色的總量是在增長的:

 

 

若是不但願對整個屏幕區域進行運動處理,能夠自定義運動區域;運動檢測只會發生在這些區域。以下圖,能夠看到咱們已經定義了一個運動區域,這是惟一的一個區域。

 

 

如今,若是咱們在攝像頭前面作一些運動,能夠看到程序只檢測到了來自咱們定義區域發生的運動。

 

 

如今,咱們來作這樣一個測試,在咱們自定義的檢測區域範圍內,放置一個物體,而後咱們把手放在這個物體後面進行運動,固然手也是在這個自定義的檢測區域範圍內進行運動的。以下圖,能夠看到,手的運動被檢測出來了。

 

 

如今咱們使用另外一個選項,網格運動突出顯示。它會使得檢測到的運動區域基於定義的網格在紅色方塊中突出顯示,以下圖所示。

 

 

將檢測添加到應用程序中

如下是處理接收到新的幀的代碼:       

 private void videoSourcePlayer_NewFrame(object sender, NewFrameEventArgs args)
 {
  lock (this)   {     if (motionDetector != null)     {       float motionLevel = motionDetector.ProcessFrame(args.Frame);       if (motionLevel > motionAlarmLevel)       {         //快門速度2秒         flash = (int)(2 * (1000 / timer.Interval));       }       //檢查對象的數       if (motionDetector.MotionProcessingAlgorithm is BlobCountingObjectsProcessing)       {         BlobCountingObjectsProcessing countingDetector = (BlobCountingObjectsProcessing)motionDetector.MotionProcessingAlgorithm;         detectedObjectsCount = countingDetector.ObjectsCount;       }       else       {         detectedObjectsCount = -1;       }       // 積累的歷史       motionHistory.Add(motionLevel);       if (motionHistory.Count > 300)       {         motionHistory.RemoveAt(0);       }       if (顯示運動歷史ToolStripMenuItem.Checked)       DrawMotionHistory(args.Frame);
    }
  }
}

這裏的關鍵是檢測視頻幀中發生的動量,這是經過如下代碼完成的。對於本例,咱們使用的是兩級的運動報警級別,可是你也可使用任何你喜歡的級別定義。一旦超過這個閾值,就能夠實現所需的邏輯,例如發送電子郵件、開始視頻捕獲等等。

float motionLevel = motionDetector.ProcessFrame(args.Frame);
if (motionLevel > motionAlarmLevel)
{
  //快門速度2秒
  flash = (int)(2 * (1000 / timer.Interval));
} 

總結

       在這一章中,咱們學習了面部和動態檢測,還展現了一些簡單易用的代碼。咱們能夠輕鬆的將這些功能添加到本身的程序中。

相關文章
相關標籤/搜索