Python+Flask+dlib人脸识别考勤系统:从原理到实战

发布时间:2026/9/23 13:01:04
Python+Flask+dlib人脸识别考勤系统:从原理到实战 简介这份资源是面向高校计算机相关专业学生与课程设计学习者的企业考勤管理系统完整项目采用Python、Flask与dlib构建人脸识别核心前端结合Vue实现管理界面适合作为毕业设计、课程设计或技术练手参考。压缩包共416个文件约103.71MB其中32个py文件承载后端业务与识别逻辑26个html与118个js、61个css构成前端页面与交互另有79张jpg、51张png等图片素材及xml、字体等配置资源目录结构完整便于按模块查阅。项目已在Windows 10/11环境严格调试下载即用并附使用文档与部署教程可帮助读者快速理解人脸识别考勤的完整实现链路包括人脸采集、特征比对、考勤记录管理等环节同时提供排错思路与运行说明。目前已有170人学习关注适合需要完整项目方案、文档支撑与可运行源码的读者参考借鉴。1. 从一张打卡照片说起这套 Flask dlib 考勤系统到底解决什么问题很多公司还在用指纹机打卡冬天手指干裂识别不了夏天出汗也识别不了前台每个月月底对着 Excel 手动补卡补到怀疑人生。这套基于 Python Flask dlib 的人脸识别企业考勤管理系统要解决的就是这件事员工走到摄像头前系统自动认出是谁、记录时间、写入数据库后台能按部门按月份导出考勤报表。它适合两类人一类是正在找毕业设计题目的同学需要一套能跑通、能讲清楚原理、代码量适中的完整项目另一类是中小企业里负责行政或 IT 的工程师想用低成本方案替换掉老旧的指纹打卡机。整套技术栈是 Python 做后端逻辑、Flask 提供 Web 服务和页面路由、dlib 负责人脸检测和 128 维特征提取、SQLite 或 MySQL 存考勤记录。和 OpenCV 自带的 Haar 级联相比dlib 的 HOG 检测器在正脸场景下误检率更低配合 ResNet 预训练模型输出的特征向量同一个人不同角度拍的照片余弦距离能稳定控制在 0.4 以内这是它能用于考勤而不是玩具 demo 的关键。下面从环境搭建一路讲到活体检测和考勤报表把每个环节的参数和踩坑点都摊开说。2. 环境搭建与 dlib 安装为什么你的 pip install 总是翻车2.1 Python 版本与依赖选型dlib 对 Python 版本和编译工具链非常敏感。我一般锁 Python 3.8 或 3.9这两个版本在 Windows 和 Linux 上都有预编译好的 dlib wheel能省掉大量编译时间。Python 3.11 以上虽然也能装但 dlib 19.24 之前的版本没有对应 wheelpip 会直接去源码编译需要本机有 CMake 和 Visual Studio Build Tools新手在这一步翻车的概率超过七成。核心依赖清单如下包名推荐版本作用Flask2.3.xWeb 框架路由和模板渲染dlib19.24.x人脸检测 128 维特征提取opencv-python4.8.x摄像头取流、图像预处理numpy1.24.x特征向量距离计算face-recognition1.3.0对 dlib 的封装简化比对逻辑Flask-SQLAlchemy3.0.xORM管理员工和考勤表face-recognition 这个库底层就是调 dlib但它把检测、编码、比对封装成了三行代码毕业设计里用它能少写两百行胶水代码。如果你要讲原理可以不用它直接调 dlib 的get_frontal_face_detector和face_recognition_model_v1。2.2 在 Windows 上装 dlib 的可行路径先确认 Python 版本python --version # 输出应为 Python 3.8.x 或 3.9.x如果版本不对去 python.org 下载对应安装包安装时勾选 Add Python to PATH。装完后升级 pippython -m pip install --upgrade pip然后安装 CMake这是 dlib 编译的必备工具pip install cmake接着装 dlib。如果你的 Python 是 3.8/3.9 的 64 位版本直接pip install dlib19.24.2pip 会优先找 wheel找到就直接装找不到才编译。如果卡在编译阶段报错最省事的办法是去 GitHub 上找对应版本的 whl 文件手动安装但注意 whl 文件名里的 cp38、cp39 要和你的 Python 版本匹配win_amd64 要和系统架构匹配。装完验证import dlib print(dlib.__version__) # 应输出 19.24.2 detector dlib.get_frontal_face_detector() print(dlib 检测器加载成功)这段代码先导入 dlib 并打印版本号然后加载默认的 HOG 人脸检测器。如果这一步不报错说明 dlib 安装成功。参数上没有什么可调的get_frontal_face_detector()返回的是一个基于 HOG 特征的检测器对象后续所有检测都靠它。2.3 Linux 服务器上的安装差异Linux 上装 dlib 通常更顺因为 apt 能直接装编译依赖sudo apt update sudo apt install -y build-essential cmake libopenblas-dev liblapack-dev libx11-dev pip install dlib19.24.2libopenblas-dev和liblapack-dev是数值计算库dlib 的矩阵运算依赖它们。不装的话编译能过但运行时会报 BLAS 相关的链接错误。libx11-dev是图形界面相关服务器上如果只跑 Web 不显示窗口可以不装但 OpenCV 的imshow会不可用调试时要注意。提示如果你在树莓派上部署dlib 编译会非常慢建议直接用 apt 装python3-dlib虽然版本旧一点但省时间。3. 人脸检测与特征提取dlib 的 128 维向量是怎么算出来的3.1 HOG 检测器与 CNN 检测器的选择dlib 提供两种人脸检测器HOG 和 CNN。HOG 检测器基于方向梯度直方图速度快CPU 上就能跑适合考勤这种正脸场景。CNN 检测器基于 Max-Margin 目标检测精度更高能检测侧脸和小脸但需要 GPU 才跑得动CPU 上单帧要好几秒。考勤场景我一般用 HOG因为员工走到摄像头前基本都是正脸HOG 足够。代码import dlib import cv2 detector dlib.get_frontal_face_detector() img cv2.imread(employee.jpg) gray cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 第二个参数 1 表示对图像上采样一次能检测到更小的脸 faces detector(gray, 1) for face in faces: x, y, w, h face.left(), face.top(), face.width(), face.height() cv2.rectangle(img, (x, y), (x w, y h), (0, 255, 0), 2) cv2.imwrite(detected.jpg, img)detector(gray, 1)里的 1 是上采样次数。上采样会把图像放大让原本像素少的小脸变得可检测代价是计算量翻倍。考勤场景人脸离摄像头近用 0 就行如果摄像头装在远处用 1 或 2。face.left()等方法返回检测框的左上角坐标和宽高用于画框或裁剪。3.2 用 ResNet 模型提取 128 维特征检测到人脸后下一步是提取特征。dlib 用的是 ResNet-34 的变体在 300 万张人脸数据上训练过输出 128 维向量。同一个人的不同照片向量之间的欧氏距离小于 0.6不同人之间大于 0.6。这个 0.6 就是默认阈值。import dlib import numpy as np detector dlib.get_frontal_face_detector() # 加载预训练的人脸特征提取模型 sp dlib.shape_predictor(shape_predictor_68_face_landmarks.dat) facerec dlib.face_recognition_model_v1(dlib_face_recognition_resnet_model_v1.dat) img dlib.load_rgb_image(employee.jpg) faces detector(img, 1) for face in faces: shape sp(img, face) # 第三个参数 1 表示用 1 次采样计算特征越大越精确但越慢 descriptor facerec.compute_face_descriptor(img, shape, 1) vec np.array(descriptor) print(vec.shape) # (128,) print(vec[:5]) # 打印前 5 维看看shape_predictor_68_face_landmarks.dat是人脸关键点模型定位 68 个特征点眉毛、眼睛、鼻子、嘴巴、下巴轮廓。compute_face_descriptor会先用关键点做人脸对齐把脸摆正再送进 ResNet 提取特征。第三个参数是采样次数1 表示只算一次精度够用设成 10 会更稳但慢十倍。考勤场景用 1 就行。两个模型文件需要单独下载dlib 的 GitHub 仓库或官网都有注意下载后放在项目目录下路径别写错。3.3 特征比对与阈值调优拿到 128 维向量后比对就是算欧氏距离import numpy as np def compare_faces(vec1, vec2, threshold0.6): distance np.linalg.norm(vec1 - vec2) return distance threshold, distance # 假设已知员工 A 的特征向量存在数据库里 known_vec np.load(employee_a.npy) new_vec np.array(descriptor) is_same, dist compare_faces(known_vec, new_vec) print(f距离: {dist:.4f}, 是否同一人: {is_same})np.linalg.norm算的是欧氏距离。阈值 0.6 是 dlib 官方推荐值但实际用的时候要根据你的摄像头和光照调。我一般会先用 10 个员工各拍 5 张照片两两比对画出距离分布图找到类内距离和类间距离的分界点。如果误识率高把别人认成你把阈值降到 0.5如果拒识率高你自己认不出来把阈值升到 0.65。这个调参过程没有捷径必须用你自己的数据跑一遍。注意特征向量存数据库时用 BLOB 类型或直接存成 .npy 文件不要存成字符串浮点数转字符串再转回来会有精度损失。4. Flask 后端与考勤逻辑从摄像头取流到写入数据库4.1 Flask 项目结构与路由设计一个能跑的考勤系统Flask 这边至少需要这几个路由路由方法作用/GET首页显示打卡入口/registerPOST员工注册上传照片提取特征/attendancePOST打卡比对特征并记录时间/recordsGET考勤记录查询支持按日期筛选/exportGET导出 Excel 报表项目目录结构attendance_system/ ├── app.py # Flask 主程序 ├── models.py # 数据库模型 ├── face_utils.py # dlib 封装 ├── static/ │ └── known_faces/ # 存员工特征文件 ├── templates/ │ ├── index.html │ └── records.html └── attendance.db # SQLite 数据库app.py里初始化 Flask 和数据库from flask import Flask, render_template, request, jsonify from flask_sqlalchemy import SQLAlchemy import os app Flask(__name__) app.config[SQLALCHEMY_DATABASE_URI] sqlite:///attendance.db app.config[SQLALCHEMY_TRACK_MODIFICATIONS] False app.config[UPLOAD_FOLDER] static/known_faces db SQLAlchemy(app) if __name__ __main__: app.run(host0.0.0.0, port5000, debugTrue)SQLALCHEMY_DATABASE_URI指向 SQLite 文件毕业设计用 SQLite 足够部署到企业时换成 MySQL 只需改这一行。host0.0.0.0让局域网内其他机器也能访问方便测试。debugTrue开发时用上线要关掉。4.2 员工注册照片上传与特征入库注册接口接收前端传来的照片提取特征后存到数据库from flask import request, jsonify import face_recognition import numpy as np import os app.route(/register, methods[POST]) def register(): name request.form.get(name) department request.form.get(department) file request.files.get(photo) if not file or not name: return jsonify({code: 400, msg: 缺少姓名或照片}) # 保存照片 filepath os.path.join(app.config[UPLOAD_FOLDER], f{name}.jpg) file.save(filepath) # 提取特征 image face_recognition.load_image_file(filepath) encodings face_recognition.face_encodings(image) if len(encodings) 0: return jsonify({code: 400, msg: 照片中未检测到人脸}) if len(encodings) 1: return jsonify({code: 400, msg: 照片中有多张人脸请上传单人照}) # 存特征到文件路径记到数据库 vec_path os.path.join(app.config[UPLOAD_FOLDER], f{name}.npy) np.save(vec_path, encodings[0]) # 写入员工表省略 ORM 细节 # employee Employee(namename, departmentdepartment, vec_pathvec_path) # db.session.add(employee) # db.session.commit() return jsonify({code: 200, msg: 注册成功})face_recognition.face_encodings返回一个列表每张脸对应一个 128 维向量。如果列表为空说明没检测到脸长度大于 1 说明有多张脸这两种情况都要拒绝。特征存成.npy文件而不是数据库 BLOB是因为文件读写更快而且方便调试时直接np.load查看。数据库里只存文件路径。4.3 打卡接口实时比对与防重复打卡打卡是核心逻辑要处理三件事检测人脸、比对特征、防止同一人短时间内重复打卡。from datetime import datetime, timedelta app.route(/attendance, methods[POST]) def attendance(): file request.files.get(photo) if not file: return jsonify({code: 400, msg: 未收到照片}) # 临时保存 temp_path static/temp.jpg file.save(temp_path) # 提取特征 image face_recognition.load_image_file(temp_path) encodings face_recognition.face_encodings(image) if len(encodings) 0: return jsonify({code: 400, msg: 未检测到人脸}) new_vec encodings[0] # 遍历所有员工特征比对 known_dir app.config[UPLOAD_FOLDER] best_match None best_distance 1.0 for fname in os.listdir(known_dir): if not fname.endswith(.npy): continue known_vec np.load(os.path.join(known_dir, fname)) distance np.linalg.norm(known_vec - new_vec) if distance best_distance: best_distance distance best_match fname.replace(.npy, ) if best_distance 0.6: return jsonify({code: 404, msg: 未匹配到员工请先注册}) # 防重复打卡5 分钟内同一人只记一次 five_min_ago datetime.now() - timedelta(minutes5) # recent Attendance.query.filter_by(namebest_match).filter(Attendance.time five_min_ago).first() # if recent: # return jsonify({code: 200, msg: f{best_match} 已打卡请勿重复}) # 写入考勤记录 # record Attendance(namebest_match, timedatetime.now()) # db.session.add(record) # db.session.commit() return jsonify({code: 200, msg: f{best_match} 打卡成功, distance: round(best_distance, 4)})比对逻辑是遍历所有.npy文件找距离最小的那个。如果最小距离仍大于 0.6说明不是已注册员工。防重复打卡用时间窗口5 分钟内同一人只记一次避免员工站在摄像头前连续触发。best_distance返回给前端可以用于调试正式上线可以去掉。提示员工数量超过 200 人时遍历比对会变慢。常见做法是把所有特征向量加载到一个矩阵里用np.argmin一次性算完比 Python 循环快几十倍。5. 避坑与排查考勤系统上线后最容易翻车的 5 个地方5.1 现象白天能识别晚上全挂原因摄像头没有红外补光晚上光线不足dlib 的 HOG 检测器直接检测不到人脸。HOG 依赖梯度信息光线暗的时候梯度不明显检测器就瞎了。解决换带红外补光的摄像头或者在打卡点加一盏常亮灯。如果硬件改不了把 OpenCV 读到的帧先做直方图均衡化gray cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) gray cv2.equalizeHist(gray) faces detector(gray, 1)equalizeHist把灰度分布拉均匀暗部细节会变明显。但这不是万能药光线太暗还是得靠硬件。5.2 现象同一个人换个角度就认不出来原因dlib 的 128 维特征对角度敏感侧脸超过 30 度特征距离会明显变大。注册时只拍了一张正脸照打卡时稍微偏头就匹配不上。解决注册时让员工拍 3 到 5 张不同角度的照片每张提取一个特征向量打卡时和所有向量比对取最小距离。存储上可以一个员工对应多个.npy文件文件名加后缀区分。5.3 现象照片里有多个人系统随机选一个原因face_recognition.face_encodings返回列表代码里直接取encodings[0]如果照片里有两个人取到的是检测框排序后的第一个不一定是打卡的人。解决注册时强制单人照检测到多张脸直接拒绝。打卡时如果检测到多张脸选面积最大的那个因为打卡的人通常离摄像头最近if len(encodings) 1: # 按人脸框面积排序取最大的 face_locations face_recognition.face_locations(image) areas [(bottom - top) * (right - left) for top, right, bottom, left in face_locations] max_idx areas.index(max(areas)) new_vec encodings[max_idx] else: new_vec encodings[0]5.4 现象Flask 部署到服务器后上传的照片路径报错原因Windows 上路径用反斜杠Linux 上用正斜杠代码里硬编码了static\\known_faces到 Linux 上就找不到目录。解决统一用os.path.join拼接路径或者用pathlib.Path。另外 Flask 的UPLOAD_FOLDER要用绝对路径相对路径在服务器上会相对于启动目录容易出错from pathlib import Path BASE_DIR Path(__file__).resolve().parent app.config[UPLOAD_FOLDER] str(BASE_DIR / static / known_faces)5.5 现象打卡记录时间比实际早 8 小时原因服务器时区是 UTCdatetime.now()返回 UTC 时间而你在东八区。解决用datetime.now()之前先设时区或者直接用datetime.utcnow() timedelta(hours8)。更规范的做法是装pytzfrom datetime import datetime import pytz tz pytz.timezone(Asia/Shanghai) now datetime.now(tz)数据库存时间时统一存 UTC展示时再转本地时区这样跨时区部署不会乱。6. 活体检测与报表导出让这套系统从能用到好用活体检测是考勤系统从 demo 到实用的分水岭。没有活体检测员工拿手机里别人的照片对着摄像头就能代打卡。常见做法有两种一是动作配合要求员工眨眼或转头用 dlib 的 68 个关键点算眼睛纵横比EAR判断是否眨眼二是用 OpenCV 的 LBP 纹理分析区分真实人脸和屏幕翻拍。眨眼检测的实现思路dlib 的 68 个关键点里左眼是 36 到 41 号点右眼是 42 到 47 号点。眼睛纵横比公式是def eye_aspect_ratio(eye_points): # eye_points 是 6 个点的坐标列表 vertical_1 np.linalg.norm(eye_points[1] - eye_points[5]) vertical_2 np.linalg.norm(eye_points[2] - eye_points[4]) horizontal np.linalg.norm(eye_points[0] - eye_points[3]) return (vertical_1 vertical_2) / (2.0 * horizontal) # 连续帧中 EAR 小于 0.2 然后回到 0.3 以上判定为一次眨眼EAR 在眼睛睁开时约 0.3闭合时降到 0.2 以下。连续检测到 EAR 先降后升就算一次眨眼。打卡时要求员工在 3 秒内眨一次眼通过后才进入特征比对。这个逻辑用 dlib 的关键点就能做不需要额外模型。报表导出用openpyxl或pandasimport pandas as pd from flask import send_file app.route(/export) def export(): # 假设从数据库查到了记录列表 records [ {姓名: 张三, 部门: 技术部, 打卡时间: 2024-01-15 09:02:33}, {姓名: 李四, 部门: 市场部, 打卡时间: 2024-01-15 09:15:07}, ] df pd.DataFrame(records) output_path attendance_report.xlsx df.to_excel(output_path, indexFalse) return send_file(output_path, as_attachmentTrue)pd.DataFrame把记录列表转成表格to_excel写出 xlsx 文件send_file以附件形式返回给浏览器。列名直接用中文导出的报表行政人员能直接看。如果数据量大可以按月份筛选后再导出避免一次性加载几万条记录。我自己的习惯是每次改完比对阈值或换摄像头先拿 5 个员工各拍 10 张照片跑一遍全量测试统计误识率和拒识率确认没问题再上线。这套系统最怕的不是算法不准而是环境变了没重新调参。希望帮到你。本文还有配套的精品资源点击获取

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询