|
項(xiàng)目需求:某市級組織考試,在考試前需審核考生采集表中的考生照片是否合格,由于要審核的考生信息采集表有很多,原先進(jìn)行的是手動人工審核,比較費(fèi)時費(fèi)力,審核的要求也很簡單,并不判斷考生是否是圖片本人(身份驗(yàn)證有另外一套程序來進(jìn)行),只是看考生采集表中考生頭像是否是人臉(是否存在辨識不清楚,不是人臉)。因此提出需求,看是否能用程序來檢測考生信息采集表中的照片,只需找出來疑似不是人臉的考生所在文檔位置(pdf文檔)即可,存疑的考生再由人工進(jìn)行審核。 PDF文檔中有很多頁,每一頁都是如圖中的結(jié)構(gòu)。 經(jīng)過百度摸索,采用了C#+WPF+Spire.PDF+Emgu CV+MvvmLight來進(jìn)行人臉判斷的技術(shù)選型。 Emgu CV(https:///projects/emgucv/files/emgucv/)是.NET平臺下對OpenCV圖像處理庫的封裝,也就是.NET版的 Spire.PDF可以來讀取PDF文檔,同時可以讀取到PDF文檔中的圖片。 MvvmLight是WPF可以使用的一種MVVM模式的實(shí)現(xiàn)框架。 項(xiàng)目技術(shù)選型確定以后,下面就是代碼的編寫。 項(xiàng)目引用Emgu CV、Spire.PDF、MvvmLight 從官網(wǎng)下載Emgu CV后,我們把它項(xiàng)目中的haarcascade_eye.xml、haarcascade_frontalface_alt.xml兩個訓(xùn)練過的人臉識別模板放到bin/debug下,供Emgu CV使用時調(diào)用。 引用MvvmLight后,會自動在項(xiàng)目中創(chuàng)建ViewModel目錄,我們在此目錄中新建一個Pdf2FaceInfoModel.cs類,用來做為檢測結(jié)果的通知類。 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中圖片數(shù)量
/// </summary>
public int PdfImgCount
{
get => pdfImgCount;
set
{
pdfImgCount = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("PdfImgCount"));
}
}
private int pdfFaceCount { get; set; } = 0;
/// <summary>
/// Pdf中人臉數(shù)量
/// </summary>
public int PdfFaceCount
{
get => pdfFaceCount;
set
{
pdfFaceCount = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("PdfFaceCount"));
}
}
private string pdfFaceSuccess { get; set; } ="否";
/// <summary>
/// 數(shù)量相對是否存疑 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("指定的目錄中沒有發(fā)現(xiàn)PDF文件。", "錯誤", MessageBoxButton.OK, MessageBoxImage.Information);
return;
}
txtBlockInfo.Text = $"Pdf文件數(shù)量{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 人臉判斷
//檢測是否是人臉
//如果支持用顯卡,則用顯卡運(yùn)算
CvInvoke.UseOpenCL = CvInvoke.HaveOpenCLCompatibleGpuDevice;
//構(gòu)建級聯(lián)分類器,利用已經(jīng)訓(xùn)練好的數(shù)據(jù),識別人臉
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());
//把圖片從彩色轉(zhuǎn)灰度
CvInvoke.CvtColor(img, img2, Emgu.CV.CvEnum.ColorConversion.Bgr2Gray);
//亮度增強(qiáng)
CvInvoke.EqualizeHist(img2, img2);
//返回的是人臉?biāo)诘奈恢煤痛笮? 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);
}
}
}
程序運(yùn)行效果:
|
|
|