简介:这是一份面向高校学生与深度学习入门者的课程设计级人脸识别考勤演示项目,基于卷积神经网络实现人脸检测与签到流程,适合作为人工智能、机器学习相关课程的实践参考或毕业设计起步模板。压缩包共30个文件,约32.54MB,包含11个Python源码、6个pyc编译文件、3个ui界面文件,以及caffemodel与prototxt网络模型、haarcascade人脸检测xml、字体与示例图片等,覆盖从模型加载、人脸搜索到签到保存的完整链路。项目按功能拆分为cam、sign_face、save_face、launcher等模块,并配有README说明,目录结构清晰,便于按模块阅读与二次修改。目前已有173人学习下载,读者可借此理解CNN人脸识别在考勤场景中的工程落地方式,掌握模型调用、界面交互与数据保存的排错思路,快速搭建可运行的小型演示系统。
1. 从一份大二课设说起:CNN 人脸识别考勤 Demo 能跑出什么
如果你手头正好有一份cnn_face_detection-master的压缩包,里面躺着cnn_deploy.prototxt、cnn_iter_3560000.caffemodel、haarcascade_frontalface_alt.xml和一堆.ui文件,那你大概率拿到的是一个典型的「CNN 人脸识别考勤 demo」。它不是什么工业级门禁系统,而是一个用 Python + OpenCV + Caffe 模型拼起来的课程设计:摄像头抓帧、Haar 级联做人脸检测、CNN 做特征提取和比对,最后把签到结果写进界面。适合谁?适合正在做深度学习课程设计、想找一个能跑通的人脸识别考勤系统设计参考、或者需要一份带 UI 的 Python 课设源码来改的学生和刚入行的工程师。它最大的价值不是算法多先进,而是把「检测 → 识别 → 签到 → 界面」这条链路完整串起来了,你能看到每个环节的真实文件长什么样、参数写在哪、哪里容易翻车。下面我按实际拆包和复现的顺序,把这份资源讲透。
2. 拆包看结构:CNN 模型、Haar 检测器和 UI 到底怎么分工
2.1 模型文件与检测器:谁负责“找到脸”,谁负责“认出人”
拿到压缩包先别急着pip install,把目录树看清楚比什么都重要。这份 demo 的核心资产集中在models和driver两个目录。models里放的是 Caffe 格式的 CNN 部署文件:cnn_deploy.prototxt定义网络结构,cnn_iter_3560000.caffemodel是训练了 356 万次迭代后的权重。这两个文件必须成对出现,缺一个都加载不了。driver目录下的haarcascade_frontalface_alt.xml是 OpenCV 自带的 Haar 级联人脸检测器,它的任务是在每一帧里框出人脸区域,然后把这块区域裁剪、缩放后送进 CNN。很多人第一次跑的时候会疑惑:为什么不用 CNN 直接做检测?因为这份课设的定位是「识别」,检测环节用 Haar 是为了降低门槛,让没有 GPU 的机器也能实时跑。images目录里的yellow.png、mask.jpg、mark.jpg、black.jpg是 UI 素材和掩膜图,font/fzyy.TTF是界面中文字体,少了它签到界面会显示方块。
| 文件/目录 | 作用 | 是否可替换 |
|---|---|---|
cnn_deploy.prototxt | CNN 网络结构定义 | 不建议改,改了权重就对不上 |
cnn_iter_3560000.caffemodel | 训练好的权重 | 可用自己训练的替换,但输入尺寸要一致 |
haarcascade_frontalface_alt.xml | 人脸检测器 | 可换_default版本,召回率更高但误检也多 |
frame.ui/sign_face.ui | Qt Designer 界面文件 | 可改布局,但控件名要和.py里一致 |
config.py | 路径、阈值、摄像头编号等配置 | 必须按自己环境改 |
2.2 从cam.py到sign_face.py:一条完整的签到链路
这份 demo 的代码入口不止一个,但主线很清晰。cam.py负责打开摄像头、循环抓帧、调用检测和识别,sign_face.py负责签到逻辑和界面刷新,save_face.py负责录入新用户的人脸数据,launcher.py和launcher_sing_face.py是启动器。face_search.py是识别核心,里面会加载 Caffe 模型、做前向传播、计算特征向量之间的相似度。buffer和tmp目录是运行时缓存,第一次跑之前可以是空的。config.py里通常会有摄像头编号、识别阈值、模型路径、字体路径这几项,这是你复现时第一个要动的文件。
常见做法是先把config.py里的路径改成你本机的绝对路径,尤其是caffemodel和prototxt的位置。如果你在 Windows 上跑,路径里的反斜杠要转义或者直接用正斜杠。frame.py和frame.pyc是等价的,.pyc是编译后的字节码,Python 版本不一致时可能加载失败,优先用.py源文件。__init__.pyc同理,如果导入报错,把对应的.py文件找出来放同级目录。
2.3 环境依赖:Python 2 还是 Python 3,Caffe 还是 OpenCV DNN
这是整份资源里最容易翻车的地方。从.pyc文件和print风格判断,这份课设大概率是 Python 2.7 时代写的,但face_search.py里如果用了cv2.dnn.readNetFromCaffe,那 Python 3 也能跑。关键看两点:一是print有没有括号,二是ConfigParser的导入方式。如果你不想折腾 Python 2,我一般会先把face_search.py和cam.py里的print语句批量加括号,再把import ConfigParser改成import configparser。Caffe 本身在 Windows 上编译很痛苦,但 OpenCV 的dnn模块可以直接读 Caffe 模型,不需要装完整的 Caffe 框架。所以实际依赖可以压缩成:
pip install opencv-python opencv-contrib-python numpy pyqt5如果你用的是 Python 3.8 以上,opencv-python建议锁 4.5.x 版本,太新的版本对 Caffe 模型的支持偶发兼容问题。pyqt5是用来加载.ui文件的,如果你不想装 Qt Designer,直接用pyuic5把.ui转成.py也行。numpy版本不要低于 1.19,否则cv2.dnn.blobFromImage的返回类型会有差异。
提示:先跑
python -c "import cv2; print(cv2.__version__)"确认 OpenCV 能正常导入,再动模型加载的代码。很多“模型加载失败”其实是 OpenCV 没装好。
3. 让摄像头认出你:从录入人脸到签到成功的完整操作
3.1 改config.py:四个必须确认的参数
在跑任何脚本之前,打开config.py逐项核对。第一项是模型路径,cnn_deploy.prototxt和cnn_iter_3560000.caffemodel要指向你解压后的实际位置。第二项是haarcascade_frontalface_alt.xml的路径,这个文件在driver目录下。第三项是摄像头编号,笔记本内置摄像头通常是0,外接 USB 摄像头可能是1或2。第四项是识别阈值,这个值决定了特征比对时判定为“同一个人”的宽松程度,常见初始值是0.6到0.8之间,太低会认错人,太高会认不出自己。
# config.py 示例片段(按自己路径改) MODEL_PROTOTXT = r"D:/cnn_face_detection-master/models/cnn_deploy.prototxt" MODEL_WEIGHTS = r"D:/cnn_face_detection-master/models/cnn_iter_3560000.caffemodel" HAAR_CASCADE = r"D:/cnn_face_detection-master/driver/haarcascade_frontalface_alt.xml" CAMERA_INDEX = 0 RECOGNITION_THRESHOLD = 0.72 FONT_PATH = r"D:/cnn_face_detection-master/font/fzyy.TTF"逻辑说明:MODEL_PROTOTXT和MODEL_WEIGHTS是 Caffe 模型的标准加载方式,OpenCV 的readNetFromCaffe需要这两个参数。HAAR_CASCADE用于cv2.CascadeClassifier。CAMERA_INDEX传给cv2.VideoCapture。RECOGNITION_THRESHOLD是余弦距离或欧氏距离的判定门限,具体看face_search.py里用的是哪种度量。参数怎么改:如果你发现签到总是失败,先把阈值调到0.8试一次;如果别人站你前面也能签到,调到0.6。
3.2 录入人脸:save_face.py的采集逻辑与命名规则
录入是签到的前提。跑save_face.py之前,确认buffer目录存在且可写。这个脚本通常会打开摄像头,用 Haar 检测人脸,把检测到的人脸区域裁剪成固定尺寸(比如128x128或224x224),然后保存到buffer或指定目录下。文件名一般用你的学号或姓名拼音,这样后续识别时可以直接从文件名读标签。采集时注意三点:光线要均匀,不要背光;脸要正对摄像头,偏转角度不要超过 15 度;每次采集保持同一表情,不要一会儿笑一会儿严肃。
# save_face.py 核心采集逻辑示意 import cv2 import os cam = cv2.VideoCapture(0) detector = cv2.CascadeClassifier(HAAR_CASCADE) count = 0 person_id = input("输入学号或姓名拼音: ") while True: ret, frame = cam.read() gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) faces = detector.detectMultiScale(gray, 1.3, 5) for (x, y, w, h) in faces: count += 1 face_img = gray[y:y+h, x:x+w] face_img = cv2.resize(face_img, (128, 128)) cv2.imwrite(f"buffer/{person_id}_{count}.jpg", face_img) cv2.rectangle(frame, (x, y), (x+w, y+h), (0, 255, 0), 2) cv2.imshow("save_face", frame) if cv2.waitKey(1) & 0xFF == 27 or count >= 30: break cam.release() cv2.destroyAllWindows()逻辑说明:detectMultiScale的1.3是缩放步长,5是邻域阈值,这两个值越大检测越少但越快。count >= 30表示采集 30 张就停,实际可以按需调整。保存的图片是灰度图,因为 Haar 检测和后续 CNN 输入通常都用灰度。参数说明:person_id建议用纯英文或数字,避免中文文件名在跨平台时出问题。如果你采集完发现buffer里图片数量不对,检查detectMultiScale是不是把侧脸漏掉了,可以换成haarcascade_frontalface_default.xml再试。
3.3 跑通签到:cam.py与sign_face.py的启动顺序
录入完成后,先跑cam.py看摄像头能不能正常出画面,再跑sign_face.py走签到流程。有些版本的launcher.py会同时启动摄像头和界面,但依赖关系没理清的话容易卡死。我一般会分开跑:先python cam.py,确认窗口标题和画面正常;再python sign_face.py,这时界面会加载frame.ui或sign_face.ui,签到按钮触发识别逻辑。识别时face_search.py会做三件事:用 Haar 框脸、把脸送进 CNN 得到特征向量、和buffer里已录入的特征做比对。如果比对成功,界面会显示姓名和签到时间;如果失败,检查buffer里有没有对应人的图片,以及阈值是不是太严。
# face_search.py 识别核心示意 net = cv2.dnn.readNetFromCaffe(MODEL_PROTOTXT, MODEL_WEIGHTS) def extract_feature(face_img): blob = cv2.dnn.blobFromImage(face_img, 1.0, (128, 128), (104, 117, 123)) net.setInput(blob) return net.forward().flatten() def recognize(face_img, known_features): feat = extract_feature(face_img) best_id, best_score = None, float("inf") for pid, ref_feat in known_features.items(): dist = np.linalg.norm(feat - ref_feat) if dist < best_score: best_id, best_score = pid, dist return best_id if best_score < RECOGNITION_THRESHOLD else "unknown"逻辑说明:blobFromImage的(104, 117, 123)是 Caffe 常用的均值减除参数,顺序是 BGR。net.forward()返回的是全连接层输出,flatten()拉成一维向量。距离度量用欧氏距离,阈值RECOGNITION_THRESHOLD直接决定判定结果。参数怎么改:如果你换了自训练的模型,输入尺寸(128, 128)要和cnn_deploy.prototxt里的input_dim一致,均值减除参数也要和训练时保持一致,否则特征分布会偏移。
注意:
sign_face.py和cam.py同时占用摄像头会报Device or resource busy,确保同一时间只有一个进程在抓帧。
4. 避坑与排查:这份课设最容易翻车的五个地方
4.1 现象:cv2.dnn.readNetFromCaffe报错“Can't load layer”
原因:cnn_deploy.prototxt里用了 OpenCV DNN 不支持的层类型,比如某些自定义的Python层或旧版 Caffe 的Slice层参数格式。解决:先用cat cnn_deploy.prototxt | grep layer看层类型,如果出现Python或MemoryData,说明这个模型不是为 OpenCV DNN 准备的。常见做法是把prototxt里不支持的层注释掉,或者换用cv2.dnn.readNetFromCaffe能识别的标准层版本。如果改不动,就装完整 Caffe 用caffe.Net加载,但 Windows 上编译 Caffe 的血泪经验告诉我,能绕就绕。
4.2 现象:摄像头画面卡在第一帧,或者窗口无响应
原因:cv2.waitKey的延时参数太小,或者 Qt 界面和 OpenCV 的imshow在同一个线程里抢事件循环。解决:把cv2.waitKey(1)改成cv2.waitKey(30),给 GUI 留出刷新时间。如果用了 PyQt5,不要在 Qt 主线程里直接跑while True的摄像头循环,改成QTimer定时触发抓帧。另外检查cam.py里有没有cam.release()和cv2.destroyAllWindows(),没有的话第二次跑就会卡住。
4.3 现象:签到界面中文显示为方块
原因:font/fzyy.TTF没有正确加载,或者config.py里的FONT_PATH指向了不存在的文件。解决:确认fzyy.TTF在font目录下,路径用绝对路径。如果还是方块,用cv2.putText画中文本身就不支持,需要转成 PIL 的ImageFont再画回去。常见做法是先把 OpenCV 的 BGR 帧转成 PIL Image,用ImageDraw.text写中文,再转回 OpenCV 格式显示。
4.4 现象:识别率极低,同一个人每次都被判成 unknown
原因:录入时的人脸和签到时的光照、角度差异太大,或者 CNN 特征提取时的预处理不一致。解决:录入和签到必须用同一套预处理流程,包括灰度化、尺寸缩放、均值减除。如果save_face.py保存的是彩色图,而face_search.py按灰度图处理,特征就会对不上。另外检查buffer里的图片是不是被覆盖了,有些脚本每次启动会清空buffer,导致之前录入的数据丢失。
4.5 现象:ImportError: No module named 'ConfigParser'
原因:Python 2 的ConfigParser在 Python 3 里改名为configparser,大小写敏感。解决:全局搜索import ConfigParser,改成import configparser,同时把ConfigParser.ConfigParser()改成configparser.ConfigParser()。如果代码里还有print "xxx"这种 Python 2 语法,一并加括号。.pyc文件如果和.py源文件版本不匹配,直接删掉.pyc,让 Python 重新编译。
5. 进阶玩法:换模型、加活体检测和把签到数据落库
这份 demo 跑通之后,真正有意思的是改它。第一个方向是换模型:cnn_iter_3560000.caffemodel是别人训练好的,你可以用自己的数据集重新训练一个 Caffe 模型,或者把face_search.py里的readNetFromCaffe换成readNetFromONNX,用 ONNX 格式的轻量人脸识别模型替换。换模型时唯一要盯住的是输入尺寸和均值减除参数,这两个不一致,特征向量就是乱的。第二个方向是加活体检测:Haar 检测加 CNN 识别有一个天然漏洞,拿一张照片对着摄像头也能签到。常见做法是在cam.py抓帧后加一个眨眼检测或纹理分析,用dlib的 68 点关键点算眼睛纵横比,连续几帧低于阈值就判定为活体。第三个方向是把签到结果落库:现在很多课设版本是把签到记录写进tmp目录的文本文件,你可以改成 SQLite,建一张attendance表,字段包括学号、姓名、签到时间、置信度。
import sqlite3 from datetime import datetime conn = sqlite3.connect("attendance.db") conn.execute(""" CREATE TABLE IF NOT EXISTS attendance ( id INTEGER PRIMARY KEY AUTOINCREMENT, person_id TEXT NOT NULL, sign_time TEXT NOT NULL, confidence REAL ) """) def log_attendance(person_id, confidence): conn.execute( "INSERT INTO attendance (person_id, sign_time, confidence) VALUES (?, ?, ?)", (person_id, datetime.now().strftime("%Y-%m-%d %H:%M:%S"), confidence) ) conn.commit()逻辑说明:person_id对应录入时的学号或姓名拼音,confidence存识别时的距离值或相似度分数,方便后续排查误判。参数说明:sqlite3不需要额外安装,Python 自带。如果你要支持多摄像头,在表里再加一个camera_id字段。落库之后,签到界面可以加一个“今日签到记录”的表格,直接从数据库读,比读文本文件稳得多。
从那以后我每次拿到这种课设压缩包,都强制先跑一遍python -c "import cv2; print(cv2.__version__)"和python -c "import numpy; print(numpy.__version__)",确认基础环境没坑再动模型文件。这份 CNN 人脸识别考勤 demo 的价值不在算法多深,而在于它把一条完整的链路摊开给你看,改哪里、卡哪里、怎么绕,都清清楚楚。希望帮到你。
本文还有配套的精品资源,点击获取