OpenCV+MySQL+QT构建人脸识别考勤系统:从摄像头到数据库完整实战
发布时间:2026/9/10 12:03:42来源:尧图网络
简介基于OpenCVMySQLQT实现的人脸识别考勤系统源码包是一份适用于毕业设计、课程设计及期末大作业的完整项目面向计算机、通信、人工智能等专业的学生和开发者。资源整体共12个文件以C源文件.cpp、头文件.h为主辅以Qt界面文件.ui、工程配置文件.pro和项目说明文档压缩包仅17KB代码精简、模块划分清晰。项目打通了OpenCV图像处理、MySQL数据持久化与QT交互界面三大环节覆盖人脸检测、特征提取、身份比对与考勤写入等核心流程适合用来理解图像识别与数据库结合的实际业务场景。目前已有753人浏览学习代码经过本地调试可快速运行既可以作为课程作业的完整方案也能在基础上二次开发是积累工程实践经验的良好参考。1. 为什么建议用OpenCVMySQLQT做一套人脸识别考勤系统人脸考勤类课设最常见的失败不是算法跑不通而是摄像头一开界面就卡、识别出来了记录却没进数据库。这套基于OpenCVMySQLQT的源码把“检测-识别-入库-显示”整条链路补齐了主界面用Qt Widgets识别层用OpenCV的face模块持久化走MySQL适合做毕业设计、课程大作业或期末大作业的计算机、通信、人工智能、自动化相关专业学生参考。我会直接按源码里的文件构成讲清楚每条代码解决什么问题、参数为什么这样设、换到你的机器上要改哪里。对已经有几年经验的开发者这个组合不算新但能把LBPH调参、Qt线程模型、MySQL驱动加载这几个老坑一次踩明白也算值得花一个下午过一遍。2. 系统架构与人脸识别方案选型为什么是LBPHMySQL2.1 从源码文件看模块职责拿到压缩包先看文件整个工程没有拆得很碎但功能边界是清晰的mainwindow.h/cpp和mainwindow.ui是主界面customtabstyle.h/cpp处理Tab标签自绘puttext.h/cpp负责把中文和考勤状态叠加到视频帧上main.cpp是程序入口opencvdemo.pro则是qmake工程文件最后还有一个app.ico做程序图标。各文件对应职责可以用一张表说清楚文件职责main.cpp初始化QApplication创建主窗口对象mainwindow.cpp主界面逻辑启动摄像头线程、触发识别、操作数据库customtabstyle.cpp自绘Tab样式美化识别结果页和考勤统计页puttext.cpp在视频帧上绘制中文姓名、签到状态、时间戳opencvdemo.pro指定Qt模块、OpenCV头文件和库文件路径从这份清单能看出工程把OpenCV相关代码分散在mainwindow和puttext两个模块里MySQL操作直接写在主窗口逻辑中。这种组织形式并不优雅但对课程设计来说反而是优点老师想让你演示哪个功能你能立刻说出在哪个函数里改代码。2.2 人脸识别算法选择LBPH的边界在哪项目标题写的是“人脸识别”而不是“人脸检测”两者经常被混用。检测是在图像中回答“有没有人脸、脸在哪里”OpenCV里用Haar级联或DNN模型识别是回答“这张脸是谁”。从工程文件来看源码应该使用了OpenCV face模块中的LBPHFaceRecognizer也就是局部二值模式直方图。LBPH的原理是把人脸图像划分成小块对每块计算局部二值模式得到直方图再将所有小块拼接成特征直方图识别时用直方图距离做比对。它不像EigenFaces那样对整体光照变化敏感也不需要像深度学习那样准备上千张训练图。对考勤场景一个班级或部门的几十个人每人采集20到30张样本LBPH的识别率已经够用而且训练时间秒级完成。答辩时只要说出“LBPH的confidence越小越匹配”这个细节基本不会被追问住。当然边界也很明显LBPH对侧脸、遮挡、强背光非常敏感。用在固定工位正脸打卡没有问题如果要做闸机上的非配合识别就得换DNN模型这部分后面会讲怎么替换。2.3 MySQL表设计人脸图片不该进数据库这套系统至少需要两张表员工表和考勤表。员工表存工号、姓名和人脸标签考勤表存员工ID、打卡时间和打卡类型。样本图片不要直接塞进BLOB字段否则数据库会越背越重备份也麻烦。常见做法是把训练图片按照“label_序号.jpg”命名放在本地目录MySQL只存储业务关联关系。CREATE DATABASE face_attendance DEFAULT CHARACTER SET utf8mb4; CREATE TABLE employee ( id INT PRIMARY KEY AUTO_INCREMENT, emp_no VARCHAR(20) UNIQUE NOT NULL, emp_name VARCHAR(50) NOT NULL, face_label INT UNIQUE NOT NULL ); CREATE TABLE attendance ( record_id INT PRIMARY KEY AUTO_INCREMENT, employee_id INT NOT NULL, check_time DATETIME NOT NULL, check_type TINYINT NOT NULL DEFAULT 1 COMMENT 1签到 2签退, CONSTRAINT fk_att_emp FOREIGN KEY (employee_id) REFERENCES employee(id) );这里的字符集用utf8mb4而不是utf8是为了避免中文姓名出现乱码face_label加上UNIQUE约束因为LBPH训练时label必须唯一否则不同员工的特征会被混在一起check_type用TINYINT不用字符串后面写“当日到岗人数”统计SQL时会方便很多。课程设计里能主动考虑到这几个点评阅老师会觉得你确实在工程层面做过取舍。2.4 Qt和OpenCV的线程协作模式摄像头读取属于阻塞操作如果直接在主线程里写while循环不断read窗口会假死到无法拖动。因此工程里必然会有一个采集线程把每个Mat帧转成QImage后通过信号发回主线程主线程只负责刷新QLabel和响应按钮事件。识别计算也应该放到子线程避免每帧predict耗尽CPU。在Qt中实现多线程常见做法是自定义CaptureThread继承QThread在run()里循环读取摄像头并发送frameReady信号。信号槽连接时使用QueuedConnection让跨线程调用进队列而不是直接执行这样UI线程不会被识别逻辑卡住。项目没有采用更现代的QObjectmoveToThread方式可能是为了课程设计好讲解但这反而是个能展开聊的点两种线程模型各有什么优缺点答出来就是加分项。3. 环境搭建与工程配置从.pro到摄像头线程3.1 依赖版本与安装要点我拆这个项目时使用的组合是OpenCV 4.5.2、Qt 5.15.2和MySQL 5.7。压缩包里的opencvdemo.pro.user记录了作者本机的Qt版本和编译器Kit直接打开项目如果提示配置无效最简单的处理是在Qt Creator里删除.pro.user文件再重新选择编译器Kit。网上搜opencv安装教程时注意看作者用的OpenCV版本号4.5.2和4.8、4.9的库文件名不同导致.pro里的LIBS各不相同。MySQL如果不想装完整版用免安装版也可以但要注意把bin目录加入系统PATH否则程序运行时找不到libmysql.dll。3.2 opencvdemo.pro怎么配才不报错源码里已经存在opencvdemo.pro但里面的路径一定来自作者机器。我习惯把所有第三方库放到工程目录下的thirdparty文件夹用$$PWD代替绝对路径方便整个目录拷贝到任意电脑编译。下面是一份实际可用的.pro配置QT core gui sql greaterThan(QT_MAJOR_VERSION, 4): QT widgets CONFIG c11 TARGET FaceAttendance TEMPLATE app SOURCES main.cpp \ mainwindow.cpp \ customtabstyle.cpp \ puttext.cpp HEADERS mainwindow.h \ customtabstyle.h \ puttext.h FORMS mainwindow.ui INCLUDEPATH $$PWD/thirdparty/opencv/include LIBS $$PWD/thirdparty/opencv/x64/mingw/lib/libopencv_world452.dll.a RC_ICONS app.ico需要解释几个点QT sql如果缺少Qt Creator会直接报“Project ERROR: Unknown module(s) in QT: sql”不是代码问题而是安装Qt时没有勾选SQL模块INCLUDEPATH里的$$PWD指.pro文件所在目录这样换机器不用改路径LIBS链接的是OpenCV的world整合库MinGW使用.dll.a文件MSVC使用.lib文件混用会在链接阶段报上百个未解析符号。RC_ICONS用于设置程序图标如果不填编译出来的窗口就没有app.ico这个图标。3.3 摄像头线程的最小实现拿到源码后先看mainwindow构造函数里是否创建了QThread子类。如果作者用QTimer定时抓帧帧率通常上不去且UI线程容易抖动。我一般会写独立的CaptureThread核心代码只有几十行class CaptureThread : public QThread { Q_OBJECT public: void setPause(bool pause) { m_pause pause; } protected: void run() override { cv::VideoCapture cap(0); if (!cap.isOpened()) { emit errorOccurred(无法打开摄像头); return; } cv::Mat frame; while (!isInterruptionRequested()) { if (m_pause) { msleep(20); continue; } cap frame; if (frame.empty()) continue; emit frameReady(matToQImage(frame)); msleep(30); } } signals: void frameReady(const QImage image); };这里有两个容易被忽略的细节cap读到的Mat是BGR格式转成QImage前需要调用cvtColor成RGB否则画面会偏蓝QImage对象本身不持有像素数据frame每帧会被覆盖因此发送信号时要使用img.copy()做一次深拷贝否则主线程拿到的图像可能已经被下一帧数据覆盖。msleep(30)就是把帧率限制在30fps左右CPU占用与流畅度之间取平衡。3.4 编译链接阶段的经典报错即使.pro配置正确换到新环境依然有高频报错。下面这张表收集了最常见的问题和对应处理方式编译/运行错误真实原因处理方式Project ERROR: Unknown module(s) in QT: sqlQt安装时未勾选数据库模块重新运行Qt安装器勾选Qt SQLcannot find -lopencv_world452LIBS路径或文件名不匹配进入实际lib目录替换成真实文件名QMYSQL driver not loaded缺少qsqlmysql.dll或libmysql.dll将Qt的sqldrivers插件和MySQL的dll拷到exe目录运行时VCRUNTIME140.dll缺失缺少VC运行库安装VC_redist.x64.exe或随exe一起发布这些错误踩过一次就长记性。尤其是QMYSQL driver not loaded很多人以为是MySQL连接语句写错其实程序压根没加载到驱动插件。4. 核心功能实现人脸注册、实时识别与考勤入库4.1 人脸样本采集与模型训练系统上线前得先让员工注册人脸。我的设计方式是主界面点“新增人员”输入工号和姓名后自动分配一个递增label然后摄像头连续抓取当前人脸用Haar级联检测器找出人脸区域裁剪缩放成100x100灰度图保存为dataset/目录下的“label_序号.jpg”。这样训练时不需要额外维护图片列表。cv::CascadeClassifier detector; detector.load(haarcascade_frontalface_default.xml); std::vectorcv::Mat faces; std::vectorint labels; for (int i 1; i 30; i) { std::string path cv::format(dataset/%d_%d.jpg, label, i); cv::Mat img cv::imread(path, cv::IMREAD_GRAYSCALE); cv::equalizeHist(img, img); faces.push_back(img); labels.push_back(label); } cv::Ptrcv::face::LBPHFaceRecognizer model cv::face::LBPHFaceRecognizer::create(); model-train(faces, labels); model-save(face_model.xml);这里训练前做了一次equalizeHist也就是直方图均衡化能有效减少左右脸光照差异。LBPH构造参数不要随便改radius1、neighbors8对100x100的输入已经足够稳定改大radius可能会丢失纹理细节。训练完成后模型保存为face_model.xml程序启动时直接load就不用每次重启都重新训练。4.2 predict识别与置信度阈值实时识别阶段每一帧先做灰度化和人脸检测再取面积最大的人脸区域做缩放然后调用predict得到label和confidence。这里最大的坑是置信度阈值不同人脸库、不同图像尺寸下距离范围差别很大不能照抄别人的数值。int label -1; double confidence 0.0; model-predict(grayFace, label, confidence); if (confidence 60.0) { // 命中员工label对应employee表 }从实际测试来看confidence小于40时基本确定是同一个人40到60之间有可能撞脸大于60建议弹窗走人工确认。考勤系统宁可漏打也不要打错卡这是业务底线。下面是我在不同光照条件下的参考阈值场景建议阈值说明固定工位正脸60光线稳定可适当放宽门禁角度出入50侧脸会增大特征距离多人共用摄像头45降低误识宁可漏报实际使用中如果发现某人总是识别失败先不要急着降阈值先检查注册时的样本照片是否模糊、表情是否单一这比调算法参数有效得多。4.3 puttext.cpp如何解决OpenCV中文乱码OpenCV自带的cv::putText不支持中文直接传入中文字符串会出现乱码甚至std::length_error。工程里单独放一个puttext.cpp说明作者大概率用了QPainter把中文画到QImage上再贴回Mat。我自己封装时会保留一个与cv::putText相似接口void PutText::putTextZh(cv::Mat frame, const std::string text, cv::Point pt, cv::Scalar color) { cv::Mat rgb; cv::cvtColor(frame, rgb, cv::COLOR_BGR2RGB); QImage img(rgb.data, rgb.cols, rgb.rows, rgb.step, QImage::Format_RGB888); QPainter painter(img); painter.setPen(QColor(color[2], color[1], color[0])); painter.setFont(QFont(Microsoft YaHei, 12)); painter.drawText(pt.x, pt.y, QString::fromLocal8Bit(text.c_str())); painter.end(); cv::cvtColor(rgb, frame, cv::COLOR_RGB2BGR); }这里注意三点颜色参数是cv::Scalar按BGR顺序存储转成QColor时要倒序否则彩色字体会红蓝互换drawText的坐标是文字左上角而cv::putText的坐标是文字基线左下角如果发现文字明显偏上或偏下把y坐标增加10到15像素即可输出前必须再转回BGR因为OpenCV中后续显示和图像保存都默认BGR顺序。4.4 考勤写入与防重复打卡识别到员工后不能直接插入数据库否则员工站在摄像头前两秒考勤表就多出几十条记录。我的做法是先查询当天这个员工是否已经有签到记录没有才插入然后在视频画面叠加“签到成功”。QSqlQuery query; query.prepare(SELECT COUNT(*) FROM attendance WHERE employee_id ? AND DATE(check_time) CURDATE()); query.addBindValue(label); query.exec(); query.next(); bool hasChecked query.value(0).toInt() 0; if (!hasChecked) { QSqlQuery insert; insert.prepare(INSERT INTO attendance(employee_id, check_time, check_type) VALUES (?, NOW(), 1)); insert.addBindValue(label); insert.exec(); // 未开启事务时这里不需要手动commitQMYSQL默认自动提交 }代码里使用参数绑定而不是字符串拼接主要目的是防SQL注入。虽然课程设计没有安全审查但老师看到addBindValue会认为你理解参数化查询。DATE(check_time)CURDATE()无法走索引考勤数据只有几千条时没有问题后面做月度统计时再按月分表或加复合索引就能优化。识别成功后还要让主窗口刷新QTableView注意QSqlDatabase连接不能跨线程使用刷新UI必须回到主线程通常通过信号槽把label传回去由主窗口重新select一次。5. 进阶优化与排错技巧让考勤系统真正能落地5.1 训练集张数和阈值的调参经验LBPH每个员工至少要20张注册图低于10张时识别率会明显下降因为光照和角度变化没有被覆盖。我倾向于采集30张覆盖正脸、左右侧头、抬头低头各若干张训练前统一做直方图均衡化。识别时也先对截取的人脸区域做一遍equalizeHist这样阈值可以设在60而不容易误报。如果某个员工总是识别失败优先删掉模糊注册图重新采集而不是继续放宽阈值因为阈值拉高会把另一个员工识别成他。5.2 打包发布时容易漏的DLL课程设计答辩经常要在演示机运行提前打包很关键。在Qt Creator里选择Release编译然后打开命令行工具执行windeployqt --release FaceAttendance.exe这条命令会自动收集Qt的platforms、styles、imageformats等插件但OpenCV和MySQL的DLL它不管。还需要手动把opencv_world452.dll从OpenCV的bin目录拷贝到exe目录再把MySQL的libmysql.dll和Qt的qsqlmysql.dll放进对应位置。qsqlmysql.dll必须放在exe目录下的sqldrivers文件夹里否则程序启动时仍会提示QMYSQL driver not loaded。5.3 低成本提分给考勤表加个复合索引人脸识别考勤系统如果只按employee_id查询当天记录数据量小看不出问题但课程设计想体现MySQL优化意识可以在考勤表上加一个复合索引ALTER TABLE attendance ADD INDEX idx_emp_date (employee_id, check_time);加了这条索引后查询当天是否签到会先从索引定位员工再按时间范围过滤避免全表扫描。代码和业务逻辑完全不用改只需要重新执行一次建索引语句。这个改动虽然简单但体现的不是“会调用OpenCV接口”而是意识到数据库结构会影响业务查询效率放在答辩演示或设计文档里都算亮点。本文还有配套的精品资源点击获取
网站建设高端定制企业官网