項目需求:某市級組織考試,在考試前需審覈考生採集表中的考生照片是否合格,因爲要審覈的考生信息採集表有不少,原先進行的是手動人工審覈,比較費時費力,審覈的要求也很簡單,並不判斷考生是不是圖片本人(身份驗證有另一套程序來進行),只是看考生採集表中考生頭像是不是人臉(是否存在辨識不清楚,不是人臉)。所以提出需求,看是否能用程序來檢測考生信息採集表中的照片,只需找出來疑似不是人臉的考生所在文檔位置(pdf文檔)便可,存疑的考生再由人工進行審覈。框架
PDF文檔中有不少頁,每一頁都是如圖中的結構。async
通過百度摸索,採用了C#+WPF+Spire.PDF+Emgu CV+MvvmLight來進行人臉判斷的技術選型。this
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官方帶的有訓練過的人臉識別模板,能夠直接使用。spa
Spire.PDF能夠來讀取PDF文檔,同時能夠讀取到PDF文檔中的圖片。操作系統
MvvmLight是WPF可使用的一種MVVM模式的實現框架。.net
項目技術選型肯定之後,下面就是代碼的編寫。debug
項目引用Emgu CV、Spire.PDF、MvvmLight3d
從官網下載Emgu CV後,咱們把它項目中的haarcascade_eye.xml、haarcascade_frontalface_alt.xml兩個訓練過的人臉識別模板放到bin/debug下,供Emgu CV使用時調用。orm
引用MvvmLight後,會自動在項目中建立ViewModel目錄,咱們在此目錄中新建一個Pdf2FaceInfoModel.cs類,用來作爲檢測結果的通知類。xml
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); } } }
程序運行效果: