這篇隨筆記錄一下最近作人臉識別遇到過的一些問題的解決辦法,方便增強印象。以雙目攝像頭爲例。前端
首先是封裝:算法
視頻處理在主窗體下操做比較簡單,可是若是是將視頻處理過程封裝起來,就稍微有些麻煩了,會引發線程安全的問題。安全
首先將視頻控件的對象或者是句柄傳到接口裏,而後對該對象進行操做。異步
1 private AForge.Controls.VideoSourcePlayer VideoPlayer { get; set; } 2 private AForge.Controls.VideoSourcePlayer VideoPlayer2 { get; set; } 3 public void SetAFVideo(bool isDefault, AForge.Controls.VideoSourcePlayer video) 6 { 7 if (isDefault) 8 { 9 this.VideoPlayer = video; 10 this.VideoPlayer.Paint += VideoPlayer_Paint; 11 } 12 else 13 { 14 this.VideoPlayer2 = video; 15 } 16 }
有了視頻控件的對象後,接着是打開攝像頭ide
1 /// <summary> 2 /// 打開攝像頭 3 /// </summary> 4 /// <param name="isDefault"></param> 5 /// <param name="videoName">攝像頭名稱</param> 6 /// <returns></returns> 7 public bool AFOpenVideo(bool isDefault, string videoName) 8 { 9 FaceResult.Reset(); 10 _isStopDetect = false; 11 if (isDefault) 12 { 13 if (this.VideoPlayer == null) return false; 14 this.VideoPlayer.Show(); 15 if (_deviceVideoClr == null) 16 { 17 _deviceVideoClr = Video.GetVideoSource(videoName); 18 } 19 this.VideoPlayer.VideoSource = _deviceVideoClr; 20 this.VideoPlayer.Start(); 21 return true; 22 } 23 else 24 { 25 if (this.VideoPlayer2 == null) return false; 26 this.VideoPlayer2.Show(); 27 if (_deviceVideoGry == null) 28 { 29 _deviceVideoGry = Video.GetVideoSource(videoName); 30 } 31 this.VideoPlayer2.VideoSource = _deviceVideoGry; 32 this.VideoPlayer2.Start(); 33 return true; 34 } 35 }
關閉攝像頭。關閉攝像頭,有兩種方式,一種是在主線程中直接關閉,另一種是在子線程裏關閉this
/// <summary> /// 關閉攝像頭 /// </summary> public void CloseVideo() { _isStopDetect = true; Thread.Sleep(150); } /// <summary> /// 關閉攝像頭 /// </summary> public void CloseVideoDirect() { CancelFaceDetect(); if (this.VideoPlayer != null) { if (this.VideoPlayer.IsRunning) { VideoPlayer.SignalToStop(); VideoPlayer.Hide(); } } if (this.VideoPlayer2 != null) { if (this.VideoPlayer2.IsRunning) { VideoPlayer2.SignalToStop(); VideoPlayer2.Hide(); } } }
取消人臉比對spa
private void CancelFaceDetect() { _mEvent.Set(); }
人臉識別算法初始化線程
/// <summary> /// 人臉算法初始化 /// </summary> /// <returns></returns> public void InitFaceEngine(Action<bool, string> action) { //初始化過程 }
人臉比對過程,在子線程裏處理,防止頁面卡頓,同時每次只處理一幀。經過ManualResetEvent信號變量來終止人臉比對過程,視頻比對過程在code
FaceDetect方法裏。比對完成,必須釋放img對象佔用的內存
1 public void StartFaceDetect() 2 { 3 if (!_isFaceCompare) return; 4 _mEvent.Reset(); 5 Task.Factory.StartNew(() => 6 { 7 Task.Delay(2000).Wait(); 8 while (true) 9 { 10 try 11 { 12 if (_mEvent.WaitOne(100)) 13 { 14 FaceResult.Reset(); 15 break; 16 } 17 var imgClr = this.VideoPlayer.GetCurrentVideoFrame(); 18 var imgGry = this.VideoPlayer2.GetCurrentVideoFrame(); 19 float offsetX = VideoPlayer.Width * 1f / imgClr.Width; 20 float offsetY = VideoPlayer.Height * 1f / imgClr.Height; 21 FaceResult.OffsetX = offsetX; 22 FaceResult.OffsetY = offsetY; 23 FaceDetect(imgClr, imgGry); 24 imgClr.Dispose(); 25 imgGry.Dispose(); 26 } 27 catch (System.Exception ex) 28 { 29 throw new Exception(ex.Message); 30 } 31 } 32 }); 33 }
給人臉框選。經過_isStopDetect變量來標識,在人臉識別經過後是否關閉攝像頭orm
1 private void VideoPlayer_Paint(object sender, System.Windows.Forms.PaintEventArgs e) 2 { 3 for (int i = 0; i < FaceResult.FaceNumber; i++) 4 { 5 //根據Rect進行畫框 6 e.Graphics.DrawRectangle(_rectPen, FaceResult.X, FaceResult.Y, FaceResult.Width, FaceResult.Height); 7 if (_trackUnit.message != "" && FaceResult.X > 0 && FaceResult.Y > 0) 8 { 9 //將上一幀檢測結果顯示到頁面上 10 e.Graphics.DrawString(_trackUnit.message, _trackFont, _trackBrush, FaceResult.X, FaceResult.Y + 5); 11 } 12 } 13 if (_isStopDetect) 14 { 15 CloseVideoDirect(); 16 } 17 }
將比對結果實時告訴前端。比對的信息經過事件FaceDetectShowHandler 來通知到前端,人臉比對成功經過事件FaceDectctResultHandler通知前端,前端收到FaceDectctResultHandler後執CloseVideo(),以關閉攝像頭
1 public Action<string> FaceDetectShowHandler { get; set; } 2 public Action FaceDectctResultHandler { get; set; } 3 /// <summary> 4 /// 人臉比對 5 /// </summary> 6 /// <param name="bitClr"></param> 7 /// <param name="bitGry"></param> 8 public void FaceDetect(Bitmap bitClr, Bitmap bitGry) 9 { 10 //... 11 this.__errString = "沒有檢測到人臉,請正視相機!!"; 12 FaceDetectShowHandler?.Invoke(_errString); 13 this.__errString = "類似度高"; 14 FaceDectctResultHandler?.Invoke(); 15 16 }
經過前面的示例代碼知道,視頻比對是在一個task子線程進行,前端要收到比對信息的消息通知,也是在子線程進行,若是將消息顯示在前端界面上進行刷新,就必須經過委託來刷新界面,不然引發線程安全的問題。同理,前端收到FaceDectctResultHandler事件通知後,要關閉攝像頭,就不能直接調用close方法來關閉,而必須在主線程關閉,這裏經過一個變量_isStopDetect在paint事件裏關閉攝像頭,由於Paint事件是主線程,因此主線程裏關閉攝像頭徹底沒問題。另外關於視頻圖像的處理,有兩種方式,一種是用一個while循環,一種是經過線程池,每捕捉到一幀圖像就丟到線程池裏異步處理,可是用線程池可能會引起一個問題,就是引用類型變量在一個循環沒有處理完時,變量就被修改了,此時容易引發程序崩潰。用while循環,須要開啓一個子線程,防止頁面卡頓,是一個同步處理的過程。在關閉攝像頭時,有一個休眠150毫秒的動做,目的是讓ManualResetEvent先收到終止的信號,終止while循環,而後執行關閉攝像頭的動做,不然時間太短,致使while一直執行。