项目需求:某市级组织考试,在考试前需审核考生采集表中的考生照片是否合格,由于要审核的考生信息采集表有很多,原先进行的是手动人工审核,比较费时费力,审核的要求也很简单,并不判断考生是否是图片本人(身份验证有另外一套程序来进行),只是看考生采集表中考生头像是否是人脸(是否存在辨识不清楚,不是人脸)。因此提出需求,看是否能用程序来检测考生信息采集表中的照片,只需找出来疑似不是人脸的考生所在文档位置(pdf文档)即可,存疑的考生再由人工进行审核。
PDF文档中有很多页,每一页都是如图中的结构。
经过百度摸索,采用了C#+WPF+Spire.PDF+Emgu CV+MvvmLight来进行人脸判断的技术选型。
Emgu CV(https://sourceforge.net/projects/emgucv/files/emgucv/)是.NET平台下对OpenCV图像处理库的封装,也就是.NET版的
OpenCV的全称是:Open Source Computer Vision Library。OpenCV是一个基于(开源)发行的跨平台计算机视觉库,可以运行在Linux、Windows和Mac OS操作系统上。Emgu CV官方带的有训练过的人脸识别模板,可以直接使用。
Spire.PDF可以来读取PDF文档,同时可以读取到PDF文档中的图片。
MvvmLight是WPF可以使用的一种MVVM模式的实现框架。
项目技术选型确定以后,下面就是代码的编写。
项目引用Emgu CV、Spire.PDF、MvvmLight
从官网下载Emgu CV后,我们把它项目中的haarcascade_eye.xml、haarcascade_frontalface_alt.xml两个训练过的人脸识别模板放到bin/debug下,供Emgu CV使用时调用。
引用MvvmLight后,会自动在项目中创建ViewModel目录,我们在此目录中新建一个Pdf2FaceInfoModel.cs类,用来做为检测结果的通知类。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 | using System.ComponentModel; namespace Pdf2Face.ViewModel { public class Pdf2FaceInfoModel : INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged; private string pdfName { get ; set ; } /// <summary> /// Pdf文件名 /// </summary> public string PdfName { get => pdfName; set { pdfName = value; PropertyChanged?.Invoke( this , new PropertyChangedEventArgs( "PdfName" )); } } private int pdfImgCount { get ; set ; } = 0; /// <summary> /// Pdf中图片数量 /// </summary> public int PdfImgCount { get => pdfImgCount; set { pdfImgCount = value; PropertyChanged?.Invoke( this , new PropertyChangedEventArgs( "PdfImgCount" )); } } private int pdfFaceCount { get ; set ; } = 0; /// <summary> /// Pdf中人脸数量 /// </summary> public int PdfFaceCount { get => pdfFaceCount; set { pdfFaceCount = value; PropertyChanged?.Invoke( this , new PropertyChangedEventArgs( "PdfFaceCount" )); } } private string pdfFaceSuccess { get ; set ; } = "否" ; /// <summary> /// 数量相对是否存疑 0 正常 1存疑 /// </summary> public string PdfFaceSuccess { get => pdfFaceSuccess; set { pdfFaceSuccess = value; PropertyChanged?.Invoke( this , new PropertyChangedEventArgs( "PdfFaceSuccess" )); } } } } |
主程序只有一个界面,界面两个按钮,一个用来选择要检测pdf所在文件夹,一个用来开始检测。
主程序代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 | using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.ComponentModel; using System.Drawing; using System.Drawing.Imaging; using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; using System.Windows; using Emgu.CV; using Emgu.CV.Structure; using Microsoft.WindowsAPICodePack.Dialogs; using Pdf2Face.ViewModel; using Spire.Pdf; namespace Pdf2Face { /// <summary> /// 人脸检测功能的交互逻辑 /// </summary> public partial class MainWindow : Window { private string _pdfDirPath; private readonly string _pdfFaceSaveDir; private readonly ObservableCollection<Pdf2FaceInfoModel> facelist = new ObservableCollection<Pdf2FaceInfoModel>(); public MainWindow() { InitializeComponent(); Thread.Sleep(10000); dataGrid.ItemsSource = facelist; _pdfFaceSaveDir = $ "{AppDomain.CurrentDomain.BaseDirectory}face" ; if (!Directory.Exists(_pdfFaceSaveDir)) { Directory.CreateDirectory(_pdfFaceSaveDir); } } /// <summary> /// 选择Pdf所在目录 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void BtnChooseDir_OnClick( object sender, RoutedEventArgs e) { using ( var folderBrowser = new CommonOpenFileDialog()) { folderBrowser.IsFolderPicker = true ; folderBrowser.Multiselect = false ; folderBrowser.Title = "选择考生照片所在文件夹" ; if (folderBrowser.ShowDialog(GetWindow( this )) != CommonFileDialogResult.Ok) return ; _pdfDirPath = folderBrowser.FileName; txtBlockPath.Text = _pdfDirPath; } } /// <summary> /// 人脸识别检测 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void BtnCheck_OnClick( object sender, RoutedEventArgs e) { if ( string .IsNullOrEmpty(_pdfDirPath) || !Directory.Exists(_pdfDirPath)) { MessageBox.Show( "目录无法访问。" , "错误" , MessageBoxButton.OK, MessageBoxImage.Error); return ; } var pdfs = FileSearch(_pdfDirPath, "*.pdf" , SearchOption.AllDirectories, x => x.Length > 6); if (pdfs.Length == 0) { MessageBox.Show( "指定的目录中没有发现PDF文件。" , "错误" , MessageBoxButton.OK, MessageBoxImage.Information); return ; } txtBlockInfo.Text = $ "Pdf文件数量{pdfs.Length}" ; var doc = new PdfDocument(); Dispatcher?.InvokeAsync(async () => { await Task.Run(() => { foreach ( var pdf in pdfs) { doc.LoadFromFile(pdf); var pagenum = 1; foreach (PdfPageBase page in doc.Pages) { var newPdfFaceSaveDir = $ "{_pdfFaceSaveDir}\\{pdf.Substring(pdf.LastIndexOf('\\') + 1)}" ; if (page.ExtractImages() != null ) { if (!Directory.Exists(newPdfFaceSaveDir)) { Directory.CreateDirectory(newPdfFaceSaveDir); } var images = new List<Image>(); var model = new Pdf2FaceInfoModel { PdfName = $ "{pdf.Substring(pdf.LastIndexOf('\\') + 1)}_第{pagenum}页" }; Dispatcher?.Invoke(() => { facelist.Add(model); }); var c = 0; foreach ( var image in page.ExtractImages()) { images.Add(image); var filename = $ "{newPdfFaceSaveDir}\\{pagenum}_{c}.png" ; image.Save(filename, ImageFormat.Png); #region 人脸判断 //检测是否是人脸 //如果支持用显卡,则用显卡运算 CvInvoke.UseOpenCL = CvInvoke.HaveOpenCLCompatibleGpuDevice; //构建级联分类器,利用已经训练好的数据,识别人脸 var face = new CascadeClassifier( "haarcascade_frontalface_alt.xml" ); var eyes = new CascadeClassifier( "haarcascade_eye.xml" ); //加载要识别的图片 var img = new Image<Bgr, byte >(filename); var img2 = new Image<Gray, byte >(img.ToBitmap()); //把图片从彩色转灰度 CvInvoke.CvtColor(img, img2, Emgu.CV.CvEnum.ColorConversion.Bgr2Gray); //亮度增强 CvInvoke.EqualizeHist(img2, img2); //返回的是人脸所在的位置和大小 var facesDetected = face.DetectMultiScale(img2, 1.1, 10, new System.Drawing.Size(50, 50)); if (facesDetected.Length > 0) { model.PdfFaceCount += facesDetected.Length; model.PdfFaceSuccess = facesDetected.Length > 1 ? "是" : "否" ; //删除图片,留下的都是无法正确识别的 try { File.Delete(filename); } catch (Exception exception) { // } } img.Dispose(); img2.Dispose(); face.Dispose(); #endregion c += 1; } model.PdfImgCount = images.Count; } pagenum += 1; } doc.Close(); } }); }); } private string [] FileSearch( string directoryPath, string searchFilter, SearchOption option, Func< string , bool > func) { if (!Directory.Exists(directoryPath)) return null ; var s = Directory.GetFiles(directoryPath, searchFilter, option).Where(func).ToArray(); return s; } private void MainWindow_OnClosing( object sender, CancelEventArgs e) { Application.Current.Shutdown(0); } } } |
程序运行效果: