基于人脸识别的学生考勤签到管理系统:Python网络课设从识别到传输完整实现
2026/9/14 3:53:45 网站建设 项目流程

简介:基于人脸识别的学生考勤签到管理系统,是一份适合计算机相关专业学生课程设计、毕业设计及入门进阶的完整Python项目。项目采用客户端/服务端架构,涵盖人脸录入、训练、识别签到、签退及哈希加密等核心功能,配套运行视频和课设报告,可直接运行或基于此二次开发。压缩包共160个文件,以Python源码(py/pyc)、操作演示mp4、课程设计报告docx及项目说明md为主,另含配置、数据库与测试数据,整体仅10.16MB,便于快速下载与部署。目前已吸引269人学习下载。除可运行的源码与录屏外,报告中还给出了系统设计、关键功能模块实现、实验分析与总结展望等内容,对理解网络通信、人脸识别调用、数据库交互等关键点很有帮助。

1. 为什么“基于人脸识别的学生考勤签到管理系统”是计算机网络课设而不是 AI 课设

拿到标题《基于人脸识别的学生考勤签到管理系统 python 源码+运行视频+课设报告》时,最忌讳的是把时间花在训练深度学习模型上。这门课是计算机网络,不是模式识别,验收时老师看的是你能否把一次签到操作解释成“采集、编码、传输、解析、存储”的网络通信过程。我的做法是:识别部分直接用现成人脸库拿结果,把考勤记录通过网络上报到服务端,再在报告里写清楚 TCP 连接怎么建立、数据怎么分包、服务端怎么并发处理。适合谁:计算机网络课设需要交源码、视频和报告的本科同学,以及想把项目扩展成局域网考勤工具的工程师。

2. 拆解人脸识别考勤签到管理系统的三条技术线:模型、存储、网络

项目标题里同时出现“人脸识别”和“计算机网络课程设计”,意味着你要交付的不是一个模型,而是一个能解释“识别完成之后数据去哪里”的系统。我一般先把系统拆成三个边界:人脸特征库、考勤事件、网络通道。人脸特征库存的是学号到 128 维向量的映射;考勤事件是 JSON 文本;网络通道只负责把 JSON 稳定送到服务端。三者解耦后,python 源码只需要三个文件就能跑通,报告里还能画一张“识别端-服务端-数据库”的时序图。

2.1 人脸识别模型选型:LBPH、face_recognition、自训 CNN 该选哪个

做考勤签到管理系统,人脸识别算法不需要自己发明。常见做法是选一个离线可跑的库,把精力留给网络部分。下面三套方案是我在实际课设里见过最多的。

方案实现成本特征形式离线运行报告可写深度课程设计推荐度
OpenCV LBPH / EigenFace低,OpenCV 自带局部纹理直方图 / 特征脸传统机器学习,可解释性强较高
face_recognition中,依赖 dlib128 维人脸嵌入向量向量距离、阈值判定、特征提取
自训练 CNN高,需要 GPU 和数据集自定义高维特征模型结构、训练 loss一般,时间不够

我一般选face_recognition,因为它的识别效果在教室这类固定光照场景下足够好,而且代码量小。你只需要调用face_encodings拿到 128 维向量,再用face_distance算欧氏距离,不需要管网络结构。选 LBPH 也可以,但遇到侧脸和光线变化很容易误判,答辩时容易被追问。

环境安装是第一个坑。推荐用 Python 3.10 左右的虚拟环境,命令如下:

python -m venv venv source venv/bin/activate # Windows 使用 venv\Scripts\activate pip install opencv-python face_recognition

face_recognition依赖 dlib,Windows 上pip install dlib经常因为缺少 CMake 和 C++ 编译环境失败。遇到这种情况,降低到 Python 3.9/3.10 并安装cmake再重试;如果还不行,就把识别方案换成 OpenCV 的LBPHFaceRecognizer,它不需要 dlib,代码也能在报告里写出“传统机器学习与深度学习对比”的讨论。

2.2 系统边界:识别端、考勤服务端、数据存储和网络报文的职责

把这个考勤签到管理系统按网络课设的口吻拆开,目录结构应该是下面这样,重点在“每个文件只干一件事”。

attendance_system/ ├─ server.py ├─ register.py ├─ checkin.py ├─ features.pkl ├─ attendance.db └─ README.md

register.py负责录入:读人脸照片,提取特征,写入features.pklcheckin.py负责识别:打开摄像头,实时比对,比对成功后生成一条 JSON 考勤事件;server.py负责网络接收和落库,把 JSON 解析后写进 SQLite。features.pkl是特征库,attendance.db是考勤记录。识别端和服务端可以跑在同一台机器,也可以分别跑在两个终端,甚至两台电脑。只要checkin.py里指定服务端 IP 就能演示跨主机签到,这才是计算机网络课设该有的样子。

数据库不需要上 MySQL,SQLite 对课设完全够用。报告里可以写一句:“考勤库使用嵌入式关系数据库 SQLite,单文件部署,避免引入额外服务依赖。”这句话在答辩时很实用,能解释清楚为什么不做单独的数据库服务器。

2.3 为什么“先在本机跑通闭环”比“直接搭 Web 端”更适合计算机网络课设

有些同学会把系统做成 Flask Web 服务,浏览器打开页面再调摄像头,代码量大,答辩时还容易卡在端口占用、跨域和前端兼容性上。我更推荐先做命令行闭环:服务端server.py启动后监听端口,客户端checkin.py识别成功就发一条记录,服务端落库后回OK。本机跑通后,再把客户端里的127.0.0.1改成另一台机器的 IP,整个系统自然就变成了分布式考勤系统。这也是计算机网络课程设计最看重的部分:连接管理、数据传输、并发处理。

3. 用 Python 源码实现人脸录入和签到识别的可运行链路

这一章给出最小可运行的人脸识别部分。不追求界面,只求代码能跑通,后面的网络上报再替换掉打印语句。

3.1 注册脚本:把人脸照片转成 128 维特征并写入 features.pkl

注册是所有流程的前提。下面这段代码用argparse接收学号和照片路径,方便后面录制演示视频时批量操作。

import argparse import os import pickle import face_recognition FEATURE_FILE = "features.pkl" def register(student_id, image_path): """把一张人脸照片的 128 维特征存入 features.pkl""" image = face_recognition.load_image_file(image_path) encodings = face_recognition.face_encodings(image) # 照片里必须且只能有一个人脸 if len(encodings) != 1: print(f"照片里必须恰好有一个人脸,当前检测到 {len(encodings)} 个") return data = {} if os.path.exists(FEATURE_FILE): with open(FEATURE_FILE, "rb") as f: data = pickle.load(f) data[student_id] = encodings[0] with open(FEATURE_FILE, "wb") as f: pickle.dump(data, f) print(f"{student_id} 注册完成,特征维度 {encodings[0].shape[0]}") if __name__ == "__main__": parser = argparse.ArgumentParser(description="人脸考勤注册") parser.add_argument("student_id", help="学号") parser.add_argument("image_path", help="人脸照片路径") args = parser.parse_args() register(args.student_id, args.image_path)

逻辑说明:load_image_file读入的是 RGB 图,face_encodings会返回一个列表,列表长度就是画面里的人脸数量。课程设计建议强制要求“单人照片”,避免库里有多个特征时不知道绑定给谁。数据用pickle序列化保存,特征是 numpy 数组,直接用文本文件存会丢失精度。

3.2 签到识别脚本:摄像头帧里比对特征并生成考勤事件

签到脚本从摄像头取帧,对人脸做定位和编码,然后和特征库里的向量算距离。这里的send_attendance先只打印一条考勤事件,第 4 章会把这段替换成 Socket 上报。

import time import pickle import cv2 import face_recognition def send_attendance(student_id): """暂时只打印,后面替换为网络上报""" now = time.strftime("%Y-%m-%d %H:%M:%S") print(f"CHECKIN {student_id} {now}") with open("features.pkl", "rb") as f: known = pickle.load(f) known_ids = list(known.keys()) known_encodings = list(known.values()) cap = cv2.VideoCapture(0) while cap.isOpened(): ok, frame = cap.read() if not ok: continue # OpenCV 默认 BGR,face_recognition 需要 RGB rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) boxes = face_recognition.face_locations(rgb, model="hog") encodings = face_recognition.face_encodings(rgb, boxes) for encoding in encodings: distances = face_recognition.face_distance(known_encodings, encoding) idx = int(distances.argmin()) if distances[idx] < 0.5: student_id = known_ids[idx] send_attendance(student_id) cv2.putText(frame, f"{student_id} {distances[idx]:.2f}", (20, 40), cv2.FONT_HERSHEY_SIMPLEX, 0.9, (0, 200, 0), 2) else: cv2.putText(frame, "unknown", (20, 40), cv2.FONT_HERSHEY_SIMPLEX, 0.9, (0, 0, 255), 2) cv2.imshow("attendance", frame) if cv2.waitKey(1) == ord("q"): break cap.release() cv2.destroyAllWindows()

逻辑说明:face_distance返回的是当前人脸与特征库每一个特征的欧氏距离,距离越小越相似。用argmin取最小距离的下标,再和阈值 0.5 比较,小于阈值才认为是已知学生。注意OpenCV 的摄像头帧是 BGR,必须cvtColor转成 RGB,否则识别率会明显下降。

3.3 参数和边界:tolerance、hog/cnn、BGR/RGB、重复签到判断

直接抄代码能跑,但几个参数需要理解,否则答辩时会被问住。下面是我常用的参数经验表。

参数建议值影响
face_locations(model="hog")hogCPU 上更快;cnn更准但慢,适合离线照片处理
face_distance阈值0.5越小越严格;0.4 容易漏识别,0.6 容易误识别
VideoCapture(0)00 是内置摄像头;外接摄像头可尝试 1 或 2
注册照片人脸区域居中且大于 100x100太小会丢失人脸特征细节
cvtColor必须做漏掉则颜色通道错位,识别率暴跌

还有一个很容易忽略的重复签到问题。上面的代码每识别到一帧就触发一次send_attendance,同一个学生在摄像头前一秒会被记录 25 次。常见的处理方案是在服务端给考勤表加唯一约束UNIQUE(student_id, date),同一个学号同一天只能插入一条,第二次插入时捕获异常并返回ALREADY。这个逻辑放在网络服务端做,比放在识别端做更合适,因为识别端可能崩溃,而服务端手里的数据才是最终考勤结果。

4. 把考勤签到数据从客户端搬到服务端:Socket、粘包处理与线程模型

人脸识别部分完成后,重点切换到计算机网络课程设计:客户端把识别出的学号和当前时间发到服务端,服务端存储并响应。这是整个课设最值钱的部分。

4.1 为什么用 TCP Socket 而不是直接在本地写文件

有人会觉得识别端已经拿到了学生信息,直接sqlite3写本地数据库不就行了。这样做不是不可以,但那就变成了“单机软件”,计算机网络课设无法成立。常见的做法是让识别端成为 TCP 客户端,服务端单独进程负责接收考勤记录。对比一下三种传输方式。

方案可靠性代码量可讲的知识点课设推荐度
TCP Socket + JSON可靠,字节流有序中等三次握手、粘包、backlog、线程模型推荐
UDP Socket + JSON不可靠丢包、校验和、无连接不推荐,考勤记录不能丢
HTTP REST + Flask可靠较少HTTP 状态码、URL 设计可以,但网络底层写不了太深

考勤记录不允许丢失,所以选 TCP。但 TCP 是字节流协议,没有消息边界,多行 JSON 可能粘在一次recv里,也可能被拆成两次recv。把“粘包处理”写进代码和报告,课程设计的深度立刻就不一样了。

4.2 客户端上报函数:一行 JSON 加换行作为消息边界

给客户端定义一个send_attendance函数,替换第 3 章里的打印版本。消息格式是“一行 JSON 加一个换行符”,这里换行符就是自定义的消息边界。

import json import socket import time def send_attendance(student_id): """通过 TCP 把考勤事件发给服务端""" payload = { "student_id": student_id, "time": time.strftime("%Y-%m-%d %H:%M:%S") } data = json.dumps(payload).encode("utf-8") + b"\n" try: sock = socket.create_connection(("127.0.0.1", 8000), timeout=5) sock.sendall(data) resp = sock.recv(1024).decode("utf-8").strip() sock.close() return resp == "OK" except ConnectionRefusedError: print("服务端未启动,考勤记录未上传") return False

逻辑说明:create_connection会自己完成 TCP 三次握手,timeout=5防止摄像头线程卡死;sendall确保全部字节发出;服务端回OK后关闭连接,体现一次完整的“连接-传输-关闭”过程。换行符\n是客户端和服务端之间的协议约定,服务端也按这个规则拆包。

4.3 服务端多线程接收:recv 拼接、split 按行解析、SQLite 落库

服务端要同时处理多个签到端,所以用threading为每个连接开一个线程。这里给出完整的server.py,包含建表、接收、解析和去重。

import json import socket import sqlite3 import threading DB_FILE = "attendance.db" def init_db(): conn = sqlite3.connect(DB_FILE) conn.execute(""" CREATE TABLE IF NOT EXISTS checkin ( id INTEGER PRIMARY KEY AUTOINCREMENT, student_id TEXT NOT NULL, time TEXT NOT NULL, UNIQUE(student_id, substr(time, 1, 10)) ) """) conn.commit() conn.close() def save_record(payload): conn = sqlite3.connect(DB_FILE) try: conn.execute( "INSERT INTO checkin(student_id, time) VALUES (?, ?)", (payload["student_id"], payload["time"]) ) conn.commit() return True except sqlite3.IntegrityError: return False finally: conn.close() def handle_client(conn, addr): print(f"新连接: {addr}") buffer = b"" try: while True: chunk = conn.recv(1024) if not chunk: break buffer += chunk # 按换行符拆出一条完整消息 while b"\n" in buffer: line, buffer = buffer.split(b"\n", 1) payload = json.loads(line.decode("utf-8")) if save_record(payload): conn.sendall(b"OK\n") else: conn.sendall(b"ALREADY\n") except (ConnectionResetError, json.JSONDecodeError): pass finally: conn.close() def main(): init_db() server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) server.bind(("0.0.0.0", 8000)) server.listen(10) print("考勤服务端已启动,端口 8000") while True: conn, addr = server.accept() threading.Thread(target=handle_client, args=(conn, addr), daemon=True).start() if __name__ == "__main__": main()

逻辑说明:recv(1024)是本次读取的最大长度,不保证一次拿到完整消息,所以要把buffer不断拼接,直到出现\nsplit(b"\n", 1)确保只拆出一条消息,剩余数据继续留在buffer里,这就是完整的粘包处理。SQLite 表里的UNIQUE(student_id, substr(time,1,10))把“学号 + 当天日期”作为唯一键,第二条记录会被save_record捕获并返回失败,客户端收到ALREADY后就不会重复上报。

这里把SO_REUSEADDR设置成 1 也很重要,开发时重启服务端不会报Address already in use0.0.0.0表示监听本机所有网卡,之后客户端把 IP 改成宿舍局域网地址,就能演示跨机器签到。要做更高级的验证,可以打开 Wireshark 过滤tcp.port == 8000,直接看到 TCP 建立连接和每一帧数据的长度,这部分内容放进课设报告是很强的加分项。

5. 交付前把运行视频和课设报告做到可验收:日志、抓包和启动命令

最后一个环节是让源码、运行视频、课设报告三者互相引用。很多同学视频里跑得很顺,报告里却写不出工作量,原因就是没有把网络知识点和代码对应起来。

5.1 课设报告里最容易拿分的三个计算机网络知识点

第一,TCP 连接建立与关闭。客户端调用create_connection触发三次握手;程序退出或close()触发四次挥手。报告里画一条客户端、服务端的时序箭头,把SYNSYN-ACKACKFIN标出来。第二,TCP 粘包问题。recv(1024)不按消息边界返回数据,所以服务端用split(b"\n", 1)拆包。报告里贴一页打印出的原始buffer内容,说明一次recv可能收到两行 JSON。第三,多线程并发模型。每个客户端连接分配一个线程,用daemon=True避免主线程退出后子线程阻塞。如果能提到selectors事件驱动作为改进方向,说明你确实理解网络并发模型。

5.2 运行视频怎么录:把“失败到成功”和 Wireshark 抓包同时放进来

视频建议 3 到 5 分钟,按下面顺序录:启动服务端python server.py;注册一个测试同学;运行checkin.py签到成功;再切换一张未注册照片,服务端不落库。视频中间切入一次 Wireshark,过滤器设置tcp.port == 8000,能看到客户端到服务端的数据包长度和确认号变化。这一条能证明“识别结果真的通过网络传输了”,而不是本地脚本自己骗自己。

5.3 一份可直接照抄的启动命令和复现清单

整包交付前,我一般会在项目根目录放一个RUNLOG.md,内容就是下面这三条命令和预期输出。

python server.py python register.py 2024001 ./faces/2024001.jpg python checkin.py

复现时先起服务端,再注册一张正面人脸照片,最后启动摄像头识别。如果checkin.py没打印出CHECKIN 2024001,就打开 Wireshark 看 8000 端口有没有 SYN 包;没有 SYN 包说明客户端没到网络层,问题在摄像头或识别流程,有 SYN 但服务端没回,问题在服务端端口或防火墙。把RUNLOG.mdfeatures.pkl一起放进 zip,别人拿到后能按步骤重跑,你这套基于人脸识别的学生考勤签到管理系统才算真正交付完毕。

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询