☰
基于Qt与OpenCV的人脸识别考勤系统毕业设计全流程实践
2026/10/19 1:28:21 网站建设 项目流程

简介:这是一套面向计算机、人工智能及相关专业学生与教师的毕业设计级人脸识别考勤系统实现方案,基于Qt框架与OpenCV视觉库开发,完整解决人脸检测、特征提取、比对识别与考勤记录等核心业务流程,适用于课程设计、大作业及毕设实战。资源包共134个文件,包含42个头文件(.h)定义模块接口、41个SVG图标资源支撑UI美化、11个C++源文件(.cpp)实现核心逻辑(如usersmanage.cpp、mtcnn.cpp)、5个Qt界面文件(.ui)及4份PDF文档说明,整体压缩后仅16.54MB,结构清晰、模块解耦度高。已有693人学习下载,项目经答辩评审获98分,所有代码均通过实机调试验证可直接运行。用户可获得完整可执行工程、多模型支持(含arcface.bin、Retinaface_mobilenet-sim.bin等)、用户管理与考勤日志功能模块,以及从环境配置到部署测试的全流程技术支撑,具备扎实的学习迁移与二次开发基础。

1. 项目概述与核心价值

最近几年,无论是企业门禁还是校园签到,人脸识别技术已经从“黑科技”变成了我们身边触手可及的日常应用。对于计算机、软件工程相关专业的同学来说,如何将课堂上学到的理论,比如图像处理、C++编程、数据库操作,整合成一个看得见、摸得着的完整项目,是毕业设计环节最大的挑战,也是最能锻炼综合能力的机会。

“基于Qt+OpenCV实现的人脸识别考勤系统”这个题目,就是一个非常经典且实用的毕业设计选题。它麻雀虽小,五脏俱全,完美串联了图形界面开发(Qt)、计算机视觉核心库(OpenCV)、数据库管理以及软件工程流程。你不仅是在写代码,更是在经历一个从需求分析、技术选型、编码实现到测试部署的完整软件生命周期。最终产出的不仅仅是一份论文和源码,更是一个可以实际演示、功能完备的桌面应用程序,这在答辩时无疑是一个巨大的加分项。

这个系统的核心逻辑很清晰:通过摄像头捕捉员工或学生的面部图像,利用OpenCV进行人脸检测与识别,将识别结果(姓名、时间)记录到数据库中,并最终通过Qt开发的友好界面进行人员管理、考勤记录查询和报表生成。下面,我就结合自己带学生做类似项目的经验,把这个系统的里里外外、从设计思路到代码细节,以及那些容易踩坑的地方,给大家掰开揉碎了讲清楚。

2. 系统整体设计与技术选型考量

做一个项目,最忌讳的就是拿到题目直接开敲代码。前期花在设计和思考上的时间,后期能帮你省下数倍的调试和返工时间。这个考勤系统,我们需要从顶层思考几个关键问题。

2.1 为什么选择Qt+OpenCV这个技术栈?

这是整个项目的基石,选型理由必须充分,这往往也是论文绪论和系统设计章节需要重点阐述的内容。

  • Qt负责“面子”(前端界面与业务逻辑):

    • 跨平台性:毕业设计通常在Windows下开发,但你的代码应该具备良好的可移植性。Qt“一次编写,到处编译”的特性,让你未来如果想在Linux或macOS上运行,几乎无需修改界面代码。这对于体现项目的工程价值很有帮助。
    • 丰富的UI组件与信号槽机制:考勤系统需要登录窗口、主界面、人员信息管理表格、考勤记录查询、报表显示等。Qt Designer可以让你通过拖拽快速搭建原型,而信号槽机制(一种对象间通信方式)让界面交互(如点击按钮)和后端逻辑(如开始识别)的解耦变得异常优雅和清晰。相比于用MFC或纯Win32 API,开发效率提升不止一个量级。
    • 内置功能强大:Qt不仅提供UI,它的核心模块还涵盖了网络、数据库(Qt SQL)、多媒体(访问摄像头)、文件操作等,几乎涵盖了本项目所有非视觉计算的需求,避免了引入过多第三方库的复杂性。
  • OpenCV负责“里子”(核心视觉算法):

    • 行业标准库:在计算机视觉领域,OpenCV是事实上的标准。它提供了从最基本的图像读写、灰度化、滤波,到高级的人脸检测、特征提取等海量函数。使用它,意味着你的项目站在了巨人的肩膀上,稳定性和性能都有保障。
    • 人脸识别全流程支持:OpenCV从3.x版本开始,在其face模块中集成了多种人脸识别算法,如Eigenfaces, Fisherfaces, LBPH。特别是LBPH(局部二值模式直方图),对光照变化有一定鲁棒性,且原理相对简单,非常适合作为毕业设计算法的讲解重点。你无需从零实现复杂的深度学习模型,就能完成一个可用的识别系统。
    • 活跃的社区与资料:遇到任何关于图像处理的问题,几乎都能在OpenCV的文档和社区中找到答案或灵感,极大降低了开发和学习门槛。
  • 数据库选型(SQLite vs MySQL):

    • SQLite(推荐用于毕业设计):这是一个轻量级的、无服务器的、零配置的数据库引擎。整个数据库就是一个.db文件,可以随你的应用程序一起分发。对于单机版考勤系统,存储几百上千人的信息和考勤记录完全足够。它简化了部署,你不需要在答辩电脑上安装和配置MySQL服务,避免了临场环境问题。
    • MySQL:如果你的系统设计为C/S(客户端/服务器)架构,或者想体现对网络数据库的操作能力,可以选择MySQL。但这会引入数据库安装、用户权限管理、网络连接等额外复杂度,除非课题要求,否则对于以演示和功能完成为主的本科毕业设计,SQLite是更稳妥高效的选择。

2.2 系统核心功能模块拆解

在动手画界面之前,先用思维导图或文字把系统模块理清楚。一个基本的考勤系统应包含以下核心模块:

  1. 用户登录模块:验证管理员身份,不同角色(如管理员、普通查询员)可有不同权限。这里可以简单实现,重点不在安全,而在模块化思想。
  2. 人员信息管理模块:这是系统的“地基”。功能包括:
    • 添加新员工/学生:录入姓名、工号/学号、部门/班级等。
    • 人脸信息注册:调用摄像头拍摄多张(如10-20张)该人员的人脸照片,作为训练集的素材。这是后续识别准确率的关键。
    • 信息查询、修改、删除。
  3. 人脸考勤模块:系统的“心脏”。功能包括:
    • 实时视频流显示。
    • 人脸检测与跟踪:从视频流中框出人脸。
    • 人脸识别:将检测到的人脸特征与数据库中已注册的人脸特征进行比对,找出最匹配的人员。
    • 考勤记录:识别成功后,自动将人员ID、姓名、考勤时间(打卡时间)、类型(上班/下班)写入数据库。
  4. 考勤记录查询与统计模块:系统的“输出”。功能包括:
    • 按人员、日期范围查询详细的打卡记录。
    • 生成日报、月报统计,如出勤天数、迟到早退情况(需结合预设的上下班时间规则)。
    • 将统计结果以表格形式展示,并支持导出为Excel或CSV文件(可使用Qt的QAxObject操作Excel,或直接生成CSV)。
  5. 系统设置模块:包括摄像头选择、识别置信度阈值调整、考勤规则(上班时间)设置等。

理清了模块,你的Qt工程目录结构也就有了雏形:可以按模块分包,或者按MVC(模型-视图-控制器)的思想来组织代码,这在论文中也是很好的亮点。

3. 开发环境搭建与项目初始化

“工欲善其事,必先利其器”。一个稳定、一致的开发环境是项目顺利推进的前提。很多同学卡在第一步,就是因为环境没配好。

3.1 环境配置清单与版本选择

我强烈建议使用以下组合,这是经过大量项目验证的稳定搭配:

  • 操作系统:Windows 10/11 64位。确保系统更新,避免奇怪的系统级bug。
  • Qt:Qt 5.15.2 (LTS)。这是Qt5最后一个长期支持版本,非常稳定。不推荐初学者直接上Qt6,因为一些旧的第三方库(如某些OpenCV版本)兼容性可能有问题。安装时,勾选MSVC 2019 64-bit组件和Qt Creator。
  • 编译器:Visual Studio 2019社区版。安装时记得勾选“使用C++的桌面开发”。Qt的MSVC套件需要VS的工具链。
  • OpenCV:OpenCV 4.5.5或4.6.0。版本适中,既包含了成熟的人脸识别模块,又不会太老。一定要下载预编译好的Windows版本(通常是一个.exe自解压包),比如从OpenCV官网或GitHub Releases下载opencv-4.5.5-vc14_vc15.exe这样的文件。这省去了自己用CMake编译的麻烦。
  • 数据库:SQLite。Qt已内置支持,无需单独安装。
  • 集成开发环境(IDE):Qt Creator。它与Qt框架无缝集成,对Qt特有的语法(如信号槽)有很好的支持,管理项目非常方便。

注意:版本号尽量精确匹配我推荐的。网络上教程众多,但版本差异可能导致配置命令、CMake参数甚至API都发生变化,统一版本能避免绝大多数环境问题。

3.2 OpenCV在Qt项目中的配置详解

这是第一个技术难点。配置的本质是告诉编译器和链接器:1)头文件在哪里;2)库文件在哪里。我们以Qt Creator中配置*.pro文件为例。

  1. 解压OpenCV:将下载的opencv-4.5.5.exe解压到一个没有中文和空格的路径,例如D:\Dev\opencv。解压后你会看到build和sources文件夹,我们只需要build里的东西。

  2. 环境变量:将OpenCV的bin目录(如D:\Dev\opencv\build\x64\vc15\bin)添加到系统的Path环境变量中。这一步至关重要,它让程序运行时能找到OpenCV的DLL文件。

  3. 在Qt项目的.pro文件中添加配置: 打开你的Qt项目文件(.pro),添加以下内容。请根据你的实际路径修改INCLUDEPATH和LIBS。

    # 指定OpenCV头文件路径 win32: { INCLUDEPATH += D:/Dev/opencv/build/include # 如果你用到OpenCV的contrib模块(如更高级的人脸识别),还需要包含`include/opencv2`和`include/opencv` # INCLUDEPATH += D:/Dev/opencv/build/include/opencv2 # INCLUDEPATH += D:/Dev/opencv/build/include/opencv } # 指定OpenCV库文件路径和需要链接的库 win32: { # 库文件路径 LIBS += -LD:/Dev/opencv/build/x64/vc15/lib # 链接具体的库文件(注意区分debug和release版本) CONFIG(release, debug|release): { LIBS += -lopencv_world455 } else:CONFIG(debug, debug|release): { LIBS += -lopencv_world455d # debug版本库带‘d’后缀 } }
    • INCLUDEPATH:告诉编译器去哪里找#include <opencv2/opencv.hpp>这样的头文件。
    • LIBS:-L指定库文件目录,-l指定要链接的库名。opencv_world455是OpenCV 4.5.5将大多数模块打包成一个的“world”库,方便链接。数字455代表版本4.5.5。
  4. 验证配置:在代码中写入最简单的OpenCV代码并运行。

    #include <opencv2/opencv.hpp> #include <QDebug> void testOpencv() { qDebug() << "OpenCV version:" << CV_VERSION; // 尝试读取一张图片(确保路径正确) cv::Mat img = cv::imread("D:/test.jpg"); if(img.empty()) { qDebug() << "Image not found!"; } else { qDebug() << "Image loaded successfully, size:" << img.cols << "x" << img.rows; } }

    如果能正确输出OpenCV版本和图像信息,恭喜你,环境配置成功!

实操心得:很多同学配置失败,是因为debug和release模式链接的库搞混了。在Qt Creator左下角有构建套件选择,你编译debug版本时,必须链接带d后缀的库(如opencv_world455d.lib),否则运行时会出现“找不到入口点”等错误。务必检查.pro文件中的CONFIG条件判断是否正确。

4. 核心模块实现细节与代码剖析

环境搭好,我们就可以进入核心编码阶段了。我会挑几个最具代表性、也最容易出问题的模块,结合代码和原理详细讲解。

4.1 人脸信息注册模块:构建高质量训练集

这个模块的目标是为每个员工采集多张人脸图像,并保存到本地文件夹和数据库中。识别算法的性能,七分靠训练数据。

实现步骤与要点:

  1. 数据库设计: 首先,在SQLite中创建一张employee表。

    CREATE TABLE employee ( id INTEGER PRIMARY KEY AUTOINCREMENT, -- 自增主键 employee_id VARCHAR(20) UNIQUE NOT NULL, -- 工号,唯一 name VARCHAR(50) NOT NULL, -- 姓名 department VARCHAR(50), -- 部门 face_data_path TEXT NOT NULL, -- 人脸图片存储的目录路径 register_time DATETIME DEFAULT (datetime('now', 'localtime')) -- 注册时间 );

    face_data_path字段存储该员工人脸图片集的文件夹路径,例如./face_data/1001_张三/。这样设计避免了将图片以BLOB形式存入数据库,便于管理和备份。

  2. Qt界面设计: 使用Qt Designer创建一个对话框,包含QLineEdit(输入工号、姓名)、QComboBox(选择部门)、一个QLabel(用于显示摄像头视频)以及“开始注册”、“拍摄”、“保存”等按钮。

  3. 视频捕获与显示: Qt提供了QCamera和QVideoWidget,但和OpenCV的VideoCapture配合有时比较麻烦。一个更直接的方法是使用OpenCV的VideoCapture捕获帧,然后通过定时器(QTimer)不断将cv::Mat转换为Qt的QImage,再显示在QLabel上。

    // 在头文件中声明 #include <opencv2/opencv.hpp> #include <QTimer> #include <QLabel> class RegisterDialog : public QDialog { Q_OBJECT public: RegisterDialog(QWidget *parent = nullptr); ~RegisterDialog(); private slots: void updateFrame(); // 定时器槽函数,用于更新画面 void captureImage(); // 拍摄一张照片 private: cv::VideoCapture capture; QTimer *timer; QLabel *videoLabel; std::vector<cv::Mat> capturedImages; // 存储已拍摄的图片 QString currentSavePath; // 当前员工图片保存路径 // ... 其他控件 }; // 在.cpp文件中 void RegisterDialog::updateFrame() { cv::Mat frame; if (capture.read(frame)) { // 将BGR格式的OpenCV Mat转换为RGB格式的QImage cv::cvtColor(frame, frame, cv::COLOR_BGR2RGB); QImage img(frame.data, frame.cols, frame.rows, frame.step, QImage::Format_RGB888); // 缩放以适应QLabel,保持比例 QPixmap pix = QPixmap::fromImage(img).scaled(videoLabel->size(), Qt::KeepAspectRatio, Qt::SmoothTransformation); videoLabel->setPixmap(pix); } } void RegisterDialog::captureImage() { cv::Mat frame; if (capture.read(frame)) { capturedImages.push_back(frame.clone()); // 保存副本 // 可以立即显示一个缩略图在界面上,提示用户已拍摄X张 // 同时,建议保存到临时文件夹或内存,最后统一写入磁盘 QString tempPath = currentSavePath + QString("/temp_%1.jpg").arg(capturedImages.size()); cv::imwrite(tempPath.toStdString(), frame); qDebug() << "Captured image:" << tempPath; } }
  4. 图像预处理与保存: 直接保存原始图像效果往往不好。在保存前或训练前,应该进行预处理:

    • 灰度化:cv::cvtColor(frame, grayFrame, cv::COLOR_BGR2GRAY);
    • 人脸检测:只保存检测到的人脸区域,去除背景干扰。使用OpenCV的CascadeClassifier加载人脸检测器(haarcascade_frontalface_default.xml)。
    cv::CascadeClassifier faceCascade; faceCascade.load("D:/path/to/haarcascade_frontalface_default.xml"); // 需要提供这个XML文件路径 std::vector<cv::Rect> faces; faceCascade.detectMultiScale(grayFrame, faces, 1.1, 3, 0, cv::Size(30, 30)); if(faces.size() == 1) { // 确保画面中只有一张人脸 cv::Mat faceROI = grayFrame(faces[0]); // 截取人脸区域 // 统一缩放到固定大小,例如 100x100,这对后续训练很重要 cv::resize(faceROI, faceROI, cv::Size(100, 100)); capturedFaceImages.push_back(faceROI); // 保存预处理后的人脸 } else { // 提示用户“未检测到人脸”或“检测到多张人脸” }
    • 保存:为每个员工创建一个文件夹(以工号_姓名命名),将预处理后的faceROI图像依次保存进去,命名如1.jpg,2.jpg...。同时,将该员工的文本信息和文件夹路径插入employee表。

注意事项:

  1. 光照与角度:指导用户在注册时,应在正常办公光照环境下,拍摄包含轻微角度变化(正脸、略微左转、右转)和表情变化(微笑、严肃)的照片,10-20张为宜。可以提示用户“请缓慢左右转动头部”。
  2. 背景:尽量保持背景简单、一致。
  3. 实时反馈:在界面上清晰显示已成功拍摄的张数,并实时显示人脸检测框,让用户知道系统是否“看见”了他。
  4. 文件管理:人脸图片建议保存在项目目录外的独立位置(如D:/FaceAttendanceData),并在数据库中用绝对路径或相对于可执行文件的路径存储。这样即使程序移动,数据也不会丢失。

4.2 人脸识别考勤模块:从检测到记录的完整流程

这是系统的核心,逻辑链条较长。我们将其分解为几个子步骤。

4.2.1 人脸检测器与识别器的初始化

在程序启动或进入考勤界面时,就需要加载模型。

#include <opencv2/face.hpp> class AttendanceWidget : public QWidget { // ... private: cv::Ptr<cv::face::LBPHFaceRecognizer> recognizer; cv::CascadeClassifier faceCascade; std::vector<int> labelIds; // 存储标签(对应员工ID) std::vector<QString> labelNames; // 存储标签对应的姓名 // ... bool loadRecognizerModel(const QString& modelPath); bool loadFaceCascade(const QString& cascadePath); }; bool AttendanceWidget::loadRecognizerModel(const QString& modelPath) { try { recognizer = cv::face::LBPHFaceRecognizer::create(); recognizer->read(modelPath.toStdString()); // 读取训练好的模型文件 // 通常,训练时会把标签(员工ID)和模型一起保存。 // 我们需要从数据库或另一个文件中加载标签与姓名的映射关系。 QSqlQuery query; query.exec("SELECT id, employee_id, name FROM employee ORDER BY id"); labelIds.clear(); labelNames.clear(); while (query.next()) { labelIds.push_back(query.value(0).toInt()); // 数据库中的主键id作为标签 labelNames.push_back(query.value(2).toString()); // 姓名 } return true; } catch (cv::Exception& e) { qDebug() << "Error loading recognizer model:" << e.what(); return false; } } bool AttendanceWidget::loadFaceCascade(const QString& cascadePath) { return faceCascade.load(cascadePath.toStdString()); }

4.2.2 实时视频流处理与识别循环

和注册模块类似,使用QTimer驱动一个循环,在每一帧中执行以下操作:

  1. 捕获帧并灰度化。
  2. 人脸检测:使用faceCascade.detectMultiScale获取人脸矩形区域。
  3. 人脸识别(关键步骤):
    for (const cv::Rect& faceRect : faces) { cv::Mat faceROI = grayFrame(faceRect); cv::resize(faceROI, faceROI, cv::Size(100, 100)); // 缩放到与训练时相同的大小 // 可选:直方图均衡化,增强对比度 // cv::equalizeHist(faceROI, faceROI); int predictedLabel = -1; double confidence = 0.0; // 调用识别器进行预测 recognizer->predict(faceROI, predictedLabel, confidence); // 绘制矩形框和文字 cv::rectangle(colorFrame, faceRect, cv::Scalar(0, 255, 0), 2); QString info; if (predictedLabel != -1 && confidence < THRESHOLD) { // THRESHOLD是一个置信度阈值,例如70 // 识别成功,根据predictedLabel找到对应姓名 int index = labelIds.indexOf(predictedLabel); // 假设labelIds是QVector或已建立映射 if (index != -1) { QString name = labelNames.at(index); info = QString("%1 (%.1f)").arg(name).arg(confidence); // 触发考勤记录逻辑(防重复打卡) recordAttendance(predictedLabel, name); } } else { info = QString("Unknown (%.1f)").arg(confidence); } // 将QString info绘制到图像上(需要先将QString转为std::string) cv::putText(colorFrame, info.toStdString(), cv::Point(faceRect.x, faceRect.y - 10), cv::FONT_HERSHEY_SIMPLEX, 0.8, cv::Scalar(0, 0, 255), 2); }
    • predict函数返回两个值:predictedLabel(预测的标签,即我们训练时传入的员工ID)和confidence(置信度,距离值,越低表示越相似)。
    • 阈值(THRESHOLD)是关键:这个值需要根据你的训练数据和实际测试来调整。如果设置过高,容易误识别;过低,则认识的人也可能被认成“Unknown”。建议在系统设置模块中做成可调节的滑块。

4.2.3 考勤记录逻辑与防重复设计

在recordAttendance函数中,不能简单识别一次就记录一次,否则视频流会在一秒钟内记录数十次打卡。

void AttendanceWidget::recordAttendance(int employeeId, const QString& name) { QDateTime currentTime = QDateTime::currentDateTime(); // 防重复判断:检查该员工在最近N分钟内是否已有打卡记录 QSqlQuery query; query.prepare("SELECT COUNT(*) FROM attendance_record WHERE employee_id = ? AND datetime(check_time) > datetime(?, '-5 minutes')"); query.addBindValue(employeeId); query.addBindValue(currentTime.toString("yyyy-MM-dd hh:mm:ss")); if (query.exec() && query.next()) { if (query.value(0).toInt() > 0) { // 5分钟内已打卡,忽略此次识别 qDebug() << name << "has checked in within 5 minutes, ignored."; return; } } // 判断是上班打卡还是下班打卡(简化逻辑:根据当前时间与预设上班时间比较) QString type = "上班"; QTime nowTime = currentTime.time(); QTime workStartTime = QTime(9, 0); // 假设9点上班 if (nowTime > workStartTime.addSecs(4*3600)) { // 假设上班后4小时算下班 type = "下班"; } // 插入打卡记录 query.prepare("INSERT INTO attendance_record (employee_id, name, check_time, type) VALUES (?, ?, ?, ?)"); query.addBindValue(employeeId); query.addBindValue(name); query.addBindValue(currentTime.toString("yyyy-MM-dd hh:mm:ss")); query.addBindValue(type); if (query.exec()) { qDebug() << "Attendance recorded for" << name << "at" << currentTime.toString(); // 可以播放一个提示音或更新界面状态,给予用户反馈 QApplication::beep(); } else { qDebug() << "Failed to record attendance:" << query.lastError().text(); } }

这里设计了一个简单的防重复机制(5分钟内不重复记录)和上下班类型判断。更复杂的规则可以放在系统设置中配置。

4.3 模型训练模块:让系统“认识”员工

当有新员工注册或需要更新模型时,需要调用训练模块。训练通常在后台线程进行,避免界面卡死。

  1. 准备训练数据:遍历employee表,读取每个员工对应文件夹下所有的人脸图片,并为其分配一个唯一的整数标签(通常就用数据库中的主键id)。

    std::vector<cv::Mat> images; std::vector<int> labels; QSqlQuery query("SELECT id, face_data_path FROM employee"); while (query.next()) { int label = query.value(0).toInt(); QString path = query.value(1).toString(); QDir dir(path); QStringList filters; filters << "*.jpg" << "*.png"; dir.setNameFilters(filters); for (QString file : dir.entryList()) { QString fullPath = dir.absoluteFilePath(file); cv::Mat img = cv::imread(fullPath.toStdString(), cv::IMREAD_GRAYSCALE); // 以灰度图读取 if (!img.empty()) { cv::resize(img, img, cv::Size(100, 100)); // 统一尺寸 images.push_back(img); labels.push_back(label); } } }
  2. 训练模型:

    if (images.empty()) { qDebug() << "No training data found!"; return false; } cv::Ptr<cv::face::LBPHFaceRecognizer> model = cv::face::LBPHFaceRecognizer::create(); // 可以设置LBPH的参数,如半径、邻域点数等,一般用默认值即可 // model->setRadius(1); // model->setNeighbors(8); model->train(images, labels); model->save("face_model.yml"); // 保存模型到文件 qDebug() << "Model trained and saved with" << images.size() << "samples.";
  3. 更新运行中的识别器:训练完成后,需要通知主界面的识别模块重新加载新的face_model.yml文件。

实操心得:训练过程可能比较耗时,尤其是图片多的时候。务必使用Qt的并发框架(如QtConcurrent)或QThread在后台线程进行训练,并在界面上显示一个进度条或“训练中...”的提示,否则界面会完全卡住,用户体验极差。

5. 数据库设计与Qt SQL操作实践

数据库是系统的记忆中枢。良好的设计能简化编程逻辑。

5.1 表结构设计

除了前面提到的employee表,我们还需要一张attendance_record表来存储考勤记录。

CREATE TABLE attendance_record ( id INTEGER PRIMARY KEY AUTOINCREMENT, employee_id INTEGER NOT NULL, -- 关联employee.id name VARCHAR(50) NOT NULL, -- 冗余存储姓名,避免每次查询都联表 check_time DATETIME NOT NULL, -- 打卡时间 type VARCHAR(10), -- '上班' / '下班' -- 可以添加更多字段,如打卡设备位置(IP)、备注等 FOREIGN KEY (employee_id) REFERENCES employee(id) ON DELETE CASCADE );
  • 冗余字段:在attendance_record中存储了name,这是一种“以空间换时间”的优化。查询考勤记录时,绝大多数情况不需要关联employee表,直接显示name即可,提高了查询效率。这在读多写少的考勤查询场景中是合理的。
  • 外键约束:FOREIGN KEY确保了数据的参照完整性。当employee表中的某个员工被删除时,ON DELETE CASCADE会自动删除其所有的考勤记录。你也可以设置为ON DELETE SET NULL。

5.2 使用Qt SQL模块进行增删改查

Qt提供了QSqlDatabase,QSqlQuery,QSqlTableModel等类来操作数据库。

  1. 连接数据库(通常在程序启动时):

    bool initDatabase() { QSqlDatabase db = QSqlDatabase::addDatabase("QSQLITE"); db.setDatabaseName("./attendance.db"); // 数据库文件路径 if (!db.open()) { qDebug() << "Failed to open database:" << db.lastError().text(); return false; } // 创建表(如果不存在) QSqlQuery query; QStringList tableCreationSQLs; // 存放建表语句 tableCreationSQLs << "CREATE TABLE IF NOT EXISTS employee (...);"; tableCreationSQLs << "CREATE TABLE IF NOT EXISTS attendance_record (...);"; for (const QString& sql : tableCreationSQLs) { if (!query.exec(sql)) { qDebug() << "Create table failed:" << query.lastError().text(); } } return true; }
  2. 使用QSqlTableModel绑定UI表格: 对于人员管理、考勤记录查询这种需要表格展示的界面,使用QSqlTableModel或QSqlQueryModel配合QTableView是最高效的方式。

    // 在人员管理界面的类中 QSqlTableModel *model; QTableView *tableView; model = new QSqlTableModel(this); model->setTable("employee"); model->setEditStrategy(QSqlTableModel::OnManualSubmit); // 手动提交修改 model->select(); // 加载数据 tableView->setModel(model); tableView->setSelectionBehavior(QAbstractItemView::SelectRows); tableView->resizeColumnsToContents();

    这样,表格就会自动显示employee表中的所有数据。你只需要为“添加”、“删除”、“保存”按钮编写对应的槽函数,操作model对象即可,无需编写复杂的SQL语句。

注意事项:

  1. SQL注入:使用QSqlQuery::prepare()和addBindValue()来构造参数化查询,永远不要用字符串拼接的方式来构建SQL语句。
    // 错误做法(危险!) QString sql = "INSERT INTO employee (name) VALUES ('" + name + "')"; // 正确做法 QSqlQuery query; query.prepare("INSERT INTO employee (name, employee_id) VALUES (?, ?)"); query.addBindValue(name); query.addBindValue(empId); query.exec();
  2. 事务:当进行批量操作(如导入一批员工数据)时,使用事务可以大幅提升性能并保证原子性。
    QSqlDatabase::database().transaction(); // 执行多条INSERT语句... if (allSuccess) { QSqlDatabase::database().commit(); } else { QSqlDatabase::database().rollback(); }
  3. 数据库连接管理:整个应用使用一个全局数据库连接即可。在main函数或一个全局管理类中初始化并保持连接打开,直到程序退出。

6. 界面美化与用户体验优化

功能实现是基础,好用的界面则是锦上添花。Qt的样式表(QSS)是美化界面的利器。

  • 统一风格:为整个应用设置一个基础的QSS。

    /* 全局字体和背景 */ QWidget { font-family: "Microsoft YaHei"; font-size: 10pt; background-color: #f5f5f5; } /* 按钮样式 */ QPushButton { background-color: #4a86e8; color: white; border: none; padding: 8px 15px; border-radius: 4px; } QPushButton:hover { background-color: #3a76d8; } QPushButton:pressed { background-color: #2a66c8; } /* 表格样式 */ QTableView { alternate-background-color: #f0f0f0; selection-background-color: #cce5ff; gridline-color: #ddd; } QHeaderView::section { background-color: #e0e0e0; padding: 5px; border: 1px solid #ccc; }

    在main.cpp或主窗口构造函数中加载这个QSS文件。

  • 布局管理:熟练使用Qt的布局管理器(QHBoxLayout,QVBoxLayout,QGridLayout)。不要使用固定坐标(setGeometry),这会让界面在不同分辨率或缩放比例下变得混乱。

  • 多线程与界面响应:所有耗时的操作,如训练模型、导出大量考勤数据到Excel,都必须放在工作线程中。使用QtConcurrent::run或继承QThread。在工作线程中,绝对不能直接操作UI控件。需要通过信号槽机制,将进度、状态、结果发送给主线程(UI线程)来更新界面。

    // 在工作线程中 void TrainingThread::run() { // ... 训练过程 for (int i = 0; i < totalSteps; ++i) { // 训练一步 // ... // 发射进度信号 emit progressUpdated(i+1, totalSteps); if (QThread::currentThread()->isInterruptionRequested()) { // 处理线程中断 break; } } emit trainingFinished(success); } // 在主窗口类中连接信号槽 connect(trainingThread, &TrainingThread::progressUpdated, this, &MainWindow::onTrainingProgress); connect(trainingThread, &TrainingThread::trainingFinished, this, &MainWindow::onTrainingFinished);

7. 项目部署与答辩准备

代码写完只是第一步,如何打包成一个完整的、可以在其他电脑上运行的软件,是毕业设计的最后一道坎。

  1. 发布构建(Release Build):在Qt Creator中,将构建模式切换到Release,然后重新编译。这会在release目录下生成一个优化过的、不带调试信息的可执行文件(.exe)。

  2. 依赖库收集:你的exe文件不能单独运行,需要一堆DLL。使用windeployqt工具可以自动收集Qt相关的DLL。

    • 打开Qt 5.15.2的命令行(开始菜单 -> Qt 5.15.2 -> 5.15.2 MSVC2019 64-bit)。
    • 切换到你的release目录:cd /d D:\YourProject\build-release
    • 运行:windeployqt YourApp.exe这会把Qt Core, GUI, Widgets, SQL等必要的DLL和插件文件夹都拷贝过来。
  3. 手动添加OpenCV DLL:windeployqt不会处理OpenCV。你需要手动将OpenCV的bin目录(如D:\Dev\opencv\build\x64\vc15\bin)下的opencv_world455.dll(和可能的opencv_videoio_ffmpeg455_64.dll等)复制到你的release目录。

  4. 添加其他资源:将人脸检测的XML文件(haarcascade_frontalface_default.xml)、训练好的模型文件(face_model.yml)、数据库文件(attendance.db)以及示例人脸数据文件夹,都放到release目录下合适的位置。注意在代码中引用这些文件时,要使用相对路径(如"./model/haarcascade_frontalface_default.xml"),或者让程序在启动时自动在当前目录下查找。

  5. 测试:将整个release文件夹复制到一台没有安装Qt和OpenCV的干净Windows电脑上,运行exe,看是否能正常启动、操作。

答辩准备要点:

  • 演示文稿(PPT):不要大段贴代码。重点讲:1)项目背景与意义;2)系统架构图(展示Qt+OpenCV+SQLite的分层);3)核心功能演示(录屏或现场操作);4)关键技术与难点(如人脸识别流程、防重复打卡、多线程);5)成果展示(系统界面、数据库截图)。
  • 现场演示:确保你的笔记本摄像头工作正常。提前注册好2-3个同学的人脸信息。演示时,流畅地走一遍“管理员登录->添加人员->人脸注册->开始考勤->查询报表”的完整流程。遇到识别不出的情况,可以解释是光线或阈值设置的原因,并展示如何调整。
  • 代码讲解:老师可能会随机点开一两个源文件。确保你的代码有清晰的注释,关键函数(如人脸识别、数据库操作)要有说明。能讲清楚核心函数(如predict,detectMultiScale)的作用和参数含义。
  • 问答准备:提前思考可能的问题:
    • LBPH算法的原理是什么?(局部二值模式,计算纹理特征直方图)
    • 你的系统和市面上的人脸考勤机比有什么优缺点?(优点:成本低、可定制;缺点:受环境光影响大、安全性不如专业设备)
    • 如何提高识别率?(增加训练样本多样性、调整阈值、尝试其他算法如Eigenfaces)
    • 数据库设计为什么要这样?(解释范式、冗余字段的取舍、外键作用)

这个项目做下来,你收获的将不仅仅是一个毕业设计。你完整实践了桌面应用开发、计算机视觉算法集成、数据库设计、多线程编程和软件打包部署的全流程。无论未来是继续深造还是走向工作岗位,这段经历都会是你简历上扎实的一笔。最重要的是,你拥有了将一个想法,通过代码,一步步变成现实产品的宝贵能力。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询