
简介本资源是一套基于Python、Flask与dlib实现的人脸识别企业考勤管理系统面向计算机相关专业的毕业设计学生与课程设计学习者可帮助解决考勤场景下的人脸检测、身份识别与打卡记录管理等核心问题。压缩包共416个文件约103.71MB以118个js、61个css、51个png与79个jpg等前端资源为主配合32个py后端脚本、26个html页面及少量xml、字体与数据文件构成前后端分离的完整工程结构。该项目为个人高分毕业设计已通过导师指导与答辩评审得分97分并在Windows 10/11环境下严格调试下载即用。资源内含源码与使用文档部署教程齐全读者可据此掌握Flask路由设计、dlib人脸特征提取、数据库建模与前端页面交互等关键环节也可作为课程设计或二次开发的基础模板。目前已有170人学习关注。1. 从一份 97 分毕设拆起Flask dlib 考勤系统到底能跑出什么去年帮学弟看毕设他拿到的需求是企业考勤管理系统第一反应是上 SpringBoot 百度人脸 API结果导师一句要本地化、要能离线识别直接推翻。后来换成本地 dlib Flask 这套组合答辩拿了 97 分。这份资源就是那套方案的完整源码包核心链路是浏览器调摄像头抓帧 → 后端 Flask 接收 base64 图像 → dlib 做 68 点人脸检测 ResNet 特征提取 → 与本地人脸库比对 → 命中后写考勤记录进数据库。它解决的是人脸识别 Web 管理后台这条完整闭环不是单点 demo。适合三类人一是毕设/课程设计需要交完整系统的学生二是想学 Flask 前后端联调但不想从零搭脚手架的开发者三是需要给小型团队做本地打卡原型的工程师。整套代码在 Windows 10/11 上调试过下载解压后按文档走就能起服务省掉的是环境踩坑和模块拼装的时间。2. 环境搭建与依赖锁定dlib 编译是第一个分水岭2.1 为什么 dlib 装不上是常态dlib 不是纯 Python 包它底层是 C 模板库pip install dlib 时如果找不到预编译 wheel就会触发本地编译而本地编译又依赖 CMake 和 Visual Studio 的 C 构建工具。很多人卡在这一步报错信息通常是CMake is not installed或者error: Microsoft Visual C 14.0 or greater is required。这不是代码问题是工具链缺失。常见做法有两种一是装 CMake VS Build Tools 后源码编译耗时 10 到 30 分钟二是直接找对应 Python 版本的 dlib wheel 文件离线安装。我一般推荐第二种尤其是 Python 3.8/3.9 这两个版本wheel 资源最全。注意 Python 版本别选太新3.11 之后部分依赖的兼容性会变差这个项目在 3.8 上验证最稳。2.2 依赖安装的完整命令链先建虚拟环境别在全局环境里装dlib 和 opencv 的版本冲突会污染其他项目。# 创建虚拟环境指定 Python 3.8 python -m venv venv # Windows 激活 venv\Scripts\activate # 升级 pip老版本 pip 解析 wheel 会出错 python -m pip install --upgrade pip激活后装核心依赖顺序有讲究先装 numpy因为 dlib 和 opencv 都依赖它先装能避免重复编译。# 先装 numpy锁定 1.23 以下版本兼容性最好 pip install numpy1.24 # 装 opencv用于图像预处理和摄像头抓帧 pip install opencv-python4.5.5.64 # 装 dlib如果本地有 wheel 就指定路径 pip install dlib-19.22.99-cp38-cp38-win_amd64.whl # 装 Flask 及数据库相关 pip install Flask2.0.3 Flask-SQLAlchemy2.5.1参数说明numpy1.24是因为 1.24 之后移除了部分 dlib 依赖的旧接口opencv 选 4.5.5 是它在 Windows 上对摄像头设备的兼容性最稳Flask 2.0.3 是该项目验证过的版本2.1 之后部分扩展的导入路径有变化。装完用pip list核对一遍重点看 dlib 和 opencv 是否都显示已安装。2.3 人脸模型文件的放置位置dlib 的人脸检测和特征提取需要两个预训练模型文件shape_predictor_68_face_landmarks.dat和dlib_face_recognition_resnet_model_v1.dat。这两个文件加起来约 100MB不在 pip 包里需要单独下载后放到项目指定目录。项目文档里一般会写明路径常见是放在models/目录下。如果路径不对运行时会报Unable to open shape_predictor之类的错误这时候检查代码里dlib.shape_predictor()的入参路径是否和实际文件位置一致。提示模型文件路径建议用绝对路径或基于os.path.dirname(__file__)拼接别用相对路径否则换个启动目录就找不到文件。3. 人脸识别核心链路从抓帧到比对命中的四步拆解3.1 前端抓帧与 base64 传输浏览器端调getUserMedia拿到摄像头流画到 canvas 上再toDataURL转成 base64 字符串POST 给后端。这一步的坑在于图像质量分辨率太高传输慢太低识别率掉。项目里一般设成 640x480JPEG 质量 0.8 左右。// 前端抓帧并转 base64 const canvas document.createElement(canvas); canvas.width 640; canvas.height 480; const ctx canvas.getContext(2d); ctx.drawImage(videoElement, 0, 0, 640, 480); // 质量 0.8兼顾清晰度和传输体积 const base64Image canvas.toDataURL(image/jpeg, 0.8); fetch(/api/attendance/check, { method: POST, headers: {Content-Type: application/json}, body: JSON.stringify({image: base64Image}) });逻辑说明drawImage把视频当前帧画到 canvastoDataURL输出带data:image/jpeg;base64,前缀的字符串。后端接收后要先去掉前缀再解码否则cv2.imdecode会返回 None。参数上640x480 是识别精度和传输速度的平衡点再低到 320x240 时侧脸和戴眼镜的场景容易漏检。3.2 后端解码与 dlib 特征提取后端拿到 base64 后先解码成 numpy 数组再转灰度图然后走 dlib 的检测和特征提取流程。import base64 import cv2 import numpy as np import dlib # 初始化检测器和特征提取器 detector dlib.get_frontal_face_detector() sp dlib.shape_predictor(models/shape_predictor_68_face_landmarks.dat) facerec dlib.face_recognition_model_v1(models/dlib_face_recognition_resnet_model_v1.dat) def extract_face_descriptor(base64_str): # 去掉 data:image/jpeg;base64, 前缀 img_data base64_str.split(,)[1] img_bytes base64.b64decode(img_data) nparr np.frombuffer(img_bytes, np.uint8) img cv2.imdecode(nparr, cv2.IMREAD_COLOR) # 转灰度dlib 检测器只吃灰度图 gray cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 检测人脸第二个参数是上采样次数1 表示放大一倍检测 faces detector(gray, 1) if len(faces) 0: return None # 取第一张脸计算 68 点 shape sp(gray, faces[0]) # 提取 128 维特征向量 descriptor facerec.compute_face_descriptor(img, shape) return np.array(descriptor)逻辑说明detector(gray, 1)的第二个参数是上采样次数设 1 会把图像放大一倍再检测能提升小脸检出率但耗时翻倍。如果场景里人脸占画面比例大设 0 就够。compute_face_descriptor返回的是 128 维向量这是 dlib 的 ResNet 模型输出后续比对就是算两个向量的欧氏距离。参数上距离阈值一般设 0.6小于 0.6 判定为同一人这个值在 LFW 数据集上的准确率约 99.38%但实际考勤场景受光照影响可以放宽到 0.5 到 0.55 之间减少误识。3.3 人脸库比对与阈值判定注册时把每个员工的特征向量存进数据库打卡时逐个比对取距离最小的那个。def find_match(descriptor, threshold0.55): # 从数据库取出所有人脸特征 employees Employee.query.filter(Employee.face_encoding.isnot(None)).all() min_dist float(inf) matched None for emp in employees: # 数据库存的是 JSON 字符串转回 numpy 数组 known np.array(json.loads(emp.face_encoding)) # 计算欧氏距离 dist np.linalg.norm(descriptor - known) if dist min_dist: min_dist dist matched emp # 距离小于阈值才算命中 if min_dist threshold: return matched, min_dist return None, min_dist逻辑说明np.linalg.norm算的是两个 128 维向量的欧氏距离距离越小越像。阈值 0.55 比官方推荐的 0.6 更严是为了降低误识率——考勤场景里把别人认成你比认不出你更麻烦。如果员工数量上百逐个比对会慢常见优化是先把所有人脸特征加载进内存缓存或者用 faiss 做向量检索但毕设规模一般几十人直接循环够用。3.4 考勤记录写入与去重命中后写考勤记录这里有个容易忽略的点同一个人短时间内多次打卡要防重。from datetime import datetime, timedelta def mark_attendance(employee_id): now datetime.now() # 查最近 5 分钟内的记录防止重复打卡 recent Attendance.query.filter( Attendance.employee_id employee_id, Attendance.check_time now - timedelta(minutes5) ).first() if recent: return {status: duplicate, msg: 5 分钟内已打卡} record Attendance( employee_idemployee_id, check_timenow, check_type上班 if now.hour 12 else 下班 ) db.session.add(record) db.session.commit() return {status: ok, time: now.strftime(%H:%M:%S)}逻辑说明timedelta(minutes5)是防重窗口设太短起不到作用设太长会漏掉正常的上下班两次打卡。5 分钟是常见折中值。check_type按小时判断上下午如果企业有更复杂的班次规则这里要换成读排班表。参数上防重窗口建议做成配置项不同企业考勤规则不一样。4. Flask 后端结构与数据库设计别把逻辑全塞进路由4.1 项目目录分层拿到源码先看目录结构这个项目典型的分层是project/ ├── app.py # 入口注册蓝图 ├── models.py # SQLAlchemy 模型 ├── views/ │ ├── auth.py # 登录注册 │ ├── attendance.py # 考勤打卡 │ └── admin.py # 后台管理 ├── utils/ │ └── face_utils.py # dlib 封装 ├── static/ # css/js/模型文件 ├── templates/ # jinja2 模板 └── models/ # dlib 模型文件分层的好处是路由只负责收请求和返响应人脸处理逻辑全在utils/face_utils.py数据库操作在models.py。改识别阈值只动一个文件不用满项目找。4.2 核心数据表设计考勤系统最少三张表员工表、人脸特征表、考勤记录表。人脸特征单独一张表而不是塞进员工表是为了一个员工可以存多张人脸正脸、侧脸提升识别率。表名关键字段说明employeeid, name, dept, position员工基本信息face_encodingid, employee_id, encoding, create_time128 维特征JSON 存储attendanceid, employee_id, check_time, check_type打卡记录encoding字段用 Text 类型存 JSON 字符串别用 Blob调试时不好看。check_time加索引按时间范围查记录时快很多。4.3 蓝图注册与路由划分from flask import Flask from views.auth import auth_bp from views.attendance import attendance_bp from views.admin import admin_bp app Flask(__name__) app.config[SQLALCHEMY_DATABASE_URI] sqlite:///attendance.db app.config[SECRET_KEY] your-secret-key # 注册蓝图url_prefix 区分模块 app.register_blueprint(auth_bp, url_prefix/auth) app.register_blueprint(attendance_bp, url_prefix/api/attendance) app.register_blueprint(admin_bp, url_prefix/admin) if __name__ __main__: app.run(host0.0.0.0, port5000, debugTrue)逻辑说明url_prefix把不同模块的接口隔开前端调/api/attendance/check就知道是考勤相关。host0.0.0.0让局域网内其他设备也能访问方便用手机测试摄像头。debugTrue开发时用上线要关掉否则有安全风险。数据库用 SQLite 够毕设用要上生产换 MySQL 只需改连接字符串。5. 避坑与排查那些文档里不会写的翻车现场5.1 摄像头打不开或黑屏现象前端页面摄像头区域一片黑控制台报NotAllowedError或NotFoundError。 原因浏览器权限没给或者页面不是 HTTPS/localhost 环境。getUserMedia在非安全上下文里直接被禁。 解决本地测试用localhost或127.0.0.1访问别用局域网 IP。如果必须用 IP得配 HTTPS 证书。另外检查浏览器地址栏左侧的摄像头权限图标手动允许。5.2 识别总是返回 None现象摄像头正常但后端一直返回未检测到人脸。 原因三种可能——图像 base64 前缀没去掉导致解码失败、灰度转换漏了、上采样次数设 0 导致小脸检不出。 解决先在extract_face_descriptor里打印img.shape如果是 None 说明解码失败检查split(,)[1]有没有拿到数据。再打印len(faces)如果是 0 就把detector(gray, 1)的上采样改成 2 试试。光照太暗也会导致检测失败加个直方图均衡化cv2.equalizeHist(gray)能改善。5.3 同一个人识别成不同人现象同一个人换个角度或光线识别结果就变了。 原因注册时只存了一张正脸照特征覆盖不够或者阈值设太高把不同人认成同一人。 解决注册时引导用户多角度采集 3 到 5 张每张都提取特征存进face_encoding表。比对时算所有特征的最小距离而不是只比一张。阈值从 0.6 降到 0.5 能减少误识但会提升拒识率需要按场景权衡。5.4 数据库写入报编码错误现象打卡时db.session.commit()抛UnicodeEncodeError。 原因SQLite 默认编码和 Python 字符串编码不一致或者员工姓名里有特殊字符。 解决建库时指定charsetutf8连接字符串写成sqlite:///attendance.db?charsetutf8。员工姓名入库前做一次name.encode(utf-8).decode(utf-8)清洗。如果用的是 MySQL确保库和表的字符集都是utf8mb4。5.5 部署到服务器后模型文件找不到现象本地跑得好好的传到服务器就报Unable to open shape_predictor。 原因模型文件太大没传上去或者路径用了 Windows 的反斜杠。 解决模型文件用scp或 FTP 单独传别混在代码压缩包里容易漏。路径统一用os.path.join拼接别手写models\xxx.dat。上线前在服务器上ls -lh models/确认两个 .dat 文件都在大小对得上。6. 从能跑到好用识别率调优与答辩演示技巧把系统跑起来只是及格线答辩要拿高分得在识别率和演示效果上下功夫。我帮学弟调优时总结了几个实操点都是现场能验证的。第一是光照预处理。dlib 对光照敏感背光或侧光下检测率断崖式下跌。在extract_face_descriptor里加一步 CLAHE限制对比度自适应直方图均衡化比全局equalizeHist更稳不会把噪点也放大。# CLAHE 预处理clipLimit 控制对比度增强幅度 clahe cv2.createCLAHE(clipLimit2.0, tileGridSize(8, 8)) gray clahe.apply(gray)clipLimit2.0是经验值设太高会过增强产生伪影设太低没效果。tileGridSize设 8x8 表示把图分成 8x8 块分别均衡化适合人脸这种局部光照不均的场景。第二是多特征注册。注册接口改成接收多张图每张都提特征存库。比对时算待识别特征与所有注册特征的最小距离。实测同一个人存 3 张正脸、左转 30 度、右转 30 度识别率能从 70% 提到 90% 以上。第三是演示时的取巧。答辩现场光线不可控提前准备一段录好的视频文件代替实时摄像头用cv2.VideoCapture(demo.mp4)逐帧读识别效果稳定得多。代码里加个开关演示模式读视频正常模式读摄像头。调优项默认值调优后效果上采样次数01小脸检出率提升距离阈值0.60.5误识率下降注册照片数13识别率提升约 20%光照预处理无CLAHE逆光场景可用第四是日志留痕。每次识别把距离值、命中员工、耗时写进日志文件答辩时导师问识别率多少你能直接翻日志给数据比空口说挺准的有说服力。import logging logging.basicConfig(filenamerecognition.log, levellogging.INFO) # 在比对后记录 logging.info(femp{matched.name if matched else none}, fdist{min_dist:.4f}, cost{elapsed:.3f}s)从那以后我每次交付带识别的项目都强制走一遍多光照 多角度 日志留痕这三步不然现场翻车概率太高。这套源码的骨架已经搭好了调优就是在这上面加料。希望帮到你。本文还有配套的精品资源点击获取