news 2026/9/11 9:48:01

基于Python的人脸识别签到系统开发实战:OpenCV、face_recognition与Flask

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
基于Python的人脸识别签到系统开发实战:OpenCV、face_recognition与Flask

简介:一套基于Python的人脸识别签到系统完整工程资源,面向希望掌握OpenCV、dlib、face_recognition等库在GUI考勤场景中应用的开发者,帮助解决人脸检测、特征提取、识别签到及数据记录等核心问题。压缩包共20个文件,以6个py源码为主,配合pyc编译文件、xml模型/配置、npy特征向量文件,另含图片、文档和说明,整体仅95KB,结构紧凑,便于快速定位核心脚本与配置。目前已有3985人学习下载。资源内容覆盖人脸检测、特征提取、识别比对、GUI实时展示与签到数据下载等功能模块,附带readme说明及测试脚本,可直接阅读代码理解实现思路,也可基于其进行二次开发。代码中还包含数据集创建脚本、人脸嵌入向量存储以及摄像头调用等实用工具,对于学习人脸识别系统架构、GUI程序设计和同类签到场景开发具有较高参考价值。

1. 认识python人脸识别签到系统的真实工作量

如果你以为用Python做个“人脸识别签到系统”就是装个库、跑个脚本,那多半会在接入真实摄像头那天被卡住。实际上面临的问题很具体:光线忽明忽暗、角度不对就认不出、同一个人在不同时刻拍出的脸差异大、多人同时入场时漏检、重复签到怎么防。系统真正的核心不只在“认脸”那一下,而是从摄像头取帧、人脸检测、特征提取、身份比对、签到记录落库的完整链路,以及这些环节在不同环境下的稳定性。

这个标题适合两类人:一类是公司或学校要用轻量方案做考勤、活动签到,不想采购昂贵的门禁设备;另一类是Python开发者想系统掌握人脸识别工程化落地的流程。读完你会得到一个可复现的单机版本,也能明白换成服务化架构时要改哪些地方。下面按从选型到实战的顺序,把这条路走通。

2. 人脸识别签到系统的技术选型:OpenCV、dlib还是深度学习模型?

2.1 传统算法与深度学习模型的边界

技术选型的第一步是搞清楚不同方案的适用范围。OpenCV自带的Haar Cascade和LBPH人脸识别属于传统机器学习方法,它们体积小、速度快,对正面、光照均匀的脸效果尚可,但一旦出现侧脸、低头、暗光,准确率会明显下降。深度学习模型如FaceNet、ArcFace、Dlib的DNN人脸检测器,在复杂场景下的鲁棒性好得多,但需要更长的推理时间和更高的硬件要求。

具体到签到场景,人员通常需要在摄像头前停留一秒钟左右,这对推理速度的要求并不苛刻:单帧处理时间在100毫秒以内就能满足体验。所以这里我倾向选择基于深度学习的方案,而不是传统Haar+LBPH那种老组合。如果你是在树莓派、Jetson等边缘设备上跑,才需要去做模型量化和剪枝。

2.2 开源免费商用的人脸识别模型怎么选

现在开源免费商用的人脸识别模型已经不少,常见的包括:

  • face_recognition:封装了Dlib的HOG和CNN人脸检测,以及基于ResNet的128维特征提取。使用门槛最低,但纯CPU下速度一般。
  • OpenCV DNN:可以加载OpenFace、YOLO-Face等模型,部署简单,不引入额外依赖。
  • ArcFace(InsightFace):识别精度较高,尤其适合亚洲人面孔,模型大小适中。
  • FaceNet:Google提出的经典模型,可以在TensorFlow或PyTorch中加载,特征向量为128维。
方案特征维度硬件要求识别精度上手难度
face_recognition128CPU可跑,慢中等
OpenCV DNN + OpenFace128CPU可跑中等偏低
ArcFace512建议GPU
FaceNet128CPU/GPU均可

如果你是刚起步,我建议先用face_recognition跑通整个流程,因为它把检测、对齐、特征提取都封装好了,能快速验证签到逻辑。等确认整个系统架构没问题,再替换成ArcFace这类更重的模型提升精度。

2.3 用face_recognition库跑通最小闭环

2.3.1 安装依赖与最小代码

安装阶段最容易踩坑。Python环境建议用conda或venv独立创建,不要直接装到系统Python里。以下是Ubuntu/Debian下的安装过程:

# 创建独立环境,Python 3.8/3.9 均可 conda create -n face_attendance python=3.9 -y conda activate face_attendance # 安装face_recognition,它会自动安装dlib、numpy等 pip install face_recognition opencv-python flask

如果pip install face_recognition报错提示缺CMake或编译失败,多半是dlib编译需要这些依赖。用conda安装可以省去编译:

conda install -c conda-forge dlib pip install face_recognition

安装完成后,先写一个极简的“认脸”脚本,为后面扩展打好基础:

import face_recognition # 加载一张已知人脸图片,提取特征 known_image = face_recognition.load_image_file("known_person.jpg") known_encoding = face_recognition.face_encodings(known_image)[0] # 加载摄像头或测试图片,提取特征 test_image = face_recognition.load_image_file("test_person.jpg") test_encodings = face_recognition.face_encodings(test_image) # 比对:tolerance值越小,判定越严格 for test_encoding in test_encodings: results = face_recognition.compare_faces([known_encoding], test_encoding, tolerance=0.45) print("匹配结果:", results)

这段代码的运行流程是:load_image_file读入图片,face_encodings先检测图片中人脸位置并按人脸关键点对齐,然后通过深度模型生成128维特征向量。compare_faces内部计算两个向量之间的欧氏距离,距离小于tolerance则判定为同一人。注意tolerance是0.45左右比较好,默认0.6对签到来说太宽松,容易把长得像的两个人认成同一个。

2.3.2 参数调节对签到通过率的影响

tolerance是最关键的参数。数值越小,拒绝非本人的概率越高,但误拒率也会上升;数值越大,越容易通过,但错认风险也大。实际调试时建议准备几组样本:本人不同光线的照片、同事的照片、相似度高的照片,用这些测试集手动调这个值。

另一个参数是model,指定检测模型为hogcnnhog在CPU上跑得较快,cnn更准但慢。如果摄像头分辨率是720P,建议先用hog在低分辨率帧上做检测,检测到人脸后再在高分辨率区域上提取特征。注意代码里第一次检测没有检测到人脸时,face_encodings返回空列表,这种情况必须跳过后续比对,否则会报IndexError。

3. 搭建签到系统的核心模块:人脸检测、特征提取与比对

3.1 人脸检测的三种实现路径

在签到系统里,人脸检测是特征提取的前置步骤。常见写法有三种:

  • OpenCV Haar Cascade:cv2.CascadeClassifier,内置模型文件,最轻量但容易误检。
  • Dlib HOG + 线性SVM:dlib.get_frontal_face_detector(),对正面人脸效果好。
  • Dlib CNN或深度学习检测器:例如dlib.cnn_face_detection_model_v1,需要下载模型文件,准确率高,但速度慢。

从稳定性角度,我用face_recognition内置的检测器就够了。它的底层是HOG或CNN,封装得很干净。如果你要自己写检测层,可以把检测结果画出来,方便调试时查看摄像头视角是否合适。

3.2 从摄像头取帧并做实时检测

下面这段代码演示了从USB摄像头实时获取视频帧,每帧做人脸检测,并按帧率控制识别频率。这是签到系统的入口部分:

import cv2 import face_recognition # 打开摄像头,0表示默认摄像头 video_capture = cv2.VideoCapture(0) # 设置分辨率:过高会拖慢处理速度,过低则特征提取不准 video_capture.set(cv2.CAP_PROP_FRAME_WIDTH, 640) video_capture.set(cv2.CAP_PROP_FRAME_HEIGHT, 480) # 预先加载已知人脸编码 known_encodings = [] known_names = [] # 实际项目中请读取数据库或目录文件 # 此处仅为演示 known_image = face_recognition.load_image_file("known/张三.jpg") known_encodings.append(face_recognition.face_encodings(known_image)[0]) known_names.append("张三") # 控制检测频率,防止每帧都跑模型导致卡顿 process_this_frame = True while True: ret, frame = video_capture.read() if not ret: break # 缩小帧到1/4加速检测,特征提取时再用原图 small_frame = cv2.resize(frame, (0, 0), fx=0.25, fy=0.25) rgb_small_frame = cv2.cvtColor(small_frame, cv2.COLOR_BGR2RGB) if process_this_frame: face_locations = face_recognition.face_locations(rgb_small_frame, model="hog") face_encodings = face_recognition.face_encodings(rgb_small_frame, face_locations) recognized_names = [] for face_encoding in face_encodings: matches = face_recognition.compare_faces(known_encodings, face_encoding, tolerance=0.45) name = "未知" if True in matches: first_match_index = matches.index(True) name = known_names[first_match_index] recognized_names.append(name) process_this_frame = not process_this_frame # 将检测结果画回原帧 for (top, right, bottom, left), name in zip(face_locations, recognized_names): top *= 4 right *= 4 bottom *= 4 left *= 4 cv2.rectangle(frame, (left, top), (right, bottom), (0, 255, 0), 2) cv2.putText(frame, name, (left, top - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.8, (0, 255, 0), 2) cv2.imshow("Attendance", frame) if cv2.waitKey(1) & 0xFF == ord("q"): break video_capture.release() cv2.destroyAllWindows()

代码中做了三件关键事情:将帧缩小四分之一用于人脸检测,显著降低HOG的计算耗时;检测结果得到的人脸框坐标要乘回4倍,才能对应到原图;每隔一帧才跑一次识别,避免每帧都处理导致视频卡顿。注意如果使用CNN模型,检测耗时可能达到几百毫秒,需要进一步降低分辨率或把检测频率降到每秒一次。

3.3 特征比对的数学逻辑与阈值设定

人脸识别模型会把一张脸压缩成一个固定长度的向量,face_recognition里是128维。两个向量之间的欧氏距离越小,表示两张脸越相似。compare_faces内部等价于计算:

import numpy as np def euclidean_distance(vec1, vec2): return np.linalg.norm(np.array(vec1) - np.array(vec2))

当距离小于tolerance时判定为同一人。这个阈值怎么定才合适?我建议先在离线环境下采集数据:为每个人拍摄10张不同角度的参考照片,取全部特征向量,计算任意两张同一人的特征距离分布,再计算不同人之间的距离分布。阈值选择让两个分布重叠区域尽量小。如果重叠多,说明采集环境变化太大,考虑加入预处理。

3.4 签到记录的数据结构与数据库表

人脸比对成功只是“识别”完成,离“签到”还差最后一步:落库。签到数据必须有足够的信息支撑后续查询。以下是MySQL表设计:

CREATE TABLE attendance ( id INT AUTO_INCREMENT PRIMARY KEY, person_id INT NOT NULL COMMENT '对应人员表ID', person_name VARCHAR(50) NOT NULL, check_time DATETIME NOT NULL, device_id VARCHAR(30) DEFAULT 'camera_01', similarity FLOAT NULL COMMENT '识别时的距离值,越小越可靠', UNIQUE KEY uk_person_date (person_id, DATE(check_time)) );

这个表里UNIQUE KEY uk_person_date (person_id, DATE(check_time))是关键,它从数据库层面防止同一人同一天重复签到。如果你需要支持多次签到,可以把唯一键去掉,改用业务逻辑判断。

人员特征表可以这样建:

CREATE TABLE person_feature ( person_id INT NOT NULL, feature BLOB NOT NULL COMMENT '存储128维特征向量的二进制序列', feature_type VARCHAR(20) DEFAULT 'face_recognition', updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP );

实际存储特征时,有人会直接存成JSON字符串,但这会浪费空间且解析慢。我习惯用numpy.ndarraytobytes()转成二进制存BLOB,读取时用np.frombuffer还原。注意不同模型产出的特征向量不能混用,比如人脸库是face_recognition生成的,比对时也必须用同一模型。

4. 从单机脚本到可运行的签到服务:UI、存储与并发

4.1 签到流程得先画清楚

单机脚本只能做“识别到就打印”,要变成真正可用的系统,至少要定义流程:

  1. 初始化:加载后台页面提供的注册照片,生成特征库。
  2. 采集:摄像头捕获一帧。
  3. 检测:确认画面中是否有人脸。
  4. 比对:与特征库中所有已知人脸算距离,取最小值。
  5. 判定:最小值小于阈值则识别成功,否则提示“未登记”。
  6. 记录:识别成功后写入签到表,若当天已签到则提示“重复签到”。
  7. 反馈:在界面或终端上显示签到人姓名和时间。

其中第6步防重复签到,除了数据库唯一键外,业务层也需要加判断。否则在高并发场景下,两条请求同时通过查询验证,可能出现明细重复入库。

4.2 用Flask打包一个HTTP签到接口

为了让签到达人或考勤系统能调用,我把识别服务封装成Flask接口。这样摄像头可以放在边缘设备上,后台服务单独运行。

from flask import Flask, request, jsonify import face_recognition import numpy as np app = Flask(__name__) # 模拟内存特征库,实际应从数据库加载 known_encodings = [] known_ids = [] @app.route('/register', methods=['POST']) def register_person(): person_id = request.form.get('person_id') photo = request.files.get('photo') image = face_recognition.load_image_file(photo.stream) encodings = face_recognition.face_encodings(image) if len(encodings) == 0: return jsonify({'success': False, 'error': 'no face detected'}) known_encodings.append(encodings[0]) known_ids.append(person_id) return jsonify({'success': True}) @app.route('/checkin', methods=['POST']) def checkin(): photo = request.files.get('photo') image = face_recognition.load_image_file(photo.stream) encodings = face_recognition.face_encodings(image) if len(encodings) == 0: return jsonify({'success': False, 'error': 'no face'}) distances = face_recognition.face_distance(known_encodings, encodings[0]) min_index = int(np.argmin(distances)) min_distance = float(distances[min_index]) threshold = 0.45 if min_distance < threshold: # 此处应调用签到记录写入函数 return jsonify({'success': True, 'person_id': known_ids[min_index], 'distance': min_distance}) return jsonify({'success': False, 'error': 'not recognized'}) if __name__ == '__main__': app.run(host='0.0.0.0', port=5000, threaded=True)

这里用face_distance替换了compare_faces,因为我们需要拿到具体距离值,用于日志和调试。注册接口先提取特征,存入内存列表;实际项目中应将特征写入数据库并定期加载。threaded=True开启多线程支持并发请求,但注意Flask自带的Werkzeug服务器不适合生产,部署时用gunicorn或uWSGI。

4.3 防止并发场景下的重复签到

一个容易忽略的坑:两个并发请求同时识别同一张脸,业务层都发现没有签到记录,然后同时插入两条记录。即使数据库有唯一键,第二次插入会报错,但你需要捕获并处理。更好的做法是使用Redis分布式锁或数据库原子操作。

import redis import pymysql from contextlib import contextmanager r = redis.Redis(host='localhost', port=6379, db=0) @contextmanager def lock_person(person_id): lock_key = f"checkin_lock:{person_id}" acquired = r.set(lock_key, "1", nx=True, ex=5) if not acquired: raise Exception("duplicate request") try: yield finally: r.delete(lock_key)

checkin识别成功后,用person_id作为锁键,只有获得锁的请求才能继续写数据库。这里Redis的NX参数保证原子性,ex=5防止持锁代码异常导致死锁。

4.4 处理摄像头采集阻塞与帧丢包

使用USB摄像头时,video_capture.read()可能会因为驱动问题阻塞,导致识别线程卡死。一种常见做法是把采集和识别放到两个线程:采集线程只负责读帧并放入队列,识别线程从队列取帧处理。

import threading import queue frame_queue = queue.Queue(maxsize=10) def capture_thread(): cap = cv2.VideoCapture(0) while True: ret, frame = cap.read() if ret and not frame_queue.full(): frame_queue.put(frame) def recognition_thread(): while True: frame = frame_queue.get() # 执行人脸识别逻辑

队列长度限制为10,防止生产慢消费时内存无限增长。如果识别速度跟不上采集速度,队列满后会丢帧,这是合理的,因为不需要处理每一帧。

5. 提升识别率与处理漏检重检的实战技巧

5.1 光照和角度预处理的优先级最高

同样是带签到,为什么有人第一次用就失败,有人在各种光线环境下都很稳?差异往往在于有没有做图像预处理。我建议在送入人脸检测前,先对帧做以下几件小事:

  • 转成灰度图处理face_recognition内部会做,但你自己的预处理可以提前做直方图均衡化。
  • cv2.resize统一分辨率,避免摄像头自动曝光导致亮度突变。
  • 对侧脸、低头这类情况,可以在摄像头前设置一个“站立区”,在地面贴个标记提示人员站到指定位置。
face_recognition.face_encodings`对模糊图像很敏感。如果帧率低或曝光时间长,图像容易拖影,检测不到人脸。这种情况可增加摄像头亮度,或在软件上等待1秒稳定后再抓帧。

5.2 阈值动态调整的简单策略

在一个固定场景中,环境光照会随时间变化。早上阳光直射,下午灯照,人脸特征向量可能有偏移。可以维护一个滑动窗口,动态调整阈值:

distance_history = [] def adaptive_threshold(distances): global distance_history # 只保留最近100次有效识别距离 distance_history.extend(distances) if len(distance_history) > 100: distance_history = distance_history[-100:] std_dev = np.std(distance_history) mean_dist = np.mean(distance_history) # 阈值取均值加上2倍标准差,使误拒率保持在较低水平 return mean_dist + 2 * std_dev

这个策略的前提是大部分识别是合法的且稳定,如果恶意刷脸较多,动态阈值会被带偏。所以实际使用时,我会将动态阈值限制在一个范围内,比如0.35到0.55之间,避免偏离正常经验值。

5.3 用日志验证签到准确性

上线后如何知道系统是否可靠?给每次识别附加日志,记录原始距离、判定阈值、图像文件名等信息。

import json from datetime import datetime def log_attempt(person_name, distance, threshold, result): log_entry = { "time": datetime.now().isoformat(), "person": person_name, "distance": round(distance, 4), "threshold": threshold, "result": result } with open("attendance.log", "a", encoding="utf-8") as f: f.write(json.dumps(log_entry, ensure_ascii=False) + "\n")

然后可以用一条简单的命令分析误判情况:

grep '"result": false' attendance.log | tail -20

如果日志中distance值大量接近阈值,说明设定的阈值偏严,需要调大;如果大量成功记录的distance都很小,可以尝试调小阈值提高安全性。

5.4 戴口罩与多人同时签到的应对方案

疫情期间或特殊行业场景下,戴口罩会遮挡鼻部以下特征,直接影响识别。有两种处理思路:一是训练口罩模型,专门提取上半脸特征;二是在比对时强制只比对上半脸区域。对于签到系统,更节省成本的做法是增加一个活体检测环节,先确认是真人,再做局部特征比对。

多人同时入镜时,face_locations可能返回多个人脸,遍历比对即可。但要注意,如果人脸库很大(比如超过1000人),每帧和所有人计算欧氏距离会显著增加耗时。这时可以先用一个轻量分类器粗筛,或者把特征库加载到向量检索库如FAISS中,把比对时间控制在毫秒级。边缘场景的优化思路是一样的:尽量在低分辨率帧上筛选出可能性高的人脸,再用高分辨率特征确认身份,而不要在一开始就用高分辨率图跑全部流程。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/11 9:47:58

SpringBoot+Vue+MySQL民宿租赁系统开发实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/11 9:47:45

WorkBuddy开放平台Agent开发实战:从概念、技能编排到部署

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/11 9:45:37

用Python打造宿舍电费监控系统:爬虫、存储与自动告警实战

简介&#xff1a;面向高校宿舍管理场景的Python电费监控程序&#xff0c;旨在解决宿舍用电数据采集、统计与可视化展示问题&#xff0c;适合Python学习者、校园信息化开发者及有课设需求的学生参考。代码包共7个文件&#xff0c;以5个Python脚本为核心&#xff0c;分别实现电费…

作者头像 李华
网站建设 2026/9/11 9:43:00

Midscene.js 脚本卡顿排查指南:四步定位慢操作并提速

Midscene.js 脚本卡顿排查指南&#xff1a;四步定位慢操作并提速 【免费下载链接】midscene GUI Agent for E2E Testing 项目地址: https://gitcode.com/GitHub_Trending/mid/midscene Midscene.js 性能优化的起点&#xff0c;是先把"慢"拆开看。这篇诊断笔记…

作者头像 李华
网站建设 2026/9/11 9:41:59

认知滞后与技术迭代:如何升级思维应对AI时代

1. 认知革命的滞后性&#xff1a;我们为何仍在用旧模型理解新世界在咖啡馆里听到两位年轻人的对话&#xff1a;"现在AI都能写论文了&#xff0c;我们学这些还有什么用&#xff1f;"这句话让我意识到&#xff0c;尽管技术已迭代数代&#xff0c;但多数人对世界的理解框…

作者头像 李华
网站建设 2026/9/11 9:39:25

低功耗开发实战:从GPIO漏电到AlarmManager滥用的归因分析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华