基于.NET框架集成cv_resnet101模型开发Windows桌面人脸考勤应用最近在做一个内部小工具需要给公司的几个部门做个简单的考勤打卡系统。要求不高能拍照识别人脸、记录时间就行但得是Windows桌面程序方便在办公室电脑上用。市面上现成的方案要么太贵要么太复杂我就琢磨着自己动手。正好看到星图镜像广场上有现成的人脸检测模型比如cv_resnet101_face-detection_cvpr22papermogface效果据说不错。我就想能不能用咱们熟悉的.NET和C#做个简单的桌面应用界面做好看点然后去调用这个模型服务把识别人脸和考勤记录的功能串起来试了试还真行。这篇文章就跟你聊聊怎么一步步把这个想法落地从界面设计到调用API再到本地数据管理整个流程走一遍。1. 项目准备与环境搭建做这个应用咱们的目标很明确一个Windows桌面程序用户能上传照片或拍照程序调用后台的人脸检测模型找出人脸然后跟本地数据库里的人脸信息比对完成考勤记录。整个技术栈就围绕.NET展开。1.1 核心工具与技术选型首先得把“家伙事儿”准备好。我这里的选择是基于最常见的场景你可以根据自己习惯调整。开发框架我选择了WPF (Windows Presentation Foundation)。原因很简单它的界面设计更灵活、更现代化用XAML写前端跟C#后端代码绑定起来很方便做这种需要展示图片和结果的应用很合适。当然如果你对WinForms更熟用它也完全没问题整体思路是相通的。编程语言C#这是.NET的主力语言没得说。开发环境Visual Studio 2022。社区版就够用创建WPF项目、管理NuGet包都很顺手。模型服务关键在这里。我们不需要在本地部署复杂的深度学习环境。我们使用星图GPU平台上已经部署好的cv_resnet101_face-detection_cvpr22papermogface模型服务。它提供了一个REST API我们只需要用HTTP请求把图片发过去它就能把检测到的人脸位置框的坐标给我们返回来。这省去了大量环境配置和模型加载的麻烦。本地数据存储为了简单起见我们用SQLite数据库。它轻量一个文件就搞定非常适合这种桌面小应用。用Microsoft.Data.Sqlite这个NuGet包来操作。HTTP客户端调用模型API就用.NET自带的HttpClient稳定高效。图像处理为了把图片转换成API能接受的格式比如Base64以及把API返回的人脸框画到图片上我们会用到System.Drawing.Common库注意兼容性或者更现代的ImageSharp、SkiaSharp等。这里为了演示通用性我们先使用System.Drawing.Common。1.2 创建项目与安装NuGet包打开Visual Studio 2022新建一个“WPF应用”项目名字就叫FaceAttendanceApp吧。然后通过NuGet包管理器为项目安装以下必要的包Install-Package Microsoft.Data.Sqlite Install-Package System.Drawing.Common Install-Package Newtonsoft.Json # 用于方便地解析JSON格式的API响应安装好后我们的基础工程就准备好了。接下来我们来设计程序的“脸面”——用户界面。2. 设计桌面应用界面一个好的界面能让操作变得直观。我们的应用主要需要几个功能区域图片选择/显示区、操作按钮区、结果信息显示区。2.1 主界面布局设计我们打开MainWindow.xaml文件设计一个简单的布局。这里采用Grid进行分区。Window x:ClassFaceAttendanceApp.MainWindow xmlnshttp://schemas.microsoft.com/winfx/2006/xaml/presentation xmlns:xhttp://schemas.microsoft.com/winfx/2006/xaml Title人脸考勤系统 Height600 Width900 Grid Grid.ColumnDefinitions ColumnDefinition Width2*/ ColumnDefinition Width1*/ /Grid.ColumnDefinitions !-- 左侧图片显示与操作区 -- Border Grid.Column0 Margin10 BorderBrushGray BorderThickness1 Grid Grid.RowDefinitions RowDefinition HeightAuto/ RowDefinition Height*/ RowDefinition HeightAuto/ /Grid.RowDefinitions !-- 操作按钮 -- StackPanel Grid.Row0 OrientationHorizontal HorizontalAlignmentCenter Margin5 Button x:NameBtnSelectImage Content选择图片 ClickBtnSelectImage_Click Margin5 Padding10/ Button x:NameBtnCapture Content拍照 ClickBtnCapture_Click Margin5 Padding10/ Button x:NameBtnDetect Content检测人脸 ClickBtnDetect_Click Margin5 Padding10 IsEnabledFalse/ Button x:NameBtnCheckIn Content考勤打卡 ClickBtnCheckIn_Click Margin5 Padding10 IsEnabledFalse/ /StackPanel !-- 图片显示区域 -- Image x:NameImgDisplay Grid.Row1 StretchUniform Margin10/ Viewbox Grid.Row1 StretchUniform IsHitTestVisibleFalse Canvas x:NameFaceOverlayCanvas Width{Binding ElementNameImgDisplay, PathActualWidth} Height{Binding ElementNameImgDisplay, PathActualHeight}/ /Viewbox !-- 状态栏 -- TextBlock Grid.Row2 x:NameTxtStatus Text就绪 Margin5 HorizontalAlignmentCenter/ /Grid /Border !-- 右侧信息与记录显示区 -- Border Grid.Column1 Margin10 BorderBrushGray BorderThickness1 Grid Grid.RowDefinitions RowDefinition HeightAuto/ RowDefinition Height*/ /Grid.RowDefinitions TextBlock Grid.Row0 Text检测结果与考勤记录 FontWeightBold Margin5 HorizontalAlignmentCenter/ TextBox Grid.Row1 x:NameTxtResult Margin5 IsReadOnlyTrue VerticalScrollBarVisibilityAuto TextWrappingWrap/ /Grid /Border /Grid /Window这个界面左边用来显示图片和人脸框右边用来输出日志和结果。FaceOverlayCanvas这个Canvas控件是关键我们会在检测到人脸后在上面动态画出红色的矩形框。2.2 图片选择与显示逻辑在MainWindow.xaml.cs文件中我们先实现选择本地图片并显示的功能。using Microsoft.Win32; using System.Drawing; using System.IO; using System.Windows; using System.Windows.Media.Imaging; namespace FaceAttendanceApp { public partial class MainWindow : Window { // 存储当前加载的图片原始路径和Bitmap方便后续处理 private string _currentImagePath null; private Bitmap _currentBitmap null; public MainWindow() { InitializeComponent(); } private void BtnSelectImage_Click(object sender, RoutedEventArgs e) { var openFileDialog new OpenFileDialog { Filter 图片文件|*.jpg;*.jpeg;*.png;*.bmp, Title 选择一张包含人脸的图片 }; if (openFileDialog.ShowDialog() true) { LoadAndDisplayImage(openFileDialog.FileName); } } private void LoadAndDisplayImage(string imagePath) { try { _currentImagePath imagePath; // 用于显示的Image控件 var bitmapImage new BitmapImage(new Uri(imagePath)); ImgDisplay.Source bitmapImage; // 用于后台处理的System.Drawing.Bitmap _currentBitmap new Bitmap(imagePath); TxtStatus.Text $已加载: {Path.GetFileName(imagePath)}; BtnDetect.IsEnabled true; BtnCheckIn.IsEnabled false; // 检测前先禁用打卡 ClearFaceOverlay(); // 清除之前画的人脸框 TxtResult.AppendText($[{DateTime.Now}] 已加载图片: {imagePath}\n); } catch (Exception ex) { MessageBox.Show($加载图片失败: {ex.Message}, 错误, MessageBoxButton.OK, MessageBoxImage.Error); } } private void ClearFaceOverlay() { FaceOverlayCanvas.Children.Clear(); } // 拍照功能需要接入摄像头这里先留空你可以使用AForge.NET或OpenCvSharp等库实现 private void BtnCapture_Click(object sender, RoutedEventArgs e) { TxtResult.AppendText($[{DateTime.Now}] 拍照功能待实现需集成摄像头库\n); MessageBox.Show(拍照功能需要额外集成摄像头库如AForge.NET。, 提示, MessageBoxButton.OK, MessageBoxImage.Information); } } }这样用户就能通过按钮选择本地图片并显示出来了。接下来就是最核心的一步调用模型API识别人脸。3. 调用人脸检测模型API模型已经部署在星图GPU平台并提供了HTTP端点。我们需要知道API的地址、请求格式和响应格式。这里假设我们已经获取了类似http://your-model-service-address/predict的API地址。3.1 准备API请求数据通常这类视觉模型的API接受Base64编码的图片字符串。我们需要将System.Drawing.Bitmap转换为Base64。using System; using System.Drawing.Imaging; using System.IO; using System.Net.Http; using System.Text; using System.Threading.Tasks; using Newtonsoft.Json.Linq; namespace FaceAttendanceApp { public partial class MainWindow : Window { // 假设的API地址请替换为实际地址 private const string MODEL_API_URL http://your-model-service-address/predict; private readonly HttpClient _httpClient new HttpClient(); // 将Bitmap转换为Base64字符串 private string BitmapToBase64(Bitmap bitmap) { using (MemoryStream ms new MemoryStream()) { // 保存为Jpeg格式也可以根据API要求选择PNG bitmap.Save(ms, ImageFormat.Jpeg); byte[] imageBytes ms.ToArray(); return Convert.ToBase64String(imageBytes); } } private async void BtnDetect_Click(object sender, RoutedEventArgs e) { if (_currentBitmap null) { MessageBox.Show(请先选择一张图片。, 提示, MessageBoxButton.OK, MessageBoxImage.Warning); return; } BtnDetect.IsEnabled false; TxtStatus.Text 正在检测人脸...; TxtResult.AppendText($[{DateTime.Now}] 开始调用人脸检测API...\n); try { // 1. 准备请求数据 string imageBase64 BitmapToBase64(_currentBitmap); var requestData new { image imageBase64 // 可能还有其他参数如threshold根据API文档调整 }; string jsonData Newtonsoft.Json.JsonConvert.SerializeObject(requestData); var content new StringContent(jsonData, Encoding.UTF8, application/json); // 2. 发送POST请求 HttpResponseMessage response await _httpClient.PostAsync(MODEL_API_URL, content); response.EnsureSuccessStatusCode(); // 确保响应成功 // 3. 解析响应 string responseBody await response.Content.ReadAsStringAsync(); var jsonResponse JObject.Parse(responseBody); // 4. 处理检测结果这里需要根据实际API响应结构调整 // 假设返回格式为: { faces: [ {bbox: [x1, y1, x2, y2], confidence: 0.98}, ... ] } var faces jsonResponse[faces]?.ToObjectListFaceDetectionResult(); if (faces ! null faces.Count 0) { TxtResult.AppendText($[{DateTime.Now}] 检测到 {faces.Count} 张人脸。\n); DrawFaceBoxes(faces); BtnCheckIn.IsEnabled true; // 检测到人脸后允许打卡 TxtStatus.Text $检测完成发现 {faces.Count} 张人脸; } else { TxtResult.AppendText($[{DateTime.Now}] 未检测到人脸。\n); TxtStatus.Text 未检测到人脸; BtnCheckIn.IsEnabled false; } } catch (HttpRequestException httpEx) { TxtResult.AppendText($[{DateTime.Now}] API调用失败: {httpEx.Message}\n); MessageBox.Show($网络请求错误: {httpEx.Message}, API错误, MessageBoxButton.OK, MessageBoxImage.Error); } catch (Exception ex) { TxtResult.AppendText($[{DateTime.Now}] 处理过程出错: {ex.Message}\n); MessageBox.Show($处理错误: {ex.Message}, 错误, MessageBoxButton.OK, MessageBoxImage.Error); } finally { BtnDetect.IsEnabled true; } } // 人脸检测结果类 public class FaceDetectionResult { public float[] bbox { get; set; } // [x1, y1, x2, y2] public float confidence { get; set; } } } }3.2 在界面上绘制人脸框解析出人脸框的坐标后我们需要在图片上把它们画出来。坐标通常是归一化的0到1之间或者基于原图尺寸的像素值。这里假设API返回的是基于原图的像素坐标[x1, y1, x2, y2]。using System.Windows.Shapes; using System.Windows.Media; private void DrawFaceBoxes(ListFaceDetectionResult faces) { ClearFaceOverlay(); if (ImgDisplay.Source null || _currentBitmap null) return; double scaleX FaceOverlayCanvas.ActualWidth / _currentBitmap.Width; double scaleY FaceOverlayCanvas.ActualHeight / _currentBitmap.Height; foreach (var face in faces) { if (face.bbox.Length 4) { float x1 face.bbox[0]; float y1 face.bbox[1]; float x2 face.bbox[2]; float y2 face.bbox[3]; // 将坐标缩放到Canvas上 double canvasX1 x1 * scaleX; double canvasY1 y1 * scaleY; double canvasWidth (x2 - x1) * scaleX; double canvasHeight (y2 - y1) * scaleY; Rectangle rect new Rectangle { Width canvasWidth, Height canvasHeight, Stroke Brushes.Red, StrokeThickness 2, Fill Brushes.Transparent }; Canvas.SetLeft(rect, canvasX1); Canvas.SetTop(rect, canvasY1); FaceOverlayCanvas.Children.Add(rect); // 可选在框旁边添加置信度文本 TextBlock text new TextBlock { Text face.confidence.ToString(F2), Foreground Brushes.Yellow, FontSize 10, Background Brushes.Black }; Canvas.SetLeft(text, canvasX1); Canvas.SetTop(text, canvasY1 - 15); FaceOverlayCanvas.Children.Add(text); } } }现在点击“检测人脸”按钮程序就会把图片发给模型API并把返回的人脸框用红色矩形画在图片上。核心的识别功能就完成了。4. 实现本地考勤逻辑检测到人脸只是第一步我们还需要知道这是谁并记录他的打卡时间。这就需要一个本地的“人脸库”和考勤记录功能。4.1 设计本地数据库我们用SQLite创建两张简单的表。using Microsoft.Data.Sqlite; public class DatabaseHelper { private readonly string _connectionString; public DatabaseHelper(string dbPath face_attendance.db) { _connectionString $Data Source{dbPath}; InitializeDatabase(); } private void InitializeDatabase() { using (var connection new SqliteConnection(_connectionString)) { connection.Open(); // 创建员工表人脸库 var createEmployeeTableCmd connection.CreateCommand(); createEmployeeTableCmd.CommandText CREATE TABLE IF NOT EXISTS Employees ( Id INTEGER PRIMARY KEY AUTOINCREMENT, Name TEXT NOT NULL, EmployeeId TEXT UNIQUE NOT NULL, FaceFeatureData TEXT, -- 这里简单存储实际应存储人脸特征向量如Embedding RegisteredDate DATETIME DEFAULT CURRENT_TIMESTAMP ); createEmployeeTableCmd.ExecuteNonQuery(); // 创建考勤记录表 var createAttendanceTableCmd connection.CreateCommand(); createAttendanceTableCmd.CommandText CREATE TABLE IF NOT EXISTS AttendanceRecords ( Id INTEGER PRIMARY KEY AUTOINCREMENT, EmployeeId TEXT NOT NULL, CheckInTime DATETIME NOT NULL, ImagePath TEXT, -- 打卡时的人脸图片路径 FOREIGN KEY (EmployeeId) REFERENCES Employees (EmployeeId) ); createAttendanceTableCmd.ExecuteNonQuery(); } } // 后续添加员工、查询、插入考勤记录的方法... }这里简化了FaceFeatureData字段本该存储从人脸图片提取的特征向量Embedding。在实际完整系统中你需要用一个人脸识别模型而不仅仅是检测模型来提取特征。本文聚焦于检测和集成所以我们先用一个简单的“人名-图片”映射来模拟。实际项目中你需要集成一个特征提取模型。4.2 模拟人脸识别与考勤记录为了演示完整流程我们假设已经通过其他方式比如另一个管理功能将员工的人脸图片和特征存入了数据库。在打卡时我们简化处理将检测到的人脸区域裁剪出来与数据库中预先存储的该员工注册时的人脸图片进行简单的比对例如使用哈希或简单的图像相似度算法。请注意这只是为了演示流程生产环境务必使用专业的人脸识别SDK或服务进行1:1比对。// 在MainWindow.xaml.cs中补充 private async void BtnCheckIn_Click(object sender, RoutedEventArgs e) { if (_currentBitmap null || _currentImagePath null) { MessageBox.Show(没有可用的图片进行考勤。, 提示, MessageBoxButton.OK, MessageBoxImage.Warning); return; } TxtStatus.Text 正在比对身份并记录考勤...; TxtResult.AppendText($[{DateTime.Now}] 开始考勤流程...\n); // 模拟这里应该调用一个人脸识别服务输入当前人脸图片返回最匹配的员工ID。 // 我们这里用一个简单的输入框模拟识别结果。 var inputDialog new InputDialog(请输入员工工号, 身份确认); if (inputDialog.ShowDialog() true) { string employeeId inputDialog.Answer; // 假设通过验证 using (var db new DatabaseHelper()) { // 1. 验证员工是否存在这里应是人脸识别验证 bool isValid await db.VerifyEmployeeAsync(employeeId); // 这是一个需要实现的异步方法 if (isValid) { // 2. 记录考勤 bool success await db.InsertAttendanceRecordAsync(employeeId, _currentImagePath); if (success) { TxtResult.AppendText($[{DateTime.Now}] 员工 {employeeId} 打卡成功\n); TxtStatus.Text 打卡成功; } else { TxtResult.AppendText($[{DateTime.Now}] 打卡记录失败。\n); } } else { TxtResult.AppendText($[{DateTime.Now}] 员工身份验证失败。\n); MessageBox.Show(身份验证失败请重试或联系管理员。, 验证错误, MessageBoxButton.OK, MessageBoxImage.Error); } } } else { TxtResult.AppendText($[{DateTime.Now}] 用户取消了考勤。\n); } }InputDialog是一个简单的自定义对话框类用于输入工号。DatabaseHelper类需要实现VerifyEmployeeAsync和InsertAttendanceRecordAsync等方法。这样就完成了一个从图片上传、人脸检测、到模拟身份验证和考勤记录的完整闭环。5. 总结与后续优化思路整个项目做下来感觉用.NET做这种集成AI模型服务的桌面应用路径还是挺清晰的。核心就是把界面做好把HTTP调用封装好剩下的业务逻辑比如考勤就是传统的数据库操作了。这种架构的好处是AI模型部分可以独立部署和升级桌面客户端只管调用维护起来也方便。目前这个Demo还有很多可以完善的地方。比如人脸识别部分我们只是模拟真正要用起来你需要接入一个能够输出人脸特征向量Embedding的模型然后在本地数据库里存储和比对这些特征这才是准确的身份验证。另外拍照功能需要集成摄像头库UI也可以做得更美观比如加入打卡历史查询、报表导出等功能。安全方面也要注意API密钥、数据库文件都需要妥善管理。如果是连接远程API记得使用HTTPS。总的来说用.NET框架结合云端的AI模型能力来开发智能桌面应用是一个很实用的思路既能利用.NET生态的成熟和稳定又能享受到AI带来的智能化提升。你可以在这个基础上根据自己的具体需求把它打磨成一个真正好用的小工具。获取更多AI镜像想探索更多AI镜像和应用场景访问 CSDN星图镜像广场提供丰富的预置镜像覆盖大模型推理、图像生成、视频生成、模型微调等多个领域支持一键部署。