资讯详情

资讯详情

Python人脸识别签到系统源码解析:特征向量、SQLite考勤与避坑指南

简介基于Python的人脸识别签到系统源码面向计算机专业毕业生、课程设计学生以及需要快速落地人脸识别应用的开发者既可作为毕业设计直接使用也适合参考二次开发。资源共27个文件以8个Python脚本、7个HTML页面、SQLite数据库为核心配套配置文件、字体文件与说明文档整体约101.47MB其中HTML页面覆盖登录、用户编辑、后台管理等信息展示Python脚本负责路由控制、人脸特征提取与识别判断目录按Flask框架分层便于定位与修改。已有219人浏览学习项目经过严格调试下载后简单部署即可运行。压缩包内代码附有注释新手也能看懂关键逻辑系统功能完整包括用户管理、人脸录入、签到识别等流程界面简洁易操作。这套源码具备较高的完成度与工程实用性适合作为高分毕业设计、期末大作业的参考模板。1. 基于 python 人脸识别签到系统源码.zip这个包到底能帮你省多少事把一个人脸识别签到系统的源码包解开、跑通、最后顺利通过毕业设计答辩这个过程里最容易翻车的往往不是模型精度而是你对“这套系统是给谁用的”这件事的理解。基于 python 人脸识别签到系统源码.zip 这类项目本质上是一条完整链路摄像头抓帧人脸检测提取成 128 维特征向量与注册库比对命中后把姓名和当前时间写入 SQLite 签到表。它能替掉的是传统的按名单点名和手动登记它要面对的是光线、角度、遮挡、重复签到这些被 demo 忽略的脏现实。适合做毕设的学生也适合想快速搭一套内部考勤 Demo 的工程师。这份笔记就顺着这条链路把代码、参数和坑一次讲完。2. 人脸识别签到的技术选型从 128 维特征向量到 SQLite 签到表市面上能下到的基于 python 的人脸识别签到系统源码包差别往往不在界面而在识别层用的是哪条技术路线。这个决定会影响后面所有代码的写法所以第一步不是写代码而是把方案定下来。我见过太多人把一整套 demo 跑通了才发现识别库选错重新返工比从零写还痛苦。2.1 识别方案怎么选face_recognition、dlib 还是 OpenCV LBPH常见做法有三条路线各有各的适用场景。第一条是用face_recognition这也是我最推荐给毕设场景的方案。这个库封装了 dlib 的人脸检测和特征提取模型使用者不需要关心人脸识别图像进入神经网络到输出高维度向量的过程具体怎么实现只需要调用接口拿结果。它输出的特征就是一个 128 维的浮点向量注册时把这个人所有照片的特征存进本地文件识别时拿摄像头实时算出来的向量跟库里所有向量做欧氏距离比较距离小于阈值就算命中。第二条是直接用 dlib 写。能拿到更底层的控制权比如自己加载模型、自己调度 GPU但代价是要自己处理很多细节光是一个模型的下载和序列化格式就够折腾半天。除非你要做深度学习方向的课题否则不建议在签到系统里直接碰 dlib。第三条是 OpenCV 自带的 LBPH 人脸识别器。它的特点是训练快、纯 CPU 也能跑但识别率跟深度模型差一个量级换个发型、戴个眼镜就可能翻车。它不是不能用而是更适合放在论文里作为“传统方法与深度方法的对比实验”用来衬托主方案的优越性。另外还有一种思路是接在线人脸识别 API包括 easyai 人脸识别那一类平台。这类 API 的识别率确实高但它要求把注册人脸照片传到服务端校园场景里涉及个人信息出境和数据隐私问题答辩时很容易被老师追问。本地方案虽然精度略低但整个流程完全可控我的选择是face_recognition OpenCV 采集几乎不写模型代码把精力花在签到业务和界面这些真正能给分的地方。2.2 识别链路的五个环节与模块边界不管源码包长什么样一套能干活的人脸签到系统在逻辑上必然包含五个环节这也是你拿到任何一份源码后应该最先找出来的五块。第一是注册环节。摄像头采集用户若干帧人脸图像经过检测和特征提取把这个人对应的 128 维特征向量序列化到本地特征库文件里。这个文件后续被识别环节反复加载相当于系统的“底库”。第二是识别环节。摄像头实时读帧先做人脸检测再对检测框内的人脸做特征提取得到当前帧的特征向量然后用这个向量跟底库里所有向量算相似度。这个环节直接决定用户体验也是性能瓶颈所在。第三是签到环节。识别命中后把姓名和当前时间写入数据库。这里的核心约束是一个人在同一天只能签到一次不能刷脸一次写一条记录否则考勤数据就废了。第四是展示环节。用户需要看到摄像头画面、当前识别结果、当天已签到名单。毕业设计里最常见的实现是用 PyQt5 或 Tkinter 做桌面界面把识别结果实时渲染到界面上。第五是配置环节。识别阈值、摄像头编号、识别频率这些参数应该从配置文件读取而不是写死在代码里。这看起来是个小点但换机器调试时能省大量时间。把五个环节拆清楚之后你会发现源码包的核心价值不在某一个环节而在它们之间的数据格式约定注册时存的是什么格式识别时必须读取同一种格式识别结果怎么转换成签到记录签到记录又怎么被界面展示。顺着这条数据流去读代码比从头到尾逐行读要高效得多。2.3 三个必调参数tolerance、model、num_jitters识别效果好不好很多时候不是模型的锅而是参数没调对。face_recognition 库里有三个参数是签到系统必须理解的我把它们列成一张表。参数含义常见取值调整方向tolerance特征距离阈值判定“是不是同一个人”的界限0.5 到 0.6总是识别失败就调大总是误识别就调小model人脸检测模型hog 或 cnnCPU 机器用 hog有 NVIDIA GPU 再考虑 cnnnum_jitters提取特征时的重采样次数注册时 10 到 100识别时保持 1数值越大特征越稳定但耗时线性增加tolerance是最容易被忽略的一个参数。face_recognition 的compare_faces默认值是 0.6这个值在光线稳定的办公室环境通常够用但教室、走廊这些环境变化大的场景0.6 会出现陌生人误判为熟人的情况。我的习惯是先按 0.6 跑一版统计误识别率再往 0.4 到 0.5 方向去压一步一个脚印调到刚好够用。model参数很多人默认用 hog这没错。cnn 模型在 CPU 上检测一帧要几百毫秒直接让实时签到变成幻灯片。只有当你确实有一块可用的 NVIDIA GPU并且把识别丢到独立线程里跑时cnn 才值得考虑。num_jitters的使用场景比较特殊。识别的时候必须保持 1因为实时视频流里每秒要处理好几帧多了扛不住。但在注册环节我会把它调到 10 以上让算法对同一张人脸图像做多次重采样再取平均特征这样得到的底库特征更能抵抗光照和角度的变化属于花一次时间让后面每天都省心的做法。3. 把核心模块跑通人脸注册、实时识别与 SQLite 签到的 Python 实现方案定了接下来就是动手写代码。这一章的三个模块是人脸签到系统的骨架分别对应注册、识别、签到三个环节。我会给出可直接运行的代码并说明每一段为什么这么写。3.1 人脸注册与特征序列化从摄像头帧到本地特征库注册模块的输入是摄像头画面输出是一个包含用户名和特征向量的本地文件。这里我选择用 pickle 序列化因为它能把 Python 的 dict 结构原样存下来读回来也不需要额外解析对毕设规模的项目足够简单。import os import pickle import cv2 import face_recognition import numpy as np DB_PATH encodings.pkl def register_user(name: str): cap cv2.VideoCapture(0) if not cap.isOpened(): raise RuntimeError(摄像头打不开检查编号或占用情况) frames [] print(按空格键抓取人脸抓满 3 帧后自动注册按 q 退出) while len(frames) 3: ok, frame cap.read() if not ok: continue cv2.imshow(register, frame) key cv2.waitKey(200) if key ord( ): frames.append(frame) elif key ord(q): break cap.release() cv2.destroyAllWindows() if len(frames) 3: raise RuntimeError(采集帧数不足请重新运行) encodings [] for frame in frames: face_encs face_recognition.face_encodings(frame, num_jitters10) if face_encs: encodings.append(face_encs[0]) if not encodings: raise RuntimeError(这 3 帧里没有检测到人脸换个光线条件再试) # 多帧特征取平均降低单帧模糊或侧脸带来的偏差 avg_enc np.mean(encodings, axis0) data {} if os.path.exists(DB_PATH): with open(DB_PATH, rb) as f: data pickle.load(f) data[name] avg_enc with open(DB_PATH, wb) as f: pickle.dump(data, f) print(f注册成功{name}特征维度 {avg_enc.shape})这段代码里值得注意的有三点。第一face_encodings返回的是一个列表因为一张画面里可能有多张人脸这里只取列表第一个元素也就是画面里最靠前的那个人脸注册场景下通常就是摄像头前的用户本人。第二把三帧的特征做np.mean取平均是因为单帧可能碰巧模糊或者角度偏转平均之后特征更接近这个人的典型外观。第三特征库文件如果已存在就加载后追加而不是直接覆盖这样多次注册不同用户时不会把前面的人弄丢。注册时我一般会让用户稍微转动头部让三帧分别覆盖正面、微左、微右三个角度。这个操作不改变代码但能让底库特征更鲁棒属于零成本换识别率的细节。3.2 实时识别签到识别线程里最容易卡住的一帧识别模块要同时解决两个问题能不能认出来以及认出来的是谁。下面的函数输入一帧摄像头画面输出人脸框位置和对应的姓名。def recognize(known_names, known_encodings, frame): # 缩小帧加速识别不需要全分辨率640 宽足够 small cv2.resize(frame, (0, 0), fx0.5, fy0.5) rgb cv2.cvtColor(small, cv2.COLOR_BGR2RGB) # hog 模型对 CPU 友好upsample1 提高小脸检测率 boxes face_recognition.face_locations( rgb, modelhog, number_of_times_to_upsample1 ) encs face_recognition.face_encodings(rgb, boxes, num_jitters1) results [] for enc in encs: # tolerance 越小越严格签到场景从 0.5 起步调 matches face_recognition.compare_faces(known_encodings, enc, tolerance0.5) dists face_recognition.face_distance(known_encodings, enc) best int(np.argmin(dists)) if matches[best]: results.append(known_names[best]) else: results.append(unknown) return boxes, results这段代码有一个特别容易踩的坑颜色空间。face_recognition内部处理的图像是 RGB 顺序而cv2.imread读出来的是 BGR如果你直接把 OpenCV 的帧丢给 face_recognition检测结果会时好时坏。我在这里先cv2.cvtColor转成 RGB再往下走。另一个细节是缩小帧。cv2.resize把帧缩到一半识别耗时能砍掉一大截而识别精度几乎不受影响因为人脸检测只需要几十像素宽就能定位。实际使用中如果机器性能差我还会把 frame 宽度直接限制到 480代价是稍远距离的人脸识别率下降。识别结果里我用face_distance算出距离最小的那条记录再用compare_faces判断这个人是否在底库里。之所以不直接用compare_faces的返回值是因为它返回一个布尔列表当底库人数多了以后你很难分辨“完全陌生”和“距离刚好卡在阈值边缘”这两种情况。先取最小距离再跟阈值比较逻辑更直观。3.3 把签到写进 SQLite表结构与唯一约束识别命中之后很多人会直接往数据库里插一条记录这种做法在演示时没问题但一旦画面中的人在镜头前停留超过一秒钟签到表就会被刷出好几行。解决这个问题不是在代码里加一堆 if 判断而是从表结构上就堵死重复。CREATE TABLE IF NOT EXISTS attendances ( id INTEGER PRIMARY KEY AUTOINCREMENT, user TEXT NOT NULL, date TEXT NOT NULL, time TEXT NOT NULL, UNIQUE(user, date) );UNIQUE(user, date)是这个表的灵魂。它让“同一用户同一天只能有一条记录”变成数据库层面的约束不管代码里有多少线程、多少逻辑分支在写库数据库都会拒绝第二条同用户同日期的记录。对应 Python 侧写入时用INSERT OR IGNORE而不是普通INSERTimport sqlite3 from datetime import datetime def sign_in(user: str): conn sqlite3.connect(attendances.db) today datetime.now().strftime(%Y-%m-%d) now datetime.now().strftime(%H:%M:%S) sql INSERT OR IGNORE INTO attendances (user, date, time) VALUES (?, ?, ?) conn.execute(sql, (user, today, now)) conn.commit() conn.close()INSERT OR IGNORE的行为是如果这条记录因为唯一约束被拒绝SQLite 不会报错只是默默跳过。配合唯一约束之后签到逻辑就不需要在代码里先查一遍“今天有没有签过”既省了一次查询又避免了并发场景下的竞态条件。如果做考勤系统还想要“迟到”状态只需要再加一个status字段在写入时按当前时间判断是否晚于上课时间。4. 用 PyQt5 把签到系统包成毕业设计能交的界面线程分离、配置项与导出结果命令行版的签到系统已经能跑但毕业设计需要界面。PyQt5 是这类项目最常见的界面方案原因很实际控件齐全、文档多、跟 OpenCV 的兼容性好。这一章只讲界面工程里真正影响成败的三个点。4.1 为什么摄像头识别必须放在独立线程如果你把while True读摄像头这件事直接塞进 PyQt 主窗口里程序打开后会卡死窗口无法拖动按钮点了没反应整个界面像死机一样。原因很简单PyQt 的事件循环被摄像头读帧阻塞了。正确做法是把识别逻辑放进QThread通过信号把结果传回主线程更新界面。PyQt 的界面控件只能在主线程操作子线程里直接修改控件文本轻则警告重则闪退。from PyQt5.QtCore import QThread, pyqtSignal import cv2 import numpy as np from datetime import datetime class RecognizeThread(QThread): signed pyqtSignal(str, str) # 姓名, 时间 frame_ready pyqtSignal(object) # 处理后待显示的帧 def __init__(self, cfg): super().__init__() self.cfg cfg self._running True def stop(self): self._running False self.wait() def run(self): cap cv2.VideoCapture(self.cfg[camera_id]) frame_idx 0 while self._running and cap.isOpened(): ok, frame cap.read() if not ok: continue frame_idx 1 # 每隔 N 帧做一次识别其余帧只做显示大幅降 CPU 占用 if frame_idx % self.cfg[recognize_every] ! 0: self.frame_ready.emit(frame) continue # 这里调用 3.2 节的 recognize 函数 boxes, names recognize( self.cfg[known_names], self.cfg[known_encodings], frame ) for box, name in zip(boxes, names): if name ! unknown: self.signed.emit(name, datetime.now().strftime(%H:%M:%S)) self.frame_ready.emit(frame) cap.release()注意signed信号只在识别命中时发射frame_ready信号每帧都发射用于刷新画面。把识别频率和控制台版本的实时识别隔离开是让界面保持流畅的关键。recognize_every设成 3意味着每秒大约只做 5 次识别肉眼几乎感知不到差别CPU 占用却能降到原来的三分之一。4.2 配置模块把阈值、摄像头编号和模型选型交给配置文件写死的参数是毕业设计的大忌。答辩老师只要问一句“换一台摄像头要改哪行代码”如果你说要去源码里找分就没了。把所有运行参数收敛到一个 JSON 文件里既方便自己调试也是工程化水平的体现。{ camera_id: 0, frame_width: 640, recognize_every: 3, tolerance: 0.5, num_jitters: 10, database: attendances.db, encodings: encodings.pkl, model: hog }启动时读这个文件全局只保留一份配置对象import json def load_config(pathconfig.json): with open(path, r, encodingutf-8) as f: cfg json.load(f) return cfg这张表给出配置项的实际作用方便你按自己的机器调整。配置键作用我常用的值camera_id选择电脑的哪个摄像头笔记本内置通常是 0外接 USB 摄像头可能是 1frame_width处理前把帧缩到多少宽640 平衡精度与速度recognize_every每几帧做一次识别3视频流畅度无明显下降tolerance识别距离阈值0.5 起步误识别多就往下调配置项外置还有一个好处换机器调试时不用动一行代码就能把摄像头编号和识别频率调好。这是我在多台笔记本上演示这个项目后的血泪经验。4.3 出勤统计与导出 CSV把签到表变成答辩素材界面这部分签到列表用QTableWidget实时刷新即可。每当signed信号到来主线程往表格里插入一行或者更新当天的记录。为了让数据能带出教室、导出给评委看我把导出 CSV 的功能也做上了。import csv def export_csv(records, path): with open(path, w, newline, encodingutf-8-sig) as f: writer csv.writer(f) writer.writerow([姓名, 日期, 签到时间]) writer.writerows(records)utf-8-sig编码是给 Excel 留的后路。用纯utf-8导出的 CSV 在中文版 Excel 里打开会是乱码加一个 BOM 头就好。这个细节很多源码包都没处理好做到位了反而能成为答辩时的加分小亮点。5. 运行期避坑排查dlib 编译、摄像头黑屏、识别率玄学的五类高频问题把一套人脸识别签到系统从“代码写完”推到“能稳定演示”中间隔着五类高频问题。这一章直接按现象、原因、解决来写每一条都是真实环境里反复出现过的问题。5.1 Windows 上安装 dlib 卡在编译阶段现象pip install face_recognition时终端刷一堆 C 报错最后失败或者在pip install dlib时长时间卡在构建进度条上。原因dlib 在 Windows 上默认需要本地编译系统缺 C 编译器和 CMake或者当前 Python 版本太新没有对应的预编译包。解决在命令行依次执行pip install cmake然后安装 Visual Studio Build Tools勾选“使用 C 的桌面开发”工作负载。装完之后再执行pip install dlib和pip install face_recognition。另一个更省事的办法是换 Python 3.8 或 3.9这两个版本在 Windows 上最常能找到预编译好的 wheel 包直接从 pip 拉下来就能用。安装完成后一定要先执行python -c import dlib验证能导入再往下跑。5.2 摄像头打不开或画面全黑现象VideoCapture(0)不报错但read()一直返回 False或者画面是黑色程序当作没检测到人脸。原因笔记本内置摄像头不一定是设备 0外接摄像头和虚拟摄像头软件会占用其他编号也可能是摄像头被浏览器或其他软件抢占权限被系统拦截。解决先把启动脚本里的摄像头编号改成 1、2 逐个试多数情况下内置和外接混用时会遇到编号漂移。代码里加一段启动自检cap.isOpened()为 False 时立即打印提示而不是静默黑屏继续跑。清理后台占用摄像头的软件后重试。5.3 识别率“今天行明天不行”的玄学问题现象同一个用户白天识别正常傍晚逆光就变成 unknown或者同一台机器换个房间识别成功率断崖式下跌。原因特征向量对光照敏感注册时如果只在某一个固定光线下采集了三帧底库特征就只代表这一种光照条件。逆光、顶光、侧脸都会让实时特征偏离底库。解决注册时让人脸在画面里缓慢左右转头采集的 3 帧覆盖不同角度最好在不同时段各注册一次用多次注册的平均特征合并到底库里。tolerance 从 0.6 往下压到 0.5 的过程中每次只调 0.05不要一次跨太大。这个问题的核心是底库样本多样性属于数据问题不是模型问题。5.4 一天签了无数次签到表重复刷屏现象人站在摄像头前两秒钟数据库里多了五六条同一个人同一天的时间记录。原因识别线程每帧都在识别每帧都会调用签到函数如果没有唯一约束日志表里自然全是重复记录。解决按 3.3 节给attendances表加上UNIQUE(user, date)写入时用INSERT OR IGNORE。同时在程序内存里维护一个当日已签到集合命中后直接跳过减少无意义的数据库写入。两层防护下即使识别线程有并发问题也不会污染数据。5.5 CPU 占用过高画面掉到个位数帧率现象程序能跑但视频画面像幻灯片电脑风扇狂转CPU 占用接近满格。原因每一帧都做了完整的人脸检测和特征提取而这两个操作都是计算密集型的。连续帧之间的画面高度相似逐帧识别的收益很低浪费却很大。解决使用配置里的recognize_every跳帧每 3 帧处理一次把帧宽度缩到 640 以下如果有条件把识别线程的优先级调低保证界面渲染不被抢资源。做完这三步之后帧率通常能从个位数回到 20 帧以上识别成功率几乎不受影响。6. 交付前过一遍这四个验证点换机重跑、冷启动、阈值回退与导出结果代码写完、界面跑通不代表项目已经能答辩。我吃过一次亏把识别阈值调到 0.35在自己的工位上完美识别答辩前一天换到教室全场 unknown。后来定位到问题是注册样本太单一但那种临场翻车的滋味不想再体验第二次。所以现在每次交付前我都会花一个小时过四个验证点当成一张最小的验收清单。第一是冷启动验证。删掉encodings.pkl和数据库文件从零开始注册一个新用户再启动签到识别。这个动作确认系统不依赖历史遗留数据评委拿到手上从第一天就能独立跑起来。第二是换机重跑验证。在另一台机器上按依赖清单重新安装环境确认 dlib 能编译通过、摄像头编号可配置。这一步是最耗时的但也最能暴露环境依赖隐患。第三是阈值回退验证。识别错误时把当前帧保存到 debug 目录同时打印出最小距离对应的姓名和距离值用数据判断是光照问题还是阈值问题而不是凭感觉调参。第四是导出验证。签到几条记录后点导出按钮打开 CSV 确认中文不乱码、时间字段正确。python -m pip install -r requirements.txt python main.py我一般把系统拆成main.py作为唯一入口一行命令拉起注册和签到两种模式配合配置文件切换。这样不管谁拿到手都能在五分钟内从安装走到第一次签到成功。这一行命令背后是你对整套系统可靠性的判断它能不能在别人手里不出岔子地跑起来。提前用这份清单过一遍比答辩现场手忙脚乱强十倍希望帮到你。本文还有配套的精品资源点击获取
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →