
简介本资源是一个基于Python实现的轻量级GUI人脸识别签到系统面向人工智能初学者、高校课程设计学生及中小型考勤场景开发者解决传统人工签到效率低、易代签等问题。压缩包共20个文件含6个核心Python源码如create_dataset.py、camera_use.py、file_processing.py等、4个编译后pyc文件、4个XML配置/模型文件含Haar级联分类器、1个JPG/JPEG人脸示例图、1个Numpy特征向量文件faceEmbedding.npy及README说明文档整体仅95KB便于快速部署与二次开发。已有3987人学习下载资源结构清晰模块分工明确涵盖数据采集、特征编码、实时识别、GUI交互与签到记录全流程配套注释完整支持本地摄像头调用与离线识别无需复杂环境配置可直接运行调试并拓展为课堂考勤、会议签到等实用场景。1. 为什么用 Python 做人脸识别签到系统比买现成门禁机更可控、更可迭代你刚接手一个高校实验室的考勤需求30人以内、固定教室、无闸机硬件、要求离线运行、能导出 Excel、还要支持新成员“现场拍照注册”。采购商推的所谓“人脸识别门禁机”报价八千起步SDK 文档残缺、不开放人脸特征向量、升级靠厂商排期——结果连换张背景图都让识别率掉 40%。而用 Python 自建一套从摄像头采集→人脸检测→特征提取→相似度匹配→日志落库→导出报表全链路代码可控调试时能直接print(embedding.shape)看向量维度是否对齐误识时能回溯到某帧图像查光照干扰新增“戴口罩识别”只需替换 backbone 模型不用等固件升级。这不是炫技是把签到系统从黑匣子变成白盒工具——适合有基础 Python 能力会 pip install、写函数、读 CSV、懂 OpenCV 基础操作、愿意花半天搭起最小可行版本的工程师或教务管理员。本文就带你用 200 行核心代码在普通笔记本上跑通一个真正能投入日常使用的 Python 人脸识别签到系统不依赖云 API、不调用闭源 SDK、所有模型和逻辑全部本地化。2. 用 dlib face_recognition 构建最小闭环5 分钟跑通本地摄像头签到2.1 为什么选 face_recognition 而不是 OpenCV Haar 或 MTCNN很多人一上来就翻 OpenCV 的cv2.CascadeClassifier(haarcascade_frontalface_default.xml)但 Haar 检测器在侧脸、低光照、戴眼镜场景下漏检率极高MTCNN 虽准但需要 TensorFlow 1.x 环境与当前主流 PyTorch 生态割裂。而face_recognition库底层调用的是 dlib 的 68 点 landmark 检测 ResNet-34 编码器它在 CPU 上单帧推理约 350msi5-8250U精度远超 Haar且封装极简face_recognition.face_encodings(img)一行返回 128 维特征向量。更重要的是它默认使用cnn_face_detector基于 HOGLinear SVM 的 CNN 变体对模糊、小角度人脸鲁棒性显著优于传统方法。我们实测在 720p 教室摄像头画面中dlib 检出率 92.3%Haar 仅 68.1%测试集含 200 张不同姿态照片。这不是理论优势是真实场景下的血泪经验——别在检测环节就开始翻车。2.2 安装与环境隔离避开 dlib 编译地狱的三步法提示face_recognition依赖 dlib而 dlib 在 Windows 上编译失败率超 70%。必须绕过源码编译用预编译 wheel。# 步骤 1创建干净虚拟环境避免污染全局 Python python -m venv fr_env fr_env\Scripts\activate # Windows # fr_env/bin/activate # macOS/Linux # 步骤 2安装预编译 dlib关键 pip install --upgrade pip pip install https://github.com/jlohk/face-recognition-models/releases/download/v1.0.0/dlib-19.24.1-cp39-cp39-win_amd64.whl # 替换链接中的 cp39 为你 Python 版本python -c import sys; print(fcp{sys.version_info.major}{sys.version_info.minor}) # 步骤 3安装 face_recognition自动跳过 dlib 编译 pip install face_recognition参数说明cp39表示 Python 3.9务必与你python --version输出一致否则报ImportError: DLL load failed链接来自社区维护的 wheel 仓库非官方但经 300 项目验证比conda install -c conda-forge dlib更稳定若用 macOS ARM64M1/M2改用pip install dlib --no-depspip install face_recognition因官方 wheel 尚未支持 arm64。2.3 三分钟写完签到主循环从摄像头捕获到匹配打分import cv2 import face_recognition import numpy as np import os from datetime import datetime # 1. 加载已知人脸库文件夹下每人一张正脸照命名如 zhangsan.jpg known_encodings [] known_names [] for filename in os.listdir(known_faces): if filename.endswith(.jpg) or filename.endswith(.png): img_path os.path.join(known_faces, filename) image face_recognition.load_image_file(img_path) encodings face_recognition.face_encodings(image) if len(encodings) 0: known_encodings.append(encodings[0]) known_names.append(filename.split(.)[0]) # 2. 启动摄像头0 是默认摄像头1 是外接 USB 摄像头 video_capture cv2.VideoCapture(0) video_capture.set(cv2.CAP_PROP_FRAME_WIDTH, 1280) video_capture.set(cv2.CAP_PROP_FRAME_HEIGHT, 720) # 3. 主循环逐帧处理 while True: ret, frame video_capture.read() if not ret: break # 缩放加速face_recognition 对高分辨率帧极慢 small_frame cv2.resize(frame, (0, 0), fx0.5, fy0.5) rgb_small_frame cv2.cvtColor(small_frame, cv2.COLOR_BGR2RGB) # 检测所有人脸位置 编码 face_locations face_recognition.face_locations(rgb_small_frame) face_encodings face_recognition.face_encodings(rgb_small_frame, face_locations) # 匹配已知人脸 for (top, right, bottom, left), face_encoding in zip(face_locations, face_encodings): matches face_recognition.compare_faces(known_encodings, face_encoding, tolerance0.45) name Unknown face_distances face_recognition.face_distance(known_encodings, face_encoding) if len(face_distances) 0: best_match_index np.argmin(face_distances) if matches[best_match_index]: name known_names[best_match_index] # 打印匹配距离越小越准0.4~0.5 是安全阈值 print(fMatched {name} with distance {face_distances[best_match_index]:.3f}) # 在画面上框出人脸并标注 top, right, bottom, left [x * 2 for x in [top, right, bottom, left]] # 还原缩放 cv2.rectangle(frame, (left, top), (right, bottom), (0, 255, 0), 2) cv2.putText(frame, name, (left, top - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 0), 2) cv2.imshow(Face Recognition Check-in, frame) if cv2.waitKey(1) 0xFF ord(q): # 按 q 退出 break video_capture.release() cv2.destroyAllWindows()逻辑说明tolerance0.45是核心参数值越小越严格0.4 时几乎不误识但可能拒真0.5 时接受更多变化但需防冒名face_distance返回欧氏距离非相似度分数距离 0.45 ≈ 相似度 92%经 LFW 数据集标定cv2.resize(..., fx0.5)是性能关键原始 1280×720 帧在 CPU 上编码耗时 1.2s缩放后降至 350ms流畅度提升 3 倍known_faces/文件夹结构必须为zhangsan.jpg,lisi.png—— 文件名即姓名无空格无中文避免路径编码问题。3. 把“认出人”变成“有效签到”数据库记录、防重复、导出报表3.1 用 SQLite 存储签到日志轻量、免服务、单文件部署Python 自带sqlite3无需安装额外服务。我们设计三张表students学生基本信息id, name, student_idsessions签到场次session_id, date, locationcheckins签到记录id, student_id, session_id, timestamp, device_idimport sqlite3 from datetime import datetime def init_db(): conn sqlite3.connect(checkin.db) c conn.cursor() c.execute( CREATE TABLE IF NOT EXISTS students ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT UNIQUE NOT NULL, student_id TEXT UNIQUE ) ) c.execute( CREATE TABLE IF NOT EXISTS sessions ( session_id INTEGER PRIMARY KEY AUTOINCREMENT, date DATE NOT NULL, location TEXT DEFAULT Lab-A, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ) ) c.execute( CREATE TABLE IF NOT EXISTS checkins ( id INTEGER PRIMARY KEY AUTOINCREMENT, student_id INTEGER NOT NULL, session_id INTEGER NOT NULL, timestamp DATETIME DEFAULT CURRENT_TIMESTAMP, device_id TEXT DEFAULT webcam-01, FOREIGN KEY(student_id) REFERENCES students(id), FOREIGN KEY(session_id) REFERENCES sessions(session_id), UNIQUE(student_id, session_id) -- 防止同一人同场次重复签到 ) ) conn.commit() conn.close() init_db()参数说明UNIQUE(student_id, session_id)是防重复签到的核心约束插入重复时抛sqlite3.IntegrityError而非覆盖device_id字段预留多终端支持如未来接入树莓派摄像头location默认Lab-A实际部署时可从配置文件读取避免硬编码。3.2 实时签到逻辑匹配成功后写入数据库并去重在主循环的匹配分支中插入# 替换原匹配后的 print 语句加入数据库写入 if matches[best_match_index]: name known_names[best_match_index] # 1. 获取学生 ID从 students 表查 conn sqlite3.connect(checkin.db) c conn.cursor() c.execute(SELECT id FROM students WHERE name ?, (name,)) result c.fetchone() if result is None: # 新用户自动注册生产环境建议关闭此功能改为管理员审核 c.execute(INSERT INTO students (name) VALUES (?), (name,)) student_id c.lastrowid conn.commit() else: student_id result[0] # 2. 获取当前场次 ID每日只开一场按日期查 today datetime.now().date().isoformat() c.execute(SELECT session_id FROM sessions WHERE date ?, (today,)) session_result c.fetchone() if session_result is None: c.execute(INSERT INTO sessions (date) VALUES (?), (today,)) session_id c.lastrowid conn.commit() else: session_id session_result[0] # 3. 尝试插入签到记录UNIQUE 约束自动拦截重复 try: c.execute( INSERT INTO checkins (student_id, session_id) VALUES (?, ?), (student_id, session_id) ) conn.commit() print(f[✓] {name} signed in at {datetime.now().strftime(%H:%M:%S)}) except sqlite3.IntegrityError: print(f[!] {name} already checked in today) finally: conn.close()关键点每次签到触发 3 次查询 1 次插入但 SQLite 在单文件模式下并发写入锁粒度小30 人教室完全够用UNIQUE约束比应用层判断更可靠——即使两个进程同时执行数据库保证只存一条新用户自动注册是开发期便利功能上线前务必注释掉INSERT INTO students部分改为人工导入名单。3.3 导出 Excel 报表用 pandas 一键生成带格式的考勤表import pandas as pd def export_daily_report(date_strtoday): 导出指定日期的签到报表 if date_str today: date_str datetime.now().date().isoformat() conn sqlite3.connect(checkin.db) # 关联查询学生名、学号、签到时间 query SELECT s.name, s.student_id, c.timestamp FROM checkins c JOIN students s ON c.student_id s.id JOIN sessions ses ON c.session_id ses.session_id WHERE ses.date ? ORDER BY c.timestamp df pd.read_sql_query(query, conn, params(date_str,)) conn.close() if df.empty: print(fNo check-ins found for {date_str}) return # 添加序号列 df.insert(0, No., range(1, len(df) 1)) # 导出 Excel需安装 openpyxlpip install openpyxl filename fcheckin_report_{date_str}.xlsx df.to_excel(filename, indexFalse, engineopenpyxl) # 用 openpyxl 加粗表头 from openpyxl import load_workbook wb load_workbook(filename) ws wb.active for cell in ws[1]: cell.font openpyxl.styles.Font(boldTrue) wb.save(filename) print(f✅ Report exported: {filename}) # 调用示例 export_daily_report() # 导出今日报表参数说明engineopenpyxl是必须指定的否则to_excel默认用 xlwt不支持 .xlsxopenpyxl.styles.Font(boldTrue)让表头加粗提升可读性df.insert(0, No., ...)插入序号列方便人工核对实际部署时可将此函数绑定到 GUI 按钮或设置定时任务每日凌晨自动生成。4. 避坑指南人脸识别签到系统在真实教室环境的 5 个致命陷阱4.1 现象摄像头画面正常但face_recognition.face_locations()总返回空列表原因face_recognition默认使用modelhogHOGSVM对低光照、背光、侧脸极不敏感而教室常见场景是窗户在身后背光、投影仪亮光干扰局部过曝、学生歪头看屏幕侧脸。解决强制启用 CNN 检测器精度提升 35%但 CPU 负载翻倍# 替换原 face_locations 行 face_locations face_recognition.face_locations(rgb_small_frame, modelcnn)注意CNN 模型需额外下载mmod_human_face_detector.datface_recognition会自动下载首次运行较慢且必须确保dlib版本 ≥ 19.22。4.2 现象同一个人多次签到face_distance返回值波动极大0.32 → 0.51 → 0.43原因face_recognition的编码器对图像质量极度敏感——轻微运动模糊、白平衡偏移、JPEG 压缩失真都会导致特征向量漂移。而教室摄像头自动白平衡频繁调整造成同一人脸不同帧编码差异。解决对每张注册照片做预处理并在实时帧中取多帧平均# 注册时对 known_faces 中每张图做标准化 def preprocess_face_image(img_path): img face_recognition.load_image_file(img_path) # 转灰度 直方图均衡化增强低光照细节 gray cv2.cvtColor(img, cv2.COLOR_RGB2GRAY) clahe cv2.createCLAHE(clipLimit2.0, tileGridSize(8,8)) enhanced clahe.apply(gray) # 转回 RGB 供 face_recognition 使用 enhanced_rgb cv2.cvtColor(enhanced, cv2.COLOR_GRAY2RGB) return enhanced_rgb # 实时匹配时缓存最近 3 帧编码取平均向量 frame_encodings [] if len(face_encodings) 0: frame_encodings.append(face_encodings[0]) if len(frame_encodings) 3: frame_encodings.pop(0) if len(frame_encodings) 3: avg_encoding np.mean(frame_encodings, axis0) matches face_recognition.compare_faces(known_encodings, avg_encoding, tolerance0.42)4.3 现象签到成功后Excel 报表里学生姓名全是乱码如æŽå原因SQLite 默认使用 UTF-8但pandas.to_excel()在 Windows 上若未指定encoding会用系统默认编码GBK导致中文写入 Excel 时错乱。解决to_excel不处理编码而是确保数据库读取时指定 charset# 修改数据库连接方式 conn sqlite3.connect(checkin.db) conn.text_factory str # 强制文本以 str 类型返回Python 3 默认但显式声明更稳 # 或者用 pandas.read_sql_query 时加参数 df pd.read_sql_query(query, conn, params(date_str,), dtypestr)4.4 现象程序运行 2 小时后卡死cv2.VideoCapture.read()返回False原因OpenCV 的 VideoCapture 在长时间运行时存在内存泄漏尤其在 Windows 上驱动兼容性差。解决添加心跳检测与自动重连# 在主循环开头加入 ret, frame video_capture.read() if not ret: print([⚠] Camera disconnected, attempting reconnection...) video_capture.release() video_capture cv2.VideoCapture(0) video_capture.set(cv2.CAP_PROP_FRAME_WIDTH, 1280) video_capture.set(cv2.CAP_PROP_FRAME_HEIGHT, 720) continue4.5 现象多人同时出现在画面中系统只识别最左边的人其余被忽略原因face_recognition.face_encodings()默认只返回第一个检测到的人脸编码当传入num_jitters1且未指定known_face_locations时。解决显式遍历所有检测到的位置# 原代码中 face_encodings 行改为 face_encodings face_recognition.face_encodings( rgb_small_frame, face_locations, # 显式传入位置确保一一对应 num_jitters1 # 抗噪参数1默认10更准但更慢 ) # 后续 zip 循环保持不变自然支持多人5. 进阶技巧用 face_recognition OpenCV 实现“活体检测”防照片代签5.1 为什么必须加活体检测真实教训告诉你去年我们部署在某中学计算机教室的系统第三周就被学生用手机相册里的自拍照片“刷脸”通过——因为face_recognition只校验人脸特征不区分是真人还是屏幕反射。测试显示iPhone 12 屏幕播放静态人脸视频识别通过率 89%打印 A4 照片贴在平板上通过率 73%。这已不是技术 demo而是考勤失效。解决方案不是上红外双摄硬件而是用纯软件活体检测利用眨眼、头部微动、瞳孔反光等生物信号。5.2 基于眼部纵横比EAR的眨眼检测15 行代码实现import math def eye_aspect_ratio(eye): 计算单只眼睛的纵横比 EAR (|p2-p6| |p3-p5|) / (2*|p1-p4|) A math.dist(eye[1], eye[5]) B math.dist(eye[2], eye[4]) C math.dist(eye[0], eye[3]) return (A B) / (2.0 * C) # 在主循环中获取 landmarks 后计算 EAR face_landmarks_list face_recognition.face_landmarks(rgb_small_frame, face_locations) for face_landmarks in face_landmarks_list: left_eye face_landmarks[left_eye] right_eye face_landmarks[right_eye] left_ear eye_aspect_ratio(left_eye) right_ear eye_aspect_ratio(right_eye) avg_ear (left_ear right_ear) / 2.0 # EAR 0.22 表示闭眼经 1000 帧标定 if avg_ear 0.22: blink_counter 1 else: if blink_counter 2: # 连续 2 帧闭眼才计为一次眨眼 blink_count 1 blink_counter 0 else: blink_counter 0 # 要求至少眨眼 1 次才允许签到防静态照片 if blink_count 1 and matches[best_match_index]: # 执行签到逻辑... blink_count 0 # 重置避免重复触发参数说明math.dist(p1, p2)是 Python 3.8 内置函数替代np.linalg.norm(np.array(p1)-np.array(p2))更轻量blink_counter防抖单帧闭眼可能是眨眼连续 2 帧才确认blink_count累计达到 1 即解锁签到之后重置确保每次签到都需主动眨眼。5.3 头部姿态估计用 solvePnP 判断是否正对镜头def get_head_pose(landmarks, frame_shape): 估算头部旋转角pitch, yaw, roll # 3D 模型点单位mm基于标准人脸尺寸 object_pts np.float32([ [0, 0, 0], # 鼻尖 [0, -330, -65], # 下巴 [-225, 170, -135], # 左眼左角 [225, 170, -135], # 右眼右角 [-150, -150, -125], # 左嘴角 [150, -150, -125] # 右嘴角 ]) # 2D 图像点像素坐标 image_pts np.float32([landmarks[nose_tip][0], landmarks[chin][0], landmarks[left_eye][0], landmarks[right_eye][0], landmarks[top_lip][0], landmarks[bottom_lip][0]]) # 相机内参近似值实际应标定 focal_length frame_shape[1] center (frame_shape[1]/2, frame_shape[0]/2) camera_matrix np.array([ [focal_length, 0, center[0]], [0, focal_length, center[1]], [0, 0, 1] ], dtypedouble) # 求解姿态 success, rotation_vec, translation_vec cv2.solvePnP( object_pts, image_pts, camera_matrix, None ) if not success: return None, None # 转为欧拉角 rotation_mat, _ cv2.Rodrigues(rotation_vec) pose_mat cv2.hconcat((rotation_mat, translation_vec)) _, _, _, _, _, _, euler_angle cv2.decomposeProjectionMatrix(pose_mat) return euler_angle[0][0], euler_angle[1][0], euler_angle[2][0] # pitch, yaw, roll # 在主循环中调用 if face_landmarks_list: pitch, yaw, roll get_head_pose(face_landmarks_list[0], frame.shape) # 正面阈值pitch ∈ [-20°, 20°], yaw ∈ [-25°, 25°], roll ∈ [-15°, 15°] if abs(pitch) 20 and abs(yaw) 25 and abs(roll) 15: head_pose_ok True落地要点solvePnP需要精确的 3D 模型点这里用标准人脸尺寸近似误差 5°足够用于活体判断yaw左右摇头最易被照片欺骗故阈值设得最严±25°实际部署时可将head_pose_ok与blink_count 1同时满足作为签到前提双重防伪。5.4 我的最终工作流每天早上 5 分钟完成系统健康检查我给自己定了个铁律每次上课前用这三步快速验证系统可用性摄像头自检运行python -c import cv2; ccv2.VideoCapture(0); print(c.read()[0])输出True即通人脸库校验python -c import face_recognition; print(len(face_recognition.face_encodings(face_recognition.load_image_file(known_faces/zhangsan.jpg))))输出1表示注册图可编码活体检测压测对着摄像头快速眨眼 3 次 左右摇头观察控制台是否打印[✓] zhangsan signed in...且 Excel 报表更新。这比写 100 行单元测试更直观——毕竟教室里没 Jenkins只有学生等着打卡。系统不是越复杂越好而是越简单越可靠。我把face_recognition当作一个高精度传感器把 OpenCV 当作它的信号调理电路把 SQLite 当作数据记录仪——不追求 SOTA 模型只确保每个模块在教室环境下 99% 时间可用。希望帮到你。本文还有配套的精品资源点击获取