同系列文章:
源代码
FrClientWindow.xaml
<Window x:Class="Splash.FrClientWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:controls="clr-namespace:AForge.Controls;assembly=AForge.Controls" mc:Ignorable="d" d:DesignHeight="710" d:DesignWidth="1280" Title="汉王人证比对客户端 By 秦建辉" Icon="dog.ico" SizeToContent="WidthAndHeight" WindowStartupLocation="CenterScreen" Loaded="Window_Loaded" Closing="Window_Closing"> <Grid> <Grid.ColumnDefinitions> <ColumnDefinition Width="Auto"/> <ColumnDefinition/> </Grid.ColumnDefinitions> <WindowsFormsHost Grid.Column="0" Margin="4" Width="730" Height="534"> <controls:VideoSourcePlayer x:Name="vsp"/> </WindowsFormsHost> <Grid Grid.Column="1"> <Grid.RowDefinitions> <RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/> </Grid.RowDefinitions> <GroupBox Grid.Row="0" Margin="4" Padding="0,4"> <Grid> <Grid.RowDefinitions> <RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/> </Grid.RowDefinitions> <Grid.ColumnDefinitions> <ColumnDefinition/> <ColumnDefinition/> </Grid.ColumnDefinitions> <Border Grid.Row="0" Grid.Column="0" BorderThickness="1" BorderBrush="LightGray" Width="128" Height="168" Margin="4"> <Image Name="imageVideo" HorizontalAlignment="Center" VerticalAlignment="Center"/> </Border> <Border Grid.Row="0" Grid.Column="1" BorderThickness="1" BorderBrush="LightGray" Width="128" Height="168" Margin="4"> <Image Name="imagePhoto" HorizontalAlignment="Center" VerticalAlignment="Center"/> </Border> <Label Content="实时照" Grid.Row="1" Grid.Column="0" HorizontalAlignment="Center" VerticalAlignment="Center" VerticalContentAlignment="Center" HorizontalContentAlignment="Center" Foreground="#666666"/> <Label Content="证件照" Grid.Row="1" Grid.Column="1" HorizontalAlignment="Center" VerticalAlignment="Center" VerticalContentAlignment="Center" HorizontalContentAlignment="Center" Foreground="#666666"/> </Grid> </GroupBox> <GroupBox Grid.Row="1" Margin="4"> <Grid> <Grid.RowDefinitions> <RowDefinition/> <RowDefinition/> </Grid.RowDefinitions> <Image Name="imageAnswer" Grid.Row="0" Width="128" Height="128"/> <Grid Grid.Row="1"> <Grid.ColumnDefinitions> <ColumnDefinition/> <ColumnDefinition/> </Grid.ColumnDefinitions> <Label Grid.Column="0" Content="相似度:" HorizontalContentAlignment="Right" VerticalContentAlignment="Center" FontSize="22" Foreground="#333333"/> <TextBlock Name="textBlockScore" Grid.Column="1" VerticalAlignment="Center" FontSize="22" Foreground="#333333"/> </Grid> </Grid> </GroupBox> <GroupBox Grid.Row="2" Margin="4"> <Grid> <Grid.RowDefinitions> <RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/> </Grid.RowDefinitions> <Grid.ColumnDefinitions> <ColumnDefinition Width="Auto"/> <ColumnDefinition/> <ColumnDefinition Width="Auto"/> <ColumnDefinition/> </Grid.ColumnDefinitions> <Label Grid.Row="0" Grid.Column="0" Content="姓名" Foreground="#555555"/> <Label Grid.Row="0" Grid.Column="2" Content="性别" Foreground="#555555"/> <Label Grid.Row="1" Grid.Column="0" Content="民族" Foreground="#555555"/> <Label Grid.Row="1" Grid.Column="2" Content="出生" Foreground="#555555"/> <Label Grid.Row="2" Grid.Column="0" Content="证件号" Foreground="#555555"/> <Label Grid.Row="3" Grid.Column="0" Content="地址" Foreground="#555555"/> <TextBlock Name="textBlockName" Grid.Row="0" Grid.Column="1" VerticalAlignment="Center" TextWrapping="Wrap" Foreground="#777777"/> <TextBlock Name="textBlockGender" Grid.Row="0" Grid.Column="3" VerticalAlignment="Center" TextWrapping="Wrap" Foreground="#777777"/> <TextBlock Name="textBlockNationality" Grid.Row="1" Grid.Column="1" VerticalAlignment="Center" TextWrapping="Wrap" Foreground="#777777"/> <TextBlock Name="textBlockBirth" Grid.Row="1" Grid.Column="3" VerticalAlignment="Center" TextWrapping="Wrap" Foreground="#777777"/> <TextBlock Name="textBlockSN" Grid.Row="2" Grid.Column="1" Grid.ColumnSpan="3" VerticalAlignment="Center" TextWrapping="Wrap" Foreground="#777777"/> <TextBlock Name="textBlockAddress" Grid.Row="3" Grid.Column="1" Grid.ColumnSpan="3" VerticalAlignment="Center" TextWrapping="Wrap" Foreground="#777777"/> </Grid> </GroupBox> <GroupBox Grid.Row="3" Margin="4" Header="服务器设置"> <Grid> <Grid.RowDefinitions> <RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/> </Grid.RowDefinitions> <Grid.ColumnDefinitions> <ColumnDefinition Width="Auto"/> <ColumnDefinition/> </Grid.ColumnDefinitions> <Label Grid.Row="0" Grid.Column="0" Margin="4" Content="服务器地址"/> <Label Grid.Row="1" Grid.Column="0" Margin="4" Content="服务器端口"/> <TextBox Grid.Row="0" Grid.Column="1" VerticalContentAlignment="Center" Margin="4" x:Name="textBoxServerIP" Text="192.168.135.98"/> <TextBox Grid.Row="1" Grid.Column="1" VerticalContentAlignment="Center" Margin="4" x:Name="textBoxServerPort" Text="50052"/> </Grid> </GroupBox> </Grid> </Grid> </Window>
FrServerWindow.xaml.cs
/* ---------------------------------------------------------- * 文件名称:FrClientWindow.xaml.cs * 作者:秦建辉 * * 微信:splashcn * * 博客:http://www.firstsolver.com/wordpress/ * * 开发环境: * Visual Studio V2017 * .NET Framework 4.5.2 * * 版本历史: * V1.0 2017年10月16日 * 基于gRPC构建的人证比对客户端实现 ------------------------------------------------------------ */ using AForge.Video.DirectShow; using Com.FirstSolver.CR; using Com.FirstSolver.FR; using Com.FirstSolver.Splash; using Grpc.Core; using System; using System.Collections.Generic; using System.Drawing; using System.Threading; using System.Windows; using System.Windows.Media.Imaging; namespace Splash { public partial class FrClientWindow : Window { /// <summary> /// 人脸框画笔 /// </summary> private System.Drawing.Pen FaceReaderPen = new System.Drawing.Pen(System.Drawing.Color.Green, 1.0f); /// <summary> /// 身份证读卡器 /// </summary> private IDCardReader CardReader = null; /// <summary> /// 身份证读卡器同步事件 /// </summary> private ManualResetEvent CardReaderSyncEvent = new ManualResetEvent(true); /// <summary> /// 捕获图像事件 /// </summary> private AutoResetEvent CaptureArrivedEvent = new AutoResetEvent(false); /// <summary> /// 关闭人证比对线程 /// </summary> private volatile bool ShouldStop = false; /// <summary> /// 人证比对通过阈值 /// </summary> private float Threshold = 0.7f; /// <summary> /// 核验通过 /// </summary> internal BitmapSource ImagePass; /// <summary> /// 核验失败 /// </summary> internal BitmapSource ImageDeny; public FrClientWindow() { InitializeComponent(); // 核验通过图片 ImagePass = System.Windows.Interop.Imaging.CreateBitmapSourceFromHBitmap( Properties.Resources.pass.GetHbitmap(), IntPtr.Zero, Int32Rect.Empty, BitmapSizeOptions.FromEmptyOptions()); // 核验拒绝图片 ImageDeny = System.Windows.Interop.Imaging.CreateBitmapSourceFromHBitmap( Properties.Resources.deny.GetHbitmap(), IntPtr.Zero, Int32Rect.Empty, BitmapSizeOptions.FromEmptyOptions()); } private void Window_Loaded(object sender, RoutedEventArgs e) { InitializeCamera(); // 初始化摄像头 InitializeCardReader(); // 初始化读卡设备 InitializeFaceIdThread(); // 初始化人证比对线程 } private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e) { CloseFaceIdThread(); // 关闭人证比对线程 CloseCardReader(); // 关闭读卡设备 CloseCamera(); // 关闭摄像头 FaceReaderPen.Dispose(); } // 初始化人证比对线程 private void InitializeFaceIdThread() { if (!ThreadPool.QueueUserWorkItem(FaceIdThreadHandler)) { MessageBoxPlus.Show(this, "创建人证比对线程失败,程序退出…", "错误", MessageBoxButton.OK, MessageBoxImage.Error); this.Close(); } } // 关闭人证比对线程 private void CloseFaceIdThread() { ShouldStop = true; CaptureArrivedEvent.Set(); } // 人证比对线程处理器 private void FaceIdThreadHandler(Object state) { while (true) { CaptureArrivedEvent.WaitOne(); if (ShouldStop) break; this.Dispatcher.Invoke(new Action(async () => { Channel channel = null; try { // 获取基准图像 using (Bitmap Photo = (imagePhoto.Source as BitmapImage).ToBitmap()) { channel = new Channel(textBoxServerIP.Text, Convert.ToInt32(textBoxServerPort.Text), ChannelCredentials.Insecure); var client = new FrService.FrServiceClient(channel); using (var call = client.Compare()) { FaceIdRequestMsg request = new FaceIdRequestMsg(); // 传送基准图像 request.Index = 1; request.Photo = Google.Protobuf.ByteString.CopyFrom(Photo.ToBytes()); await call.RequestStream.WriteAsync(request); await call.ResponseStream.MoveNext(); var responseBase = call.ResponseStream.Current; if (responseBase.Status == 1) { // 提取人脸特征成功 using (Graphics g = Graphics.FromImage(Photo)) { // 用矩形框标记人脸位置 FrFaceRect Face = responseBase.Face; g.DrawRectangle(FaceReaderPen, Face.Left, Face.Top, Face.Right - Face.Left + 1, Face.Bottom - Face.Top + 1); } imagePhoto.Source = Photo.ToBitmapImage(); // 传送视频图像 while (true) { if (ShouldStop) break; // 抓取视频图像 request.Index++; using (Bitmap bmp = vsp.GetCurrentVideoFrame()) { request.Photo = Google.Protobuf.ByteString.CopyFrom(bmp.ToBytes()); await call.RequestStream.WriteAsync(request); await call.ResponseStream.MoveNext(); var response = call.ResponseStream.Current; if (response.Status == 1) { using (Graphics g = Graphics.FromImage(bmp)) { // 用矩形框标记人脸位置 FrFaceRect Face = response.Face; g.DrawRectangle(FaceReaderPen, Face.Left, Face.Top, Face.Right - Face.Left + 1, Face.Bottom - Face.Top + 1); } imageVideo.Source = bmp.ToBitmapImage(); if (response.Score >= Threshold) { imageAnswer.Source = this.ImagePass; // 通过 } else { imageAnswer.Source = this.ImageDeny; // 拒绝 } textBlockScore.Text = response.Score.ToString(); break; } } } } else { MessageBoxPlus.Show(this, "不合格的基准图像!", "错误", MessageBoxButton.OK, MessageBoxImage.Error); } } } } catch (Exception exception) { string ErrorMessage = exception.Message; if (string.IsNullOrEmpty(ErrorMessage)) ErrorMessage = exception.InnerException.Message; MessageBoxPlus.Show(this, ErrorMessage, "比对异常", MessageBoxButton.OK, MessageBoxImage.Error); } finally { if (channel != null) channel.ShutdownAsync().Wait(); CardReaderSyncEvent.Set(); // 进入读卡状态 } })); if (ShouldStop) break; } } // 初始化摄像头 private void InitializeCamera() { CloseCamera(); // 关闭摄像头 // 设定初始视频设备 FilterInfoCollection VideoDevices = new FilterInfoCollection(FilterCategory.VideoInputDevice); if (VideoDevices.Count > 0) { vsp.VideoSource = new VideoCaptureDevice(VideoDevices[0].MonikerString); vsp.Start(); } else { MessageBoxPlus.Show(this, "未发现摄像头!", "摄像头错误", MessageBoxButton.OK, MessageBoxImage.Error); } } // 关闭摄像头 private void CloseCamera() { if (vsp.VideoSource != null && vsp.IsRunning) { // 停止视频 vsp.SignalToStop(); vsp.WaitForStop(); } } // 初始化读卡器 private void InitializeCardReader() { CloseCardReader(); // 关闭读卡器 try { CardReader = new YAReader() { ExternalSyncEvent = CardReaderSyncEvent }; CardReader.OnReadCardCompleted += ReadCardCompletedHandler; // 事件处理器 if (CardReader.Start(IDCardBiometrics.Photo) == -1) { MessageBoxPlus.Show(this, "开启身份证扫描服务失败!", "读卡器错误", MessageBoxButton.OK, MessageBoxImage.Error); } } catch (Exception exception) { string ErrorMessage = exception.Message; if (string.IsNullOrEmpty(ErrorMessage)) ErrorMessage = exception.InnerException.Message; MessageBoxPlus.Show(this, ErrorMessage, "读卡器错误", MessageBoxButton.OK, MessageBoxImage.Error); return; } } // 停止身份证扫描服务 private void CloseCardReader() { if (CardReader != null) { CardReader.Close(); CardReader = null; } } // 刷身份证处理器 private void ReadCardCompletedHandler(object sender, ReadCardCompletedEventArgs e) { if (e == null || e.Photo == null) return; this.Dispatcher.BeginInvoke(new Action(() => { // 清除结果 imagePhoto.Source = null; imageVideo.Source = null; imageAnswer.Source = null; textBlockScore.Text = string.Empty; // 显示身份证信息 textBlockName.Text = e.Name; // 姓名 textBlockGender.Text = e.Gender; // 性别 textBlockNationality.Text = e.EthnicName; // 民族 textBlockBirth.Text = e.Birth; // 出生 textBlockSN.Text = e.IDC; // 身份证号码 textBlockAddress.Text = e.Address; // 地址 // 显示身份证照片 imagePhoto.Source = e.Photo.ToBitmapImage(); CaptureArrivedEvent.Set(); })); } } }