项目需求:某市级组织考试,在考试前需审核考生采集表中的考生照片是否合格,由于要审核的考生信息采集表有很多,原先进行的是手动人工审核,比较费时费力,审核的要求也很简单,并不判断考生是否是图片本人(身份验证有另外一套程序来进行),只是看考生采集表中考生头像是否是人脸(是否存在辨识不清楚,不是人脸)。因此提出需求,看是否能用程序来检测考生信息采集表中的照片,只需找出来疑似不是人脸的考生所在文档位置(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类,用来做为检测结果的通知类。
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所在文件夹,一个用来开始检测。
主程序代码:
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); } } }
程序运行效果: