拓冰建站拓冰建站
首页 / 资讯中心 / 正文

Python人脸识别签到系统:OpenCV与face_recognition实战

简介基于Python的人脸识别签到系统完整源码与配套文档打包面向希望掌握人脸检测、特征提取与身份识别全流程的开发者可用于课堂考勤、企业门禁等场景的快速原型搭建。资料系统梳理了人脸识别关键链路从Haar特征级联或SSD/YOLO模型完成人脸检测到LBP/HOG或CNN模型提取人脸深层特征再到基于欧氏距离、余弦相似度的签到匹配同时介绍OpenCV、dlib、face_recognition等常用库的工程实践并给出摄像头数据收集、图像预处理、签到匹配与界面展示等模块的代码组织方式。整体采用模块化设计便于按需替换检测与识别算法。压缩包共62个文件以27个Python脚本为核心另含14个txt说明文档、7个xlsx签到记录表、5个shell部署脚本以及yml配置、jpg/png示例图、xml与模型文件等整体667KB目录结构清晰便于按功能模块查阅。已有132人学习下载适合具备一定Python基础、希望深入理解人脸识别签到系统原理并快速二次开发的开发者参考。1. 为什么人脸识别签到系统值得用 Python 重写一遍考勤签到这个场景几乎每个单位都有但痛点从来没消失过指纹机怕手出汗IC 卡能代刷二维码截图能转给同事。人脸识别签到不是新概念闸机、门禁机早就用上了但它们大多是封闭方案数据导不出来、识别策略改不了、活体检测黑盒。Python 的好处在于把人脸识别从「买一台设备」变成了「写一段代码」用 OpenCV 和 face_recognition 两个库在普通笔记本摄像头上就能跑通「采集人脸 → 提取特征 → 比对身份 → 写入签到记录」的完整链条而且算法、阈值、存储逻辑全部可控。这篇文章就围绕这套系统讲清楚选型、代码、参数和坑位。适合两类人一是要自己搭考勤方案的后端工程师二是想用真实项目把 OpenCV 和 Python 技术栈串起来的开发者。2. 人脸识别签到系统的技术选型从算法库到硬件边界2.1 人脸检测与识别库对比Haar、Dlib 与深度模型怎么选人脸识别拆开看是两步第一步检测出图像里有没有脸、脸在哪第二步把脸转换成可比较的特征。很多初学者把 OpenCV 的 Haar 级联当成人脸识别其实 Haar 只是检测器它输出一个矩形框不做身份区分。真正决定「认不认得出来」的是特征提取和比对算法这一步有三个常见选择。Haar 级联检测器速度极快在 CPU 上也能跑几十帧但角度大了就丢脸侧脸、低头、戴帽子都会漏检。Dlib 的 HOG 检测器对正面和轻微侧脸的鲁棒性明显更好配合 68 点关键点模型还能做对齐face_recognition 库就是基于 Dlib 封装的适合中小规模离线应用。再往上就是深度模型比如 MTCNN、RetinaFace 和 ArcFace 这类开源人脸识别模型它们对光照、角度、遮挡的容忍度更高但需要 GPU 或较新的 CPU 才能实时跑。签到场景有一个特殊性人脸数量少通常几十到几百人但要求稳定识别和低误报所以「用一个通用开源模型提取 128 维特征再做距离判断」是最实用的路线。方案检测鲁棒性特征强度CPU 实时性适合场景OpenCV Haar LBPH较差弱高实验室演示Dlib face_recognition中等中等中中小规模签到MTCNN ArcFace高强低需 GPU多人、复杂光照签到系统通常和门禁联动人脸库上限可能就几百人Dlib 方案在精度和部署成本之间最均衡。若后期要支持海量人脸比对可以把特征向量存进 Milvus 或 FAISS 这类向量数据库但那是另一个量级的问题了。选型时还要注意开源免费商用的人脸识别模型中Dlib 的 BSD 协议和 face_recognition 的 MIT 协议都相对宽松闭源商用算法虽然精度更好但可能涉及授权费用和联网调用离线签到系统反而不适合依赖外部服务。2.2 签到系统的三段式架构采集、比对、记录不管代码怎么写人脸识别签到系统的物理流程只有三个阶段人脸样本采集、实时识别比对、签到结果落库。采集阶段解决的是「这个人长什么样」的问题通常为每个用户拍摄 5 到 10 张不同角度的照片提取特征向量后序列化保存。比对阶段是核心摄像头逐帧捕获图像检测人脸并提取特征与库里所有特征计算距离小于阈值就判定为同一个人。记录阶段需要处理重复签到、补签和异常日志不能只写一行时间。我见过不少翻车的实现问题都出在架构上采集照片时没有做质量校验模糊图片也入库比对时把全部计算放在主线程导致掉帧签到记录只有一张表没有考虑到一个人在一秒内会被多帧识别到。这些问题不是算法能解决的而是要在代码结构上提前规避。# 采集阶段的关键动作检测、裁切、质量检查、提取特征 import cv2 import face_recognition cap cv2.VideoCapture(0) user_id zhangsan samples [] while len(samples) 10: ret, frame cap.read() if not ret: break rgb cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) boxes face_recognition.face_locations(rgb) if len(boxes) 1: top, right, bottom, left boxes[0] # 裁出人脸区域并缩放到统一尺寸 face_region frame[top:bottom, left:right] if face_region.shape[0] 64 or face_region.shape[1] 64: cv2.putText(frame, too small, move closer, (30, 30), cv2.FONT_HERSHEY_SIMPLEX, 0.8, (0, 0, 255), 2) cv2.imshow(capture, frame) cv2.waitKey(1) continue encodings face_recognition.face_encodings(rgb, boxes) if encodings: samples.append(encodings[0]) print(fcollected {len(samples)} samples) cv2.imshow(capture, frame) cap.release() cv2.destroyAllWindows()这段代码的人脸检测返回的是 Dlib 风格的坐标先判断是否只有一张脸再检查人脸区域尺寸最后才提取特征。face_recognition.face_encodings返回一个 128 维 float 数组这个数组就是后续比对的依据。采集时故意限制len(boxes) 1防止误把背景里的照片、电视里的人脸录入库。质量检查只做了最小判断实际可以再加一个清晰度指标比如拉普拉斯方差。2.3 硬件与数据边界摄像头分辨率、光照和摄像头位置算法不是万能的签到系统难调的不是代码而是物理环境。摄像头分辨率不是越高越好1080p 在普通 USB 摄像头上传输带宽占用高而且 Dlib 处理大图明显变慢。实际测试下来640x480 到 1280x720 之间最合适分辨率太低则远处人脸无法检测太高则每帧推理耗时成倍增加。光照影响的是根本性问题逆光环境和昏暗走廊的识别率差距极大。face_recognition 的 HOG 检测器依赖梯度特征侧光和过暗都会让梯度信息衰减。我一般会在摄像头画面里加一个简单的预处理转成灰度后用直方图均衡化再做一次高斯模糊降噪虽然不能完全解决逆光但可以让检测稳定不少。摄像头安装位置也有讲究俯拍角度超过 30 度后人脸关键点变形严重最好让镜头与人眼高度接近。另一个被忽视的边界是用户数量。Dlib 的人脸比对是线性的库里每多一个人每一帧就要多算一次距离在树莓派这类低性能设备上超过 100 人时帧率会掉到 5 帧以下。这个问题的兜底方案是先把检测到的人脸裁出来再只对裁切图做编码和比对而不是在整帧图上反复调用。树莓派如果跑不动换用带 NPU 的边缘设备或者直接上 x86 工控机更省心。3. 用 Python 实现人脸识别签到系统的核心代码3.1 人脸录入与特征提取把脸变成向量录入模块不能只是存一张照片因为照片文件本身占用空间、可被篡改比对时还要重新提取特征。正确做法是录入阶段就把人脸编码成向量然后写成 pickle 文件或者存数据库。向量化之后一张脸在小范围内就是 128 个浮点数几百人也就几万个数内存和磁盘压力都极小。import os import pickle import cv2 import face_recognition def build_face_database(data_dir, output_pathface_db.pkl): known_encodings [] known_names [] for filename in os.listdir(data_dir): if not filename.lower().endswith((.jpg, .jpeg, .png)): continue user_id os.path.splitext(filename)[0] image cv2.imread(os.path.join(data_dir, filename)) rgb cv2.cvtColor(image, cv2.COLOR_BGR2RGB) boxes face_recognition.face_locations(rgb) encodings face_recognition.face_encodings(rgb, boxes) if len(encodings) 0: print(fskip {filename}: no face found) continue known_encodings.extend(encodings) known_names.extend([user_id] * len(encodings)) with open(output_path, wb) as f: pickle.dump({encodings: known_encodings, names: known_names}, f) print(fsaved {len(known_names)} face encodings to {output_path}) if __name__ __main__: build_face_database(faces/)一个用户如果录了 5 张照片向量库里会存 5 条记录比对时任何一条匹配成功都算命中。pickle.dump的字典里同时保存了名字和编码后续加载时直接反序列化就能用。录入时如果发现len(encodings) 0说明照片里没检测到人脸可能原因是照片模糊、人脸过小或者非正脸应该直接跳过而不是强行入库。到这一步系统还不涉及签到只是完成了「把人脸转成特征」这个基础。3.2 实时签到摄像头逐帧比对与签到逻辑实时识别是系统的核心循环。每帧图像都要经过检测、编码、比对三个步骤但不需要每帧都写入数据库否则会生成大量重复记录。常见做法是加入一个冷却时间同一个用户被连续识别到后在 30 秒内只记录一次签到。import pickle import time import cv2 import face_recognition with open(face_db.pkl, rb) as f: db pickle.load(f) known_encodings db[encodings] known_names db[names] last_check_in {} TOLERANCE 0.5 COOLDOWN_SECONDS 30 cap cv2.VideoCapture(0) while True: ret, frame cap.read() if not ret: break rgb cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) boxes face_recognition.face_locations(rgb) encodings face_recognition.face_encodings(rgb, boxes) for encoding in encodings: distances face_recognition.face_distance(known_encodings, encoding) best_match_idx distances.argmin() if distances[best_match_idx] TOLERANCE: user_id known_names[best_match_idx] now time.time() if now - last_check_in.get(user_id, 0) COOLDOWN_SECONDS: last_check_in[user_id] now print(fcheck-in: {user_id} at {time.strftime(%Y-%m-%d %H:%M:%S)}) else: print(unknown face) cv2.imshow(attendance, frame) if cv2.waitKey(1) 0xFF ord(q): break cap.release() cv2.destroyAllWindows()face_distance返回一个和known_encodings同样长度的浮点数组值越小表示越相似。取最小值后先和阈值比较再判断冷却时间是否已到这是避免重复签到最关键的一步。这里的TOLERANCE是那张脸的判断标准face_recognition 官方给的经验值是 0.6但实际使用时 0.4 到 0.5 更稳妥既能过滤长得像的同事又不至于把同一个人在不同光线下的脸误判成两个人。这一段只做了控制台输出生产环境应该把签到记录写入文件或数据库。另一个注意点是当画面里出现多张脸时encodings列表里会有多个元素循环内部依次处理即可不需要额外加锁因为 Python 的 GIL 会在face_distance调用期间让出控制权但写入数据库时仍建议用sqlite3的行级锁或单线程写入队列。3.3 签到记录落库SQLite 与 Excel 导出签到数据需要一个简单可靠的存储方案。SQLite 是 Python 自带 sqlite3 模块就能操作的文件型数据库零配置、单文件、支持并发读对签到这种低频写入场景完全够用。一张表记录用户 ID、签到时间、摄像头编号和置信度就足以支撑考勤查询。import sqlite3 import time from datetime import datetime conn sqlite3.connect(attendance.db) c conn.cursor() c.execute( CREATE TABLE IF NOT EXISTS check_in_logs ( id INTEGER PRIMARY KEY AUTOINCREMENT, user_id TEXT NOT NULL, check_in_time TEXT NOT NULL, conf REAL, camera_id TEXT ) ) def record_check_in(user_id, confidence, camera_idcam01): now datetime.now().strftime(%Y-%m-%d %H:%M:%S) c.execute( INSERT INTO check_in_logs (user_id, check_in_time, conf, camera_id) VALUES (?, ?, ?, ?), (user_id, now, confidence, camera_id), ) conn.commit()conf字段在 SQLite 里用 REAL 类型存储保存的是1 - distances[best_match_idx]语义上表示「这个人的脸有多像数据库里的那个人」。倒班场景需要统计上下午打卡次数SQL 就能轻松解决SELECT user_id, date(check_in_time) as day, MIN(check_in_time) as first_check_in FROM check_in_logs GROUP BY user_id, date(check_in_time);MIN(check_in_time)取的是当天第一次签到时间这在计算迟到时非常有用。如果需要给行政同事导出 Excelpandas.read_sql_query()一段代码就能把查询结果直接输出成 xlsx不需要额外的 form 表单或打印页面。4. 参数调优与排错识别不准、漏检、乱签到的应对4.1 影响识别准确率的 5 个关键参数签到系统上线后最常见的反馈是「我不认识我了」或者「我跟隔壁桌撞脸了」。这背后是几个参数的博弈。TOLERANCE是比对阈值调大会让同一个人在不同光线下的多张照片更容易匹配成功但也可能把两个人误判成同一个调小会降低误报但用户稍微换个发型就识别失败。DETECTION_MODEL可以从hog换成cnnCNN 模型准确率更高但需要 GPUCPU 跑 720p 视频只能到 1-2 帧适合用在录入照片这种非实时环节。NUM_JITTERS是提取特征时的重采样次数默认值 1调成 10 或 100 会对图片做畸变扰动后再平均特征录入时用 100 次可以让同一张照片的特征更稳定但处理时间会拉长到几秒。UPSAMPLE_TIMES是小脸检测的放大参数在 4 米外的位置人脸只有几十个像素时设置为 1 或 2 能显著提高漏检率代价是 CPU 负担成倍增加。最后的摄像头参数最容易忽略用cap.set(cv2.CAP_PROP_FRAME_WIDTH, 640)可以锁定分辨率很多 USB 摄像头默认输出 4:3 的 1280x960 大画面反而让 Dlib 处理变慢。4.2 常见运行报错与解决方案face_recognition 库安装本身就是一道坎。在 Windows 上pip install face_recognition会触发编译 dlib常见的报错是CMake must be installed to build the following extensions: dlib。解决办法是先装 Visual Studio Build Tools 选择 C 桌面开发组件然后pip install dlib最后再装 face_recognition。Linux 上则要注意libX11、libgtk等依赖apt install build-essential cmake libgtk-3-dev libboost-all-dev走一遍再 pip 安装会顺畅很多。运行时报错里高频的是Face recognition model file not found这代表关键点模型未下载或路径不对。模型文件在启动时会被 Dlib 拉起失败后要把 model 文件放进环境变量DLIB_MODELS_PATH指定的目录。另一个高频坑是摄像头在部分笔记本上被占用或驱动不兼容cap.isOpened()返回 False 时不要直接读帧先输出一段诊断文字。报错信息根因排查手段dlib.DLIB_USE_BLAS警告无 BLAS/LAPACK 加速库装 OpenBLAS 后重新编译AttributeError: NoneType object has no attribute shape摄像头读取失败检查cap.isOpened()kaboom: a face was sampled but sample face is not fully present检测到的人脸不在画面内降低UPSAMPLE_TIMES4.3 防作弊活体检测与签到频率限制照片开门、视频打卡是签到系统绕不开的作弊场景。基于人脸特征比对的技术框架本身无法区分活体和照片因为照片里的人脸同样能提取出特征向量。一个轻量缓解方案是要求用户「眨眼」或「点头」通过连续几帧的关键点距离变化来判断但实现复杂且用户配合度低。更实用的做法是把签到场景限定为固定摄像头的单人通道同时记录摄像头编号配合打卡时间统计异常。代码层面的防重复签到是底线一个人 30 秒内只能签一次同时在数据库里对user_id和check_in_time加唯一索引双保险避免并发插入产生重复记录。真正的活体检测需要引入mediapipe采集虹膜、点头、摇头等动作序列属于进阶功能建议在基础签到跑通后再迭代。5. 把签到数据变成考勤看板Pandas 实时统计与异常标记签到系统的价值不在签到那一瞬间而在数据沉淀之后的统计分析。用 Pandas 直接连接 SQLite可以几行代码生成迟到人数、早退趋势、缺卡名单。日常考勤看板通常是「按天查询最早和最晚打卡时间」这里有一个容易踩坑的地方check_in_time最好存 ISO 格式字符串否则 SQLite 的日期函数无法正确解析。import sqlite3 import pandas as pd conn sqlite3.connect(attendance.db) df pd.read_sql_query(SELECT * FROM check_in_logs, conn) df[check_in_time] pd.to_datetime(df[check_in_time]) df[date] df[check_in_time].dt.date daily_stats ( df.groupby([user_id, date]) .agg(first_check_in(check_in_time, min), last_check_out(check_in_time, max), count(check_in_time, nunique)) .reset_index() ) daily_stats[late] daily_stats[first_check_in] pd.Timestamp(09:05:00)用nunique统计每天签到次数是因为一个人不可能在同一分钟内签两次这个字段能直接筛出异常重复打卡。late列按项目值守时间 9 点整加 5 分钟宽限计算行政人员关注的就是这个字段。如果摄像头是固定的还可以把camera_id加进分组用来排查同一个用户在不同摄像头下同时打卡的异常事件。进阶玩法是给daily_stats增加一个滚动迟到率字段用滚动窗口观察个人迟到趋势是否变坏比只出日报更有实际价值。另一个实用技巧是把历史特征向量导出为 JSON 文件配合不同的TOLERANCE做离线回放测试模拟当天摄像头录制的帧找出最不容易误判的阈值。这种做法相当于给签到系统加了一层回归测试以后换摄像头或调整算法时不必依赖现场实测就能评估影响。本文还有配套的精品资源点击获取
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门