ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

Python+OpenCV+dlib实现人脸识别签到系统:从选型到避坑

2026/9/28 7:05:19 拓冰建站 浏览量
Python+OpenCV+dlib实现人脸识别签到系统:从选型到避坑 简介利用OpenCV与深度学习技术Python人脸识别签到项目为具备一定Python基础的开发者提供了完整的智能考勤实现方案。资源覆盖图像采集、人脸特征提取、身份匹配、签到记录、考勤统计及数据导出等环节可应用于公司会议、学校课堂、活动签到等场景帮助初学者理解计算机视觉与数据库管理结合的实际项目流程。压缩包共23个文件包含3个Python核心脚本、2个UI界面设计、3个Markdown说明文档、4个XML配置文件、5个PNG示例图片以及CSV考勤数据文件整体大小仅1.93MB目录结构紧凑便于快速查阅与二次开发。已有1482人学习下载。资源不仅提供可直接运行的工程文件还附带了界面设计和数据存储示例读者可据此梳理人脸识别签到系统的模块划分替换为本地数据集或调整GUI界面快速搭建属于自己的考勤工具。1. 为什么“Python人脸识别签到”总被低估一台旧电脑就能跑很多团队一提人脸识别签到第一反应就是上云、上大模型、买门禁一体机。实际做过一轮你会发现绝大多数办公场景根本用不到那么重的方案。用Python把OpenCV和dlib生态接起来一台不带GPU的旧电脑就能把考勤、签到、陌生人提醒这件事跑得明明白白。人脸识别签到这事核心不是“识别谁”而是“比对之后怎么处理签到”。识别只占20%的工作量剩下80%都在人脸上怎么存、阈值怎么设、漏签怎么补、记录怎么查。这篇笔记按“先选型 - 再写代码 - 再落库 - 最后避坑”的顺序把一套能直接搬进公司或教室的签到系统讲清楚。适合正在搭门禁签到、实验室考勤、培训点名这类场景的人也适合手里有摄像头但不知道下一步怎么做的初学者。懂一点Python基础就能跟完熟手可以直接跳到第5章的坑里翻一翻。2. 识别方案怎么选LBPH、128维特征向量与深度学习模型的取舍2.1 三类主流人脸识别路线的适用边界做签到的人脸识别本质上分三步人脸检出、特征提取、特征比对。市面上能用的Python方案大致归三类我按实际踩过的项目给你排个序。第一种是OpenCV自带的LBPHLocal Binary Pattern Histogram。它提取的是纹理直方图训练快、模型小一个人几十张样本就能训。但LBPH对姿态和光线非常敏感稍微侧脸、暗光误识率就上去了。适合做小范围、环境可控、识别量少的实验真拿到办公室打卡用容易被同事骂。第二种是face_recognition库加dlib这是当前做签到系统的“万金油”。它把检测封装成一行特征直接输出128维浮点向量比对用欧氏距离或余弦相似度。face_recognition对普通办公环境的鲁棒性远好于LBPH而且不需要GPU纯CPU跑一次检测加比对在0.2秒以内。缺点是dlib在Windows上编译容易坑你一把安装时要耐心。第三种是深度学习方案比如insightface的ArcFace或OpenVINO推理。识别精度最高但对环境和工程要求也高要么有GPU要么得配置OpenVINO推理而且部署体积大。我的判断是如果你做的是一次签到几十人的教室点名LBPH够用如果是日常考勤、访客登记、需要长期跑face_recognition最省心如果要求带口罩识别或跨年龄段比对再去上深度学习。实践里常用方案是face_recognition下面代码都按它来。2.2 建人脸库采集规范、命名约定与数据清洗识别准不准一半看算法一半看录入的人脸库。我见过太多项目代码没问题最后因为人脸库太脏导致签到翻车。人脸库的建立要守住三个原则一人至少两张正脸照、照片用近景、文件名别用中文。采集时用1080P摄像头人在摄像头正前方50~80厘米光线均匀摘掉帽子、口罩、墨镜。每张人脸在画面里的占比至少1/3以上。保存时按“姓名_工号”的规则命名比如zhang_wei_001.jpg。不要用中文文件名face_recognition在一些Windows环境下读中文路径会出现编码问题这是第一个坑。数据清洗这一步很多人跳过。录入前先用下面这段代码把人脸检出如果检不出或检出了两张就删掉重拍。你不想签到的时候发现某位同事的人脸库里存的是一张糊了半张脸的自拍。import face_recognition for path in [zhang_wei_001.jpg, li_na_002.jpg]: image face_recognition.load_image_file(path) boxes face_recognition.face_locations(image, modelhog) print(f{path}: 检出 {len(boxes)} 张脸) if len(boxes) ! 1: print(该样本不合格请重新拍摄)这里face_locations返回的是每张脸的边框坐标列表。model参数选“hog”是因为它是CPU上的检测器跑得快不需要GPU。如果你在录入门禁这种对质量要求高、机器又带GPU的场景可以换用modelcnn检得更准。上面的代码每次读到图片时先做一次质检保证入库样本只有一张正脸能做到这一步后面少很多怪问题。人脸库的组织结构我一般按目录来而不是把所有人脸都堆在一个文件夹。“known_faces/按人名分目录”的做法有两个好处一是新增员工时不用改代码直接建目录二是可以按目录名批量生成特征文件出问题也好定位是谁的样本不对。3. 注册端与识别端人脸特征提取和实时比对的代码落地3.1 注册模块从照片到128维特征文件注册模块做的事是把录入的图片变成一份特征数据落到磁盘上。到了这步你才算真正把“人脸照片”变成“机器能比对的数学对象”。face_recognition内部用dlib的ResNet模型把一张112x112的归一化脸映射成一个128维浮点向量。将来签到比对时比的就是这个向量和实时采集向量的距离。下面的代码把某个人目录下的所有照片提取特征计算平均向量存成“员工ID.npy”文件。为什么要取平均一个员工如果录了3张照片直接比对3次会有歧义取平均向量后整个系统只需要比对一次速度快也简洁。import os import numpy as np import face_recognition PERSON_DIR known_faces/zhang_wei_001 encodings [] for fname in os.listdir(PERSON_DIR): path os.path.join(PERSON_DIR, fname) image face_recognition.load_image_file(path) enc_list face_recognition.face_encodings(image) if len(enc_list) 1: encodings.append(enc_list[0]) else: print(f跳过 {fname}: 检出 {len(enc_list)} 张脸) if encodings: avg_encoding np.mean(encodings, axis0) np.save(encodings/zhang_wei_001.npy, avg_encoding) print(特征保存完成维度:, avg_encoding.shape)np.mean是对所有同一个人样本向量的每个维度取平均值得到一条更鲁棒的特征。这里有一个关键参数face_encodings默认用dlib的68点模型做对齐所以录入照片尽量是正脸侧脸超过30度会让特征值偏移明显。保存出来的npy文件就是签到时的比对基准你可以理解成每个人的“数字指纹”。注册端还应该顺手把员工的姓名与ID成对存在一个单独文件里。我习惯用JSON存映射因为入库和展示都要用到。import json person_map {zhang_wei_001: 张伟} with open(encodings/person_map.json, w, encodingutf-8) as f: json.dump(person_map, f, ensure_asciiFalse, indent2)这个映射放在这个路径下后续签到识别端启动时直接load就行。用JSON而不是CSV是中文名字在CSV里经常被Excel打开时搞乱码JSON的utf-8编码更省心。3.2 实时识别端摄像头循环、欧氏距离和阈值判断识别端是整套系统里天天在跑的模块。它的循环逻辑不复杂打开摄像头逐帧读取检测人脸提取特征与库里所有人比对取最小值判断是否小于阈值。关键在于这个“阈值”的取值直接决定误识率和漏识率的平衡。face_recognition内置的compare_faces默认阈值是0.60.6在办公场景偏松容易把长得像的同事签错。我自己测试下来室内光线均匀的条件下0.45到0.50之间表现比较好。低于0.45开始出现大量漏识高于0.55误签风险递增。刚部署时先用0.5跑两周再根据实际日志微调。import cv2 import face_recognition import numpy as np import json # 加载人脸库 known_encodings np.load(encodings/zhang_wei_001.npy).reshape(1, -1) with open(encodings/person_map.json, r, encodingutf-8) as f: person_map json.load(f) SIM_THRESHOLD 0.50 # 欧氏距离阈值越小越严格 video_capture cv2.VideoCapture(0) # 0 代表默认摄像头 while True: ret, frame video_capture.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) boxes face_recognition.face_locations(rgb_small, modelhog) encodings face_recognition.face_encodings(rgb_small, boxes) for encoding in encodings: distances np.linalg.norm(known_encodings - encoding, axis1) min_dist np.min(distances) matched_id list(person_map.keys())[np.argmin(distances)] if min_dist SIM_THRESHOLD: print(f签到成功: {person_map[matched_id]}, 距离 {min_dist:.3f}) else: print(f未登记人员最小距离 {min_dist:.3f})上面这段是整个识别端的骨架。np.linalg.norm计算128维向量的欧氏距离比余弦相似度更直接也符合face_recognition作者的默认口径。把帧缩小一半再送给人脸检测是为了把单帧处理时间从0.4秒压到0.2秒左右。签到场景不会要求每帧都处理你完全可以在检测到新的人脸后才开始比对。这段代码里没有做“当天已签过”的过滤所以直接跑会瞬间签到几十次。这就是为什么第4章要把签到记录和去重逻辑一块讲。4. 签到记录的落库与补签避免同一个人一天录十条4.1 表结构与双重写库SQLite留给查询Excel留给行政实时识别只解决“他是谁”签到系统真正值钱的是“他几点来今天来没来来了几次”。去重的逻辑用不上复杂的数据库一个SQLite文件就能扛住几百人一年的签到数据。下面是表结构created_at存识别发生时间sign_date是业务日期这俩分开是因为深夜加班场景里识别时间可能是第二天凌晨而考勤日期还挂在昨天。import sqlite3 conn sqlite3.connect(attendance.db) c conn.cursor() c.execute( CREATE TABLE IF NOT EXISTS sign_log ( id INTEGER PRIMARY KEY AUTOINCREMENT, employee_id TEXT NOT NULL, employee_name TEXT NOT NULL, sign_date TEXT NOT NULL, created_at TEXT NOT NULL DEFAULT (datetime(now, localtime)), UNIQUE(employee_id, sign_date) ) ) conn.commit()UNIQUE(employee_id, sign_date)就是去重约束。这个约束比在代码里先查再插靠谱因为多线程或多进程同时写入时代码里的先查后插会踩到竞态条件导致同一个人同一天被记两次。只要数据库表建好这个唯一索引第二次插入直接抛异常你捕获之后忽略就行。签到落库的代码紧接着识别模块识别成功且通过当日去重后分别写SQLite和一个Excel文件。Excel双写是一个耐人寻味的做法行政那边常年只会用Excel你给他们一份SQLite数据库他们不会用也不会看。签到系统如果做得让行政用不起来那这个项目就废了一半。所以每天结束后自动导出当天签到记录到Excel。import datetime import pandas as pd def save_sign(employee_id, employee_name): today datetime.date.today().isoformat() try: c.execute( INSERT INTO sign_log (employee_id, employee_name, sign_date) VALUES (?, ?, ?), (employee_id, employee_name, today), ) conn.commit() append_to_excel(employee_id, employee_name, today) except sqlite3.IntegrityError: print(f{employee_name} 今天已签到忽略重复记录) def append_to_excel(employee_id, employee_name, sign_date): record {工号: employee_id, 姓名: employee_name, 日期: sign_date, 签到时间: datetime.datetime.now().strftime(%H:%M:%S)} df pd.DataFrame([record]) with pd.ExcelWriter(sign_records.xlsx, engineopenpyxl, modea, if_sheet_existsoverlay) as writer: df.to_excel(writer, sheet_name签到记录, startrowwriter.sheets[签到记录].max_row, headerFalse, indexFalse)append_to_excel里用openpyxl的append模式每次在已有工作表下面追加一行不会覆盖前面的签到数据。这一段如果你是单机部署完全够用。但我得提醒Excel双写只适合轻量场景如果公司有几百号人频繁打开同一个xlsx文件会撞锁到时候再换成只写SQLite、单独搞一个导出按钮。4.2 漏签与补签默认补签逻辑必须留个口子签到系统上线后最头疼的不是识别不准而是“明明来了却没签上”。常见情况有三种一是早上踩着截止时间跑进办公室摄像头抓得太快没拍到正脸二是两个同事并排进门互相遮挡三是光线逆光检测器直接没检出人脸。一旦漏签负责人第一个找的人是做系统的你。我做了个务实的设计系统里保留“管理员补签”入口。识别端失败的人脸保存为一张缩略图文件名带时间戳存在missed_frames目录下。管理员每天看一眼目录确认某张缩略图是人N号员工后手动执行补签。补签时和正常签到走同一个保存函数但把识别时间替换成图片时间这样考勤统计数据才不会被破坏。missed_frame cv2.resize(frame, (0, 0), fx0.4, fy0.4) # 识别距离超过阈值时只存图不存人脸库 cv2.imwrite(fmissed_frames/{datetime.datetime.now().strftime(%Y%m%d_%H%M%S)}.jpg, missed_frame)保存缩略图时控制大小不然一早上逆光十几个人全没签上那个目录一天几百MB就满了。给missed_frames目录加一个定时清理超过7天自动删除。补签操作要记录操作人留下审计痕迹能避免后续有人拿补签功能做手脚。还要注意时间边界问题。签到系统跑在考勤周期上如果是夜间加班到凌晨一点识别发生时间第二天凌晨按业务日期算成“今天”所以我在save_sign里统一用datetime.date.today()来定sign_date不要用created_at的时间戳直接截日期。这个坑我见过不止一次很多同事写的签到系统一到跨天就炸。5. 人脸识别签到的5个高频坑现象、原因与排查步骤5.1 光线变化导致识别率骤降现象上午九点签到正常下午靠窗位置逆光一个人站到摄像头前开始“查无此人”距离高达0.6以上。整个下午签到成功率跌到一半。原因人脸检测器对光照太敏感逆光时脸部的局部特征基本被吃没。HOG检测器尤其明显它靠梯度直方图找脸强光下梯度信息被淹没。这不是阈值问题是特征根本没有提取出来。解决物理干预优先。窗帘贴上或者把摄像头朝向从窗户改为朝向室内。软件层面加一个简易的直方图均衡化处理让暗部细节被拉伸出来。另外把capture的曝光调低一档很多摄像头在OpenCV里能用CAP_PROP_EXPOSURE调。注意这个属性在不同的摄像头驱动上表现不一致有些USB摄像头调了没有反应那就靠物理遮光。5.2 相似脸误签阈值过低认错人现象两个男同事都戴眼镜、脸型偏方A打卡时日志里同时出现B的名字而且distances只有0.49低于0.5的阈值。原因这类情况通常不是算法坏掉了是阈值定得太松。0.5对大多数人是安全的但库里有相似脸时确实会交叉。还有可能是人脸库中B的样本照片是几年前拍的现在的B和A越来越像跨时间的特征漂移造成相似度升高。解决把阈值从0.5下调到0.45再观察两周。同时给疑似误签的识别记录加一个“复核”状态标记为警告的记录每天由管理员批量核对。要注意人脸库里的样本也要每半年更新一次让特征跟着本人的面貌走。日常运维中把更新样本常态化比临时调阈值治本得多。5.3 用照片刷脸蒙混签到现象有人把同事的照片打印出来举着照片往摄像头前一放系统直接通过了。甚至有人拿手机翻拍一张照片也过了。原因face_recognition纯粹做静态特征比对没有任何活体判定。照片和真人在128维向量上距离确实很近。这是这类轻量方案的通病不怪哪个人脸识别库。解决做两级防护。第一级是动作活体界面随机提示“眨眼”或“点头”让OpenCV检测68个关键点确认眼睛或头部的相对位置变化。第二级是照片质量检测统计检测到的脸的面积、锐度、是否有周期性变化。其实光靠帧间变化也能挡掉9成的翻拍攻击因为照片是静止的连续几帧的人脸框位置和像素差值几乎为零真人会有微动。5.4 多人同框时漏检现象签到高峰期三五个人同时站在摄像头前系统只认出来一两个剩下的人被忽略门口排起了队。原因face_recognition的face_locations在多人场景下会漏掉被遮挡或侧脸的人。它本身没有跟踪机制每一帧都是独立检测两个人在画面上靠得近时可能被合并成一个框。还有可能是小帧缩放太多本来100x100的脸缩成40像素特征检测器直接放弃。解决不要把所有期望压在一个摄像头视角上。把摄像头装在正对着门的墙面上让排队方向垂直于镜头这样每个人都能轮流完整进入画面。同时把缩放比从0.5调到0.7牺牲一点帧率换检出率。再配合一个简单的IOU去重作为后处理连续几帧框位置变化小于阈值才判定为有效签到。多目标场景下我再加一个cv2的跟踪器比每帧全量检测便宜得多。5.5 CPU占用失控笔记本风扇狂转、帧率掉到一帧现象程序启动后CPU占用直接拉满到90%以上取景画面卡得像幻灯片风扇声音大到隔壁工位都能听见。原因face_recognition的检测器和编码器都是算力猛兽。一个1920x1080的画面不做缩放直接送进face_locationsCPU得用半秒钟算。再对每个人做face_encodings又需要一次前向推理整个循环里处理时间超过采集间隔帧会被越积越慢。解决把画质档位降到720P处理检测线程和识别线程拆开用一个队列缓冲最近5帧检测线程只处理最新的帧识别端在检测到人脸后做最小间隔限制。还有一个更有效的招把RGBA降成RGB避免格式转换的额外开销。你看很多“跑不动”的问题不是代码不行是流程没裁剪。6. 进阶技巧把签到系统从“能跑”推向“能交付”6.1 用批量比对模式验证全员工误识率与拒识率上线前别急着拿真人去试先拿历史照片做一次全量回放。把之前存过的员工照片按库内依次比对记录每一个人的距离分布。这一步能让你提前知道阈值要不要调。具体是加载所有人脸特征到内存对库里每个样本与全库做距离矩阵看相似的五个人之间距离是否逼近阈值。比对结果里如果有别人进了0.5以内就别等上线后再翻车。6.2 把实时识别封装为局域网HTTP接口多个门口部署时我不建议每台电脑都跑一遍完整识别。用一个设备做人脸识别识别成功后把event推到内网的接口上各门口只负责抓图。常见做法是用Flask写一个/sign接口接收图片返回员工ID和签到状态。这样后续扩展门禁、访客系统都在同一条数据管道上加东西不用重写识别端。这里强调一下签到系统的验证不要只盯着单次识别准不准要同时看“当天去重”和“补签日志”这两个环节。我自己的习惯是每两周导出一次签到记录人工抽三天数据对一遍确认没有遗漏和异常记录。做久了你会发现最后大家不信的是签到系统本身信的是你处理异常签到的方式。 这套方案前后不用花一分钱买识别服务一台普通机器加一个USB摄像头就能跑。真要说有什么要花钱的地方大概就是把mbp的钉子户同事带去喝杯奶茶请他帮忙在Windows上编一编dlib。希望帮到你。 p a hrefhttps://download.csdn.net/download/csdn1561168266/85841420 stylecolor:#ec7500;font-size:14px; 本文还有配套的精品资源点击获取 /a img altmenu-r.4af5f7ec.gif srchttps://csdnimg.cn/release/wenkucmsfe/public/img/menu-r.4af5f7ec.gif stylewidth:16px;margin-left:4px;vertical-align:text-bottom;cursor:text; /p