简介基于深度学习的人脸识别签到系统是一套完整的毕业设计项目源码主要面向计算机相关专业学生及需要快速搭建签到功能的后端开发者。项目以Python实现覆盖人脸检测、特征提取、身份比对及签到记录管理等核心流程并配有Web管理界面可有效替代传统人工点名或刷卡签到适合用于课程设计、毕业设计或小型场景的考勤演示。资源包共含27个文件大小约102.26MB其中8个Python脚本负责后端业务与人脸识别逻辑7个HTML模板实现登录、注册、首页及404等页面另有数据库文件、配置文档、字体文件及样式表等结构清晰便于按模块理解与二次开发。压缩包内提供完整的依赖清单与初始化说明读者可自行搭建虚拟环境后运行获得包含管理员账号、数据库迁移及基础前端界面在内的可执行项目节省从零搭建的时间。目前已有4467人学习下载适合需要参考实际工程结构并快速上手人脸识别签到场景的开发者。1. 基于深度学习的人脸识别签到系统从照片注册到自动签到的完整链路有人问我毕设里做“基于深度学习的人脸识别签到系统”最难的不是训练模型而是把识别模型和 Web 签到流程真正串起来。这套 Y27.zip 里的 faceRegister-master 源码走的正是这条路深度学习部分用 face_recognition 库封装好的 ResNet 特征提取业务部分用 Flask 写管理后台和签到页面数据落在 SQLite 里照片注册、人脸比对、签到记录一条线打通。适合两类人一类是拿它当毕设底座的在校生另一类是想快速验证“人脸识别门禁机”式签到逻辑的从业者。下文按部署、代码、排错的顺序拆最后给进阶调参建议。2. 系统全貌与选型Flask face_recognition SQLite为什么这套组合适合复现2.1 目录结构与模块职责先说解压后看到什么。Y27.zip 解出来是 faceRegister-master顶层文件不少但按用途分其实就七组。我习惯拿到一个项目先画目录地图不然很容易在一个 .py 文件里转晕分组文件/目录职责应用入口app.pyFlask 主程序命令行管理db、init、runserver识别逻辑functions.py人脸编码提取、比对、签到核心函数接口层api.py对外 API给前端或第三方调用数据模型models/init.pySQLite 表结构定义数据库迁移migrations/、alembic.ini、env.py、script.py.mako、versions/Alembic 迁移脚本负责表结构的版本管理模型权重faceRecognitonModels/深度学习预训练权重识别链路的核心依赖前端模板templates/index、login、add_user、edit_user、404、500、base.html、static/styles.cssJinja2 模板与样式工具脚本fontToImg.py、font/simsun.ttc把中文文本渲染成图片的工具数据库data.sqlite运行期生成的 SQLite 库文件说明文档README.md、README项目说明另一个是依赖说明这里有个细节值得单独提醒model 权重目录在压缩包里叫 faceRecognitonModels少了个字母 i不是 Recognition。我第一次打开时以为是文件名写错了结果发现整个项目内部引用都按这个拼写来的。这种“将错就错”的命名在毕业设计源码里很常见你解压后千万别手滑改成标准拼写不然 functions.py 加载模型时会直接找不到路径。从职责划分能看出这不是一个把识别逻辑写死在页面里的 demo而是分了入口、服务、模型、迁移四层。api.py 单独拎出来意味着签到端可以不是浏览器而是校园卡机、手机端甚至人脸识别门禁机外设去调接口这在答辩演示时是个很加分的切入点。2.2 face_recognition 的深度学习原理与模型文件识别链路的核心是 face_recognition 库它的底层是 dlibdlib 里跑的是一个 ResNet 变体——29 层卷积网络输入 150×150 的标准化人脸图输出一个 128 维的特征向量。训练是在 LFW 数据集上做的官方给的准确率是 99.38%对签到这种“一对多找人”的场景足够用。faceRecognitonModels 目录里放的就是这套深度学习链路需要的三个权重文件常见情况是这样的组合dlib_face_recognition_resnet_model_v1.dat 负责把检测到的人脸框转成 128 维向量shape_predictor_68_face_landmarks.dat 负责定位眼睛、鼻子、嘴角等 68 个关键点用于人脸对齐mmod_human_face_detector.dat 是带 CNN 的人脸检测器用来框出人脸位置。如果你解压后看到目录里只有其中一两个不用慌face_recognition 库本身还自带一份默认权重缺的会自动回退到默认加载只是识别精度会略有差异。选型理由值得展开说。为什么不自己训练一个 CNN因为毕设和工程复现要的是“能用”不是“从零造轮子”。自己训一个识别网络你得先凑几万张标注人脸配 CUDA 环境训几十个 epoch最后准确率还不一定比预训练模型好。而 face_recognition 这个方案把“深度学习环境配置”的成本压到了最低模型文件打进包里代码调用就三行。如果你去对比 ArcFace 之类的方案就会发现那套要配 MXNet 或 InsightFace 环境依赖链长得多单是编译就能劝退一半人。这套源码选了 face_recognition等于把深度学习部分做成了黑匣子你只需要关心输入图片、输出向量、比对阈值这三个口子剩下的交给预训练权重。对毕业设计来说这个取舍非常务实——你把精力省下来去写签到业务和页面答辩时讲识别原理反而更从容。2.3 数据流与表结构从注册照片到签到记录整个系统的数据流我梳理成六步管理员在 add_user.html 提交学号、姓名和照片 → functions.py 提取照片里的人脸编码 → 编码序列化后存入 SQLite 的 user 表 → 签到端摄像头取帧 → 提取当前人脸编码 → 与库中全部编码算距离小于阈值判为同一人 → 写入 attendance 表并跳转签到结果页。模型的表结构在 models/init.py 里典型实现是两张表代码结构大致如下# models/__init__.py 里的表结构常见做法是用户表和签到表分开 from datetime import datetime from flask_sqlalchemy import SQLAlchemy db SQLAlchemy() class User(db.Model): __tablename__ user id db.Column(db.Integer, primary_keyTrue) student_id db.Column(db.String(20), uniqueTrue, nullableFalse) # 学号唯一约束 name db.Column(db.String(50), nullableFalse) face_encoding db.Column(db.Text) # 128 维向量JSON 序列化后存文本 role db.Column(db.String(10), defaultuser) # admin / user class Attendance(db.Model): __tablename__ attendance id db.Column(db.Integer, primary_keyTrue) student_id db.Column(db.String(20), nullableFalse) check_time db.Column(db.DateTime, defaultdatetime.now)说明一下这里的取舍。face_encoding 用 Text 而不是用 BLOB 二进制是为了让数据库文件可以直接被导出、用文本工具查看甚至手动修改这对调试很有帮助——我排错时经常直接把 data.sqlite 拖进 SQLiteStudio看某条记录的编码是否为空一眼就能定位问题。role 字段区分管理员和普通用户init 命令生成的管理员账户 role 值是 admin普通签到用户是 user页面上的编辑、删除按钮就是靠这个字段控制显隐的。签到表只存了学号和签到时间没有存人脸照片因为照片在注册时已经用来生成编码运行期比对只发生在“编码 vs 编码”之间不需要再回查照片。如果你后续想给签到记录加现场抓拍图那就需要在表里加一个 image_path 字段并把摄像头帧保存到磁盘这是后话。3. 环境搭建与部署venv 隔离、依赖安装、数据库迁移一条龙3.1 virtualenv 创建与依赖安装部署的第一步是建虚拟环境。为什么必须用 virtualenv 而不是直接 pip install 到全局因为这套依赖里有 dlib 和 face_recognition它们对 numpy 和编译工具链的版本非常敏感全局环境里只要有一个包版本冲突后面 import 阶段就会翻车。虚拟环境相当于给这个项目单独划了一间隔离舱装坏了删掉重建就是成本极低。Windows 下的完整命令链如下pip install virtualenv virtualenv venv venv\Scripts\activate说明一下每条命令的作用。第一条装 virtualenv 工具本身第二条在当前目录创建名为 venv 的虚拟环境目录里面会自带一份独立的 Python 解释器和 pip第三条激活环境激活成功后命令行提示符前面会出现 (venv) 前缀这一步很多人会漏。如果你在 Linux 或 macOS 上操作激活命令要换成 source venv/bin/activateWindows 的 Scripts 目录在 Linux 下是不存在的——这个差异我踩过在 Windows 上写的笔记拿到 Mac 上直接复制会报“目录不存在”。激活之后安装依赖pip install -r requirements.txtrequirements.txt 里锁的是这套源码声明过的依赖至少包含 Flask、Flask-Script、Flask-Migrate、face_recognition、numpy 这几个核心项。我没法替你核实每个包的具体版本号但强烈建议装的时候留意 pip 输出的版本信息尤其是 dlib 和 face_recognition 这一对——face_recognition 依赖特定版本的 dlib如果 pip 自动给你装了不兼容的版本后面跑 functions.py 时会报属性错误。装完可以执行 python -c import face_recognition; print(face_recognition.version) 验证一下能出版本号说明 dlib 和 face_recognition 的链路是通的。3.2 数据库升级与管理员初始化依赖装完先别急着启动还有两步初始化要做顺序不能反python app.py db upgrade python app.py init第一条 db upgrade 是跑 Alembic 迁移。为什么不用 db.create_all()因为 create_all 只会“创建不存在的表”如果你的 data.sqlite 已经存在但缺字段它不会帮你补而迁移脚本是版本化的upgrade 会把 models 里的表结构对外面这几个版本文件逐个比对缺哪张表、缺哪个字段都补上。这相当于给数据库上了后悔药改坏了可以 downgrade 回退。第二条 init 是生成管理员账户。这个命令在 app.py 里是用 Flask-Script 的 Manager 注册的执行完它会在 user 表里插入一条学号为 000000、密码为 666666 的记录。注意这个密码是明文还是哈希取决于 models 里的 User 类有没有写密码哈希逻辑——如果是毕设源码很多直接明文存的你自己上线前务必改成 werkzeug.security 的 generate_password_hash。提示管理员账户只有这一条学号 000000密码 666666登录页面用它进后台。如果 init 忘了跑直接 runserver 启动再去登录页面会一直提示密码错误。3.3 启动服务与验证页面初始化完成后启动命令最简单python app.py runserver这个 runserver 是 Flask-Script 提供的默认监听 127.0.0.1:5000debug 模式默认开。浏览器访问 http://127.0.0.1:5000先看到的是登录页。用 000000 / 666666 登录进去左侧应该有添加用户、编辑用户、签到记录这几个入口。如果你要局域网测试——比如用手机摄像头对着电脑屏幕做识别演示——建议改成这样启动python app.py runserver -h 0.0.0.0 -p 8080-h 0.0.0.0 表示监听所有网卡这样同一局域网里的手机、平板都能访问-p 8080 是换端口避开 5000 可能被占用的冲突。手机访问时用电脑的局域网 IPWindows 上敲 ipconfig 查 IPv4 地址Linux/Mac 上敲 ifconfig 或 ip addr。注意改了 host 后Windows 防火墙会弹窗询问是否允许 Python 通过一定要点允许不然手机端永远连不上——这个坑后面排错章节还会提。启动后建议按这个顺序做冒烟验证登录 → 添加一个真实用户并传正面照片 → 回到首页用摄像头或上传图片测识别 → 看签到记录里有没有生成新条目。四步全通说明环境是健康的再往下拆代码才有意义。4. 核心代码拆解functions.py 的识别链路与 fontToImg 的文本渲染4.1 人脸编码提取与比对128 维向量是怎么算出来的functions.py 是整个项目的识别心脏。它对外暴露的核心函数常见实现是这样的# functions.py 里的编码提取函数image_path 指向一张本地照片 import face_recognition def get_face_encoding(image_path): image face_recognition.load_image_file(image_path) locations face_recognition.face_locations(image, modelhog) if len(locations) ! 1: return None # 没检测到人脸或者检测到多张人脸都返回空 encoding face_recognition.face_encodings(image, locations)[0] return list(encoding)说明几点。face_locations 默认走 HOG 检测器这是 CPU 上的快速检测一张普通照片几十毫秒出结果如果你把 model 参数改成 cnn它会调 dlib 的 CNN 检测器精度更高但慢得多而且需要编译时带 CUDA 支持否则还是跑 CPU。签到场景我一般用默认的 hog 就够了CNN 留给光线复杂的大合影场景。返回值特意转成 list而不是保留 numpy 的 ndarray原因是为了 JSON 序列化。numpy 数组不能直接 json.dumps转成 list 后 128 个浮点数可以存进 SQLite 的 Text 字段读出来再 json.loads 还原——这是整个项目数据流转最关键的格式约定。比对函数的典型实现# functions.py 里的比对函数known_encodings 是库里全部用户的编码列表 import numpy as np def match_face(known_encodings, target_encoding, tolerance0.6): # face_distance 返回与所有人的欧氏距离越小越像 distances face_recognition.face_distance(known_encodings, target_encoding) index int(np.argmin(distances)) if distances[index] tolerance: return index # 命中库中第 index 个用户 return -1逻辑上讲人脸比对本质是算 128 维空间里的欧氏距离。同一个人不同角度、不同光线的两张照片距离一般在 0.3 到 0.5 之间不同的人基本都在 0.6 以上。tolerance 默认 0.6就是这个项目的判定阈值——小于等于 0.6 算同一人大于 0.6 拒绝。想更严就把 0.6 改成 0.45想更松改成 0.7具体怎么调我在最后一章展开。4.2 管理员添加用户与照片入库添加用户这个页面是 add_user.html对应 app.py 里的一个路由。表单提交学号、姓名、照片三个字段后端处理流程按 Flask 惯例组织# app.py 里的添加用户路由结构按 Flask 常规写法还原 import json, os from flask import request, redirect, url_for, flash app.route(/add_user, methods[GET, POST]) def add_user(): if request.method POST: student_id request.form[student_id].strip() name request.form[name].strip() photo request.files[photo] save_path os.path.join(uploads, student_id .jpg) photo.save(save_path) encoding get_face_encoding(save_path) if encoding is None: os.remove(save_path) # 提取失败要清理临时文件 flash(未检测到人脸请上传清晰的正面照片) return redirect(url_for(add_user)) user User(student_idstudent_id, namename, face_encodingjson.dumps(encoding)) db.session.add(user) db.session.commit() flash(添加成功) return redirect(url_for(index)) return render_template(add_user.html)这里有个容易忽略的坑照片先保存再提取提取失败后 os.remove 这行是必须的。不然每次传一张模糊照片就留下一个垃圾文件uploads 目录会越堆越满而且这些没入库的照片会泄漏到静态目录里成为安全隐患。我见过不少翻车现场就是忘了这行清理答辩时被老师翻到 uploads 里一堆无效图片。另一个细节是学号做了 strip 去空格。很多表单提交的学号带了隐形空格如果不去掉存入数据库时 20230001 和 20230001 会被当成两个不同用户签到端比对时永远匹配不上。4.3 fontToImg.py 与自定义字体签到界面的中文渲染fontToImg.py 这个脚本加的有点突兀但它是“让界面看起来完整”的关键。OpenCV 的 putText 函数不支持中文你直接往上写字输出的图片里全是问号。项目里这个脚本的常见用途是把中文姓名渲染到签到成功或失败的提示图上做法是用 PIL 加载 font/simsun.ttc 字体文件先在 Pillow 里把中文画到一张图上再转成 OpenCV 的 BGR 格式或直接作为 JPEG 保存。# fontToImg.py 的核心逻辑PIL 画中文再输出给 OpenCV 或直接存图 from PIL import Image, ImageDraw, ImageFont def draw_cn_text(text, font_pathfont/simsun.ttc, font_size48): font ImageFont.truetype(font_path, font_size) # 先测量文字尺寸生成恰好能容纳的底图 bbox font.getbbox(text) img Image.new(RGB, (bbox[2] 20, bbox[3] 20), color(255, 255, 255)) draw ImageDraw.Draw(img) draw.text((10, 10), text, fontfont, fill(0, 0, 0)) return img def cn_text_to_bgr(text): img draw_cn_text(text) return img # 需要叠加时转成 numpy 数组再走 OpenCV这个脚本的存在提醒一件事解压后千万不要为了省空间把 font/simsun.ttc 删掉。simsun.ttc 是十几 MB 的中文字体文件看着像占地方的“大块头”但实际上 templates 和签到结果页凡是需要中文叠加的地方都依赖它。一旦缺失轻则姓名显示成方框重则页面 500——这种隐藏依赖不看代码根本发现不了。另外如果你嫌 simsun 宋体不好看可以把这个 ttc 换成微软雅黑 msyh.ttc 或思源黑体的 otf 文件只要把 fontToImg.py 里的 load 路径指过去就行代码逻辑不用动。字体属于纯视觉资源换掉不影响任何业务逻辑。5. 常见问题与排查dlib 编译失败、识别不准、数据库迁移翻车的 5 个现场5.1 现象一pip 安装 face_recognition 时在 dlib 上编译报错现象执行 pip install -r requirements.txt装到 dlib 时刷出一长串红色报错最后一行是 error: command gcc failed 或者 cl.exe failed装不下去。原因dlib 在 Windows 上默认走源码编译需要 C 编译器和 CMake。你的机器上如果只有 Python没有 Visual Studio 的 C 构建工具链编译必然失败。在 Linux 上对应的是缺 g 和 cmake。解决Windows 上先装 Visual Studio Build Tools安装时勾选“使用 C 的桌面开发”和 CMake 工具装完重启再跑 pip install dlib。更省事的办法是直接去 pypi 或阿里的镜像源找对应 Python 版本的 dlib 预编译 wheelpip install 指定 wheel 文件路径跳过编译。我一般建议优先用预编译 wheel能把整个安装时间从十几分钟压缩到两分钟也会少踩一堆编译器的玄学错误。5.2 现象二添加用户时提示“未检测到人脸”现象add_user 页面传一张照片后端 flash 提示检测失败用户加不进去。原因三种最常见——照片分辨率太低人脸区域小于 150×150照片不是正脸侧脸超过 45 度导致关键点定位失败或者照片是压缩过度的网图人脸部分已经糊成色块。解决先确认照片里人脸至少占画面四分之一把人脸裁出来单独存一张图再试。用手机前置摄像头拍正脸避免逆光。项目里 get_face_encoding 返回 None 时只会给一句“未检测到人脸”没有具体原因所以排查时建议临时在 functions.py 里把 locations 的打点数量打印出来看是 0 还是 1——0 说明检测器没找到人脸大于 1 说明照片里有不止一张脸而注册接口要求只能一张。5.3 现象三两个人长得像签到互相认错现象A 同学用自己照片注册B 同学对着摄像头系统却签到了 A。原因tolerance 用的默认 0.6对长得像的脸来说这个阈值太松。face_recognition 的距离分布里双胞胎或者眉眼接近的人可能只有 0.55 的差距0.6 的阈值直接就误判了。解决把比对函数的 tolerance 从 0.6 降到 0.45 到 0.5 之间。降阈值会带来另一个代价——同一人因为光线变化、戴眼镜摘眼镜导致距离变大可能会被拒签这时需要给每个用户多注册几张照片正面、左侧、右侧各一张比对时取“与本人所有照片的最小距离”作为最终距离。这个“一人多图”的思路正是人脸识别门禁机实际部署时的标配做法。5.4 现象四python app.py db upgrade 卡死或报错现象init 执行时报 user 表已存在或 upgrade 报 operational error no such table: alembic_version。原因data.sqlite 已经存在而且是之前用旧版代码跑出来的库表结构不完整或者 data.sqlite 是空文件Alembic 的版本表没建起来。毕设源码里 data.sqlite 经常跟着压缩包一起发出来里面可能残留开发者本地跑过的脏数据直接拿去 upgrade 就会和新迁移脚本冲突。解决备份现有 data.sqlite 后把它删掉重新执行 python app.py db upgrade python app.py init。如果不想丢数据就先手工给旧库补 alembic_version 表再用 upgrade head 强制对齐——但毕业设计场景不值得这么折腾删库重建五分钟搞定。从那以后我拿到这种带 .sqlite 的源码包第一反应永远是把它的 data.sqlite 改名备份再初始化而不是直接用原文件。5.5 现象五runserver 启动提示端口 5000 被占用现象python app.py runserver 启动即报 Address already in use或者页面一直转圈。原因5000 是调试服务常用端口本机可能已有其他进程占用比如之前跑过的 Flask 残留进程、别的开发服务器。解决一条命令查占用Windows 上 netstat -ano | findstr :5000Linux/Mac 上 lsof -i :5000拿到 PID 后 taskkill /PID 进程号 /FWindows或 kill -9 进程号Linux/Mac。不想杀进程就按第三章说的启动时指定端口 python app.py runserver -p 8080。另外提醒一句如果用了 -h 0.0.0.0 局域网访问Windows 防火墙弹窗一定要点允许不然手机端一直 connection refused还以为是代码问题。6. 进阶玩法调 tolerance、批量导入照片与出门禁机风格的验收6.1 阈值调优的正确姿势如果你要把这套系统从“能跑”调到“能用”第一个要动的是 tolerance。别直接猜用数据说话写一个脚本把库中每个用户的注册照和一张现场自拍照做 face_distance 统计算同人距离的均值和跨人距离的均值取二者的中点为阈值。实操时我一般先设 0.5然后用 10 个人的测试集跑一遍统计误识率和拒识率再微调——0.5 偏严就涨到 0.55偏松就降到 0.45。6.2 批量导入把一整个班的照片一次性灌进去手动在页面添加用户太慢可以用一个独立脚本循环调用 functions.py 的接口# batch_import.py批量导入目录下的证件照文件名即学号 import os, json from functions import get_face_encoding from models import db, User def import_folder(folder_path): for filename in os.listdir(folder_path): if not filename.lower().endswith(.jpg): continue student_id filename.split(.)[0] encoding get_face_encoding(os.path.join(folder_path, filename)) if encoding is None: print(跳过, student_id) continue db.session.add(User(student_idstudent_id, namestudent_id, face_encodingjson.dumps(encoding))) db.session.commit()脚本会跳过检测不到人脸的照片并把跳过的学号打印出来方便你回头单独补录。这个功能对答辩前临时录入十几个同学非常实用。6.3 出门禁机风格的验收最后验收时把摄像头对准电脑屏幕用手机前置摄像头拍摄屏幕里的签到页模拟真实的人脸识别门禁机场景。重点测三件事光线变化时是否稳定开灯、关灯各测一次、戴眼镜与摘眼镜是否都通过、两个人同时出现在画面里会不会误判。这三项过了整套系统在技术层面的完成度就立住了。关于这套系统的定位我的态度是它不是工业级产品但作为毕业设计和签到逻辑验证结构完整、链路清晰识别精度在可控场景下也够用。真正的生产部署还需要换 MySQL、加 Redis 缓存、做分布式会话但那已经是下一步的事了。从那以后我每次拿到这种带模型权重的 Flask 项目第一件事都是看 requirements.txt 和模型目录是否齐整第二件事是检查 data.sqlite 是不是脏数据然后才敢动 pip install——这套顺序帮我避开过好几次深夜排错的局面。希望帮到你。本文还有配套的精品资源点击获取