简介:这份资源是面向计算机相关专业在校学生、教师及企业员工的「基于人脸识别的学生考勤签到管理系统」完整课程设计包,可作为计算机网络课程设计、毕业设计、大作业或初期项目立项演示使用。项目采用客户端与服务端架构,涵盖人脸录入、人脸训练、人脸识别签到、签退及哈希加密等功能,并配套课设报告、运行视频与说明文档,便于理解系统总体设计与关键模块实现。压缩包共160个文件,以52个py源码、78个pyc编译文件为主,另含mp4运行演示、docx课设报告、pkl模型、db数据库、xml人脸定位文件及xlsx数据表等,整体约10.16MB,目录结构清晰。目前已有270人学习下载。读者可据此掌握人脸特征提取与yml模型识别、签到数据上传与展示、LSTM用户画像构建等思路,并参考打包与运行说明排查端口占用、中文路径等问题,具备较高的学习借鉴与二次开发价值。
1. 从一份课设压缩包说起:人脸识别考勤系统到底能跑出什么效果
很多同学拿到「基于人脸识别的学生考勤签到管理系统」这个题目时,第一反应是去搜免费 Python 源码大全,下载一个压缩包,解压、装依赖、跑起来,然后发现摄像头打不开、识别率感人、数据库报错。问题不在于代码本身有多难,而在于大多数人跳过了「这套系统由哪几块拼起来」这个基本认知。这套系统的本质是三个模块的串联:人脸检测负责在画面里找到人脸的位置,人脸识别负责判断这张脸是谁,考勤签到负责把识别结果和时间戳写进数据库并生成签到记录。Python 在这个链路里扮演的是胶水语言的角色,把 OpenCV、face_recognition、Flask、SQLite 这些库粘在一起。适合谁?计算机网络课程设计选了这个题的同学、想做一个能演示的课堂项目的人、以及刚学完 Python 基础想找个完整项目练手的人。运行视频和课设报告的价值在于帮你理解系统全貌,但真正要跑通,你得自己把环境配好、把摄像头调通、把数据库建对。
2. 人脸识别考勤系统的技术选型:为什么是 Python + OpenCV + face_recognition
2.1 三条技术路线的取舍逻辑
做学生考勤签到管理系统,人脸识别部分有三条常见路线。第一条是 OpenCV 自带的 Haar 级联或 LBPH 算法,优点是零额外依赖、CPU 就能跑,缺点是对光照和角度极其敏感,换个教室灯光就翻车。第二条是 dlib + face_recognition 组合,底层用 HOG 或 CNN 做检测,用 ResNet 做 128 维特征嵌入,识别精度在课堂场景下够用,安装稍微麻烦但社区资料多。第三条是调用百度 AI 或腾讯云的人脸识别 API,精度最高但需要联网、有调用次数限制,课设答辩时如果网络不通就尴尬了。
我一般推荐第二条路线。原因很直接:face_recognition 库把 dlib 的复杂接口封装成了三行代码就能完成人脸编码和比对,对课设来说开发效率最高。而且它是纯本地的,答辩现场不依赖网络,老师看着也放心。OpenCV 在这里的角色是读摄像头帧、做图像预处理、画检测框,和 face_recognition 配合使用不冲突。
计算机网络课程设计的角度还需要考虑一件事:这套系统要不要做 C/S 架构。如果只是单机跑,Flask 起一个本地 Web 页面展示签到结果就够了。如果要体现网络编程的部分,可以把人脸识别放在服务端,客户端通过 HTTP 请求上传图片或视频帧,服务端返回识别结果。后者更贴合课程设计的要求,但复杂度也更高。
2.2 环境搭建:从 Python 安装到依赖库版本锁定
Python 安装本身没什么好说的,官网下载 3.8 到 3.10 之间的版本都行。关键是 dlib 的安装,这是整个环境搭建里最容易卡住的地方。Windows 上直接 pip install dlib 大概率编译失败,因为需要 CMake 和 Visual Studio 的 C++ 编译工具链。我一般会建议用 conda 装 dlib,或者直接下载预编译的 whl 文件。
# 创建虚拟环境,避免污染全局包 python -m venv attendance_env # 激活虚拟环境 # Windows: attendance_env\Scripts\activate # macOS/Linux: source attendance_env/bin/activate # 先装 CMake 和 dlib 的依赖 pip install cmake pip install dlib # 再装 face_recognition pip install face_recognition # 装 OpenCV 和 Web 框架 pip install opencv-python flask flask-sqlalchemy # 验证安装 python -c "import face_recognition; import cv2; print('OK')"这段命令的逻辑是:虚拟环境隔离依赖,cmake 是 dlib 编译的前置条件,dlib 装好之后 face_recognition 才能正常安装。参数方面,opencv-python 装的是包含 GUI 功能的完整版,如果你只需要处理图片不需要 imshow,可以换成 opencv-python-headless 减小体积。flask-sqlalchemy 用来操作 SQLite 数据库,课设场景下比直接写 SQL 更省事。
注意:dlib 安装失败时,先确认 cmake 是否在 PATH 里,再确认 Visual Studio Build Tools 是否装了「使用 C++ 的桌面开发」工作负载。macOS 上需要先 brew install cmake。
2.3 数据库表结构设计:三张表撑起整个考勤逻辑
考勤系统的数据层不需要搞得太复杂,三张表足够:学生信息表存姓名、学号、人脸编码;签到记录表存学号、签到时间、签到状态;课程表存课程名称和上课时间段。人脸编码用 BLOB 或 TEXT 字段存 128 维浮点数组的序列化结果。
from flask_sqlalchemy import SQLAlchemy import json db = SQLAlchemy() class Student(db.Model): __tablename__ = 'students' id = db.Column(db.Integer, primary_key=True) student_no = db.Column(db.String(20), unique=True, nullable=False) name = db.Column(db.String(50), nullable=False) face_encoding = db.Column(db.Text) # 存 JSON 序列化的 128 维向量 def set_encoding(self, encoding): self.face_encoding = json.dumps(encoding.tolist()) def get_encoding(self): return json.loads(self.face_encoding) class AttendanceRecord(db.Model): __tablename__ = 'attendance_records' id = db.Column(db.Integer, primary_key=True) student_no = db.Column(db.String(20), db.ForeignKey('students.student_no')) check_time = db.Column(db.DateTime, default=db.func.now()) status = db.Column(db.String(10), default='正常') # 正常/迟到/缺勤 class Course(db.Model): __tablename__ = 'courses' id = db.Column(db.Integer, primary_key=True) course_name = db.Column(db.String(100), nullable=False) start_time = db.Column(db.String(10)) end_time = db.Column(db.String(10))这里用 Text 字段存 JSON 而不是 BLOB,好处是可读性强,调试时直接看数据库就能知道编码内容。face_encoding 的 128 维向量是 face_recognition 库的标准输出,每张人脸对应一个 128 维浮点数组,两个人脸的相似度通过欧氏距离判断,距离小于 0.6 通常认为是同一个人。签到状态默认「正常」,后续可以根据签到时间和课程开始时间做比对,超过 10 分钟标记为「迟到」。
3. 从摄像头到签到记录:人脸识别考勤系统的完整实现链路
3.1 人脸注册:把学生照片变成 128 维编码
注册环节是整个系统的入口。你需要为每个学生拍一张清晰的正面照,然后用 face_recognition 提取编码存入数据库。这一步的质量直接决定了后续识别的准确率。
import face_recognition import os import json from models import db, Student def register_student(image_path, student_no, name): """从图片文件注册学生人脸""" # 加载图片 image = face_recognition.load_image_file(image_path) # 检测人脸位置 face_locations = face_recognition.face_locations(image) if len(face_locations) == 0: return {'success': False, 'msg': '未检测到人脸,请重新拍照'} if len(face_locations) > 1: return {'success': False, 'msg': '检测到多张人脸,请确保照片中只有一人'} # 提取 128 维编码 encodings = face_recognition.face_encodings(image, face_locations) encoding = encodings[0] # 存入数据库 student = Student(student_no=student_no, name=name) student.set_encoding(encoding) db.session.add(student) db.session.commit() return {'success': True, 'msg': f'{name} 注册成功'}这段代码的关键参数是 face_locations 的数量判断。如果检测到多张人脸,说明照片不合格,必须让用户重新拍。face_encodings 的第二个参数传入 face_locations 可以避免重复检测,提升速度。实际使用中,我建议注册照片用 640x480 以上的分辨率,光线均匀,人脸占画面三分之一以上。如果照片质量差,提取出的编码和现场识别时的编码距离会偏大,导致认不出来。
3.2 实时识别:摄像头帧处理与签到判定
实时识别是整个系统最核心也最容易出问题的环节。基本流程是:读一帧画面,缩放,检测人脸位置,提取编码,和数据库里所有已注册编码做比对,找到距离最小的且小于阈值的那一个,判定为签到成功。
import cv2 import face_recognition import numpy as np from datetime import datetime from models import db, Student, AttendanceRecord def load_known_faces(): """从数据库加载所有已注册学生的人脸编码""" students = Student.query.all() known_encodings = [] known_info = [] for s in students: known_encodings.append(np.array(s.get_encoding())) known_info.append({'student_no': s.student_no, 'name': s.name}) return known_encodings, known_info def start_attendance(course_id, tolerance=0.5): """启动摄像头进行实时考勤""" known_encodings, known_info = load_known_faces() if not known_encodings: print('没有已注册的学生') return cap = cv2.VideoCapture(0) # 0 表示默认摄像头 cap.set(cv2.CAP_PROP_FRAME_WIDTH, 640) cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 480) # 记录已签到学生,避免重复签到 checked_in = set() while True: ret, frame = cap.read() if not ret: break # 缩小到 1/4 加速处理 small_frame = cv2.resize(frame, (0, 0), fx=0.25, fy=0.25) rgb_small = cv2.cvtColor(small_frame, cv2.COLOR_BGR2RGB) # 检测人脸位置 face_locations = face_recognition.face_locations(rgb_small) face_encodings = face_recognition.face_encodings(rgb_small, face_locations) for encoding, location in zip(face_encodings, face_locations): # 与已知编码比对 distances = face_recognition.face_distance(known_encodings, encoding) best_idx = np.argmin(distances) if distances[best_idx] < tolerance: info = known_info[best_idx] # 画框和名字 top, right, bottom, left = [v * 4 for v in location] cv2.rectangle(frame, (left, top), (right, bottom), (0, 255, 0), 2) cv2.putText(frame, info['name'], (left, top - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.8, (0, 255, 0), 2) # 签到逻辑 if info['student_no'] not in checked_in: record = AttendanceRecord( student_no=info['student_no'], check_time=datetime.now(), status='正常' ) db.session.add(record) db.session.commit() checked_in.add(info['student_no']) print(f"{info['name']} 签到成功") cv2.imshow('Attendance', frame) if cv2.waitKey(1) & 0xFF == ord('q'): break cap.release() cv2.destroyAllWindows()这段代码有几个关键参数需要解释。tolerance 默认 0.6,我设成 0.5 是为了降低误识别率,代价是可能漏掉一些角度不好的人脸。缩小到 1/4 是速度优化的常用手段,640x480 缩到 160x120 后检测速度能提升 4 倍左右,代价是远处的小脸可能检测不到。checked_in 集合用来防止同一个人被重复签到,这个逻辑在实际使用中必须有,否则摄像头对着一个人会不停写数据库。
3.3 签到记录查询与 Web 展示
课设报告里通常需要一个界面来展示签到结果。用 Flask 起一个简单的 Web 服务,提供签到记录查询和导出功能就够了。
from flask import Flask, render_template, jsonify from models import db, AttendanceRecord, Student app = Flask(__name__) app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///attendance.db' db.init_app(app) @app.route('/records') def get_records(): """查询所有签到记录""" records = db.session.query( AttendanceRecord, Student.name ).join( Student, AttendanceRecord.student_no == Student.student_no ).order_by( AttendanceRecord.check_time.desc() ).all() result = [] for record, name in records: result.append({ 'student_no': record.student_no, 'name': name, 'check_time': record.check_time.strftime('%Y-%m-%d %H:%M:%S'), 'status': record.status }) return jsonify(result) @app.route('/export') def export_csv(): """导出签到记录为 CSV""" import csv from io import StringIO from flask import Response records = db.session.query( AttendanceRecord, Student.name ).join( Student, AttendanceRecord.student_no == Student.student_no ).all() output = StringIO() writer = csv.writer(output) writer.writerow(['学号', '姓名', '签到时间', '状态']) for record, name in records: writer.writerow([ record.student_no, name, record.check_time.strftime('%Y-%m-%d %H:%M:%S'), record.status ]) output.seek(0) return Response( output.getvalue(), mimetype='text/csv', headers={'Content-Disposition': 'attachment; filename=attendance.csv'} )查询接口用 join 把签到记录和学生姓名关联起来,前端展示时不需要再查一次学生表。导出功能用 StringIO 在内存里生成 CSV,避免写临时文件。这两个接口配合一个简单的前端页面就能满足课设演示的需求。如果课程设计对网络编程有要求,可以在客户端和服务端之间加一层 HTTP 通信,客户端负责采集图像,服务端负责识别和存储。
4. 避坑指南:人脸识别考勤系统最常见的五个翻车现场
4.1 摄像头打不开或画面全黑
现象:运行程序后 cv2.VideoCapture(0) 返回 False,或者窗口一片漆黑。原因通常是摄像头被其他程序占用,或者 OpenCV 没有正确识别到设备。Windows 上还要检查隐私设置里是否允许应用访问摄像头。解决办法:先关掉其他可能占用摄像头的程序(微信、钉钉、Zoom),然后在设备管理器里确认摄像头驱动正常。如果还是不行,试试 cv2.VideoCapture(1) 或 cv2.VideoCapture(-1),有时候默认索引不是 0。
4.2 识别率低,同一个人经常认不出来
现象:注册时好好的,现场识别时距离值经常在 0.6 以上,导致判定失败。原因有三个:注册照片和现场光照差异大、人脸角度偏差大、摄像头分辨率太低。解决办法:注册时多拍几张不同角度的照片,每张都提取编码,识别时取最小距离。或者把 tolerance 从 0.5 放宽到 0.55,但要注意误识别率会上升。最根本的办法是改善现场光照,避免逆光和侧光。
4.3 dlib 安装报错 CMake not found
现象:pip install dlib 时提示 CMake 找不到或者编译失败。原因:dlib 需要本地编译,依赖 CMake 和 C++ 编译器。解决办法:Windows 上先 pip install cmake,再确认 Visual Studio Build Tools 装了 C++ 工作负载。macOS 上 brew install cmake。如果还是不行,直接下载 dlib 的预编译 whl 文件,用 pip install xxx.whl 安装。conda 用户可以用 conda install -c conda-forge dlib,通常比 pip 顺利。
4.4 数据库写入报错或签到记录重复
现象:签到成功但数据库里没有记录,或者同一个人被反复写入。原因:db.session.commit() 没有调用,或者 checked_in 集合逻辑有漏洞。解决办法:每次 add 之后必须 commit,否则数据只在内存里。checked_in 集合要在循环外初始化,循环内判断后再添加。如果程序中途重启,checked_in 会清空,同一个人可能被再次签到,这时候需要在数据库层面加唯一约束,比如 (student_no, date) 联合唯一。
4.5 多线程下摄像头帧读取延迟高
现象:画面卡顿,识别延迟明显,按 q 退出要等好几秒。原因:人脸检测和识别在主线程里同步执行,每帧都要跑一遍 dlib 的 HOG 检测,CPU 占用高。解决办法:把检测频率降低,比如每 3 帧检测一次,中间帧复用上一次的检测结果。或者用多线程,一个线程专门读摄像头帧,另一个线程做识别。课设场景下,降低检测频率是最简单的优化手段,把 cv2.waitKey(1) 改成 cv2.waitKey(30) 也能减少 CPU 占用。
5. 进阶技巧:用 Flask 做前后端分离的考勤系统
如果你想让课设看起来更有「网络编程」的味道,可以把人脸识别做成一个独立的服务,前端通过 HTTP 请求调用。具体做法是:Flask 提供一个 /recognize 接口,接收前端上传的图片,返回识别结果;前端用 HTML5 的 getUserMedia 调摄像头,定时截帧上传。这样客户端和服务端解耦,答辩时也更好讲架构。
from flask import Flask, request, jsonify import face_recognition import numpy as np import base64 from io import BytesIO from PIL import Image app = Flask(__name__) @app.route('/recognize', methods=['POST']) def recognize(): """接收 base64 图片,返回识别结果""" data = request.json.get('image', '') if not data: return jsonify({'success': False, 'msg': '没有图片数据'}) # 解码 base64 图片 img_bytes = base64.b64decode(data.split(',')[1]) img = Image.open(BytesIO(img_bytes)) img_array = np.array(img) # 检测和识别 face_locations = face_recognition.face_locations(img_array) face_encodings = face_recognition.face_encodings(img_array, face_locations) results = [] for encoding, location in zip(face_encodings, face_locations): distances = face_recognition.face_distance(known_encodings, encoding) best_idx = np.argmin(distances) if distances[best_idx] < 0.5: results.append({ 'name': known_info[best_idx]['name'], 'student_no': known_info[best_idx]['student_no'], 'confidence': round(1 - distances[best_idx], 3) }) return jsonify({'success': True, 'faces': results})这个接口的关键点是 base64 解码和 PIL 图片转换。前端传过来的图片通常是 data:image/jpeg;base64,xxxx 格式,需要去掉前缀再解码。known_encodings 和 known_info 在服务启动时加载一次,避免每次请求都查数据库。confidence 用 1 减去距离值,让前端展示更直观。
验证方法很简单:用 Postman 或 curl 发一张 base64 图片到 /recognize,看返回的 JSON 里有没有正确的人名。如果返回空数组,检查图片里有没有人脸、known_encodings 是否为空、tolerance 是否太严。
我自己的习惯是,每次改完识别逻辑,先用一张已知人脸图片跑一遍单元测试,确认距离值在合理范围内,再开摄像头做实时测试。这样能把「代码逻辑错误」和「环境问题」分开排查,省掉很多来回折腾的时间。希望帮到你。
本文还有配套的精品资源,点击获取