
简介这是一份基于C#和Windows Forms的人脸识别打卡系统源码面向桌面应用开发者与计算机视觉初学者重点展示界面设计、人脸识别与考勤数据存储如何衔接成完整项目。包内共有69个文件压缩后约3.3MB以C#源代码和Visual Studio工程文件为主同时包含动态链接库、可执行程序、数据库、配置文件与图像样本解压后即可浏览和运行也保留了便于打包发布的安装项目。项目采用Git仓库分支管理源码覆盖人脸图像预处理、特征匹配、打卡信息写入、多线程调用及异常处理等关键环节整体模块划分清晰适合逐段学习与二次开发也方便后续维护扩展。已有469人学习下载对课程设计、毕业设计或希望掌握C#视觉开发的读者能提供从界面构建到数据库交互的完整参考。1. winform人脸识别打卡一份能直接跑通的C#桌面考勤工程考勤打卡这个需求听着简单真做起来比想象中麻烦。指纹机怕磨损、刷卡机怕代刷外购人脸识别门禁机又贵又难和公司现有系统打通。这份基于winform平台的C#人脸识别打卡系统源码把摄像头采集、人脸比对、打卡记录落库、界面交互全链路串在一个工程里拿来编译就能跑是很少见的适合直接改造成内部考勤工具的完整案例。它解决一线开发者的两个核心问题人脸识别库怎么在桌面上离线跑通以及打卡记录怎么可靠地写进本地数据库。这份资源适合刚入门C#桌面开发、想找一个有实战深度的项目的学习者也适合正在做考勤机对接、想把手动打卡改成自动识别的在职工程师。2. 先看懂工程结构FaceCheckIn_App的目录、依赖与打开方式拿到压缩包之后别急着双击.sln先把目录结构过一遍。很多新手翻车都是因为不清楚哪个文件夹是源码、哪个是 IDE 生成的缓存结果把不该提交的文件也当成项目的一部分编译报错了也不知道去查哪里。这一章我把工程里的关键文件逐个拆开讲清楚。2.1 从.sln到.master目录Visual Studio项目的基本盘压缩包解压后是一层套一层的结构外层是FaceCheckIn_App-master里面才是解决方案和项目目录。master这个词来自 Git 的默认分支名说明作者是用 Git 做版本管理的这点从.gitignore文件也能佐证。FaceCheckIn_App.sln是解决方案文件它负责把项目、依赖和构建配置组织在一起双击它就能用 Visual Studio 打开整个工程。工程里还有一层.vs目录里面放着ProjectSettings.json和VSWorkspaceState.json。这两个文件是 Visual Studio 的本地工作区状态记录了你打开了哪些文件、断点设在哪里、窗口布局长什么样属于 IDE 的私有缓存不影响编译通常也不该提交到 Git 里。作者既然把它一起放进压缩包说明这个包是直接从本地目录打包的不是从 Git 仓库拉完再打包的。这个细节对你没有实际影响只是提醒你如果后续你自己建仓库记得把.vs目录写进.gitignore。打开解决方案之后你会看到FaceCheckIn_App项目。这个项目才是真正的源码所在它下面至少应该包含Form相关的主窗口代码、Program.cs入口文件以及若干负责图像处理和数据库操作的类文件。README.md是作者写的项目说明建议先读它里面一般会有运行前提和配置步骤。slnx.sqlite这个文件在早前版本的 Visual Studio 里不存在它是新版 IDE 用来缓存解决方案状态的辅助文件和业务数据库没有关系。看清这一层你就知道哪些文件能删、哪些不能动了。2.2 Setup目录与References发布配置与第三方引用Setup目录是这个工程里容易被忽略但实际很重要的部分。它通常对应一个安装部署项目用来把编译好的 exe 和依赖一起打包成安装程序。在FaceCheckIn_App.sln里你可能会看到一个名为Setup的项目类型标记它负责决定安装包的名字、安装路径、桌面快捷方式以及需要随安装包分发的文件列表。这部分单独拆出来讲是有价值的因为很多人写完考勤系统不知道怎么做成给行政同事能双击安装的包这个目录就是现成的答案。References是 C# 项目里的引用管理节点在解决方案资源管理器里以树形结构展示。人脸识别打卡系统一般至少会引用这些程序集System.Windows.Forms负责界面控件System.Drawing负责图像处理System.Data负责数据库访问还可能会有AForge.Video.DirectShow或者Emgu.CV这类第三方库用于摄像头采集和人脸检测。检查 References 的意义在于你可以一眼判断作者用的是哪条识别技术路线——引用了 Emgu.CV 就是走 OpenCV 的 C# 封装引用了一堆带具体厂商前缀的 DLL 则可能是接的商用 SDK。打开项目后建议先按一下CtrlShiftB编译一次。如果 References 完整、第三方包已经在包目录里编译应该能直接通过如果报错缺少某个引用优先检查packages文件夹存不存在、app.config里的绑定重定向是否正常而不是急着重装 Visual Studio。编译通过之后把Setup项目设为启动项目再生成一次你就能在输出目录拿到一个可以拷走的安装文件这一步的具体操作我会在最后一章展开。3. 人脸识别的方案选型摄像头采集、特征比对与阈值调参人脸识别是这套系统的灵魂也是最多人觉得玄学的地方。很多人以为人脸识别必须接云 API其实在 C# 桌面应用里完全可以离线跑通。这一章讲清楚识别库怎么选、摄像头帧怎么抓、特征比对阈值怎么定全程围绕源码里实际会出现的代码展开让你拿到工程后知道该改哪里。3.1 识别库选型OpenCV、Emgu CV还有商用SDK各自适合什么市面上常见的人脸识别方案有三条路线。第一条是直接调用 OpenCV 的 Haar Cascade 或 LBP 级联分类器做人脸检测再用 LBPH 或 EigenFace 做特征比对完全离线、免费、部署简单缺点是传统特征对光照和角度敏感识别准确率在 95% 左右适合公司内部这种受控环境。第二条是用 Emgu CV这是 OpenCV 的 C# 封装API 比直接 P/Invoke 原生库友好得多C# 工程里引用它之后可以像写普通 .NET 代码一样做人脸检测和训练。第三条是接商用 SDK比如 ArcFace、EasyAI 这类人脸识别引擎识别率能到 99% 以上支持活体检测但通常需要申请 AppID 和 Secret Key且部分功能依赖联网授权不适合完全内网部署。判别当前工程用的是哪条路线看 References 就行。我在 2.2 节讲过引用了 Emgu.CV 就是走 OpenCV 封装路线引用带厂商名的 DLL 就是商用 SDK 路线。从源码包的大小和离线部署的特征来看这份工程大概率走的是离线路线也就是 Emgu CV 或原生 OpenCV。在下面的讲解里我按最常见的 Emgu CV LBPH 组合来展开这个组合在 C# 桌面考勤系统里最常用也是新手最容易调通的一条路。3.2 摄像头帧捕获与图像预处理的C#实现摄像头采集是人脸识别的第一道工序。在 C# winform 里最常用的库是 AForge.NET它专门解决摄像头枚举、帧捕获和视频流处理的问题。下面是项目里典型的摄像头初始化代码using AForge.Video; using AForge.Video.DirectShow; // 1. 枚举本机所有摄像头设备 FilterInfoCollection cameras new FilterInfoCollection(FilterCategory.VideoInputDevice); if (cameras.Count 0) { MessageBox.Show(未检测到摄像头请检查设备连接); return; } // 2. 选择第一个摄像头并启动视频流 VideoCaptureDevice device new VideoCaptureDevice(cameras[0].MonikerString); device.NewFrame OnNewFrame; device.Start();这段代码做了两件事第一步枚举设备FilterCategory.VideoInputDevice是 AForge 专门用来筛选视频输入设备的过滤条件返回的cameras集合里每一项代表一个摄像头含设备名和唯一标识字符串第二步用VideoCaptureDevice打开第一个摄像头并订阅NewFrame事件这个事件会在每一帧图像到达时触发。这里有一个常见问题是摄像头多了以后不一定是第一个就是你要用的所以实际项目里一般会在窗体上放一个下拉框把cameras里的设备名绑定进去让用户自己选。帧捕获之后到人脸检测之间还有一个图像预处理环节。摄像头直接输出的帧是彩色的而且受环境光影响明显不适合直接送给人脸识别器。常见的预处理是转灰度图然后做直方图均衡化增强对比度private void OnNewFrame(object sender, NewFrameEventArgs eventArgs) { Bitmap original (Bitmap)eventArgs.Frame.Clone(); // 转灰度图BT709算法是高清视频标准的灰度转换系数 Bitmap gray Grayscale.CommonAlgorithms.BT709.Apply(original); // 直方图均衡化提升暗光环境下的人脸对比度 ImageGray, byte img new ImageGray, byte(gray); img._EqualizeHist(); // 这里的 img 下一步送给检测器做人脸定位 RecognizeFace(img); }这段代码里Grayscale.CommonAlgorithms.BT709用的是高清视频标准里的灰度转换系数比直接取 RGB 平均值更接近人对亮度的感知。_EqualizeHist()是直方图均衡化作用是把图像灰度分布拉开让暗光下的人脸轮廓更清晰。每一帧Clone()出来的 Bitmap 必须及时释放否则长时间运行后内存会持续上涨最后摄像头画面变成幻灯片。3.3 特征注册与人脸比对从录入员工到识别打卡有了预处理后的灰度图下一步是检测人脸区域并提取特征。在 Emgu CV 里人脸检测用 HaarCascade特征比对用 LBPHFaceRecognizer。LBPH 的原理是把人脸划分成小网格在每个网格里提取局部二值模式纹理特征再组合成一条高维特征向量比对就是算两条向量之间的距离。下面是项目的特征比对器和注册流程的核心参数设置using Emgu.CV.Face; // 注册员工人脸传入裁剪好的人脸灰度图 LBPHFaceRecognizer recognizer new LBPHFaceRecognizer( radius: 1, // LBP采样半径1表示只取紧邻像素 neighbors: 8, // 采样邻域点数8是人脸识别里最常用的值 gridX: 8, // 水平方向网格数网格越细精度越高 gridY: 8, // 垂直方向网格数 threshold: 120.0); // 比对阈值低于该值判定为同一个人 // 训练模型一组灰度人脸图 对应的员工ID recognizer.Train(faceImages, labels); // 预测输入实时人脸返回匹配的员工ID和置信度分数 FaceRecognizer.PredictionResult result recognizer.Predict(testFace); if (result.Distance 120.0) { // 识别成功result.Label 就是员工ID RecordAttendance(result.Label); }这里每个参数都有实际意义。radius和neighbors决定 LBP 特征算子提取微观纹理的力度半径太大或邻域太多会把噪声也当成特征。gridX和gridY决定代表人脸的空间分辨率网格数量翻倍特征向量的维度会以平方级别增加训练时间和内存占用都会明显上升8×8 是速率和精度之间的折中值。最关键的是threshold它决定什么情况算识别成功——阈值设置得过大不同人也会被当作同一个人设置得过小同一个人换个角度就识别失败。最稳妥的做法是在正式使用前拿员工的 20 到 30 张不同角度照片跑一遍测试画出分数分布区间再定阈值。商用 SDK 路线的代码结构和这个不完全一样但逻辑骨架一致先注册人脸拿到 feature 向量再拿实时特征去比对。如果你手上的工程引用的是 ArcFace找到初始化引擎的代码把它的阈值参数对着这一节的思路去调就行比对阈值的位置和含义是相通的。4. 打卡业务逻辑数据库设计、识别回调与UI防卡死人脸识别只是前端能力打卡系统真正要落地还得靠后端的业务逻辑。这一章从数据库表设计讲到识别成功后的写入流程再到 winform 界面怎么不被耗时任务卡死三条线串起来你就能看懂整个工程的数据流向。4.1 数据库设计员工表、打卡记录表与字段约束打卡系统需要存两类数据员工信息和人脸特征以及每一次的打卡记录。工程里用 SQLite 的可能性最大因为它是单文件数据库、免安装服务适合桌面考勤系统这种单机场景。下面是两张核心表的建表语句-- 员工表存员工基本信息和人脸特征 CREATE TABLE employees ( id INTEGER PRIMARY KEY AUTOINCREMENT, emp_no TEXT NOT NULL UNIQUE, -- 工号唯一约束防止重复录入 emp_name TEXT NOT NULL, -- 姓名 face_feature BLOB, -- 人脸特征向量序列化后存二进制 photo_path TEXT, -- 登记照片的本地路径便于回溯 registered_at DATETIME DEFAULT CURRENT_TIMESTAMP -- 录入时间 ); -- 打卡记录表每次识别成功追加一条 CREATE TABLE attendance_logs ( id INTEGER PRIMARY KEY AUTOINCREMENT, emp_no TEXT NOT NULL, -- 工号关联员工表 check_time DATETIME DEFAULT CURRENT_TIMESTAMP, -- 打卡时间 review_status TEXT DEFAULT NORMAL -- NORMAL正常 / LATE迟到 / EARLY早退 );这两张表的设计有几个值得注意的点。emp_no在员工表里加了UNIQUE这保证了一个工号不能被重复录入注册避免了人脸特征被覆盖的情况。face_feature用BLOB类型存序列化后的特征向量不要存图片本身图片走photo_path路径引用文件这样可以控制数据库体积。attendance_logs里刻意没有存员工姓名只存工号需要显示姓名时再关联查询这是为了减少冗余、避免工号改名时多处不同步。打卡记录的写入时机是识别成功回调之后。有个常见设计失误是把每次识别成功都当成一次打卡结果同一人在摄像头前站了十秒就打了十次卡。所以实际项目里一般会加一个防重复逻辑比如同一工号两分钟内的重复识别不再写入这个防重逻辑可以在 C# 代码里做也可以在表上建唯一索引约束。4.2 识别成功到写入打卡记录C#完整链路实现当第 3 章的 LBPH 识别器返回一个低于阈值的PredictionResult之后系统需要立即完成三件事查出员工信息、判断是否重复打卡、写入打卡记录。下面是这条链路的参考代码private void RecordAttendance(int empLabel) { try { using (SQLiteConnection conn new SQLiteConnection(Data SourceAttendance.db)) { conn.Open(); // 第一步根据识别器返回的Label查出工号 string findSql SELECT emp_no, emp_name FROM employees WHERE id id; SQLiteCommand findCmd new SQLiteCommand(findSql, conn); findCmd.Parameters.AddWithValue(id, empLabel); SQLiteDataReader reader findCmd.ExecuteReader(); if (!reader.Read()) { statusLabel.Text 识别失败未找到对应员工; return; } string empNo reader[emp_no].ToString(); string empName reader[emp_name].ToString(); // 第二步判断两分钟内是否已经打过卡防止重复记录 string dupSql SELECT COUNT(*) FROM attendance_logs WHERE emp_no empNo AND check_time datetime(now, -2 minutes); SQLiteCommand dupCmd new SQLiteCommand(dupSql, conn); dupCmd.Parameters.AddWithValue(empNo, empNo); long count (long)dupCmd.ExecuteScalar(); if (count 0) { statusLabel.Text ${empName} 已经打过卡了; return; } // 第三步写入打卡记录 string insertSql INSERT INTO attendance_logs(emp_no, check_time, review_status) VALUES(empNo, datetime(now, localtime), NORMAL); SQLiteCommand insertCmd new SQLiteCommand(insertSql, conn); insertCmd.Parameters.AddWithValue(empNo, empNo); insertCmd.ExecuteNonQuery(); statusLabel.Text $打卡成功{empName} {DateTime.Now:HH:mm:ss}; } } catch (SQLiteException ex) { // 数据库异常不能影响主线程记录日志后提示用户 File.AppendAllText(error.log, ${DateTime.Now} {ex.Message}\r\n); } }这段代码里值得关注的是参数化查询的使用。所有拼接进 SQL 的值都通过Parameters.AddWithValue传入而不是直接拼字符串这能从根本上避免 SQL 注入。datetime(now, -2 minutes)是 SQLite 自带的日期计算函数这条语句在数据库层面完成防重复判断比先查所有记录再到 C# 里比较时间要高效得多。写入时用datetime(now, localtime)存的是本地时间避免 UTC 和本地时区不一致导致考勤时间错乱。这段代码我特意用了DateTime.Now:HH:mm:ss来格式化状态标签因为状态栏是用户第一时间看到反馈的地方。实际开发中我一般会把这三步拆成FindEmployee、IsDuplicate、InsertLog三个独立方法方便做单元测试——考勤逻辑涉及钱和纪律测过的代码才敢上生产环境。4.3 UI不卡死状态栏与进度条的跨线程更新技巧winform 开发里最常见的翻车点是把耗时的人脸识别操作直接写在按钮点击事件里运行起来整个窗口立即假死鼠标移动都没反应。原因很简单winform 的 UI 消息循环被阻塞了界面画不出来。解决方案是让耗时的识别操作跑在工作线程里只把结果通过Invoke机制传回 UI 线程。下面是标准写法private async void btnStart_Click(object sender, EventArgs e) { btnStart.Enabled false; toolStripStatusLabel1.Text 正在初始化摄像头…; toolStripProgressBar1.Style ProgressBarStyle.Marquee; // 跑马灯样式 // 耗时识别逻辑放后台任务UI线程不阻塞 await Task.Run(() { while (isRecognizing) { Bitmap frame CaptureFrame(); int label RecognizeFace(frame); // 跨线程更新UI必须通过Invoke回到UI线程 this.Invoke((Action)(() { if (label 0) { toolStripStatusLabel1.Text 识别成功正在写入记录…; RecordAttendance(label); } else { toolStripStatusLabel1.Text 未匹配人脸请靠近摄像头; toolStripProgressBar1.Value 0; } })); Thread.Sleep(50); // 控制识别频率别让CPU占满 } }); btnStart.Enabled true; toolStripProgressBar1.Style ProgressBarStyle.Continuous; }这里的核心是await Task.Run。async和await是 .NET 4.5 引入的异步编程语法Task.Run把耗时的人脸识别逻辑放到线程池线程执行UI 线程通过await挂起等待但不阻塞消息循环所以窗体仍然能响应鼠标拖动和点击。this.Invoke((Action)(() { ... }))是跨线程更新 UI 的标准做法Invoke会把委托封送到 UI 线程上执行。之所以必须这么做是因为 winform 的控件不是线程安全的直接在工作线程里改toolStripStatusLabel1.Text会随机抛异常有时候能跑一天才报一次错排查起来极其痛苦。ProgressBarStyle.Marquee是进度条的跑马灯模式适合不知道具体耗时的任务它会让进度条一直流动给用户一个程序还活着的反馈。Thread.Sleep(50)的作用是限流人脸识别本身吃 CPU如果每一帧都做全流程识别CPU 会一直满载机器发热、风扇狂转。50 毫秒的间隔大概对应每秒 20 帧的识别频率实测够用又不至于太消耗资源。5. 避坑手册人脸识别打卡系统最常见的5个翻车点人脸识别考勤系统是我做过的最难一次调通的项目之一坑主要不在 C# 语法而在摄像头、环境光、系统权限这些看不见的地方。下面这 5 条是我认为最有必要记录的踩坑经验每条都按现象 → 原因 → 解决的顺序写你照着排查能省下至少一个下午的调试时间。5.1 摄像头画面黑屏但设备枚举正常现象FilterInfoCollection能枚举出摄像头device.Start()也不报错但OnNewFrame事件里的图像是全黑的。原因这个问题的根源不在 AForge 的采集代码而在摄像头本身。很多笔记本内置摄像头默认被其他应用占用或者驱动的首选输出格式不是 AForge 默认请求的 RGB24。另外虚拟摄像头软件的缓存帧也会导致画面僵在第一帧。解决先打开系统自带的相机应用确认摄像头本身正常再检查是否有别的进程占用了设备。在代码里可以尝试设置device.VideoResolution device.VideoCapabilities里非 RGB 的兼容格式一般可以找到能出画面的组合。如果用的是 USB 摄像头换个 USB 口也常能解决驱动识别异常。5.2 光线过亮或过暗导致识别率骤降现象上午靠窗工位的同事识别成功率高下午背着光坐的时候老是识别失败戴眼镜的同事时好时坏。原因LBPH 这类传统特征对光照变化非常敏感。强光下的人脸过曝细节纹理丢失逆光下的人脸整体偏暗直方图集中在低灰度区间前面讲的_EqualizeHist()虽然能提升对比度但面对极端光照还是不够。解决最有效的办法是摄像头选宽动态范围的型号软件层面把预处理从单帧直方图均衡化升级为 CLAHE对比度受限自适应直方图均衡化它在 Emgu CV 里是CLAHE类能限制噪声放大的同时增强局部对比度。还可以在注册人脸时多录几张不同方向光照的照片LBPH 的网格特征会把这部分差异学进模型里。5.3 win10/win11系统下摄像头调用失败现象代码在 win7 上跑得好好的换到 win10/win11 上device.Start()报未找到设备或直接崩溃。原因win10 开始系统对摄像头的隐私权限做了严格管控桌面应用没有被授予摄像头访问权限时DirectShow 拿不到视频流。这个是操作系统层面的限制不是代码问题。解决让用户检查设置 → 隐私和安全性 → 摄像头确保允许桌面应用访问摄像头。另外在app.config里加上requestedExecutionLevel声明为asInvoker不要用管理员权限运行因为管理员权限反而可能导致应用的识别身份与系统隐私策略不一致。我在交付项目时都会在 README 里写死这一步因为几乎每个换新电脑的同事都会踩一遍。5.4 识别线程未停止导致程序退出异常现象关闭窗体时程序报访问已释放的句柄或者进程结束后摄像头指示灯依然亮着。原因关闭 winform 窗体时VideoCaptureDevice的NewFrame事件还在触发识别线程还在跑窗体销毁后事件回调里尝试访问已释放的控件就抛异常。这只是表象根源是没有在窗体关闭事件里做资源释放。解决在FormClosing事件里先执行device.SignalToStop()停掉视频流再device.WaitForStop()等待采集线程完全退出之后把isRecognizing标志置为false让识别循环退出。顺序不能反先停循环再停摄像头否则可能因线程仍在读取帧而崩溃。5.5 SQLite数据库写入死锁现象程序运行一段时间后打卡记录写入越来越慢最后界面假死SQLiteException报 database is locked。原因这是一个隐蔽的并发问题。人脸识别回调所在的线程和 UI 线程几乎同时发起写操作时SQLite 的默认配置只允许一个写者在某个时间点持有锁另一个写者等待超过busy_timeout就会抛异常。工程里如果每次识别都在回调里建连接连接池放大后更容易触发。解决方案是全局只维护一个SQLiteConnection单例所有写操作通过lock关键字串行化同时把连接字符串的busy_timeout设为 3000 毫秒。我在自己的项目里写了一个静态的DbHelper类把所有数据库操作包在lock块里从那以后再也没有出现过 locked 错误。6. 验证流程与打包发布从本地联调到生成安装程序系统写完之后最大的风险是没验证就交付。人脸识别考勤系统牵扯到考勤数据上线前至少要跑通一遍完整的功能链路再打成安装包给行政同事用。6.1 本地联调三个必测场景第一个场景是注册。打开员工登记页面选一张人脸照片或现场抓拍确认face_feature能写入数据库用 SQLite 的可视化工具打开表确认BLOB字段非空。第二个场景是正常打卡让测试人员站在摄像头前确认 1 秒内出识别结果状态栏显示姓名和打卡时间attendance_logs表新增一条记录。第三个场景是负面测试让一个未注册的人站在摄像头前确认系统提示未匹配人脸且不写记录。这三个场景走完等于把注册、识别、写入、防误判四段逻辑都验证了一遍。另外要测试长时间运行稳定性让程序开着摄像头跑一晚上第二天起来看内存占用有没有持续上涨画面帧率有没有明显下降。6.2 打包成安装程序Visual Studio部署项目的完整配置这一节用 Visual Studio 的 Setup 项目把工程打包成真正的安装程序。先把解决方案配置切换到 Release在解决方案上右键选择添加 → 新建项目找到其他项目类型 → 安装和部署 → Visual Studio Installer选择Setup Project。建好之后在项目上右键添加 → 项目输出选择FaceCheckIn_App的主输出这一步会把 exe 和它依赖的所有 DLL 自动带进去。然后配置安装路径建议设为[ProgramFilesFolder]制造商名\FaceCheckIn_App。最后右键项目点生成Setup项目的输出目录里会出现一个setup.exe和.msi文件拷到别的电脑上双击就能装。打包时有一个参数容易漏如果工程用了AForge.Video.DirectShow等带原生 DLL 的第三方库要确认这些文件被标记为随程序集复制。打开这些 DLL 文件的属性把复制本地设为True否则装到没有开发环境的电脑上会报找不到模块。还有一个细节是给快捷方式指定图标用管理员权限运行时图标不会被系统缓存卡住这个虽然不影响功能但很影响第一印象。当年我第一次交付这个考勤系统时就是把识别逻辑直接写进按钮事件里导致窗体假死被测试同事录屏整个窗口无响应的截图挂在了部门公告栏。从那以后我每次做任何涉及摄像头和耗时常任务的 winform 页面都会强制走一遍这个流程先开摄像头确认帧率和画面再拿不同光照下的照片测阈值最后才接数据库写入验证。识别类功能的调试节奏和普通增删改查完全不同数据不对可以回滚识别不准只能调参重测。希望这篇笔记能帮到你。本文还有配套的精品资源点击获取