news 2026/9/26 7:41:04

Python+Flask+OpenCV人脸识别签到系统源码包:毕设落地与工程实践

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Python+Flask+OpenCV人脸识别签到系统源码包:毕设落地与工程实践

简介:这是一套面向计算机相关专业学生与开发者的深度学习人脸识别签到系统完整项目,基于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.x
Flask-SQLAlchemyORM,操作 SQLite2.x/3.x
Flask-Migrate数据库迁移,基于 Alembic3.x/4.x
OpenCV (cv2)图像采集、预处理、人脸检测4.x
NumPy矩阵运算,图像数据底层1.2x
Pillow图像处理,配合字体渲染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 用 set,Linux/macOS 用 export) export FLASK_APP=app.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里被 import,Alembic 检测不到表变化,生成的迁移文件是空的。检查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_path=None): """从一帧图像中检测人脸区域""" 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 参数说明: # scaleFactor=1.1 每次图像尺寸缩小比例,越小检测越慢但越全 # minNeighbors=5 每个候选框保留的最少邻域数,越大越严格 # minSize=(30,30) 最小人脸尺寸,小于此值忽略 faces = face_cascade.detectMultiScale( gray, scaleFactor=1.1, minNeighbors=5, minSize=(30, 30) ) return faces, gray

scaleFactor和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, axis=0) # 模型推理 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(hour=0, minute=0, second=0, microsecond=0) existing = AttendanceRecord.query.filter( AttendanceRecord.user_id == user_id, AttendanceRecord.check_time >= today_start ).first() if existing: return {"success": False, "msg": "今日已签到"} record = AttendanceRecord( user_id=user_id, check_time=now, status="normal" ) 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 版本,严格按它装。如果文件里没锁,手动装numpy<2和匹配的opencv-python版本。用pip install numpy==1.26.4 opencv-python==4.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_size=30): """在 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, font=font, 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)), scalefactor=1.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 faces

confidence阈值设 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(), mimetype="text/csv", headers={"Content-Disposition": "attachment;filename=attendance.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")

这套系统本身不复杂,但工程细节决定它能不能稳定跑起来。把环境、路径、参数这三样管住,剩下的就是业务逻辑的微调了。希望帮到你。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/26 7:40:15

CCF-BDCI基金相关性预测源码解析:课设毕设答辩全流程指南

简介&#xff1a;这份资源是面向计算机相关专业学生与从业者的CCF-BDCI基金相关性预测课程实训完整资料包&#xff0c;聚焦机器学习预测类赛题的方案复现与工程实践&#xff0c;可用于毕业设计、课程设计、作业提交或项目初期立项演示。包内整合了项目源码、技术报告、答辩PPT及…

作者头像 李华
网站建设 2026/9/26 7:38:10

数据准确性测试结果呈现:从用例设计到缺陷归因的实战指南

1. 数据准确性测试到底是什么——先明确测试对象和边界1.1 功能测试里的数据准确性&#xff0c;和数据分析里的数据质量不是一回事我见过太多测试同学一听到"数据准确性"就下意识往数据仓库、ETL、指标口径那边想&#xff0c;觉得这是数仓团队或者数据分析师才需要操…

作者头像 李华
网站建设 2026/9/26 7:37:11

AI工作台养虾实录:WorkBuddy自定义指令与Skill配置指南

今年五月初&#xff0c;我蹲在两个空荡荡的虾塘边&#xff0c;手机里装着刚下好的WorkBuddy。塘是朋友转租给我的&#xff0c;虾苗已经交过定金&#xff0c;可那会儿我连"增氧机该开多久"这种基础问题都答不上来。一个养虾纯小白&#xff0c;手里最像样的生产工具居然…

作者头像 李华
网站建设 2026/9/26 7:36:57

网络安全证书选择全攻略:CEH/OSCP/CISP/CISSP对比分析

1. 先从证书焦虑说起&#xff1a;这行到底认什么&#xff0c;不认什么我是那种典型的"半路出家"安全人。大学学的不是网安&#xff0c;实习的时候被分到等保测评项目组&#xff0c;天天对着测评表单打勾勾&#xff0c;心里发虚——客户问一句"你们渗透怎么做的&…

作者头像 李华
网站建设 2026/9/26 7:36:55

WAF编码绕过深层原理:解码栈不一致与多层攻击实战

1. 为什么一个编码字符能让WAF瞬间“失明”&#xff1a;解析差异的根源1.1 在WAF眼里&#xff0c;请求不是一串字符串&#xff0c;而是“一串待猜的代码”很多刚开始接触WAF绕过的人会陷入一个误区&#xff1a;觉得WAF就是在请求里找敏感关键词&#xff0c;比如看到union、sele…

作者头像 李华