Python人脸识别签到系统全流程:摄像头识别到考勤记录

发布时间:2026/10/10 11:46:53
Python人脸识别签到系统全流程:摄像头识别到考勤记录
简介这是一套基于OpenCV与深度学习模型实现的Python人脸识别签到系统源码包面向具备Python基础、希望完成课堂或会议自动签到的开发者覆盖图像采集、人脸特征提取、身份匹配、考勤记录与数据导出等完整流程。压缩包共23个文件约1.93MB其中包含3个Python主程序、2个UI界面文件、4个XML配置、5张图片以及Markdown说明文档、CSV考勤数据等便于直接查看逻辑、修改界面或扩展功能。资源已吸引1483人学习适合作为毕业设计、课程项目或企业考勤系统改造的参考原型。除核心代码外还提供项目说明、界面资源与运行配置文件可帮助理解FaceNet/MTCNN等模型在本地应用中的落地方式以及Tkinter/PyQt界面与考勤数据管理模块的整合思路。1. Python人脸识别签到从摄像头到考勤记录我为什么建议先做最小闭环人脸识别签到的需求场景太常见了小团队考勤、培训签到、实验室门禁甚至教室点名。买一台人脸识别门禁机要几百上千还要配云端管理后台数据还不一定导得出来。而用Python加一个普通USB摄像头几百行代码就能跑通从人脸检测到签到记录的全流程成本低、逻辑透明还能随时改规则。这个方案不是要硬刚大厂的商用系统而是给“刷脸签个到记录谁几点来了”这种需求一个可控的落地路径。这篇文章会按“选型→注册→识别→记录→避坑”的顺序讲每一步都给可复现代码你照着抄就能跑通第一版。2. 人脸识别技术选型OpenCV、dlib 还是 face_recognition2.1 先搞清楚人脸识别链路的四个步骤任何一个人脸识别系统本质上都是四步走人脸检测、人脸对齐、特征提取、特征比对。人脸检测负责在画面里把人脸框出来对齐是把脸摆正防止歪头影响精度特征提取是把人脸转换成一组数值向量比对则是拿这组向量和库里提前存好的向量算距离。很多人只盯着“识别”两个字实际上前三步决定了最后的准确率第四步只是一个余弦相似度或欧氏距离的计算。理解了链路你就能明白为什么不能直接从网上找一个模型就开始用。模型只是特征提取的一部分人脸检测用的是另一个模型对齐可能是几何变换比对阈值也需要根据实际场景调。把这些零部件拼起来才是Python人脸识别签到的完整方案。如果只想做个带签到记录的demo最省事的路径就是直接用封装好的库把中间细节包起来。2.2 三种常用方案的优缺点和适用规模方案特征提取方式优点缺点适合场景OpenCV LBPH局部二值模式直方图安装简单、CPU快、无需额外模型对光照和角度极敏感准确率低10人以内、固定角度、环境稳定的功能验证dlib ResNet深度学习网络输出128维向量准确率高离线可用可微调阈值安装麻烦需要CMake和编译器CPU推理慢中小团队、离线环境、对精度有要求face_recognition封装dlib直接提供接口API友好几行代码完成检测和特征提取依赖dlib模型文件约100MB速度一般快速原型、50人左右签到、开发效率优先我一般会直接推荐face_recognition尤其是你只有半天时间想要跑通签到流程的时候。它把人脸检测和128维特征提取都封装好了调一个函数就能拿到人脸向量。缺点是它比较吃资源如果你要在树莓派这种设备上跑那就改成dlib的HOG检测加上ResNet特征或者干脆用OpenCV自带的LBPH——但LBPH的准确率在签到场景里真的会翻车光线一变就认不出人。2.3 环境安装一条命令解决大部分依赖选型定了以后环境安装是第一个门槛。face_recognition底层依赖dlib而dlib在Windows上装起来有不少玄学。我的建议是用Python 3.8或3.9配合pip直接装成功率最高。python -m venv venv source venv/bin/activate # Windows下用 venv\Scripts\activate pip install opencv-python face_recognition numpy这段命令创建虚拟环境并安装依赖。opencv-python提供摄像头捕获和图像处理face_recognition提供人脸识别核心能力numpy用来处理特征向量。参数说明如果你在Windows上遇到dlib编译错误先装Visual Studio Build Tools勾选C桌面开发组件或者直接安装预编译版比如用pip install dlib19.24.4指定版本这个版本在多数环境下有预编译wheel。装完以后跑一下python -c import face_recognition不报错就说明环境通了。这一步是你后面所有代码的地基地基没打好后面全是坑。3. 人脸注册与特征入库采集照片、提取128维向量、存成文件3.1 人脸注册要解决的核心问题签到前必须先把人的“脸”存进库里。很多人以为注册就是拍一张照片保存下来等到签到时拿照片对比原始图片。这种做法的最大问题是每次比对都要重新提取特征速度慢而且存原图有隐私风险。正确的做法是注册时就把人脸转换成一个128维的特征向量库里只存向量和对应的姓名、工号。比对时也是把摄像头抓到的脸转换成向量然后算向量距离。这样不仅速度快而且即使原始照片泄露也无法还原人脸图像。这里还要注意一张脸可能在不同角度下特征不同所以注册时最好多拍几张。常见做法是一个人拍三到五张取平均特征。这样能减少姿态和表情带来的干扰。如果是临时测试先用一张高清正面照也行但真实使用我建议至少三张。3.2 编写注册脚本自动检测人脸并提取特征下面这个脚本会遍历photos/目录下以人名为文件夹存放的照片提取每张脸的特征把同一个人名的所有特征取平均最后保存到face_db.npz。import os import cv2 import numpy as np import face_recognition face_db {} data_dir photos for person_name in os.listdir(data_dir): person_dir os.path.join(data_dir, person_name) if not os.path.isdir(person_dir): continue encodings [] for img_name in os.listdir(person_dir): img_path os.path.join(person_dir, img_name) # 用OpenCV读图再转成RGBface_recognition需要RGB格式 img cv2.imread(img_path) # 中文路径下OpenCV可能会读取失败下面这行是健壮性处理 if img is None: with open(img_path, rb) as f: data np.frombuffer(f.read(), dtypenp.uint8) img cv2.imdecode(data, cv2.IMREAD_COLOR) img_rgb cv2.cvtColor(img, cv2.COLOR_BGR2RGB) # 检测人脸并提取特征location传空默认检测最大人脸 face_locations face_recognition.face_locations(img_rgb, modelhog) if len(face_locations) ! 1: print(f{img_path}: 检测到 {len(face_locations)} 张脸跳过) continue encoding face_recognition.face_encodings( img_rgb, known_face_locationsface_locations, num_jitters10 )[0] encodings.append(encoding) if encodings: avg_encoding np.mean(encodings, axis0) face_db[person_name] avg_encoding print(f已注册 {person_name}用了 {len(encodings)} 张照片) np.savez_compressed(face_db.npz, **face_db)这段代码先遍历人名目录然后对每张照片调用face_recognition.face_locations做人脸检测再用face_encodings提取128维向量。关键参数modelhog表示用HOG模型CPU就能跑如果换成cnn更准但慢适合GPU或离线处理。num_jitters10表示对图片进行10次轻微抖动采样后取平均特征数值越大越稳定但耗时成倍增加注册时设为10识别时通常设为1就够了。3.3 特征库的存储与加载上面脚本生成的是压缩的npz文件打开以后是一个字典键是人名值是128维浮点向量。这个文件就是你的签到系统“黑匣子”之外那部分——注册和识别共用同一个库所以必须确保注册质量。如果某张照片拍到一半脸在外面检测到0张脸会跳过如果拍到多个人也跳过避免特征乱套。加载的时候用np.load(face_db.npz, allow_pickleTrue)得到的是一个类似字典的对象可以直接用键访问。我建议在正式使用前写个小脚本把库里每个人的特征和名字打印出来确认数量对得上。特征库文件很小一百人也就几十KB复制迁移都很方便不像存照片那样占空间。4. 签到主流程实现摄像头识别、数据库记录、去重防刷4.1 签到逻辑的边界条件识别出“这个人是谁”只是半程另半程是把“谁在什么时候打了卡”记下来。签到场景有几个边界条件必须处理同一人同一天不能重复签到、非库内人员要提示“未注册”、无人脸时不误报、摄像头画面里出现多张脸时怎么处理。我的常见做法是每次识别到脸后先比对特征库如果距离小于阈值就判定为签到成功然后查数据库看此人今天是否已经有记录有则提示“已签到”没有则插入一条新记录。阈值设置很关键。face_recognition默认的比对阈值是0.6欧氏距离越小越严格。实战中0.5到0.55更常见因为签到画质通常一般阈值太松会导致不同人被误认成同一人。这个值不是玄学最好拿你们团队的人脸数据跑一遍画出相同人和不同人的距离分布然后取分界点。后文会在避坑章节详细说。4.2 实时识别数据库写入代码下面这个脚本从face_db.npz加载特征库打开摄像头逐帧检测人脸并识别识别成功后写入SQLite数据库。import sqlite3 import datetime import numpy as np import cv2 import face_recognition # 加载特征库 data np.load(face_db.npz, allow_pickleTrue) known_names list(data.keys()) known_encodings [data[name] for name in known_names] # 初始化SQLite建表 conn sqlite3.connect(attendance.db) conn.execute(CREATE TABLE IF NOT EXISTS checkin ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, check_time TEXT NOT NULL, date TEXT NOT NULL )) conn.commit() def is_checked_in_today(name): today datetime.date.today().isoformat() cur conn.execute( SELECT 1 FROM checkin WHERE name? AND date?, (name, today) ) return cur.fetchone() is not None def record_checkin(name): now datetime.datetime.now() today now.date().isoformat() time_str now.strftime(%H:%M:%S) conn.execute( INSERT INTO checkin (name, check_time, date) VALUES (?,?,?), (name, time_str, today) ) conn.commit() print(f{name} 签到成功时间 {time_str}) # 摄像头识别循环 cap cv2.VideoCapture(0) cap.set(cv2.CAP_PROP_FRAME_WIDTH, 640) cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 480) while True: ret, frame cap.read() if not ret: print(摄像头读取失败) break # 缩小画面加快处理同时保持比例 small_frame cv2.resize(frame, (0, 0), fx0.5, fy0.5) rgb_small cv2.cvtColor(small_frame, cv2.COLOR_BGR2RGB) # 检测人脸位置 face_locations face_recognition.face_locations(rgb_small, modelhog) if face_locations: face_encodings face_recognition.face_encodings(rgb_small, face_locations) for face_encoding in face_encodings: # 与库中所有人比对拿到最小距离 distances face_recognition.face_distance(known_encodings, face_encoding) min_index np.argmin(distances) min_distance distances[min_index] if min_distance 0.5: name known_names[min_index] if is_checked_in_today(name): print(f{name} 今天已签到) else: record_checkin(name) else: print(未注册人脸) else: print(未检测到人脸) if cv2.waitKey(1) 0xFF ord(q): break cap.release() cv2.destroyAllWindows() conn.close()这段代码的逻辑很直白先缩小画面到一半尺寸因为face_recognition处理大图很慢缩小后速度提升明显。face_locations返回每个检测到的人脸位置然后对每个位置提取特征。face_distance计算当前脸与库中所有人脸的欧氏距离取最小距离的那个作为候选和0.5的阈值比较。为防同一天重复签到先查SQLite里今天有没有记录有则忽略没有则插入。参数说明CAP_PROP_FRAME_WIDTH和CAP_PROP_FRAME_HEIGHT分别设置摄像头分辨率640x480比较平衡画质和速度。fx0.5表示缩小一半识别速度大约是原图的四倍。识别的阈值0.5是硬编码的实际使用建议根据测试调整到0.45~0.55之间。这里的SQLite表结构提供了date字段专门用于日期去重。4.3 为什么用SQLite而不是Excel签到记录至少要支持查询、去重、统计用Excel文件直接append看似简单但并发写入时会锁文件而且查询某个人今天是否已签到需要遍历整个表格。SQLite是单文件数据库Python内置不需要额外服务完美匹配单人签到场景。后面如果要导出Excel直接用pandas读SQLite再写excel几分钟就能搞定。如果你需要多人同时从不同摄像头签到那SQLite也可以只要写入操作串行化即可。但像“同一时间多个摄像头同时人脸识别”这种需求最好还是用MySQL或PostgreSQLSQLite在并发写入多的时候会报database locked。好在多数签到场景是单人单机SQLite足够。5. 常见问题与排查光线、乱码、装不上、识别不准的4个实例5.1 dlib安装失败导致face_recognition无法导入现象执行pip install face_recognition时控制台报错Failed to build dlib或者导入face_recognition时提示找不到_dlib_pybind11。原因dlib需要C编译Windows上缺少Visual Studio Build Tools或者Python版本太新比如3.12没有预编译wheel。解决先降级Python到3.8或3.9这版本dlib的预编译支持最成熟。然后执行pip install dlib19.24.4如果还不行去下载dlib的预编译wheel文件用pip install 下载的文件.whl离线安装。装好后再装face_recognition。Linux上则先装build-essential和cmake。这是整个方案里最容易劝退的一步但只要装过一次后面就顺畅了。5.2 中文用户名和照片路径乱码导致特征提取失败现象注册脚本遍历photos/张三时cv2.imread返回None或者os.listdir出来的文件名在打印时乱码。原因OpenCV的imread不支持中文路径Windows下尤其严重。os.listdir本身没问题但终端编码不一致会导致显示乱码进而干扰判断。解决统一用cv2.imdecode读图也就是先读二进制数据再解码。我在注册脚本里已经写了这个健壮性处理。路径和文件名不要直接用中文建议用拼音或工号作为目录名显示名存在数据库里。比如目录叫zhangsan库里映射到“张三”。这样既避开编码坑又方便后续扩展。5.3 识别阈值设错导致“张冠李戴”现象A来签到结果系统提示B已签到或者B今天明明没来库里却有了B的签到记录。原因face_distance返回的最小距离小于阈值但可能A和B长得像或者摄像头画质差导致特征混淆。阈值设成0.6时误报率明显升高。解决做一次阈值校准。拿库里每个人的一张新照片计算它与库中所有人的距离观察“同一个人”的距离分布和“不同人”的距离分布。一般同人距离在0.25~0.45之间不同人通常大于0.55。取0.45~0.5作为阈值比较安全。如果想让识别更谨慎宁可拒识也不误报把阈值降到0.4。5.4 摄像头画面正常但识别时间和实际签到时间差几秒现象人已经走到摄像头前屏幕上要过1~2秒才显示签到成功而且连续几张画面都提示“未注册”。原因face_recognition的HOG检测在CPU上每帧大约60~150毫秒但如果画面里有多个脸或者分辨率设置过高循环会卡顿。还有一个隐藏点代码里每次识别都对整帧所有人脸做特征提取如果人站在远处脸很小特征质量差距离会变大导致提示未注册。解决把摄像头分辨率降到640x480缩小帧到0.5倍识别速度能提升到每秒10帧左右。同时限制只处理画面中心区域比如只对距离摄像头最近的人脸做识别避免多人互相干扰。如果还嫌慢可以跳帧——每第3帧才做一次完整识别其余帧只显示画面。签到不需要实时连续识别一两秒的延迟完全能接受。6. 进阶用质量检测和阈值配置把误报率压到最低6.1 给签到加一个人脸质量门槛很多人不知道识别不准的核心原因往往不是模型而是输入的人脸太模糊。摄像头帧中人或走动、或低头抓到的脸可能只有10像素宽特征自然不稳定。我建议在识别前增加一个“人脸质量检测”只有清晰度达标的画面才进入比对流程。常见做法是用cv2.Laplacian计算人脸区域的方差方差低于某个值就判定为模糊。下面的代码片段可以直接插入识别循环def is_face_sharp(face_image, threshold50): # face_image是人脸区域BGR图像转为灰度后计算拉普拉斯方差 gray cv2.cvtColor(face_image, cv2.COLOR_BGR2GRAY) variance cv2.Laplacian(gray, cv2.CV_64F).var() return variance threshold这段代码的关键参数是threshold我一般在50到100之间取光线暗或摄像头低端时降低到30否则会误杀很多正常脸。把这个函数放在特征提取之前如果返回False就直接跳过这一帧。这个步骤能显著减少“晃一下脸就被误识别”的情况尤其是有人在镜头前经过时不会因为闪过半张脸就打了卡。6.2 用多线程把摄像头采集和识别分开单线程下摄像头读取和模型识别是串行的识别慢时画面会卡顿而且可能会丢帧。我现在习惯用两个线程一个线程只负责cap.read()把最新帧放入队列另一个线程从队列取帧做人脸识别。这样即使识别耗时200毫秒视频流依然流畅。import threading import queue frame_queue queue.Queue(maxsize2) def capture_loop(cap): while True: ret, frame cap.read() if ret and not frame_queue.full(): frame_queue.put(frame) # 在主循环里改成从队列取帧 def recognition_loop(): while True: frame frame_queue.get() # 后面走人脸检测逻辑这里maxsize2限制了队列长度避免生产速度大于消费速度时内存无限增长。如果队满了就直接丢弃旧帧保证识别的是最新画面。这个模式也方便后面扩展成多摄像头每个摄像头一个采集线程识别线程共享一个特征库和数据库。对签到这种低并发场景双线程已经足够不用上复杂的并发框架。6.3 把误签到的“后悔药”做好最后想提醒你的是无论识别准不准一定要给管理员留一个手动撤销或补签的入口。哪怕识别准确率99%每天几百人次签到下来也会出个把错。我的做法是在SQLite里加一个is_valid字段默认1如果管理员发现某条记录是误签就把它更新为0统计时只统计is_valid1的记录。这比删数据好记录留痕可追溯。conn.execute(ALTER TABLE checkin ADD COLUMN is_valid INTEGER DEFAULT 1)这条DDL给已存在的表加字段不影响已有数据。使用中多一个这样的软删除机制能避免很多尴尬。我曾经因为没做这个设计某天误签了一个人结果月底对账时怎么都解释不通最后只能手动改数据库。那次之后我所有签到项目都保留撤销接口这也是我踩坑踩出来的习惯。识别阈值、环境安装、摄像头参数都有可能随设备变化但数据可修正是系统设计层面的底线。希望这篇笔记能帮你把一个能跑、好用、不坑的Python人脸识别签到系统搭起来少走我走过的弯路。本文还有配套的精品资源点击获取