关于人脸识别的视频图片处理


这篇随笔记录一下最近做人脸识别遇到过的一些问题的解决办法,方便加强印象。以双目摄像头为例。

首先是封装:

视频处理在主窗体下操作比较简单,但是如果是将视频处理过程封装起来,就稍微有些麻烦了,会引起线程安全的问题。

首先将视频控件的对象或者是句柄传到接口里,然后对该对象进行操作。

 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         }

有了视频控件的对象后,接着是打开摄像头

 1  /// 
 2         /// 打开摄像头
 3         /// 
 4         /// 
 5         /// 摄像头名称
 6         /// 
 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         }

关闭摄像头。关闭摄像头,有两种方式,一种是在主线程中直接关闭,另外一种是在子线程里关闭

/// 
        /// 关闭摄像头
        /// 
        public void CloseVideo()
        {
            _isStopDetect = true;
            Thread.Sleep(150);
        }
        /// 
        /// 关闭摄像头
        /// 
        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();
                }
            }
        }

取消人脸比对

 private void CancelFaceDetect()
        {
            _mEvent.Set();
        }

人脸识别算法初始化

 /// 
        /// 人脸算法初始化
        /// 
        /// 
        public void InitFaceEngine(Action<bool, string> action)
        {
               //初始化过程
        }

人脸比对过程,在子线程里处理,防止页面卡顿,同时每次只处理一帧。通过ManualResetEvent信号变量来终止人脸比对过程,视频比对过程在

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变量来标识,在人脸识别通过后是否关闭摄像头

 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  /// 
 4  /// 人脸比对
 5  /// 
 6  /// 
 7  /// 
 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
一直执行。