news 2026/10/19 17:49:31

基于人脸识别与MySQL的考勤管理系统设计与实现

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
基于人脸识别与MySQL的考勤管理系统设计与实现

人脸考勤系统,这类项目在网上是真不少见,但我接手过的几个相似项目里,大多都停在“能跑通 Demo”的阶段:摄像头一开,识别出来了,然后往屏幕上一丢,就完事了。真要做成一套可用、数据还能闭环的考勤系统,难点根本不在人脸识别模型本身,而是你把人脸特征存哪儿、怎么让 MySQL 承载识别结果、考勤规则怎么落到数据上。这篇文章,我就以“基于人脸识别和 MySQL 的考勤管理系统”为主线,把从设计到落地的完整路径过一遍。

整个项目可以拆成三块:人脸识别模块、数据库模块、考勤业务模块。人脸识别解决“你是谁”,数据库解决“怎么存”,业务模块解决“怎么用”。三块凑齐了,系统才能真正替代传统打卡机。这篇文章适合手里已经有点 Python 基础、想自己搞一套离线考勤系统的开发者,也适合在高校做课程设计或者在企业里做内部工具的人参考。我后面给的所有参数、SQL、代码片段,都是在实际调试中验证过的,你可以直接抄。

1. 项目整体设计与技术选型

1.1 考勤场景到底在解决什么问题

先把需求掰开来看。任何考勤系统,本质上就三件事:确认身份、记录时间、生成报表。但真到了落地阶段,你还会遇到一堆“隐性需求”,比如代打卡怎么防、光线差的时候能不能识别、一个人在同一天重复刷脸怎么处理、月底统计能不能一键导出来。

我在做这类系统时,习惯先画一遍用户流程:员工来到摄像头前,人脸被识别,系统判断当前时间是上班时段还是下班时段,写入签到记录或签退记录;管理员在后台能查某个人某天的上下班时间,也能拉出来一个月的汇总表。这套流程看似朴素,但每一步都会牵涉人脸识别准确率、数据库读写策略、考勤规则边界条件等因素。

如果只做识别而不做考勤业务,那充其量是个“人脸门禁”;如果只做数据库而不做识别,那和传统打卡软件也没区别。所以这个项目的核心价值就在两者的结合:把识别的结果实时转化为考勤记录,并支持后续统计查询。做完你会明显感觉到,真正花时间的不是算法部分,而是怎么把算法结果安全合理地写进数据库。

1.2 识别方案对比:为什么我选了离线库方案

人脸识别这部分有多种技术路线,我在选型时做了几个方向的对比。

一种是 OpenCV 自带的 LBPH 识别方案。它基于局部二进制模式直方图,属于传统机器学习,优点是环境依赖少、代码简单,但缺点非常致命:对光照变化、人脸角度很敏感,同一个员工换个光线就可能认不出来。在考勤这种需要高可靠性的场景里,我基本上直接排除。

另一种是云端人脸识别 API。精度高、接入快,但有个硬约束:每次识别都要把图片传上去,涉及隐私和网络成本。企业内部用可能无所谓,但有些项目明确要求数据不出内网,这条就卡死了。

最后就是我选定的方案:Python + face_recognition 库 + MySQL。face_recognition 底层基于 dlib,人脸特征用 128 维向量表示,在普通 CPU 上即可完成推理,单张人脸的特征提取耗时大约 0.1 到 0.2 秒,比对 1000 人的库也就是几十毫秒的事。完全离线部署,数据在自己手里,精度对于考勤场景来说足够(在控制环境和采集质量的条件下,正确率能做到 95% 以上,这个后面细说)。

我还对比过一个更重的方案:用 MTCNN 做人脸检测、ArcFace 做人脸特征提取,模型精度确实最高,但需要自己准备标注数据训练或微调,部署也要考虑 GPU 机器,对一个小型考勤系统来说有点杀鸡用牛刀了。如果你后续要做到几万人规模,再考虑也不迟。

技术选型这块,我的建议是:中小规模(几百人到千把人)离线考勤,face_recognition 方案性价比最高,开发周期也能压到一两周。

1.3 系统模块划分与数据流向

我把系统拆成四个模块:

  • 注册模块:录入员工照片,提取人脸特征,写入 MySQL。
  • 识别模块:调用摄像头实时识别,输出“是谁”以及“置信度”。
  • 考勤业务模块:拿到识别结果后,结合当前时间,判断签退还是签到,更新或插入考勤记录。
  • 管理端模块(可选):提供查询、统计、导出报表的入口。

你可能注意到,我没有把摄像头单独列出来,是因为摄像头只负责“提供画面”,真正的识别在收到画面帧之后才开始。数据流向大致是这样的:摄像头采集 → 人脸检测定位 → 特征提取 → 与数据库中的特征比对 → 产生识别结果 → 写入考勤表。这条链路里,每一步都可能成为瓶颈,后面我会逐个说。

2. 数据库设计:把“人脸特征”和“考勤流水”安顿好

2.1 员工表设计:特征字段到底用什么类型

数据库设计是整个系统的地基,特别是员工表里要存人脸特征,这里有个细节很容易踩坑:特征是一个 numpy 浮点数组(128 个 float),你不能直接存成 VARCHAR,也不能用普通 BLOB 硬塞,需要考虑序列化格式和字段长度。

我的做法是先用 pickle 将 numpy 数组序列化,再用 base64 编码成字符串,最后存入 MEDIUMTEXT 字段。base64 的好处是存储内容对 MySQL 完全友好,不会出现二进制字符导致的写入异常。128 个 float64 值序列化后大约在 5KB 以内,MEDIUMTEXT 完全够用。

建表 SQL 大致如下:

CREATE TABLE employees ( id INT AUTO_INCREMENT PRIMARY KEY, emp_no VARCHAR(20) NOT NULL UNIQUE COMMENT '员工编号', name VARCHAR(50) NOT NULL COMMENT '姓名', department VARCHAR(100) COMMENT '部门', face_encoding MEDIUMTEXT COMMENT '人脸特征(base64)', photo_path VARCHAR(255) COMMENT '原始照片路径', created_at DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

有几个设计点要说明。emp_no 必须加唯一索引,它对应到每个员工,后续所有考勤记录都通过 emp_no 关联这个表。face_encoding 作为文本而不是 BLOB,主要是为了导出和排查方便,MySQL 客户端里能直接看到内容,不会出现一堆乱码。当然你也可以用 BLOB,后续读取时反序列化稍微省一步,但维护体验差一些。

2.2 考勤记录表与请假表:时间字段和状态字段怎么搭配

考勤记录表是整个系统写入最频繁的表,设计上要兼顾查询和防重复。

CREATE TABLE attendance ( id INT AUTO_INCREMENT PRIMARY KEY, emp_no VARCHAR(20) NOT NULL COMMENT '员工编号', work_date DATE NOT NULL COMMENT '考勤日期', check_in_time DATETIME COMMENT '签到时间', check_out_time DATETIME COMMENT '签退时间', status TINYINT DEFAULT 1 COMMENT '1正常 2迟到 3早退 4缺勤 5请假', UNIQUE KEY uk_emp_date (emp_no, work_date) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

关键设计有两处。第一,联合唯一索引 (emp_no, work_date) 保证一个员工一天只产生一条考勤记录,避免多次识别产生重复数据。第二,status 字段初始写入后,可能因为“迟到”变成“早退”吗?不会,我后面用状态位只做汇总标识,原始时间字段始终保留,这样即使你之后想重新计算某天是否迟到,也依然能从 check_in_time 里算出来。简单说,原始数据要留,状态字段本质是缓存,方便统计时直接聚合。

请假表单独建一张,结构不能和 attendance 混在一起,否则请假记录会搞乱每日考勤的唯一性。

CREATE TABLE leave_records ( id INT AUTO_INCREMENT PRIMARY KEY, emp_no VARCHAR(20) NOT NULL, leave_date DATE NOT NULL, leave_type VARCHAR(20) COMMENT '事假/病假/年假', reason VARCHAR(255), status TINYINT DEFAULT 0 COMMENT '0待审批 1通过 2拒绝', KEY idx_emp_leave (emp_no, leave_date) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

请假记录和考勤记录的联动是后续统计模块的工作:某天如果存在审批通过的请假记录,那当天考勤不判迟到早退,直接算请假。

2.3 索引与分表思考:当考勤数据超过百万行怎么办

考勤表的数据增长是线性的,假设公司有 500 人,一年就是 500 × 365 约 18 万条,三年就是 50 万条以上。这个量级在 MySQL 里其实并不大,但如果你在统计时直接对整表做 GROUP BY,查询会越跑越慢。

我的建议是两条腿走路。第一,索引一定建全:除了联合唯一索引,推荐再建 (work_date, emp_no) 普通索引,因为日报和月报都是按照日期维度来查询的,命中日期条件后,扫描结果集就小很多。第二,如果确实需要长期保留历史数据,可以考虑按年份归档,比如每年年初把上一年数据迁移到 attendance_2024 这种备份表,主表只保留当前年度和上一年度的数据。这种方案比 MySQL 分区简单得多,也几乎不增加开发成本。

3. 人脸识别模块的完整实现

3.1 环境搭建与依赖安装

这块我建议在 Python 3.8 到 3.10 之间操作,太新或者太旧都会遇到莫名的编译问题。核心依赖只有三样:

pip install opencv-python pip install face_recognition pip install pymysql

最容易出问题的是 face_recognition,它会连带安装 dlib,而 dlib 在 Windows 上需要 CMake 和 C++ 编译环境。如果安装报错,建议先去官网装好 Visual Studio Build Tools,勾选“使用 C++ 的桌面开发”工作负载,再重新安装。在 Linux 服务器上则要先装 CMake 和编译依赖,一句命令的事:

sudo apt install build-essential cmake

装完后用下面这段代码验证环境:

import face_recognition import cv2 img = face_recognition.load_image_file("test.jpg") faces = face_recognition.face_locations(img) print(f"检测到 {len(faces)} 张人脸")

如果是零张,大概率是图里人脸太小或者角度太大;如果报错提示无法加载图片,检查一下 opencv-python 是否正确,两者独立安装但存在版本兼容问题,我遇到过一次 opencv 4.x 读取图片后和 face_recognition 内部解码冲突的情况,后来统一升级到最新版本解决。

3.2 注册流程:从照片到特征入库

注册流程的目的是把员工的人脸特征从图片变成数据库里一条记录。核心代码如下:

import face_recognition import pymysql import base64 import pickle def register_employee(emp_no, name, department, photo_path): # 1. 加载图片并提取特征 image = face_recognition.load_image_file(photo_path) face_encodings = face_recognition.face_encodings(image) if len(face_encodings) == 0: raise RuntimeError("未在图片中检测到人脸,请更换照片或调整角度") if len(face_encodings) > 1: raise RuntimeError("检测到多张人脸,请使用单人证件照") encoding = face_encodings[0] # 128 维向量 # 2. 序列化特征 encoding_b64 = base64.b64encode(pickle.dumps(encoding)).decode("utf-8") # 3. 写入数据库 conn = pymysql.connect( host="localhost", user="root", password="123456", database="attendance_db", charset="utf8mb4" ) cursor = conn.cursor() sql = ("INSERT INTO employees (emp_no, name, department, face_encoding, photo_path) " "VALUES (%s, %s, %s, %s, %s)") cursor.execute(sql, (emp_no, name, department, encoding_b64, photo_path)) conn.commit() cursor.close() conn.close()

注意我做了两个人脸数量的判断,这不是多余代码。实际注册时,有人可能不小心提交了合影,如果代码不做拦截,特征向量会提取到第一张人脸,但没法和具体的人对应上,后期识别会出现张冠李戴。多人脸时直接拒绝注册,让操作人换张照片,这个体验更安全。

还有一个实操经验:注册用的照片质量,直接决定了后期识别的成功率。我建议至少满足三点:正脸、光照均匀、无遮挡。最好让员工在办公区现场用摄像头拍一张,而不是拿身份证照片扫描,因为它和现场环境光的色差很大,识别率会下降。

3.3 识别流程:摄像头实时比对到底应该怎么做

实时识别流程通常分成两段:检测和比对。检测负责找出画面里的人脸位置,比对负责把人脸特征和库里的特征做距离计算。face_recognition 库提供了便捷封装,完整代码如下:

import face_recognition import pickle import base64 import numpy as np from scipy.spatial import distance import cv2 def load_known_faces(cursor): """加载数据库中所有员工特征到内存""" cursor.execute("SELECT emp_no, name, face_encoding FROM employees") rows = cursor.fetchall() known_faces = [] for emp_no, name, enc_b64 in rows: encoding = pickle.loads(base64.b64decode(enc_b64)) known_faces.append((emp_no, name, encoding)) return known_faces def recognize(unknown_encoding, known_faces, threshold=0.4): """返回最佳匹配的员工信息,未匹配返回 None""" best_match = None min_distance = float("inf") for emp_no, name, encoding in known_faces: dist = distance.euclidean(unknown_encoding, encoding) if dist < min_distance: min_distance = dist best_match = (emp_no, name) if best_match and min_distance <= threshold: return best_match, min_distance return None, min_distance def main_loop(): # 数据库连接与已知人脸加载省略 video_capture = cv2.VideoCapture(0) while True: ret, frame = video_capture.read() if not ret: continue # 缩小画面加快检测速度 small_frame = cv2.resize(frame, (0, 0), fx=0.5, fy=0.5) # 把 BGR 转为 RGB,因为 face_recognition 默认使用 RGB rgb_frame = cv2.cvtColor(small_frame, cv2.COLOR_BGR2RGB) face_locations = face_recognition.face_locations(rgb_frame) face_encodings = face_recognition.face_encodings(rgb_frame, face_locations) for face_encoding, face_location in zip(face_encodings, face_locations): match, dist = recognize(face_encoding, known_faces) if match: emp_no, name = match # 此处联动考勤业务模块 handle_attendance(emp_no) print(f"识别成功: {name}, 距离: {dist:.3f}")

这里有几个细节值得展开。第一,使用face_recognition.face_locations先定位,再用face_recognition.face_encodings提取特征,这是目前最稳的调用姿势。很多初学者直接调face_recognition.face_encodings(frame),如果图片里有多个人脸,它会返回多个特征,而你不一定知道谁是谁,反而容易出错。第二,scipy 的欧氏距离计算,也可以用 numpy 自己算,np.linalg.norm(unknown - known)效果一样。第三,帧率优化方面,我一般设置每 2 到 3 秒抽一帧做识别,既能保证识别结果及时,又能显著降低 CPU 占用。完整时间去跑每帧识别,CPU 会一直保持在 100% 附近,过一会儿就会发热降频,识别速度反而更慢。

3.4 阈值怎么定:识别率还是误识率,这是个平衡题

face_recognition 库官方文档给出的默认阈值是 0.4 的欧氏距离,距离越小表示两个人脸越相似。我在实际项目里测试过,0.4 对大部分场景偏严,可能导致少部分员工因为光线原因被拒识别;但如果放宽到 0.5,又容易出现两个人长得比较像的时候互相误认。

我的建议是先用一段时间的真实环境数据做校准。具体做法是:让 10 个员工分别在正常光线下采集图像,算他们自己的特征和库里特征的欧氏距离;再随机抽两台机器做跨人比对,记录最小距离和最大距离。如果同一人距离的中位数是 0.35,而不同人之间的最小距离是 0.48,那阈值取 0.40 到 0.43 之间就相对安全。距离分布越集中,阈值设定越有把握。

如果发现同一人的距离经常超过 0.5,说明注册照片和现场环境的差异太大,我不建议为了迁就它把全局阈值明显调低,更好的做法是补充注册多张照片:正面、侧脸、日常表情各一张,识别时只要任一张特征匹配就通过。这个办法在实际项目里把准确率从 91% 拉到了 96% 以上。

3.5 活体检测与防作弊:不能只靠一张脸

考勤场景下,照片攻击是最常见的作弊手段:别人拿张 A4 纸打印的照片对着摄像头晃一下。纯静态人脸识别完全防不住,必须要加活体检测。

最简单的做法是动作指令配合:识别成功后,屏幕随机提示“眨眨眼”或“摇摇头”,摄像头捕捉到对应动作才确认签到。这种方法实现成本低,但用户操作成本高,排长队时会让人烦躁。

进阶做法是用眨眼检测:调用 OpenCV 的人脸关键点检测,实时计算眼睛纵横比(EAR)。当一个人自然眨眼一次时,EAR 会先变小再恢复,这可以作为“活体”的判据。亲测这个方法识别稳定的员工问题不大,但戴眼镜员工在弱光环境下的眨眼参数波动比较大,误判率会上升。

从项目边界管理来说,我更建议先做简单活体,然后在部署层面加强:考勤设备固定安装,摄像头正对人员通道,管理员可复查照片留存。系统里每一条考勤记录,我还会保存一张识别时的现场截图,后续有人质疑时能追溯,这个细节往往比算法防作弊更有威慑力。

4. 考勤业务逻辑与联动实现

4.1 一天一条记录:第一次刷脸是签到,第二次是签退

先明确业务规则:员工早上第一次识别,记录为签到;当天再次识别,记录为签退;之后如果再识别,约定为重复签退或忽略,由业务决定。我采用一天一条记录 + 每次识别做判断的处理方式,因为这样统计月份考勤时最简单。

逻辑代码如下:

from datetime import datetime, time as dtime def handle_attendance(emp_no): now = datetime.now() today = now.date() conn = get_connection() cursor = conn.cursor() # 1. 查询当天是否已有记录 cursor.execute( "SELECT id, check_in_time FROM attendance WHERE emp_no = %s AND work_date = %s", (emp_no, today) ) row = cursor.fetchone() if row is None: # 2. 首次识别:插入签到记录,并根据上班时间判断是否迟到 check_in_limit = dtime(9, 0) # 9:00 上班 status = 1 if now.time() <= check_in_limit else 2 # 1正常 2迟到 cursor.execute( "INSERT INTO attendance (emp_no, work_date, check_in_time, status) " "VALUES (%s, %s, %s, %s)", (emp_no, today, now, status) ) conn.commit() print(f"{emp_no} 签到成功,状态: {status}") else: # 3. 已有签到记录,更新签退时间 cursor.execute( "UPDATE attendance SET check_out_time = %s WHERE id = %s", (now, row[0]) ) conn.commit() print(f"{emp_no} 签退成功")

如果你想支持上午、下午两次上下班的复杂规则,那就要考虑拆成四段记录,数据结构也会复杂不少。我的建议是:如果业务没有强制要求,优先做“一天一条 + 签入签出”的模式,统计简单,管理也方便。

4.2 跨天与时间边界:深夜加班的签退归属哪一天

考勤系统最容易出错的就是跨天问题。假设员工晚上 22:30 签到加班,次日凌晨 01:30 签退,签到日期是 12 月 1 日,签退日期是 12 月 2 日。如果用check_out_time直接更新到 12 月 1 日的记录里,会出现一个诡异现象:签退时间比签到时间还早。

最稳妥的方式是约定:签到日期的自然日即考勤归属日。如果签退发生在次日凌晨,仍然更新到签到那天的记录里,但需要在展示时做特殊处理。如果签退发生在 24 小时后,那就应该视为下一次签到记录的开头。我建议新增一个 sign_type 字段区分“上班签到”和“加班签到”,避免加班记录混在正常考勤里干扰月度统计。

我实际项目里遇到过最烦的情况是员工横跨两天:22:30 第一次刷脸进入公司,02:30 第二次刷脸离开。如果不做区分,系统会认为上班签到是 22:30,签退是 02:30,共 4 小时;但实际上他是来加班 4 小时。这里就需要“加班签到”的入口,员工选择“上班”或“加班”模式后再识别,或者用时段自动推断:18:00 之后首次识别视为加班。自动推断简单粗暴,但节假日排班时会有误差,所以我的最终方案还是人脸确认后弹出按钮让人选。

4.3 管理端统计:SQL 怎么写才高效

月度统计的核心需求是:某员工一个月内,正常出勤多少天、迟到多少次、早退多少次、缺勤多少天、请假多少天。基于前面的表结构,聚合 SQL 可以这么写:

SELECT emp_no, COUNT(*) AS total_days, SUM(CASE WHEN status = 1 THEN 1 ELSE 0 END) AS normal_days, SUM(CASE WHEN status = 2 THEN 1 ELSE 0 END) AS late_days, SUM(CASE WHEN status = 3 THEN 1 ELSE 0 END) AS early_leave_days, SUM(CASE WHEN status = 4 THEN 1 ELSE 0 END) AS absent_days, SUM(CASE WHEN status = 5 THEN 1 ELSE 0 END) AS leave_days FROM attendance WHERE work_date BETWEEN '2024-11-01' AND '2024-11-30' GROUP BY emp_no;

如果你还需要查询某个人的每日明细并导出 Excel,可以用 Python 的 openpyxl 生成 xlsx 文件,或者在管理端页面里用 DataTables 做前端表格,数据接口返回 JSON 即可。这个部分不是核心难点,但注意 SQL 里的索引使用:WHERE 条件命中 work_date 范围,再 GROUP BY emp_no,前面的辅助索引加上之后,百万行以下的数据量都没有问题。

4.4 请假和考勤怎么联动

处理请假后,当天的考勤不能直接缺失,否则会被统计成缺勤。我的做法是:请假审批通过后,管理端自动在 attendance 表插入一条记录,status 置为 5(请假),check_in_time 和 check_out_time 留空。这样月度 SQL 统计时,请假天数直接被 SUM(CASE WHEN status=5) 抓出来,不会混入缺勤。

你可能会问,为什么不在统计 SQL 里额外关联 leave_records,而是提前在 attendance 里造一条?因为考勤统计通常只以 attendance 表为准,逻辑越单一越不容易出错。离职后的补录、历史修改也不用处理 join 的时序问题。

5. 实操过程中踩过的坑与排查实录

5.1 识别失败:先查环境,再查阈值,最后查特征数据

我排查识别成功率低的问题有一套固定顺序,因为这样可以快速缩小范围。

第一步看现场图像质量。把摄像头抓到的原图保存下来,肉眼检查有没有严重过曝、逆光、侧脸过多。大部分识别失败都是图像质量造成的,而不是算法问题。某次项目里投诉率很高的工位附近,我过去一看,摄像头正对窗户,员工下午全逆光,换了个补光角度后,识别率立马上来了。

第二步检查数据库里的特征是否完好。之前发生过一次诡异现象:一部分员工始终识别成功,另一部分始终失败,而且失败的那些人换了新照片注册也还是失败。排查后发现,早期注册过程里,有一批员工的人脸特征被 pickle 序列化后,因为编码问题在写入时截断了,特征向量长度已经不是 128,导致距离计算混乱。后来我在注册代码里加了长度校验,识别时也做向量长度断言,问题不再出现。

第三步才是调阈值。如果大部分人都能识别,就个别距离在 0.42 到 0.48 之间被拒,你把阈值调到 0.46 通常就能解决。但如果一组人里面的互相距离都不稳定,说明注册库本身质量不行,调整阈值只是饮鸩止渴。

5.2 MySQL 连接问题:短连接风暴和编码坑

考勤设备是长时间运行的,如果你每识别一次就建一次数据库连接,连接建立的开销迟早会拖垮系统。因为识别频率高,高峰期可能每秒都有连接请求,MySQL 默认的一些连接限制会被打爆,报错“Too many connections”。

方案有两个。第一,使用连接池,比如 pymysql 搭配 DBUtils 的 PooledDB,环形复用连接。第二,识别主循环保持一个长连接,周期性 ping 保活,只有当连接断开时才重连。第二种方案更轻量,适合单台考勤机。我的生产程序里采用的是长连接 + 异常重连机制。

编码问题是另一个高频坑。MySQL 建库时使用 utf8mb4,建表时也指定 utf8mb4,Python 连接时再传charset="utf8mb4",三处保持一致,中文字段名和人名才能正常读写。如果哪一处用了默认 latin1,写入的中文就会变成问号,而且这种数据一旦写入,很难恢复,只能删掉重建。

5.3 性能问题:摄像头卡顿、识别排队

如果跑识别的主循环只有单线程,在员工排队考勤的高峰期,画面会被前一个人的识别任务阻塞,导致屏幕卡顿。我采用的优化手段是设置一个简单的识别锁:新帧到来时,如果上一次识别还没结束,就直接跳过这一帧,只更新画面显示。这样实际识别频率可能从每秒 5 次降到每秒 2 次,但因为识别任务不阻塞画面线程,体验反而好很多。

更进一步,可以把识别放到生产者-消费者队列里:摄像头作为生产者,持续读帧放队列;识别线程作为消费者,从队列取帧识别。识别完成后,把结果放到结果队列,主线程从结果队列拿结果并触发考勤逻辑。这个方案在树莓派上也能流畅跑起来,只是内存和 CPU 占用会高一些。

另外,如果员工数量达到千人级别,每次比对都遍历全部特征会开始吃力。我建议在数据库里加一个部门字段作为过滤条件:考勤机固定部署在某个楼层或部门区域,识别前先只取该部门的特征子集,通常能把候选数量缩小到几十人,比对主体就瞬间完成了。或者直接用 numpy 矩阵化计算,一次算完所有距离,利用 numpy 底层向量化代替 Python 循环,速度提升明显。

5.4 一张速查表:常见问题、原因与对策

现象可能原因分析与对策
某人始终无法识别注册照片与当前差异太大重新现场采集注册照片
识别结果张冠李戴特征向量截断或序列化异常校验特征长度,重建特征数据
早高峰识别排队严重单线程阻塞画面抽帧识别降低频次,或分离识别线程
数据库连接报错连接数超限改用连接池或长连接复用
中文乱码编码设置不一致库、表、连接三处统一 utf8mb4
同一人距离忽高忽低光照变化或侧脸增加补光、多角度注册照片
照片照片能通过识别缺少活体检测增加眨眼检测或动作指令确认

6. 写在最后的经验体会

做完这个考勤系统,我最深的体会是:人脸识别只是整个链路最亮眼的那个环节,真正决定系统好不好用的,是那些琐碎的业务规则和数据可靠性。你可以在识别准确率上花大量时间挠头,但如果你没有设计好一天一条记录的考勤表,或者没有处理跨天签退,上线第一天就会被行政同事叫去改需求。先把流程梳理清楚,再写代码,这条原则在这里体现得非常明显。

最后再分享一个小技巧:所有考勤记录写入数据库后,务必保留人脸识别时的现场摄像头截图。截图按“员工编号_日期_时间”命名,存到独立目录。这一步能给你省去很多不必要的争执——九成以上的“我没迟到”“这不是我”的争议,看一眼截图就结束了。从产品角度看,留痕能力甚至比识别精度更重要。

这个项目后续还能往不少方向扩展:把识别端和管理端都做成 Web 服务,在浏览器里看实时考勤;加一台备用识别设备做故障切换;把报警规则接入企业微信或钉钉,迟到自动提醒。底层的识别和存储架构已经立住,往哪个方向加功能都很顺手。做系统集成就是这样,基础打得稳,后面的事情自然顺。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/19 17:47:07

SQL注入攻防全解析:原理手法与防御实践

SQL注入这个话题&#xff0c;隔三差五就在安全资讯里刷个脸&#xff0c;从早期的数据拖库到如今各种穿云盒、小程序接口被“顺手牵羊”&#xff0c;十几二十年过去了&#xff0c;它依然稳稳坐在漏洞榜单的前排。很多开发同学一听“SQL注入”就头大&#xff0c;觉得看不见摸不着…

作者头像 李华
网站建设 2026/10/19 17:43:35

逆滤波越除越崩?维纳滤波如何在频域稳住图像复原的PSNR

简介&#xff1a;面向图像处理课程作业与实验场景&#xff0c;这份资源提供了一套完整的大气湍流退化图像复原方案。主要内容是使用大气湍流模型对原始图像进行退化&#xff0c;并叠加高斯噪声&#xff0c;随后分别通过维纳滤波和逆滤波实现复原对比&#xff0c;最终以PSNR和MS…

作者头像 李华
网站建设 2026/10/19 17:37:49

SQL子查询完全指南:分类、执行原理、性能优化与避坑实践

1. 子查询是什么&#xff0c;为什么后端和数据分析都得掌握 我第一次真正被 SQL 子查询“教育”了一回&#xff0c;是在一个统计报表的改造现场。一条主查询里嵌套了三个子查询&#xff0c;老板在旁边催着我定位慢在哪&#xff0c;而我盯着屏幕只能把每个括号一层层拆开人工推演…

作者头像 李华
网站建设 2026/10/19 17:37:06

深度生成模型实战指南:原理、五大主流路线与训练避坑

如果只看地址deeplearningbook_020-1&#xff0c;很多人会以为这是某个压缩包里随手命名的文件。我最初拿到这串编号&#xff0c;也只是把它当成一份深度学习资料的章节标记&#xff0c;翻进去才发现里面讲的是深度生成模型这一整块硬核内容。毫不夸张地说&#xff0c;这段内容…

作者头像 李华
网站建设 2026/10/19 17:36:50

Max+395本地部署千问:Gufo与Halogen推理引擎实测对比

最近一直在折腾本地大模型部署&#xff0c;手头正好有一块“Max395”平台&#xff0c;算力标称395 TOPS的NPU加统一内存&#xff0c;是个很适合跑量化模型的紧凑设备。我在上面把千问开源模型分别用Gufo和Halogen两套推理方案跑了一遍&#xff0c;从7B到14B&#xff0c;量化等级…

作者头像 李华
网站建设 2026/10/19 17:32:49

STM32F407 USB HID 键盘鼠标开发实战:零驱动原生识别

简介&#xff1a;本资源是基于STM32F407系列MCU实现USB HID通信的完整嵌入式开发项目&#xff0c;面向嵌入式初学者、STM32进阶开发者及USB协议学习者&#xff0c;解决自定义HID设备&#xff08;如专用键盘、游戏手柄&#xff09;在Windows/Linux免驱通信的工程落地问题。压缩包…

作者头像 李华