用Python+OpenCV+MySQL,从零搭建一个带GUI的人脸识别考勤系统(附完整源码)
·
从零构建Python人脸识别考勤系统:OpenCV+MySQL+PyQt全栈实战
考勤管理是企业和学校日常运营中不可或缺的环节,传统的人工签到或刷卡方式存在代签、漏记等问题。我曾为某培训机构开发过一套人脸识别考勤系统,上线后考勤异常率下降了83%。本文将带你从零开始,用Python+OpenCV+MySQL构建一个带GUI的完整解决方案。
1. 环境准备与项目架构
1.1 开发环境配置
推荐使用Python 3.8+环境,以下是核心依赖库:
pip install opencv-python==4.5.5.64
pip install opencv-contrib-python==4.5.5.64
pip install PyMySQL==1.0.2
pip install PyQt5==5.15.7
pip install face-recognition==1.3.0
关键组件版本说明 :
| 组件 | 版本 | 备注 |
|---|---|---|
| OpenCV | 4.5.5 | 包含contrib模块 |
| dlib | 19.24.0 | 人脸关键点检测依赖 |
| face-recognition | 1.3.0 | 封装了dlib的人脸识别功能 |
注意:dlib在Windows下的安装可能需要先安装CMake和Visual Studio Build Tools
1.2 项目目录结构
attendance_system/
├── core/ # 核心功能模块
│ ├── face_engine.py # 人脸识别引擎
│ └── db_connector.py # 数据库操作
├── ui/ # 界面相关
│ ├── main_window.py # 主界面
│ └── dialogs/ # 各种对话框
├── config/
│ └── settings.ini # 配置文件
├── resources/ # 静态资源
│ ├── faces/ # 人脸图像存储
│ └── icons/ # 程序图标
└── app.py # 程序入口
2. 人脸识别核心引擎开发
2.1 人脸检测与特征提取
import cv2
import face_recognition
import numpy as np
class FaceEngine:
def __init__(self, tolerance=0.6):
self.known_face_encodings = []
self.known_face_ids = []
self.tolerance = tolerance
def load_faces_from_db(self, db_records):
"""从数据库加载已注册人脸"""
for record in db_records:
image = face_recognition.load_image_file(record['image_path'])
encoding = face_recognition.face_encodings(image)[0]
self.known_face_encodings.append(encoding)
self.known_face_ids.append(record['user_id'])
def process_frame(self, frame):
"""处理视频帧并返回识别结果"""
rgb_frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
face_locations = face_recognition.face_locations(rgb_frame)
face_encodings = face_recognition.face_encodings(rgb_frame, face_locations)
results = []
for (top, right, bottom, left), face_encoding in zip(face_locations, face_encodings):
matches = face_recognition.compare_faces(
self.known_face_encodings, face_encoding, self.tolerance)
user_id = "Unknown"
if True in matches:
first_match_index = matches.index(True)
user_id = self.known_face_ids[first_match_index]
results.append({
"user_id": user_id,
"location": (left, top, right, bottom)
})
return results
2.2 性能优化技巧
- 多尺度检测 :在不同缩放比例下检测人脸,提高检出率
- 帧采样 :对视频流每N帧处理一次,降低CPU负载
- 异步处理 :将人脸识别任务放入独立线程,避免阻塞UI
# 示例:异步处理实现
from threading import Thread
from queue import Queue
class AsyncFaceProcessor:
def __init__(self, face_engine):
self.input_queue = Queue(maxsize=1)
self.output_queue = Queue(maxsize=1)
self.face_engine = face_engine
self.thread = Thread(target=self._process_frames)
self.thread.daemon = True
self.thread.start()
def _process_frames(self):
while True:
frame = self.input_queue.get()
results = self.face_engine.process_frame(frame)
self.output_queue.put(results)
3. MySQL数据库设计
3.1 数据表结构
CREATE TABLE `users` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`employee_id` varchar(20) NOT NULL,
`name` varchar(50) NOT NULL,
`department` varchar(50) DEFAULT NULL,
`face_image_path` varchar(255) DEFAULT NULL,
`register_time` datetime DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `employee_id` (`employee_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE `attendance_records` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`user_id` int(11) NOT NULL,
`check_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
`check_type` enum('IN','OUT') NOT NULL,
`device_id` varchar(50) DEFAULT NULL,
`image_path` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `user_id` (`user_id`),
CONSTRAINT `attendance_records_ibfk_1` FOREIGN KEY (`user_id`)
REFERENCES `users` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
3.2 Python数据库操作封装
import pymysql
from configparser import ConfigParser
class DBOperator:
def __init__(self):
config = ConfigParser()
config.read('config/settings.ini')
self.connection = pymysql.connect(
host=config.get('database', 'host'),
user=config.get('database', 'user'),
password=config.get('database', 'password'),
database=config.get('database', 'dbname'),
charset='utf8mb4',
cursorclass=pymysql.cursors.DictCursor
)
def register_user(self, user_data, image_path):
"""注册新用户并保存人脸图像"""
with self.connection.cursor() as cursor:
sql = """INSERT INTO users
(employee_id, name, department, face_image_path)
VALUES (%s, %s, %s, %s)"""
cursor.execute(sql, (
user_data['employee_id'],
user_data['name'],
user_data['department'],
image_path
))
user_id = cursor.lastrowid
self.connection.commit()
return user_id
def add_attendance_record(self, user_id, check_type, image_path=None):
"""添加考勤记录"""
with self.connection.cursor() as cursor:
sql = """INSERT INTO attendance_records
(user_id, check_type, image_path)
VALUES (%s, %s, %s)"""
cursor.execute(sql, (user_id, check_type, image_path))
self.connection.commit()
def get_user_by_employee_id(self, employee_id):
"""根据工号查询用户信息"""
with self.connection.cursor() as cursor:
sql = "SELECT * FROM users WHERE employee_id = %s"
cursor.execute(sql, (employee_id,))
return cursor.fetchone()
4. PyQt5界面开发
4.1 主界面设计
from PyQt5.QtWidgets import (
QMainWindow, QWidget, QVBoxLayout, QHBoxLayout,
QLabel, QPushButton, QListWidget, QTabWidget
)
from PyQt5.QtCore import Qt, QTimer
from PyQt5.QtGui import QImage, QPixmap
class MainWindow(QMainWindow):
def __init__(self, face_engine, db_operator):
super().__init__()
self.face_engine = face_engine
self.db_operator = db_operator
self.init_ui()
self.init_camera()
def init_ui(self):
self.setWindowTitle("人脸识别考勤系统")
self.setGeometry(100, 100, 1024, 768)
# 中央部件
central_widget = QWidget()
self.setCentralWidget(central_widget)
# 主布局
main_layout = QHBoxLayout(central_widget)
# 左侧视频区域
left_panel = QWidget()
left_layout = QVBoxLayout(left_panel)
self.video_label = QLabel()
self.video_label.setAlignment(Qt.AlignCenter)
self.video_label.setMinimumSize(640, 480)
left_layout.addWidget(self.video_label)
self.status_label = QLabel("准备就绪")
left_layout.addWidget(self.status_label)
# 右侧功能区域
right_panel = QWidget()
right_layout = QVBoxLayout(right_panel)
self.tab_widget = QTabWidget()
# 考勤记录标签页
attendance_tab = QWidget()
attendance_layout = QVBoxLayout(attendance_tab)
self.attendance_list = QListWidget()
attendance_layout.addWidget(self.attendance_list)
self.tab_widget.addTab(attendance_tab, "考勤记录")
# 用户管理标签页
user_tab = QWidget()
user_layout = QVBoxLayout(user_tab)
self.user_list = QListWidget()
user_layout.addWidget(self.user_list)
add_user_btn = QPushButton("添加用户")
add_user_btn.clicked.connect(self.show_add_user_dialog)
user_layout.addWidget(add_user_btn)
self.tab_widget.addTab(user_tab, "用户管理")
right_layout.addWidget(self.tab_widget)
# 将左右面板加入主布局
main_layout.addWidget(left_panel, 70)
main_layout.addWidget(right_panel, 30)
def init_camera(self):
self.capture = cv2.VideoCapture(0)
self.timer = QTimer(self)
self.timer.timeout.connect(self.update_frame)
self.timer.start(30) # 30ms更新一帧
def update_frame(self):
ret, frame = self.capture.read()
if ret:
# 人脸识别处理
results = self.face_engine.process_frame(frame)
# 绘制识别结果
for result in results:
left, top, right, bottom = result['location']
cv2.rectangle(frame, (left, top), (right, bottom), (0, 255, 0), 2)
cv2.putText(frame, result['user_id'],
(left + 6, bottom - 6),
cv2.FONT_HERSHEY_SIMPLEX,
0.5, (255, 255, 255), 1)
# 显示图像
rgb_image = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
h, w, ch = rgb_image.shape
bytes_per_line = ch * w
qt_image = QImage(rgb_image.data, w, h, bytes_per_line, QImage.Format_RGB888)
self.video_label.setPixmap(QPixmap.fromImage(qt_image))
4.2 用户注册对话框
from PyQt5.QtWidgets import (
QDialog, QFormLayout, QLineEdit, QDialogButtonBox,
QFileDialog, QLabel
)
class AddUserDialog(QDialog):
def __init__(self, parent=None):
super().__init__(parent)
self.setWindowTitle("添加用户")
self.setFixedSize(400, 300)
layout = QFormLayout(self)
self.employee_id_edit = QLineEdit()
self.name_edit = QLineEdit()
self.department_edit = QLineEdit()
self.face_image_label = QLabel("未选择图片")
self.browse_button = QPushButton("浏览...")
self.browse_button.clicked.connect(self.select_image)
button_box = QDialogButtonBox(
QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
button_box.accepted.connect(self.accept)
button_box.rejected.connect(self.reject)
layout.addRow("工号:", self.employee_id_edit)
layout.addRow("姓名:", self.name_edit)
layout.addRow("部门:", self.department_edit)
layout.addRow("人脸照片:", self.face_image_label)
layout.addRow(self.browse_button)
layout.addRow(button_box)
self.image_path = None
def select_image(self):
file_name, _ = QFileDialog.getOpenFileName(
self, "选择人脸照片", "", "Images (*.png *.jpg *.jpeg)")
if file_name:
self.image_path = file_name
self.face_image_label.setText(file_name.split('/')[-1])
def get_user_data(self):
return {
'employee_id': self.employee_id_edit.text(),
'name': self.name_edit.text(),
'department': self.department_edit.text(),
'image_path': self.image_path
}
5. 系统集成与部署
5.1 主程序入口
import sys
from PyQt5.QtWidgets import QApplication
from core.face_engine import FaceEngine
from core.db_connector import DBOperator
from ui.main_window import MainWindow
def main():
# 初始化各组件
db_operator = DBOperator()
face_engine = FaceEngine()
# 从数据库加载已注册人脸
users = db_operator.get_all_users()
face_engine.load_faces_from_db(users)
# 创建应用
app = QApplication(sys.argv)
window = MainWindow(face_engine, db_operator)
window.show()
sys.exit(app.exec_())
if __name__ == "__main__":
main()
5.2 打包为可执行文件
使用PyInstaller打包:
pyinstaller --onefile --windowed --icon=resources/icons/app.ico \
--add-data "config/settings.ini;config" \
--add-data "resources;resources" \
app.py
打包注意事项 :
- 确保所有资源文件路径使用相对路径
- 数据库配置文件需要随程序一起打包
- 人脸图像存储目录需要有写入权限
6. 实际应用中的优化建议
-
光照补偿 :在低光照环境下,使用直方图均衡化提升识别率
def enhance_contrast(image): gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8,8)) return clahe.apply(gray) -
活体检测 :防止照片攻击
- 眨眼检测
- 头部姿态估计
- 纹理分析
-
考勤规则配置 :
- 迟到/早退时间阈值
- 异常考勤提醒
- 考勤统计报表
-
性能监控指标 :
| 指标 | 正常范围 | 监控方法 |
|---|---|---|
| 识别准确率 | >95% | 定期测试集验证 |
| 单帧处理时间 | <200ms | 性能分析工具 |
| 内存占用 | <500MB | 系统监控 |
在实际部署中,我发现人脸注册环节的质量控制至关重要。建议采集多角度、不同光照条件下的人脸图像,注册时进行质量评估,确保特征提取的准确性。系统运行稳定后,可以考虑加入考勤异常自动预警功能,当检测到连续多次识别失败或异常考勤记录时,自动发送通知给管理员。
更多推荐


所有评论(0)