简介这是一套面向计算机相关专业学生与开发者的深度学习人脸识别签到系统完整项目基于Python、Flask与OpenCV构建可作为毕业设计、课程设计或项目立项演示使用。项目实现了人脸注册、识别签到、用户管理等核心功能并配套数据集与详细文档适合具备一定Python基础、希望快速上手计算机视觉实战的读者。资源包共28个文件约101.47MB包含8个Python源码文件、7个HTML页面模板、4个数据文件以及数据库、配置文件、样式表与字体资源等结构清晰便于按模块阅读与二次开发。目前已有340人学习下载。项目代码经过测试运行成功读者可获取完整源码、数据集与说明文档直接用于毕设或课设也可在此基础上修改扩展功能是学习Flask与OpenCV人脸识别集成的实用参考。1. 从一份能跑通的毕设包说起Flask OpenCV 人脸签到到底怎么落地很多同学做毕设时卡在同一个地方算法跑得通但一做成 Web 系统就散架。摄像头能识别人脸可用户注册、签到记录、后台管理全得自己从零搭。这份基于 Python Flask OpenCV 的人脸识别签到系统源码包解决的正是这个断层——它把深度学习人脸识别、Flask 后端、SQLite 数据库和前端模板串成了一条完整链路拿到手就能跑改一改就能当毕设交。它适合三类人计算机相关专业做毕设或课程设计的学生想快速理解「人脸识别 Web 签到」完整工程结构的开发者以及需要一套可演示、可扩展原型的从业者。包里带了数据集、详细文档和迁移脚本不是那种只丢几个 .py 文件的半成品。下面我按实际拆包和复现的顺序把这份资源讲透。2. 拆开压缩包先看什么目录结构与技术栈对应关系2.1 从文件清单反推系统架构拿到一个陌生项目我习惯先看目录再动手。这份包的根目录结构大致是这样faceRegister-master/ ├── app.py # Flask 应用入口 ├── api.py # 接口层处理前端请求 ├── functions.py # 核心业务逻辑人脸注册、识别、签到 ├── faceRecognitonModels/ # 人脸识别模型相关代码 │ └── __init__.py ├── models/ # 数据库模型定义 ├── templates/ # Jinja2 前端模板 │ ├── base.html │ ├── index.html │ ├── login.html │ ├── add_user.html │ ├── edit_user.html │ ├── 404.html │ └── 500.html ├── static/ │ └── styles.css ├── migrations/ # Alembic 数据库迁移 │ ├── versions/ │ ├── env.py │ └── script.py.mako ├── alembic.ini ├── font/ │ ├── fontToImg.py # 字体转图片工具 │ └── simsun.ttc ├── data.sqlite # SQLite 数据库文件 ├── requirements.txt ├── test.py └── README.md这个结构透露了几个关键信息。app.py是 Flask 的启动入口api.py和functions.py做了接口与逻辑的分离说明作者不是把所有代码堆在一个文件里。migrations/目录配合alembic.ini说明数据库表结构是通过 Alembic 管理的不是手写 SQL 建表。faceRecognitonModels/目录名里有个拼写小瑕疵Recogniton 少了个 i但不影响运行改的时候注意别引用错。font/目录里的simsun.ttc和fontToImg.py值得单独说一句。人脸识别签到系统通常需要在识别结果上叠加中文文字比如显示姓名而 OpenCV 自带的cv2.putText不支持中文会显示成乱码。作者用字体文件把中文转成图片再叠加这是常见做法。data.sqlite是预置的数据库文件里面可能已经有测试数据第一次跑之前建议先看一眼。2.2 技术栈版本与依赖确认requirements.txt是复现的第一道关卡。这份包的核心依赖大致包括依赖作用常见版本区间FlaskWeb 框架路由与模板渲染2.xFlask-SQLAlchemyORM操作 SQLite2.x/3.xFlask-Migrate数据库迁移基于 Alembic3.x/4.xOpenCV (cv2)图像采集、预处理、人脸检测4.xNumPy矩阵运算图像数据底层1.2xPillow图像处理配合字体渲染9.x/10.x实际版本以requirements.txt为准我这里给的是常见区间。装依赖时最容易翻车的是 OpenCV 和 NumPy 的版本兼容——NumPy 2.x 刚出那阵不少 OpenCV 版本还没跟上装完 import 就报错。稳妥做法是先装requirements.txt里锁定的版本别自己升级。# 建议在虚拟环境里操作避免污染全局 python -m venv venv # Windows venv\Scripts\activate # macOS / Linux source venv/bin/activate # 按锁定版本安装 pip install -r requirements.txt装完先别急着跑app.py用一条命令验证核心库能不能正常导入# verify_env.py import cv2 import numpy as np import flask import sqlalchemy print(OpenCV:, cv2.__version__) print(NumPy:, np.__version__) print(Flask:, flask.__version__) print(SQLAlchemy:, sqlalchemy.__version__) # 验证 OpenCV 的人脸检测器能否加载 cascade_path cv2.data.haarcascades haarcascade_frontalface_default.xml face_cascade cv2.CascadeClassifier(cascade_path) print(Cascade loaded:, not face_cascade.empty())这段代码做了两件事打印各库版本确认没有装串加载 OpenCV 自带的 Haar 级联分类器确认cv2.data路径下的模型文件存在。如果Cascade loaded输出False说明 OpenCV 安装不完整重装opencv-python而不是opencv-python-headless后者不带 GUI 相关数据文件。2.3 数据库初始化与迁移执行data.sqlite虽然预置了但如果你改了模型或者想从干净状态开始得走一遍迁移。Alembic 的配置在alembic.ini和migrations/env.py里Flask-Migrate 会读取app.py里的应用实例。# 设置 Flask 应用入口Windows 用 setLinux/macOS 用 export export FLASK_APPapp.py # 查看当前迁移状态 flask db current # 如果数据库是空的或想重建先升级到最新 flask db upgrade # 如果改了 models/ 里的表结构生成新迁移 flask db migrate -m add new field flask db upgrade这里有个坑flask db migrate依赖模型是否被正确导入。如果models/下的模型类没有在app.py或env.py里被 importAlembic 检测不到表变化生成的迁移文件是空的。检查migrations/env.py里有没有from models import *或类似的导入语句。数据库表结构通常包含用户表存姓名、学号/工号、人脸特征、签到记录表关联用户和时间戳。具体字段以models/下的定义为准用 SQLite 命令行或 DB Browser 打开data.sqlite就能看到。3. 人脸注册与识别链路从摄像头帧到签到记录3.1 人脸检测与特征提取的实现路径这份包的人脸识别链路核心在functions.py和faceRecognitonModels/里。常见做法是OpenCV 负责检测人脸区域深度学习模型负责提取特征向量然后比对或分类。先看人脸检测部分。OpenCV 提供了 Haar 级联和 DNN 两种方式。Haar 级联速度快但误检率偏高DNN 模块可以加载 Caffe 或 TensorFlow 模型精度更好。这份包大概率用的是 Haar 级联做初筛因为依赖少、跑得快适合毕设演示场景。# 人脸检测的典型流程基于 functions.py 的逻辑还原 import cv2 import numpy as np def detect_faces(frame, cascade_pathNone): 从一帧图像中检测人脸区域 if cascade_path is None: cascade_path cv2.data.haarcascades haarcascade_frontalface_default.xml face_cascade cv2.CascadeClassifier(cascade_path) # 转灰度Haar 级联在灰度图上工作 gray cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) # detectMultiScale 参数说明 # scaleFactor1.1 每次图像尺寸缩小比例越小检测越慢但越全 # minNeighbors5 每个候选框保留的最少邻域数越大越严格 # minSize(30,30) 最小人脸尺寸小于此值忽略 faces face_cascade.detectMultiScale( gray, scaleFactor1.1, minNeighbors5, minSize(30, 30) ) return faces, grayscaleFactor和minNeighbors是两个需要根据实际场景调的参数。摄像头离得远、人脸小就把minSize调小误检多比如把窗户格子当人脸就把minNeighbors调大。我一般先在静态图上试调好了再上摄像头。特征提取部分如果用的是深度学习模型常见方案是 FaceNet 或 ArcFace 的轻量版输出 128 维或 512 维特征向量。faceRecognitonModels/__init__.py里应该封装了模型加载和推理的接口。由于包内没有明确给出模型文件格式实际使用时需要确认模型权重是否包含在压缩包里还是需要单独下载。# 特征提取与比对的逻辑框架 def extract_feature(face_img, model): 将人脸图像转为特征向量 # 预处理调整到模型输入尺寸归一化 face_resized cv2.resize(face_img, (160, 160)) face_normalized face_resized.astype(float32) / 255.0 # 增加 batch 维度 face_batch np.expand_dims(face_normalized, axis0) # 模型推理 feature model.predict(face_batch)[0] return feature def cosine_similarity(feat1, feat2): 余弦相似度值越接近 1 越相似 dot np.dot(feat1, feat2) norm np.linalg.norm(feat1) * np.linalg.norm(feat2) return dot / (norm 1e-6)比对时设一个阈值比如余弦相似度大于 0.6 判定为同一人。阈值调高误识率降低但拒识率升高调低则相反。毕设演示场景下0.5 到 0.7 之间比较常见具体看模型和数据集。3.2 Flask 路由与签到接口的串联app.py和api.py负责把识别能力暴露成 HTTP 接口。典型的路由设计包括/首页、/login登录、/add_user注册人脸、/api/recognize识别签到、/edit_user编辑用户。# app.py 路由注册的典型结构 from flask import Flask, render_template, request, jsonify from api import recognize_face, register_face app Flask(__name__) app.route(/) def index(): return render_template(index.html) app.route(/add_user, methods[GET, POST]) def add_user(): if request.method POST: name request.form.get(name) user_id request.form.get(user_id) # 调用注册逻辑保存人脸特征到数据库 result register_face(name, user_id) return jsonify(result) return render_template(add_user.html) app.route(/api/recognize, methods[POST]) def recognize(): # 接收前端传来的图像数据base64 或文件 image_data request.json.get(image) result recognize_face(image_data) return jsonify(result)前端模板用 Jinja2 渲染base.html定义公共布局其他页面继承它。static/styles.css管样式。摄像头采集一般用 JavaScript 的getUserMediaAPI把帧转成 base64 发给后端。// 前端摄像头采集与发送的典型写法 const video document.getElementById(video); const canvas document.getElementById(canvas); // 请求摄像头权限 navigator.mediaDevices.getUserMedia({ video: true }) .then(stream { video.srcObject stream; }) .catch(err { console.error(摄像头打开失败:, err); }); // 抓帧并发送到后端 function captureAndSend() { const context canvas.getContext(2d); context.drawImage(video, 0, 0, 320, 240); const imageData canvas.toDataURL(image/jpeg); fetch(/api/recognize, { method: POST, headers: { Content-Type: application/json }, body: JSON.stringify({ image: imageData }) }) .then(res res.json()) .then(data { if (data.success) { alert(签到成功 data.name); } else { alert(识别失败请重试); } }); }这里有个实际部署时才会暴露的问题浏览器出于安全考虑getUserMedia只在localhost或 HTTPS 下可用。如果你把 Flask 跑在局域网 IP 上用另一台设备访问摄像头调不起来。解决办法是配 HTTPS 证书或者用localhost做演示。3.3 签到记录写入与查询签到成功后functions.py里会把记录写进 SQLite。表结构通常包含用户 ID、签到时间、签到状态。查询时按时间倒序取最近记录展示在首页或管理页。# 签到记录写入的典型逻辑 from datetime import datetime from models import db, AttendanceRecord def mark_attendance(user_id): 写入一条签到记录 now datetime.now() # 检查今天是否已签到避免重复 today_start now.replace(hour0, minute0, second0, microsecond0) existing AttendanceRecord.query.filter( AttendanceRecord.user_id user_id, AttendanceRecord.check_time today_start ).first() if existing: return {success: False, msg: 今日已签到} record AttendanceRecord( user_iduser_id, check_timenow, statusnormal ) db.session.add(record) db.session.commit() return {success: True, time: now.strftime(%Y-%m-%d %H:%M:%S)}这段逻辑里防重复签到是实际使用中必须考虑的。如果不加判断同一个人站在摄像头前多抓几帧就会写多条记录。用「当天是否已有记录」做去重是最简单的做法更严谨的可以加时间窗口比如 5 分钟内不重复。4. 跑起来才会遇到的坑环境、模型与摄像头排查4.1 依赖版本冲突导致 import 失败现象pip install -r requirements.txt装完运行python app.py报ImportError: numpy.core.multiarray failed to import或AttributeError: module numpy has no attribute float。原因NumPy 版本与 OpenCV 或其他依赖不兼容。NumPy 1.24 移除了np.float等别名如果代码里用了旧写法就会报错NumPy 2.x 与部分 OpenCV 版本 ABI 不匹配。解决先确认requirements.txt里锁定的 NumPy 版本严格按它装。如果文件里没锁手动装numpy2和匹配的opencv-python版本。用pip install numpy1.26.4 opencv-python4.9.0.80这类明确版本号的方式别让 pip 自己解析。4.2 摄像头打不开或黑屏现象前端页面加载了但视频区域一直黑屏控制台报NotAllowedError或NotFoundError。原因三种可能——浏览器没授权摄像头权限getUserMedia在非 localhost 的非 HTTPS 环境下被禁用摄像头被其他程序占用。解决先检查浏览器地址栏的权限图标确认摄像头已允许。如果是局域网访问改用localhost或配 HTTPS。关掉其他占用摄像头的程序腾讯会议、OBS 等。在代码里加错误回调把具体错误打出来navigator.mediaDevices.getUserMedia({ video: true }) .then(stream { video.srcObject stream; }) .catch(err { // 区分错误类型 if (err.name NotAllowedError) { console.error(用户拒绝了摄像头权限); } else if (err.name NotFoundError) { console.error(未检测到摄像头设备); } else { console.error(摄像头错误:, err); } });4.3 人脸检测框偏移或识别率低现象检测到的人脸框位置偏了或者同一个人换个角度就识别不出来。原因Haar 级联对侧脸和光照变化敏感detectMultiScale参数没调好特征提取时人脸对齐没做。解决先调scaleFactor和minNeighbors在静态测试图上找到一组能稳定框住人脸的参数。如果还是不行换 DNN 人脸检测器OpenCV 的cv2.dnn模块加载 Caffe 模型。识别率低的话检查注册时存的特征向量是否做了归一化比对时用的距离度量是否和注册时一致。4.4 数据库迁移报「table already exists」现象执行flask db upgrade时报错说表已存在。原因data.sqlite里已经有表了但 Alembic 的版本记录没同步。或者之前手动建过表。解决最干净的做法是删掉data.sqlite重新flask db upgrade。如果不想丢数据先备份然后用flask db stamp head把当前状态标记为最新版本再执行后续迁移。4.5 中文显示成方框或乱码现象识别结果里想显示姓名但 OpenCV 画出来的中文全是问号或方框。原因cv2.putText只支持 ASCII 字符不支持中文。解决这份包里的font/fontToImg.py就是干这个的——用 PIL 把中文渲染成图片再贴到 OpenCV 帧上。确认simsun.ttc字体文件路径正确PIL 的ImageFont.truetype能加载它。from PIL import Image, ImageDraw, ImageFont import cv2 import numpy as np def put_chinese_text(img, text, position, font_size30): 在 OpenCV 图像上叠加中文 img_pil Image.fromarray(cv2.cvtColor(img, cv2.COLOR_BGR2RGB)) draw ImageDraw.Draw(img_pil) font ImageFont.truetype(font/simsun.ttc, font_size) draw.text(position, text, fontfont, fill(255, 0, 0)) return cv2.cvtColor(np.array(img_pil), cv2.COLOR_RGB2BGR)字体路径用相对路径时注意工作目录。Flask 启动时的工作目录是项目根目录所以font/simsun.ttc能直接找到。如果从其他目录启动就得改成绝对路径。5. 把这套系统改造成你自己的扩展点与验证习惯5.1 从 Haar 换到 DNN 人脸检测Haar 级联在毕设演示里够用但如果你想在论文里体现「深度学习」可以把检测环节也换成 DNN。OpenCV 支持加载 Caffe 或 ONNX 格式的人脸检测模型精度提升明显。# 用 OpenCV DNN 模块做人脸检测 def detect_faces_dnn(frame, model_path, config_path): 基于 DNN 的人脸检测精度优于 Haar net cv2.dnn.readNetFromCaffe(config_path, model_path) h, w frame.shape[:2] blob cv2.dnn.blobFromImage( cv2.resize(frame, (300, 300)), scalefactor1.0, size(300, 300), mean(104.0, 177.0, 123.0) ) net.setInput(blob) detections net.forward() faces [] for i in range(detections.shape[2]): confidence detections[0, 0, i, 2] if confidence 0.5: # 置信度阈值 box detections[0, 0, i, 3:7] * np.array([w, h, w, h]) x1, y1, x2, y2 box.astype(int) faces.append((x1, y1, x2 - x1, y2 - y1)) return facesconfidence阈值设 0.5 是起点实际用的时候根据误检情况调。DNN 检测器需要额外的模型文件.caffemodel和.prototxt这些不在原始包里得自己找。换检测器之后后续的特征提取和比对逻辑不用动接口保持一致就行。5.2 签到数据的导出与统计原始包里的签到记录只存在 SQLite 里管理端能看但不好做统计。加一个导出 CSV 的接口答辩时演示数据流转会很加分。import csv from io import StringIO from flask import Response app.route(/api/export_attendance) def export_attendance(): 导出签到记录为 CSV records AttendanceRecord.query.order_by( AttendanceRecord.check_time.desc() ).all() output StringIO() writer csv.writer(output) writer.writerow([用户ID, 姓名, 签到时间, 状态]) for r in records: writer.writerow([r.user_id, r.user_name, r.check_time, r.status]) output.seek(0) return Response( output.getvalue(), mimetypetext/csv, headers{Content-Disposition: attachment;filenameattendance.csv} )导出功能不复杂但要注意编码。CSV 用 UTF-8 带 BOM 格式Excel 打开才不会乱码。StringIO在 Python 3 里处理文本没问题如果数据量大换成流式写入。5.3 验证改造是否成功的检查清单改完代码别急着交按这个顺序过一遍检查项验证方式通过标准依赖完整pip check无冲突提示数据库迁移flask db current显示最新版本号人脸注册注册一个新用户数据库里能查到特征记录人脸识别用注册过的脸签到返回成功且写入记录重复签到同一人连续签到两次第二次提示已签到中文显示识别结果含中文姓名画面正常显示无方框异常页面访问不存在的路由返回 404 页面而非报错这份清单是我每次改完人脸相关项目都会走的流程。尤其是「重复签到」和「中文显示」这两项看着简单实际最容易在答辩现场翻车。5.4 一个我踩过的坑模型文件路径写死最后说一个血泪经验。这份包里的模型加载路径如果写成了绝对路径比如D:/project/faceRecognitonModels/weights.h5换台电脑就跑不了。我见过太多毕设因为这种问题在答辩现场打不开。从那以后我每次拿到这种项目第一件事就是把所有文件路径改成基于os.path.dirname(__file__)的相对路径或者用 Flask 的app.root_path拼。改完在另一台机器上 clone 下来跑一遍确认没有硬编码路径残留。这个习惯帮我省了至少三次现场翻车。import os # 基于当前文件位置拼路径不依赖工作目录 BASE_DIR os.path.dirname(os.path.abspath(__file__)) MODEL_PATH os.path.join(BASE_DIR, faceRecognitonModels, weights.h5) FONT_PATH os.path.join(BASE_DIR, font, simsun.ttc)这套系统本身不复杂但工程细节决定它能不能稳定跑起来。把环境、路径、参数这三样管住剩下的就是业务逻辑的微调了。希望帮到你。本文还有配套的精品资源点击获取