打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
「图像处理」C#+AForge.Net+DlibDotNet实现人脸识别

折腾了两天才算是有点成果了。整理一下吧。

用C# WinForm开发,使用AForge调用摄像头,加上Dlib(DotNet)实现一下人脸识别

 

目录


1 AForge.Net调用摄像头

1.1 安装AForge.Net的依赖包

操作摄像头需要用到【AForge.Video.DirectShow】。

从NuGet里查找进行安装,安装时会同时安装它的依赖项:【AForge.Video】和【AForge】

1.2 设计WinForm界面

界面如下:

 图中蓝色字体标注了我对每个控件的定义的ID,方便对应下文的代码

左边的【VideoSourcePlayer】控件(AForge中的控件)是摄像头的画面显示,右边的【PictureBox】是后面做人脸识别的显示框。

下方的【PictureBox】是拍照预览框。

期望是程序运行时,检测摄像头设备,添加到【coBox_camList】中,用户选择要用的相机设备,该设备所支持的分辨率自动添加到【coBox_Reslution】列表中,并自动选中默认分辨率,点击【打开】按钮即可显示摄像头画面并实时进行人脸检测。

1.3 添加代码

上面界面对应的代码(Form1.cs)如下:

  1. //-----------form1.cs
  2. using System;
  3. using System.Drawing;
  4. using System.Windows.Forms;
  5. using AForge.Video; //引用命名空间
  6. using AForge.Video.DirectShow; //引用命名空间
  7. namespace AForgeCamera
  8. {
  9. public partial class AForgeCamera : Form
  10. {
  11. private FilterInfoCollection CaptureDevices; //设备列表
  12. private VideoCaptureDevice captureDevice; //摄像头设备
  13. private VideoCapabilities[] videoCapabilities; //摄像头能力列表
  14. private VideoCapabilities videoCapabilitie; //单一摄像头能力(分辨率等)
  15. public AForgeCamera()
  16. {
  17. InitializeComponent();
  18. //控件状态等初始化
  19. btn_cam.Enabled = false;
  20. btn_cam.Text = "打开";
  21. btn_takePic.Enabled = false;
  22. pBox_view.SizeMode = PictureBoxSizeMode.StretchImage;
  23. pBox_faceDst.SizeMode = PictureBoxSizeMode.StretchImage;
  24. //获取摄像头并添加到coBox_CamList
  25. CaptureDevices = new FilterInfoCollection(FilterCategory.VideoInputDevice);
  26. foreach (FilterInfo filterInfo in CaptureDevices)
  27. {
  28. coBox_CamList.Items.Add(filterInfo.Name);
  29. }
  30. faceDetection = new FaceDetection();
  31. }
  32. private void AForgeCamera_FormClosing(object sender, FormClosingEventArgs e)
  33. {
  34. //主窗口关闭,必要的清理
  35. captureDevice.Stop();
  36. CaptureDevices.Clear();
  37. AVPlayer_Cam1.VideoSource = null;
  38. AVPlayer_Cam1.Stop();
  39. }
  40. private void coBox_CamList_SelectedIndexChanged(object sender, EventArgs e)
  41. {
  42. //选择摄像头后
  43. coBox_Resolution.Items.Clear(); //先清理上次选择的摄像头支持的分辨率
  44. //获取摄像头设备
  45. FilterInfo filterInfo = CaptureDevices[coBox_CamList.SelectedIndex];
  46. captureDevice = new VideoCaptureDevice(filterInfo.MonikerString);
  47. //获取所选择的摄像头分辨率列表并添加
  48. videoCapabilities = captureDevice.VideoCapabilities;
  49. foreach (VideoCapabilities capabilitie in videoCapabilities)
  50. {
  51. coBox_Resolution.Items.Add(capabilitie.FrameSize.Width.ToString() +
  52. "×" + capabilitie.FrameSize.Height.ToString());
  53. }
  54. //选中默认分辨率 触发coBox_Resolution_SelectedIndexChanged()
  55. if (coBox_Resolution.Items.Count > 0)
  56. {
  57. coBox_Resolution.SelectedIndex = 0;
  58. }
  59. }
  60. private void coBox_Resolution_SelectedIndexChanged(object sender, EventArgs e)
  61. {
  62. //获取选择的分辨率,选择时没有关闭摄像头时进行关闭
  63. videoCapabilitie = videoCapabilities[coBox_Resolution.SelectedIndex];
  64. btn_cam.Enabled = true;
  65. btn_takePic.Enabled = true;
  66. if (AVPlayer_Cam1.IsRunning)
  67. {
  68. captureDevice.Stop();
  69. AVPlayer_Cam1.VideoSource = null;
  70. AVPlayer_Cam1.VideoSource = null;
  71. btn_cam.Text = "打开";
  72. }
  73. }
  74. //打开和关闭摄像头
  75. private void btn_cam_Click(object sender, EventArgs e)
  76. {
  77. if (btn_cam.Text == "打开")
  78. {
  79. captureDevice.VideoResolution = videoCapabilitie;
  80. AVPlayer_Cam1.VideoSource = captureDevice;
  81. captureDevice.SimulateTrigger();
  82. AVPlayer_Cam1.Start();
  83. btn_cam.Text = "停止";
  84. btn_takePic.Enabled = true;
  85. }
  86. else
  87. {
  88. AVPlayer_Cam1.Stop();
  89. AVPlayer_Cam1.VideoSource = null;
  90. btn_cam.Text = "打开";
  91. btn_takePic.Enabled = false;
  92. }
  93. }
  94. //拍照并预览
  95. private void btn_takePic_Click(object sender, EventArgs e)
  96. {
  97. pBox_view.Image = AVPlayer_Cam1.GetCurrentVideoFrame();
  98. }
  99. }
  100. }

运行效果如下:

(啊。。。。。保存gif好费劲,感谢ScreenToGif,是个好软件)

1.4 补充说明

1.4.1 关于VideoSourcePlayer 控件

         它的实例化对象就是界面上的图像预览框,在使用时,把摄像头设备赋值给它的【videoSource】属性。可以理解为该控件其实是【VideoCaptureDevice】的一个复制品,或者说是一个客户端。我们在打开摄像头和关闭摄像头时,使用了如下方式:

  1. //指定视频来源
  2. AVPlayer_Cam1.VideoSource = captureDevice;
  3. //打开
  4. AVPlayer_Cam1.Start();
  5. //关闭
  6. AVPlayer_Cam1.Stop();

实际上,在指定了视频源后,可以直接操作视频源也是可以的,如下代码也是能实现打开和关闭摄像头。

  1. //指定视频来源
  2. AVPlayer_Cam1.VideoSource = captureDevice;
  3. //打开
  4. captureDevice.Start();
  5. //关闭
  6. captureDevice.Stop();

1.4.2 关于拍照

本文拍照这里采用的是:

pBox_view.Image = AVPlayer_Cam1.GetCurrentVideoFrame();

即使用【VideoSourcePlayer】控件来实现,但是我一直想直接通过【VideoCaptureDevice】对象直接拍照,发现实现不了。

【VideoCaptureDevice】类下面有几个关于拍照和视频帧的东西:

  1. ------AForge.Video.DirectShow源码-----
  2. //获取和设置 触发拍照功能
  3. public bool ProvideSnapshots { get; set; }
  4. //拍照触发的事件
  5. public event NewFrameEventHandler SnapshotFrame;
  6. //新帧产生的事件
  7. public event NewFrameEventHandler NewFrame;
  8. //模拟外触发
  9. public void SimulateTrigger();

        从文档来看,先设置【ProvideSnapshots 】为“true”,接着把【SnapshotFrame】绑定到对图片处理方法(例如预览、保存),接着调用【SimulateTrigger()】来模拟外触发拍照,这样的拍照方法实现应该是最理想的,but我试了不成功,搜索发现好像是硬件不支持。要不然就是我的操作不对,如果有谁测试成功了,还请分享一下。

         另一个是 NewFrame 事件,即摄像头获取到新的帧后触发的事件,也就是说可以不用【VideoSourcePlayer】来显示画面,可以用下面的方式通过【PictureBox】来实现。

  1. //给事件绑定方法
  2. captureDevice.NewFrame += new NewFrameEventHandler(NewFrameEvent);
  3. //事件具体方法
  4. private void NewFrameEvent(object sender, NewFrameEventArgs eventArgs)
  5. {
  6. if (InvokeRequired)
  7. {
  8. this.Invoke(new NewFrameEventHandler(FaceDetection), new object[] { sender, eventArgs });
  9. }
  10. else
  11. {
  12. //获取到图像(BitMap)
  13. //pBox_video是用于代替上文AVPlayer_Cam1的PictureBox控件
  14. pBox_video.Image= (Bitmap)eventArgs.Frame.Clone();
  15. }
  16. }

看到这个,应该都能猜到了,我们可以利用这个方法获取所拍摄的图像进行人脸识别、视频录制、图像处理等操作。

 

2 添加人脸识别方法

人脸识别使用了Dlib库,这是一个C++开发的目标检测的库,大多是都是用Python进行调用开发,但是这里要在.Net平台第哦啊用,找了一下,发现是.Net平台对应的库:DlibDotNet。

2.1 安装DlibDotNet和人脸数据

一样,NuGet上查找进行安装,搜索安装【DlibDotNet】就好。

接着就是去官网下载人脸数据,即匹配人脸所需要的一个 “.dat”文件。

下载地址:http://www.dlib.net/files/

要下载的文件是:【shape_predictor_5_face_landmarks.dat】或【shape_predictor_68_face_landmarks.dat】

分别是识别出人脸中的5个特征点和68个特征点,当然识别的越多越难也就越慢。

按需要下载后,放到合适的目录下,比如我放在项目exe目录下的“face_data”文件夹下。

2.2 人脸识别方法

添加一个【FaceDetection.cs】文件,添加代码如下:

  1. using System.Drawing;
  2. using DlibDotNet;
  3. using DlibDotNet.Extensions;
  4. namespace AForgeCamera
  5. {
  6. public class FaceDetection
  7. {
  8. private string faceDataPath;
  9. // 人脸数据文件路径名称属性
  10. public string FaceDataPath { get => faceDataPath; set => faceDataPath = value; }
  11. public FaceDetection()
  12. {
  13. //默认文件路径
  14. faceDataPath = @"face_data\shape_predictor_68_face_landmarks.dat";
  15. }
  16. /// <summary>
  17. /// 进行人脸识别
  18. /// </summary>
  19. /// <param name="image">图像</param>
  20. /// <param name="numOfFaceDetected"> 识别到的人脸数目</param>
  21. /// <returns></returns>
  22. public Bitmap FaceDetectionFromImage(Bitmap image, out int numOfFaceDetected)
  23. {
  24. numOfFaceDetected = 0;
  25. if (image != null)
  26. {
  27. // 图像转换到Dlib的图像类中
  28. Array2D<RgbPixel> img = BitmapExtensions.ToArray2D<RgbPixel>(image);
  29. using (var faceDetector = Dlib.GetFrontalFaceDetector())
  30. using (var shapePredictor = ShapePredictor.Deserialize(faceDataPath))
  31. {
  32. // 检测人脸
  33. var faces = faceDetector.Operator(img);
  34. // 遍历检测到的人脸区域
  35. foreach (var rect in faces)
  36. {
  37. //绘制脸部区域
  38. Dlib.DrawRectangle(img, rect, new RgbPixel { Blue = 255 }, 3);
  39. // 人脸区域中识别脸部特征
  40. var shape = shapePredictor.Detect(img, rect);
  41. // 简单绘制识别到的特征(用线连起来)
  42. for (uint i = 1;i < shape.Parts; i++)
  43. {
  44. Dlib.DrawLine(img, shape.GetPart(i), shape.GetPart(i - 1), new RgbPixel { Red = 255 });
  45. }
  46. }
  47. numOfFaceDetected = faces.Length;
  48. }
  49. return BitmapExtensions.ToBitmap<RgbPixel>(img);
  50. }
  51. return image;
  52. }
  53. }
  54. }

上面的代码中【FaceDetectionFromImage】就是从 Bitmap图像中识别人脸的并将区域于特征绘制到图像上并返回图像的函数。

结合1.4.2中说明的【event NewFrame】,就可以实现了。

2.3 人脸识别应用

在调用摄像头的代码中,添加调用人脸识别的方法。

首先,在其中(调用摄像头的界面源码 Form1.cs)中,新增人脸识别的调用方法,该方法就是AForge摄像头设备【captureDevice.NewFrame】事件要绑定的方法。

  1. private void FaceDetection(object sender, NewFrameEventArgs eventArgs)
  2. {
  3. if (InvokeRequired)
  4. {
  5. this.Invoke(new NewFrameEventHandler(FaceDetection), new object[] { sender, eventArgs });
  6. }
  7. else
  8. {
  9. //获取拍摄的图像
  10. Bitmap img = (Bitmap)eventArgs.Frame.Clone();
  11. int numFaces = 0;
  12. //进行人脸识别以及图像显示,更新界面的人脸识别数目
  13. pBox_faceDst.Image = faceDetection.FaceDetectionFromImage(img, out numFaces);
  14. lb_FaceNum.Text = numFaces.ToString();
  15. }
  16. }

接着,在打开摄像头的地方,添加事件绑定的代码(既然有绑定,就有解绑):

  1. private void btn_cam_Click(object sender, EventArgs e)
  2. {
  3. if (btn_cam.Text == "打开")
  4. {
  5. captureDevice.VideoResolution = videoCapabilitie;
  6. AVPlayer_Cam1.VideoSource = captureDevice;
  7. //重点是这一句代码!!-------------------------↓
  8. captureDevice.NewFrame += new NewFrameEventHandler(FaceDetection);
  9. captureDevice.SimulateTrigger();
  10. AVPlayer_Cam1.Start();
  11. btn_cam.Text = "停止";
  12. btn_takePic.Enabled = true; }
  13. else
  14. {
  15. //还有这一句代码!!-------------------------↓
  16. captureDevice.NewFrame -= new NewFrameEventHandler(FaceDetection);
  17. AVPlayer_Cam1.Stop();
  18. AVPlayer_Cam1.VideoSource = null;
  19. btn_cam.Text = "打开";
  20. btn_takePic.Enabled = false;
  21. }
  22. }

其次还有就是在切换分辨率时,会关闭摄像头数据,也得解绑事件,还有就是程序关闭的时候要解绑!这些就不贴代码了。

最终效果如下:

3 遇到的坑

3.1 找不到【DlibDotNetNative.dll】和【DlibDotNetNativeDnn.dll】

从NuGet中安装了DlibDotNet,写完代码编译时,可能会在VS的错误列表中看到  “无法复制xxxx\DlibDotNetNative.dll,找不到该文件”等错误,因为现在新版VS新建的C#项目都是对应“AnyCPU”的,而下载的包中,这两个dll在“x64/x86”目录下,所以找不到,一种方法:在错误信息说明的路径下,新建“AnyCPU”等路径,然后从x86目录下找到这两个dll,复制进去;要不然就是把项目的【解决方案平台】切换成x64。

编译通过后运行可能还会报错,把这两个dll复制到编译成成的exe目录下就好。

3.2 图片转换:Bitmap->Array2D<RgbPixel>

Array2D<RgbPixel> img = BitmapExtensions.ToArray2D<RgbPixel>(image);

开始的时候这个没法用,看了DlibDotNet源码发现,这个转换的功能在【DlibDotNet.Extensions】里面,但是找不到【DlibDotNet.Extensions.dll】这个库,NuGet里面也没有,于是,,,gitHub下载源码(https://github.com/takuya-takeuchi/DlibDotNet),手动编译项目中的【DlibDotNet.Extensions】,得到这个dll,然后复制到项目进行添加引用就解决了。

3.3 图像转换抛异常

解决了上面的问题,又特喵有新的问题了,图像转换抛异常,显示不支持这个格式(C# Bitmap的图像格式:PixelFormat.Format24bppRgb)的转换,还是这一句对应的内部代码问题。

Array2D<RgbPixel> img = BitmapExtensions.ToArray2D<RgbPixel>(image);

但是看官方的示例源码,也是随便打开一个Bitmap然后这么操作的啊,折腾了好久,发现他们这个版本新的更新中新增了<BgrPixel>图像类型,然后再格式转换的映射字典中,给C# 中的Format24bppRgb对应了两个Dlib内部的格式RgbPixel和BgrPixel,但是又暂时不支持BgrPixel的图像的转换,后续的BgrPixel类型图像的绘图等也还都没有添加。

图像在转换时要先查询格式映射表,但是映射字典中 第二次的BgrPixel覆盖了第一次的RgbPixel ,所以就会抛异常。

怎么搞:修改源码重新编译!

在源码的【DlibDotNet-master\src\DlibDotNet.Extensions\Extensions\BitmapExtensions.cs】文件中,把BgrPixel相关的字典映射删掉,如下方代码中注释掉的部分

  1. OptimumConvertImageInfos[PixelFormat.Format8bppIndexed] = new[]
  2. {
  3. new ConvertInfo<ImageTypes> { Type = ImageTypes.UInt8 }
  4. };
  5. OptimumConvertImageInfos[PixelFormat.Format24bppRgb] = new[]
  6. {
  7. //这里把Format24bppRgb 和 RgbPixel 对应了起来
  8. new ConvertInfo<ImageTypes>{ Type = ImageTypes.RgbPixel, RgbReverse = true }
  9. };
  10. //OptimumConvertImageInfos[PixelFormat.Format24bppRgb] = new[]
  11. //{
  12. // //这里又把Format24bppRgb 和 BgrPixel对应了起来
  13. // new ConvertInfo<ImageTypes>{ Type = ImageTypes.BgrPixel, RgbReverse = false }
  14. //};
  15. OptimumConvertImageInfos[PixelFormat.Format32bppArgb] = new[]
  16. {
  17. new ConvertInfo<ImageTypes> { Type = ImageTypes.RgbAlphaPixel, RgbReverse = true }
  18. };

删掉之后,重新编译,编译生成的DlibDotNet.dll替换掉NuGet中下载的,就解决了。

当然,如果这确实是个bug,后面的版本应该会修改掉,我下载时 是 19.18.0.20190928版本,其他人用的时候如果没碰到这些错误就不用这么折腾了。

本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
【热】打开小程序,算一算2024你的财运
你们可以看到几张人脸?
双摄 人脸解锁!华为荣耀畅玩7C宣布3.12发布
EV录屏怎么把自己的摄像头放进去,摄像头好的,但是人像很花,看不清人脸
备课小工具,实验记录
winform 加载AForge.dll中的videoSourcePlayer。
使用AForge控件采集摄像头图像。
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服