ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Python+Flask+dlib人脸识别考勤系统:从环境搭建到打卡落库全流程

Python+Flask+dlib人脸识别考勤系统:从环境搭建到打卡落库全流程 简介本资源是一套基于Python、Flask与dlib实现的人脸识别企业考勤管理系统面向计算机相关专业的毕业设计学生与课程设计学习者可直接作为高分项目参考或二次开发基础。项目已通过导师指导与答辩评审在Windows 10/11环境下完成调试下载后即可运行并配有完整部署教程。压缩包共416个文件约103.71MB其中32个py文件承载后端业务与dlib人脸识别逻辑26个html与118个js、61个css构建前端交互界面另有79张jpg、51张png等图片资源用于界面展示与测试素材整体结构清晰、模块分明。目前已有170人学习浏览。读者可获得完整源码、数据库与配置文件、使用文档及部署说明便于快速理解人脸识别考勤的签到、记录管理与后台维护流程也能作为课程设计或项目实战的参考模板降低从零搭建的门槛。1. 从一张考勤表说起PythonFlaskdlib 到底能做出什么很多做毕业设计的同学第一次听到「人脸识别考勤管理系统」时脑子里浮现的是公司门口那台刷脸打卡机。但真到自己动手问题立刻变成摄像头怎么调人脸怎么比对打卡记录怎么存Web 界面怎么串起来这套基于 PythonFlaskdlib 的方案解决的正是这条完整链路——从摄像头采集、人脸检测与特征提取到 Flask 提供接口和页面再到考勤数据的落库与查询。它适合两类人一是需要一份能跑通、能答辩、能讲清楚原理的毕业设计二是想快速搭一个内部考勤原型的小团队。dlib 负责「认人」Flask 负责「对外服务」Python 把两者粘起来这就是整个系统的骨架。下面按实际落地顺序拆开讲。2. 环境搭建与 dlib 安装为什么这一步最容易翻车2.1 选型理由为什么是 dlib 而不是别的人脸识别库不少OpenCV 自带 LBPH、face_recognition 封装了 dlib、还有 InsightFace 这类深度学习方案。选 dlib 的核心理由是它在 CPU 上就能跑不需要 GPU依赖相对可控而且face_recognition这个封装库把调用简化到几行代码。对于毕业设计场景答辩老师关心的是「你有没有理解人脸识别的流程」而不是「你用了多新的模型」。dlib 的 HOG 检测器加 128 维特征向量原理清晰、可解释性强讲起来不虚。但 dlib 的安装是出了名的玄学。它在 Windows 上需要 C 编译环境直接pip install dlib大概率报错。常见做法是先装 CMake 和 Visual Studio Build Tools再用 conda 装预编译版本。我一般会推荐 conda 路线省去编译的麻烦。2.2 用 conda 装 dlib 的最小命令# 创建独立环境避免污染系统 Python conda create -n attendance python3.9 -y conda activate attendance # 用 conda-forge 频道装 dlib这是预编译好的不用本地编译 conda install -c conda-forge dlib -y # 再装 face_recognition 和其他依赖 pip install face_recognition flask opencv-python numpy pillow逻辑说明conda-forge 提供的 dlib 是编译好的二进制包避开了 Windows 上 MSVC 编译器的坑。Python 版本选 3.9 是因为 dlib 和 face_recognition 在这个版本上兼容性最稳3.11 以上偶尔会有 wheel 缺失的问题。参数上-c conda-forge指定频道很关键默认频道里 dlib 的版本可能偏旧。装完后验证import dlib import face_recognition print(dlib version:, dlib.__version__) print(face_recognition ok)如果这行能打印出版本号说明环境通了。如果报ImportError: DLL load failed多半是 conda 环境没激活或者系统缺 VC 运行库装一个 Visual C Redistributable 即可。2.3 Flask 项目骨架与依赖清单环境通了之后先把项目结构定下来。一个能跑的最小结构如下attendance_system/ ├── app.py # Flask 主程序 ├── face_utils.py # 人脸注册与识别逻辑 ├── db.py # 数据库操作 ├── static/ │ └── known_faces/ # 存放注册人脸图片 ├── templates/ │ ├── index.html # 打卡页面 │ └── admin.html # 考勤查询页面 └── requirements.txtrequirements.txt内容Flask2.3.2 face_recognition1.3.0 opencv-python4.8.0.74 numpy1.24.3 Pillow10.0.0这里 Flask 用 2.3.x 而不是 3.x是因为部分老教程的写法在 3.x 上有兼容问题毕业设计求稳不求新。opencv-python 用来做摄像头采集和图像预处理numpy 是 face_recognition 的底层依赖。3. 人脸注册与识别128 维特征向量怎么用3.1 原理从一张脸到一串数字dlib 的人脸识别分两步。第一步是检测用 HOG方向梯度直方图或 CNN 找到图中人脸的位置返回一个矩形框坐标。第二步是编码把检测到的人脸区域送进一个预训练的 ResNet 模型输出一个 128 维的浮点向量。这个向量就是这张脸的「指纹」——同一个人不同角度的照片向量距离很近不同人的向量距离远。比对时用欧氏距离两个 128 维向量相减、平方、求和、开根号。face_recognition 默认阈值是 0.6距离小于 0.6 判定为同一人。这个阈值可以调调低更严格可能认不出调高更宽松可能认错。3.2 注册人脸把照片变成特征库import face_recognition import os import pickle KNOWN_FACES_DIR static/known_faces def register_faces(): 遍历 known_faces 目录为每张图片生成 128 维编码 known_encodings [] known_names [] for filename in os.listdir(KNOWN_FACES_DIR): if not filename.lower().endswith((.jpg, .png, .jpeg)): continue # 文件名即员工姓名如 张三.jpg name os.path.splitext(filename)[0] filepath os.path.join(KNOWN_FACES_DIR, filename) image face_recognition.load_image_file(filepath) # 只取第一张脸的编码注册照要求单人正面 encodings face_recognition.face_encodings(image) if len(encodings) 0: print(f[警告] {filename} 未检测到人脸跳过) continue if len(encodings) 1: print(f[警告] {filename} 检测到多张脸只取第一张) known_encodings.append(encodings[0]) known_names.append(name) print(f[注册成功] {name}) # 把编码和姓名序列化保存避免每次启动重新计算 with open(face_data.pkl, wb) as f: pickle.dump({encodings: known_encodings, names: known_names}, f) return known_encodings, known_names逻辑说明face_recognition.load_image_file读进来的是 RGB 格式的 numpy 数组注意不是 OpenCV 默认的 BGR。face_encodings返回一个列表每张脸一个 128 维向量。注册照的要求是单人、正面、光线均匀否则检测不到或编码质量差。序列化成 pkl 是为了启动时直接加载不用每次遍历图片重新算——人脸编码计算比较耗时一张图大概 0.5 到 1 秒。参数说明face_encodings有个可选参数num_jitters默认是 1表示对图像做几次抖动采样。调大比如 10会让编码更鲁棒但速度线性下降。注册阶段可以用 5 到 10识别阶段用默认 1 保证实时性。3.3 实时识别摄像头帧的比对流程import face_recognition import cv2 import pickle import numpy as np def load_known_faces(): with open(face_data.pkl, rb) as f: data pickle.load(f) return data[encodings], data[names] def recognize_from_camera(): known_encodings, known_names load_known_faces() video_capture cv2.VideoCapture(0) # 0 表示默认摄像头 # 每 3 帧处理一次降低 CPU 占用 process_this_frame True while True: ret, frame video_capture.read() if not ret: break if process_this_frame: # OpenCV 是 BGRface_recognition 要 RGB先转换 rgb_frame cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) # 缩小到 1/4 加速检测 small_frame cv2.resize(rgb_frame, (0, 0), fx0.25, fy0.25) face_locations face_recognition.face_locations(small_frame) face_encodings face_recognition.face_encodings(small_frame, face_locations) face_names [] for encoding in face_encodings: # 计算与所有已知人脸的距离 distances face_recognition.face_distance(known_encodings, encoding) best_match_index np.argmin(distances) if distances[best_match_index] 0.6: name known_names[best_match_index] else: name 未知 face_names.append(name) process_this_frame not process_this_frame # 画框和名字坐标要放大回原尺寸 for (top, right, bottom, left), name in zip(face_locations, face_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.9, (0, 255, 0), 2) cv2.imshow(Attendance, frame) if cv2.waitKey(1) 0xFF ord(q): break video_capture.release() cv2.destroyAllWindows()逻辑说明核心优化是「隔帧处理」和「缩小尺寸」。人脸检测是 CPU 大户每帧都跑会卡。缩小到 1/4 后检测速度提升约 4 倍代价是远处的小脸可能漏检。face_distance返回的是待识别编码与每个已知编码的欧氏距离取最小值对应的姓名。阈值 0.6 是经验值实际项目里可以根据误识率调整。参数说明fx0.25, fy0.25是缩放比例如果摄像头分辨率是 640x480缩小后是 160x120检测够用。如果人脸离得远可以改成 0.5。process_this_frame的翻转逻辑让检测每两帧执行一次识别帧率大约减半但视觉上仍然流畅。4. Flask 接口与考勤落库把识别结果变成打卡记录4.1 数据库设计三张表搞定考勤系统的数据模型不复杂三张表足够表名字段说明employeesid, name, face_encoding_path, created_at员工信息face_encoding_path 指向注册照attendanceid, employee_id, check_time, status打卡记录status 区分上班/下班adminid, username, password_hash管理员账号用 SQLite 就够了Flask 自带支持不用额外装数据库服务。建表语句import sqlite3 def init_db(): conn sqlite3.connect(attendance.db) c conn.cursor() c.execute( CREATE TABLE IF NOT EXISTS employees ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL UNIQUE, face_encoding_path TEXT, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ) ) c.execute( CREATE TABLE IF NOT EXISTS attendance ( id INTEGER PRIMARY KEY AUTOINCREMENT, employee_id INTEGER NOT NULL, check_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP, status TEXT DEFAULT check_in, FOREIGN KEY (employee_id) REFERENCES employees(id) ) ) conn.commit() conn.close()逻辑说明employees.name加 UNIQUE 约束防止同名员工重复注册。attendance表用外键关联员工status字段区分上班卡和下班卡。SQLite 的CURRENT_TIMESTAMP自动填时间省去手动传参。4.2 Flask 路由打卡接口与查询页面from flask import Flask, request, jsonify, render_template import sqlite3 import base64 import numpy as np import cv2 import face_recognition import pickle app Flask(__name__) def get_db(): conn sqlite3.connect(attendance.db) conn.row_factory sqlite3.Row return conn app.route(/) def index(): return render_template(index.html) app.route(/api/checkin, methods[POST]) def checkin(): 接收前端传来的 base64 图片识别后写入考勤记录 data request.get_json() if not data or image not in data: return jsonify({success: False, msg: 缺少图片数据}), 400 # 解码 base64 图片 img_data base64.b64decode(data[image].split(,)[1]) nparr np.frombuffer(img_data, np.uint8) frame cv2.imdecode(nparr, cv2.IMREAD_COLOR) rgb_frame cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) # 加载已知人脸 with open(face_data.pkl, rb) as f: known pickle.load(f) face_locations face_recognition.face_locations(rgb_frame) if len(face_locations) 0: return jsonify({success: False, msg: 未检测到人脸}) encodings face_recognition.face_encodings(rgb_frame, face_locations) distances face_recognition.face_distance(known[encodings], encodings[0]) best_idx np.argmin(distances) if distances[best_idx] 0.6: return jsonify({success: False, msg: 人脸未注册}) name known[names][best_idx] # 写入考勤记录 conn get_db() emp conn.execute(SELECT id FROM employees WHERE name ?, (name,)).fetchone() if not emp: conn.close() return jsonify({success: False, msg: 员工不存在}) conn.execute(INSERT INTO attendance (employee_id, status) VALUES (?, ?), (emp[id], check_in)) conn.commit() conn.close() return jsonify({success: True, name: name, msg: f{name} 打卡成功}) app.route(/admin) def admin(): conn get_db() records conn.execute( SELECT e.name, a.check_time, a.status FROM attendance a JOIN employees e ON a.employee_id e.id ORDER BY a.check_time DESC LIMIT 100 ).fetchall() conn.close() return render_template(admin.html, recordsrecords) if __name__ __main__: init_db() app.run(host0.0.0.0, port5000, debugTrue)逻辑说明前端通过getUserMedia拿到摄像头画面转成 base64 传给/api/checkin。后端解码后用同一套 face_recognition 逻辑识别识别成功就插一条 attendance 记录。host0.0.0.0让局域网内其他设备也能访问方便用手机测试。debugTrue开发阶段用上线要关掉。参数说明base64.b64decode前要split(,)[1]因为前端 canvas 导出的 base64 带data:image/jpeg;base64,前缀。face_distance只取encodings[0]即画面中第一张脸多人场景需要遍历。4.3 前端页面调用摄像头并提交!DOCTYPE html html head meta charsetutf-8 title人脸考勤打卡/title /head body h2请正对摄像头/h2 video idvideo width640 height480 autoplay/video canvas idcanvas width640 height480 styledisplay:none;/canvas button onclickcapture()打卡/button p idresult/p script const video document.getElementById(video); const canvas document.getElementById(canvas); // 请求摄像头权限 navigator.mediaDevices.getUserMedia({ video: true }) .then(stream { video.srcObject stream; }) .catch(err { alert(摄像头打开失败: err); }); function capture() { const ctx canvas.getContext(2d); ctx.drawImage(video, 0, 0, 640, 480); const imageData canvas.toDataURL(image/jpeg); fetch(/api/checkin, { method: POST, headers: { Content-Type: application/json }, body: JSON.stringify({ image: imageData }) }) .then(res res.json()) .then(data { document.getElementById(result).innerText data.msg; }) .catch(err { document.getElementById(result).innerText 请求失败; }); } /script /body /html逻辑说明getUserMedia是浏览器标准 API需要 HTTPS 或 localhost 才能调用。canvas.toDataURL把当前帧转成 base64 JPEG质量默认 0.92可以传第二个参数调整。fetch 提交后根据返回的msg显示结果。注意如果部署到服务器必须配 HTTPS否则浏览器会拒绝摄像头权限。本地开发用localhost不受此限制。5. 避坑与排查那些让我熬夜的瞬间5.1 摄像头能打开但识别一直返回「未知」现象前端画面正常打卡接口也返回 200但每次都是「人脸未注册」。原因最常见的是 BGR/RGB 通道搞反了。OpenCV 读进来是 BGRface_recognition 要 RGB如果忘了cvtColor编码出来的向量和注册时不一致距离自然大。解决检查所有face_encodings调用前是否做了cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)。注册和识别两条链路必须用同样的颜色空间。5.2 dlib 安装报 CMake 错误现象pip install dlib卡在Building wheel for dlib然后报 CMake 找不到编译器。原因dlib 源码编译需要 C 工具链Windows 上默认没有。解决走 conda 路线conda install -c conda-forge dlib或者装 Visual Studio Build Tools 后重试。Linux 上apt install cmake build-essential即可。5.3 识别速度慢摄像头画面卡顿现象视频流一卡一卡的CPU 占用接近 100%。原因每帧都跑人脸检测且没有缩小图像。解决加隔帧处理process_this_frame把图像缩小到 1/4 再检测。如果还慢把face_encodings的num_jitters设为 1或者改用 HOG 模型默认就是 HOGCNN 更慢。5.4 打卡记录时间不对现象数据库里的check_time比实际时间差 8 小时。原因SQLite 的CURRENT_TIMESTAMP用的是 UTC 时间。解决查询时用datetime(check_time, localtime)转换或者在插入时用 Python 的datetime.now()手动传时间。5.5 多人同时出现在画面只识别一个现象两个人站一起只返回第一个人的打卡结果。原因代码里只取了encodings[0]。解决遍历所有face_encodings对每个人分别比对和写库。注意去重同一个人短时间内重复打卡只记一次。6. 进阶技巧把识别准确率和工程性再拉一档6.1 用多张注册照提升鲁棒性单张注册照的编码容易受光照和角度影响。改进做法是每个人注册 3 到 5 张不同角度的照片识别时取最小距离def register_multiple(name, image_paths): 为同一员工注册多张照片编码存成列表 encodings [] for path in image_paths: img face_recognition.load_image_file(path) enc face_recognition.face_encodings(img) if enc: encodings.append(enc[0]) return {name: name, encodings: encodings} def recognize_best(known_list, unknown_encoding): 多编码比对取全局最小距离 best_name 未知 best_distance 1.0 for person in known_list: distances face_recognition.face_distance(person[encodings], unknown_encoding) min_d min(distances) if min_d best_distance: best_distance min_d best_name person[name] return best_name if best_distance 0.6 else 未知这样每个人的特征用多个向量表示比单个向量覆盖更多姿态。代价是比对耗时线性增加但员工数量在几十人级别时完全可接受。6.2 活体检测的简易实现照片攻击是考勤系统的经典漏洞——拿手机拍一张脸就能打卡。简易的活体检测可以用眨眼检测用 dlib 的 68 点关键点模型计算眼睛纵横比EAR连续几帧 EAR 低于阈值再恢复判定为眨眼。def eye_aspect_ratio(eye_points): # eye_points 是 6 个关键点坐标 vertical np.linalg.norm(eye_points[1] - eye_points[5]) \ np.linalg.norm(eye_points[2] - eye_points[4]) horizontal np.linalg.norm(eye_points[0] - eye_points[3]) return vertical / (2.0 * horizontal) # EAR 低于 0.2 判定为闭眼连续 2 帧闭眼后睁开算一次眨眼这个方案不能防视频回放但能挡住大部分静态照片攻击。毕业设计里加上这个答辩时是个加分项。6.3 部署到服务器时的注意事项本地跑通不等于线上能用。几个血泪经验第一Flask 自带的开发服务器不能用于生产要用 gunicorn 或 uwsgi第二摄像头采集必须在前端做服务器没有摄像头第三如果前端页面通过 HTTPS 访问后端接口也必须是 HTTPS否则混合内容会被浏览器拦截第四face_data.pkl文件要放在持久化目录容器重启不丢。我一般会在项目根目录放一个run.sh#!/bin/bash # 生产环境启动脚本 gunicorn -w 2 -b 0.0.0.0:5000 app:app --timeout 120-w 2是两个 worker 进程--timeout 120是因为人脸识别接口可能耗时较长默认 30 秒不够。这套系统我从环境搭建到跑通大概花了两天其中一天半在跟 dlib 安装和颜色空间较劲。如果你照着上面的步骤走应该能省下大半天。人脸识别考勤这个方向技术栈成熟、资料多、答辩好讲作为毕业设计是稳的选择。真正值得投入的是把识别准确率和活体检测做扎实这两块能体现你对业务的理解而不只是调库。希望帮到你。本文还有配套的精品资源点击获取
返回列表