Python+OpenCV实现人脸识别员工考勤系统:从原理到实战
2026/9/23 16:25:07 网站建设 项目流程

简介:基于Python与OpenCV的人脸识别员工考勤系统毕业设计项目包,面向计算机、软件工程等专业毕业生及需要课程设计/期末大作业的同学,提供一套已通过导师指导评审、下载即可直接运行的完整考勤项目。压缩包共669个文件,整体约197.57MB,以501个Python源码文件为核心,配合pyd动态链接库、exe可执行程序、pth模型权重和xml配置,还有bat一键启动脚本、docx文档说明、csv考勤记录、png/jpg示例图像与db数据库文件,既方便环境搭建,也便于理解人脸识别与考勤管理的完整落地流程。项目中实际涵盖人脸检测、特征提取、身份比对、上下班打卡、数据存储及报表导出等模块,代码结构清晰、注释到位,并附带详细的文档辅助说明。目前已有521人学习使用,可直接作为高分毕业设计、课程设计或期末大作业的参考范本,也可在此基础上快速二次开发。

1. 基于 Python + OpenCV 的人脸识别员工考勤系统:高分毕业设计拆开之后能直接用吗

指纹考勤要排队接触,刷卡考勤容易忘带卡,而人脸考勤只需要员工在摄像头前停一下,这正是这套系统解决的核心问题。它是一个基于 python + opencv 的人脸识别的员工考勤系统,压缩包解压之后直接带着一套虚拟环境和说明文档,属于「下载即用」的毕业设计资源。你不必先读懂全部源码,只要按文档激活环境,就能看到完整的采集、训练、识别打卡流程。它的核心不是高深的深度学习模型,而是把 LBPH 特征、OpenCV 的人脸检测和日志记录串成一条能跑通的链路。适合正在做毕业设计、课程设计,或想快速落地一个人脸识别小项目的开发者。

2. 识别原理与数据流:为什么 LBPH 够用、feature_all.csv 里存了什么

2.1 选型理由:LBPH 在毕业设计场景下的四个优势

拿到这种项目,第一步别急着跑,先搞清楚它用的识别算法。这套系统的识别核心是 OpenCV 里的 LBPH(Local Binary Patterns Histograms,局部二值模式直方图)。相比深度学习方案,LBPH 在毕业设计这个特定场景下有几个实打实的优势:它不需要 GPU,普通笔记本 CPU 就能实时识别;它需要的训练样本少,一个人 20 到 40 张人脸图就能练出一个能用的模型;它的原理在论文里好写,局部纹理特征加直方图统计,配两张示意图就能讲清楚;它是 OpenCV 内置接口,不需要额外配 TensorFlow 或 PyTorch,环境成本几乎为零。这四个优势叠加起来,决定了这类项目选 LBPH 而不是搬一个 ResNet 过来。

LBPH 的工作原理并不复杂:对图像里每个像素,取它周围一个半径为 radius 的圆上均匀分布的 neighbors 个采样点,把每个采样点的灰度值和中心像素比较,大于等于记 1,小于记 0,得到一串二进制数并转成十进制,这个值就代表该像素的局部纹理。整张图计算完之后,把图划分成 grid_x 乘 grid_y 个格子,每个格子统计灰度直方图,最后把所有直方图拼成一个一维特征向量。识别的时候,把当前人脸的特征向量和注册过的特征向量做距离比较,距离越小说明越相似。这个机制对光照有一定容忍度,因为比较的是相邻像素的相对灰度,不是绝对灰度,所以不需要像深度学习那样做大规模数据增强。

从 OpenCV 的默认参数能看出这套算法的设计取向:radius=1、neighbors=8 表示取半径为 1 的圆上的 8 个采样点,这是计算量和表达能力的折中;grid_x=8、grid_y=8 表示把图切成 8x8 共 64 个格子,每个格子单独统计直方图,保留了局部空间信息。格子切得越大表达越细,但计算和存储都会增加,对 200x200 的输入图来说,8x8 是实践里比较稳的配置。有人为了追求精度把 grid 改成 16x16,识别率提升有限,训练时间和特征文件体积倒是翻了几倍,答辩时反而不好解释。

2.2 数据流:从摄像头帧到 logcat.csv 的完整链路

这套系统的数据流分三个阶段,理解了这个链路,后面改代码就不会迷路。采集阶段,程序打开摄像头,用 OpenCV 的 Haar Cascade 级联分类器检测画面里的人脸,把检测到的人脸区域裁出来、灰度化、统一缩放到 200x200,按「工号_序号.jpg」的命名规则存到 samples 目录。训练阶段,程序读取 samples 目录下所有样本,按文件名前缀提取工号作为标签,调用 LBPH 的 train 方法拟合模型,训练完成后把每个样本的工号和特征写入 feature_all.csv。识别阶段,程序加载 feature_all.csv,重建特征和标签,再打开摄像头逐帧检测人脸,对每一张人脸调用 predict 方法,得到一个 (员工ID, 置信度) 的二元组,置信度小于阈值就判定为打卡成功,时间、工号、置信度一并追加写入 logcat.csv。

三个阶段各自独立,中间只通过 samples 目录和 CSV 文件传递数据,所以你可以单独重跑某一个阶段而不影响其他阶段。比如样本采集少了,不需要重新训练整个模型,只要补采样本后重新执行训练脚本,feature_all.csv 会被覆盖更新。这种「文件即接口」的设计在毕业设计里非常常见,也方便你写文档时画数据流图。注意识别阶段加载 feature_all.csv 后仍然要再调一次 recognizer.train,因为 LBPH 的 predict 方法面对的是一个已经训练好的模型,CSV 里的特征只是原始特征,不是模型参数。

这个链路里最容易被忽略的是 Haar Cascade 检测器和 LBPH 识别器是两套独立的东西。Haar Cascade 负责「找出人脸在哪」,输出的是人脸框的坐标;LBPH 负责「这张脸是谁」,输入的是裁好的人脸小图,输出的是 ID 和置信度。很多初学者把这两个概念混在一起,以为检测不出来就是识别没训练好,实际上检测问题通常出在 detectMultiScale 的参数上,识别问题才出在训练数据和阈值上。排查问题之前,先把这句话记在心里,能省掉大量无用功。

2.3 两个 CSV 文件的真实格式:训练特征与打卡日志

压缩包里有两个 CSV 文件值得先打开看一眼,一个是 feature_all.csv,一个是 logcat.csv。feature_all.csv 存储的是训练样本的扁平化特征,第一行是表头,第一列是员工工号,从第二列开始是 200x200 灰度图拉平后的像素值,每行对应一个样本。它的作用是在识别阶段代替 samples 目录作为数据源,这样即便样本图片被误删,只要 CSV 还在,识别功能就不受影响。表头一般长这样:label、feature_0、feature_1……一直到 feature_39999,一共 40001 列,因为 200 乘 200 等于 40000 个像素值。

logcat.csv 是考勤日志,字段相对少,一般包含时间、工号、姓名、置信度、结果五列。时间格式是标准的「年-月-日 时:分:秒」,结果字段是「通过」或「拒绝」。这个文件是追加写入的,每次识别打卡成功就在末尾新增一行,不会覆盖历史记录。答辩的时候,评委最常看的就是这个文件,它能直观证明系统确实产生了考勤记录,所以写日志时务必保证时间戳精确到秒,并且编码别出问题(第 5 章会讲编码坑)。

文件生成时机核心字段用途
feature_all.csv训练阶段label + 40000 个特征值识别阶段重建训练数据
logcat.csv识别阶段时间、工号、姓名、置信度、结果考勤记录与答辩证据

3. 环境准备与首次运行:虚拟环境激活、依赖安装与摄像头初始化

3.1 自带的虚拟环境怎么用:activate.bat 与 pyvenv.cfg 的分工

压缩包里有 activate.bat、deactivate.bat、sysconfig.cfg、pyvenv.cfg 这几个文件,它们属于同一个 Python 虚拟环境。虚拟环境的作用是把项目的依赖隔离起来,避免和你机器上其他 Python 项目互相干扰。pyvenv.cfg 记录了这个虚拟环境基于哪个 Python 版本创建,activate.bat 负责把当前命令行的 PATH 临时切换到虚拟环境的解释器目录,deactivate.bat 负责退出。也就是说,你不需要在自己机器上重新安装 OpenCV,只需要激活这个环境,所有依赖就都就位了。

激活方式在 Windows 命令行下很简单:先 cd 到项目根目录,然后执行 venv 目录下的 activate.bat。注意如果你把项目解压到了带空格的路径,比如「D:\My Projects\face_attendance」,直接运行 activate.bat 可能报错,因为路径里有空格时 Windows 的批处理解析会翻车。我一般会先把项目放到一个纯英文且无空格的路径,比如 D:\face_attendance,再执行激活命令,这一步能避免后面所有莫名其妙的路径问题。

cd D:\face_attendance venv\Scripts\activate python main.py

激活成功后,命令行提示符前面会出现 (venv) 字样,这时候你运行的 python 就是虚拟环境里的解释器。如果 activate 执行后提示「系统找不到指定的路径」,多半是批处理文件里写死了旧的绝对路径,打开 activate.bat 检查 VIRTUAL_ENV 这一行的路径是否和你解压的目录一致,不一致就改过来。还有一个细节:有些压缩包解压后文件夹名字带「(1)」或「副本」之类的后缀,也会导致路径失效,统一改成干净目录名再激活。

如果激活之后 import cv2 仍然报 ModuleNotFoundError,先别急着重装环境,检查一下当前 python 是不是虚拟环境里的。直接在命令行执行 where python,看返回的第一个路径是否指向 venv 目录。很多人的坑在于先打开了 IDE,在 IDE 的终端里运行激活脚本,但 IDE 的终端和系统终端的 PATH 传递并不完全一致,导致解释器还是系统全局的。我建议命令行操作全部在原生 cmd 里完成,不要用 IDE 内置终端跑激活。

3.2 不用自带环境时,从零装依赖的完整命令

有些情况下你不想用自带的虚拟环境,比如 Python 版本和你系统不兼容,或者你需要在服务器上跑。这时候从零安装依赖其实也不复杂,核心依赖就三个:numpy、opencv-contrib-python、opencv-python。注意这里有个坑:人脸识别模块 cv2.face 不在主包的 opencv-python 里,而在扩展包 opencv-contrib-python 里。如果你只装了 opencv-python,import cv2 不会报错,但一调用 cv2.face.LBPHFaceRecognizer_create 就会报 AttributeError。所以安装时务必把 contrib 包装上。

pip install numpy pip install opencv-contrib-python pip install opencv-contrib-python --upgrade

如果机器上已经装了 opencv-python,建议先卸载掉再装 contrib 包,两个包同时存在时 import 顺序和 DLL 加载都可能出问题,表现为 cv2 能 import,但某些模块行为异常,这种「能跑一半」的状态最折磨人。卸载命令是 pip uninstall opencv-python,卸载完再执行上面的安装命令。Python 版本方面,这套代码在 Python 3.8 到 3.10 上运行最稳,Python 3.11 以上某些版本的 numpy 和 OpenCV 组合会有编译兼容问题,如果你遇到奇怪的导入错误,可以先换个 Python 版本试试。

装完依赖之后做一个快速自检,确认关键的类和函数都在:

import cv2 import numpy as np print("OpenCV 版本:", cv2.__version__) print("LBPH 识别器:", cv2.face.LBPHFaceRecognizer_create) print("Haar 级联路径:", cv2.data.haarcascades)

这段自检代码输出的三行信息分别对应识别器、检测器、数据文件路径。cv2.data.haarcascades 指向 OpenCV 自带的 XML 分类器目录,里面包含 haarcascade_frontalface_default.xml 等文件。如果你的 OpenCV 没有 cv2.data 属性,说明装的版本太老,或者装错了非官方渠道的包,建议升级到 4.5 以上版本。升级命令是 pip install opencv-contrib-python --upgrade。

3.3 首次运行前的摄像头初始化验证

摄像头是整个系统最不可控的硬件,就算代码一行没改,换个电脑可能就打不开摄像头。所以不要直接跑 main.py,先做一个 30 秒的摄像头验证,确认设备本身没问题。常见的情况是笔记本自带摄像头索引为 0,外接 USB 摄像头索引为 1,但如果你的电脑同时装了虚拟摄像头软件,索引可能会被占用,导致程序读到的画面全是黑的或者直接报错。

import cv2 import time cap = cv2.VideoCapture(0) if not cap.isOpened(): print("摄像头 0 打不开,尝试索引 1") cap = cv2.VideoCapture(1) time.sleep(1) # 给摄像头预热时间 ok, frame = cap.read() if ok: cv2.imwrite("camera_test.jpg", frame) print("摄像头正常,测试图已保存") else: print("读帧失败,检查摄像头是否被其他程序占用") cap.release()

这段验证脚本做了两件事:一是用 isOpened 检查摄像头初始化是否成功,失败就换索引重试;二是 sleep 一秒后读一帧,把这一帧保存成图片。那个 sleep 不是摆设,很多 USB 摄像头上电后需要几百毫秒才能输出稳定画面,立刻读帧容易读到全黑帧。运行完如果生成了 camera_test.jpg,说明硬件链路没问题,可以放心跑完整系统;如果生成的是全黑图片,去系统设置里检查摄像头隐私权限——Windows 10 在「设置-隐私-摄像头」里如果关闭了「允许应用访问你的摄像头」,OpenCV 是拿不到画面权限的,但程序不会报错,只会一直返回黑帧。

4. 核心模块拆解:样本采集、模型训练与打卡判定的三段代码

4.1 员工样本采集:灰度化、统一尺寸与样本量控制

样本采集是所有识别效果的地基,这一步偷懒后面全是补不完的坑。采集逻辑不复杂:打开摄像头、检测人脸、裁出人脸区域、缩放到统一尺寸、存成文件。之所以要灰度化,是因为 LBPH 本身就是基于灰度纹理的算法,彩色信息对它没有帮助,反而增加计算量。统一尺寸到 200x200 是为了让所有样本的特征维度一致,这样才能放进同一个训练集里。

import cv2 import os sample_dir = "samples" os.makedirs(sample_dir, exist_ok=True) cap = cv2.VideoCapture(0) detector = cv2.CascadeClassifier( cv2.data.haarcascades + "haarcascade_frontalface_default.xml" ) employee_id = input("输入员工工号: ") count = 0 while count < 60: ok, frame = cap.read() if not ok: continue gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) faces = detector.detectMultiScale( gray, scaleFactor=1.1, minNeighbors=5, minSize=(80, 80) ) for (x, y, w, h) in faces: face = gray[y:y+h, x:x+w] face = cv2.resize(face, (200, 200)) cv2.imwrite(f"{sample_dir}/{employee_id}_{count}.jpg", face) count += 1 cv2.imshow("capture", frame) if cv2.waitKey(1) & 0xFF == ord("q"): break cap.release() cv2.destroyAllWindows()

代码里的关键参数需要说明一下。detectMultiScale 的 scaleFactor 是每次缩放检测窗口的比例,1.1 表示每次缩小 10%,数值越小检测越精细但越慢;minNeighbors 控制每个候选框最少需要多少个相邻检测框确认才算数,5 是一个比较均衡的值,调大会漏检,调小会误检;minSize=(80, 80) 过滤掉小于 80x80 的检测框,可以排除掉远处的小脸和背景里的噪声。count 循环到 60 张自动停止,这个数量对这个项目是够的——每人 60 张样本,训练出来的模型基本能扛住日常光线变化。如果采集过程中发现检测框经常消失,先别急着改参数,先检查光线是不是太暗或者背光,对着光源调整一下位置往往比调参数更有效。

4.2 特征训练:LBPH 参数与 feature_all.csv 落盘

样本采完之后进入训练阶段。LBPH 的 train 方法接受两个参数:样本图像列表和对应的标签列表。标签必须是整数,所以工号如果带字母,需要先映射成数字。训练完成后,把每个样本的特征写入 feature_all.csv,这一步是这套系统的一个特色:识别端不依赖 samples 目录,只依赖这个 CSV 文件。

import os import csv import numpy as np import cv2 sample_dir = "samples" feature_file = "feature_all.csv" recognizer = cv2.face.LBPHFaceRecognizer_create( radius=1, neighbors=8, grid_x=8, grid_y=8 ) features = [] labels = [] for name in sorted(os.listdir(sample_dir)): if not name.endswith(".jpg"): continue employee_id = name.split("_")[0] img = cv2.imread(os.path.join(sample_dir, name), cv2.IMREAD_GRAYSCALE) features.append(img) labels.append(int(employee_id)) recognizer.train(features, np.array(labels)) with open(feature_file, "w", encoding="utf-8-sig", newline="") as f: writer = csv.writer(f) writer.writerow(["label"] + [f"feature_{i}" for i in range(200 * 200)]) for i, img in enumerate(features): writer.writerow([labels[i]] + img.flatten().tolist()) print(f"训练完成,共 {len(labels)} 个样本,特征已写入 {feature_file}")

这段代码里 recognizer.train 的三个前置参数是 LBPH 的核心配置:radius=1 代表采样半径,neighbors=8 代表采样点数,grid_x=8、grid_y=8 代表网格划分。训练完成后把每个样本的灰度矩阵 flatten 成一行写入 CSV,这就是 feature_all.csv 的实际内容。注意这里 CSV 存的是原始像素特征,不是 LBPH 转换后的直方图,识别端加载之后需要再调用一次 train 才会在内存里生成 LBPH 直方图模型。这么设计的考虑是,CSV 文件能直观展示特征维度,答辩时打开文件就能解释特征是什么,比二进制文件更有说服力。

训练不通过样本数少于 10 张的情况最让人头疼。每人工号下只有三五张样本时,LBPH 基本是学不到稳定纹理特征的,识别距离会普遍偏高,表现为本人也被拒。所以训练样本的收集标准是:每个工号至少 20 张,覆盖正脸、左右侧转、不同表情,有条件的话在开灯和关灯两种光线下各采一部分。样本的多样性比数量更重要,30 张不同角度的样本远好于 60 张几乎一模一样的样本。

4.3 识别打卡:置信度阈值、防重复打卡与日志写入

识别阶段是系统的门面,评委和用户看到的就是这一步的结果。流程是:加载 feature_all.csv 重建训练集、打开摄像头、逐帧检测人脸、对检测到的每张脸调用 predict、置信度低于阈值就打卡。这个阶段需要处理两个实际问题:一是防止同一张脸在连续帧里重复打卡,二是置信度阈值到底怎么定。

import csv import time import numpy as np import cv2 conf_threshold = 60 repeat_minutes = 5 last_clock = {} cap = cv2.VideoCapture(0) detector = cv2.CascadeClassifier( cv2.data.haarcascades + "haarcascade_frontalface_default.xml" ) recognizer = cv2.face.LBPHFaceRecognizer_create(radius=1, neighbors=8, grid_x=8, grid_y=8) features, labels = [], [] with open("feature_all.csv", "r", encoding="utf-8-sig") as f: reader = csv.reader(f) next(reader, None) for row in reader: if len(row) < 2: continue labels.append(int(row[0])) features.append(np.array(row[1:], dtype=np.uint8).reshape(200, 200)) recognizer.train(features, np.array(labels)) def write_log(emp_id, conf, ok_flag): with open("logcat.csv", "a", encoding="utf-8-sig", newline="") as f: writer = csv.writer(f) writer.writerow([ time.strftime("%Y-%m-%d %H:%M:%S"), emp_id, f"员工{emp_id}", round(conf, 2), "通过" if ok_flag else "拒绝" ]) while True: ok, frame = cap.read() if not ok: continue frame = cv2.resize(frame, (960, 540)) gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) faces = detector.detectMultiScale(gray, scaleFactor=1.1, minNeighbors=5, minSize=(80, 80)) for (x, y, w, h) in faces: face = cv2.resize(gray[y:y+h, x:x+w], (200, 200)) pred_id, conf = recognizer.predict(face) if conf < conf_threshold: now = time.time() if pred_id not in last_clock or now - last_clock[pred_id] > repeat_minutes * 60: last_clock[pred_id] = now write_log(pred_id, conf, True) print(f"{time.strftime('%H:%M:%S')} 员工{pred_id} 打卡成功,置信度 {round(conf, 2)}") cv2.rectangle(frame, (x, y), (x + w, y + h), (0, 255, 0), 2) cv2.imshow("attendance", frame) if cv2.waitKey(1) & 0xFF == ord("q"): break cap.release() cv2.destroyAllWindows()

关键参数是 conf_threshold=60。LBPH 的置信度是特征直方图之间的距离,不是相似度百分比,这一点必须反复强调:距离越小代表越像,60 表示距离小于 60 才认账。根据我的经验,正常光线、每人 30 张样本的情况下,本人距离通常在 30 到 70 之间,陌生人距离往往超过 100。取 60 作为分界线,既能保证本人识别率,又能拦住大多数陌生人。如果识别发现本人都被拒,把阈值放宽到 70;如果陌生人也能打卡,把阈值收紧到 50。repeat_minutes=5 是防重复打卡的时间窗口,同一工号 5 分钟内不重复写日志,避免人在摄像头前多站了几秒就产生好几条记录。识别前把画面宽度降到 960,是为了减少检测区域,让 detectMultiScale 的计算量明显下降,帧率能稳定在 15 到 20 帧左右。

4.4 参数集中配置:一个 config.py 管住所有玄学

采集、训练、识别三段代码里散落着不少参数,如果每次都去源码里改,很容易改出一处忘了另一处。我习惯把这类参数全部收拢到一个 config.py 里,识别、采集模块都从它取值,这样答辩前调参只需要改一个文件。

# config.py CAMERA_INDEX = 0 SAMPLE_COUNT = 60 FACE_SIZE = 200 CONF_THRESHOLD = 60 REPEAT_MINUTES = 5 SCALE_FACTOR = 1.1 MIN_NEIGHBORS = 5 MIN_FACE_SIZE = 80

把参数集中之后,排查问题的效率会高很多。比如现场识别老是不准,先看 CONF_THRESHOLD 是不是被调过,再看样本量够不够,最后才去查代码逻辑。有一个常见的反面做法是把阈值写死在某个识别循环深处,一个小数改动要全局搜索才能定位,答辩现场调参会非常狼狈。config.py 这个文件的另外一个好处是,它天然成了你文档里的「参数说明」章节素材,每个参数写一句注释,文档就完成了三分之一。

5. 避坑与排查:五次翻车换来的运行经验

5.1 ModuleNotFoundError: No module named 'cv2.face'

这个报错是这套系统最常见的开局坑。现象很明确:import cv2 成功,但运行到 cv2.face.LBPHFaceRecognizer_create 时直接抛异常,提示找不到 cv2.face。原因在于 OpenCV 从 3.x 开始把面部识别这类非核心模块拆到了 contrib 仓库里,主包 opencv-python 不包含 face 模块,只有 opencv-contrib-python 才包含。解决方法是先卸载主包再装 contrib 包。

pip uninstall opencv-python pip install opencv-contrib-python

卸载这一步不能省,因为两个包同时存在时,Python 的 import 机制可能加载到主包的 cv2,导致 face 模块仍然不可见。装完后重新执行第 3 章的快速自检代码,看到 LBPH 识别器能正常创建就说明环境没问题了。这个坑我在不同机器上踩过三次,每次都是因为图省事直接 pip install opencv-python 就开跑。

5.2 摄像头黑屏、白屏或一闪而过

现象是运行采集脚本后,窗口弹出来但画面全黑,或者摄像头灯闪一下就灭了。排查这类问题先分清是硬件问题还是代码问题:先用系统自带的相机应用测试摄像头是否正常,如果系统相机能出画面而 OpenCV 不行,基本可以排除硬件故障。原因通常有三个:摄像头索引不对、隐私权限没开、摄像头被其他程序占用。代码层面先用第 3 章的验证脚本测试,isOpened 返回 False 就换索引,返回 True 但 read 返回黑帧就去查系统权限。

Windows 的隐私权限是最容易忽略的。系统设置里如果禁止了应用访问摄像头,OpenCV 不会得到任何错误提示,只会静默返回全黑帧。解决路径是「设置 - 隐私与安全性 - 摄像头 - 允许应用访问你的摄像头」,确保这个开关是打开的。另外,有些电脑装了虚拟摄像头软件(比如直播辅助工具),它会占住索引 0,导致真实摄像头变成索引 1,多试几个索引往往就恢复了。

5.3 人脸框跳来跳去,识别率时高时低

现象是画面里人脸框忽大忽小、有时检测到有时完全丢失,识别成功率也随之波动。原因集中在两类:一是 detectMultiScale 的参数不合理,误检和漏检的平衡没找对;二是光照条件差,背光、逆光、侧光都会让 Haar 特征的响应变得不稳定。解决分两步走,第一步把参数调到合理范围:scaleFactor 从 1.1 起步,不要低于 1.05,minNeighbors 不低于 3,不高于 8。第二步对灰度图做直方图均衡化,增强对比度后再检测。

gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) gray = cv2.equalizeHist(gray) faces = detector.detectMultiScale(gray, scaleFactor=1.1, minNeighbors=5, minSize=(80, 80))

equalizeHist 这行代码能显著改善光线不均情况下的检测稳定性,代价几乎为零。如果加了均衡化之后在强光环境下仍然频繁丢框,可以在检测前端加一个 gamma 校正,但对毕业设计来说均衡化已经够用了。记住一个原则:参数是最后的手段,先把光线调好,再动参数。

5.4 置信度阈值这回事:为什么设成 100 反而谁都打不上卡

现象是阈值设得很宽松,比如 conf_threshold=100,结果本人都识别不了;设得很严格,比如 30,结果反而经常误判。这个现象让很多人觉得 LBPH 的置信度是玄学,其实是因为没有理解置信度的量纲。LBPH 的 confidence 是特征直方图之间的卡方距离,距离值没有上限,正常范围在 0 到 200 之间波动,它和深度学习里的 softmax 概率完全不是一回事。100 这个值听起来很宽松,实际上对 LBPH 来说已经算中等严格了,如果本人相似度距离经常在 90 以上,阈值 100 当然打不上卡。

解决方法是先做一次「摸底测试」:用采集好的样本分别执行 predict,打印出本人和陌生人的实际距离范围,然后取两者中间值作为阈值。比如测出来本人距离最高 65,陌生人最低 85,阈值取 70 到 75 之间就非常稳。另外还要注意,如果本人距离整体偏高(比如全在 90 以上),这不是阈值的问题,是训练样本太少或光线变化太大,需要回去补样本,而不是继续放宽阈值。

5.5 logcat.csv 用 Excel 打开中文乱码

现象是 CSV 文件用记事本打开一切正常,但用 Excel 打开后中文全部变成乱码。原因是写入时用了 UTF-8 编码,而 Windows 的 Excel 默认按 ANSI 也就是 GBK 去解析 CSV 文件,编码对不上自然乱码。解决方案是写入时加上 BOM 头,让 Excel 识别出文件是 UTF-8 编码。我在第 4 章的代码里写的是 encoding="utf-8-sig",这个 sig 就是 BOM,它会在文件开头写入一个不可见标记,Excel 读到这个标记就会用 UTF-8 解码。如果已经生成了乱码文件,用记事本打开后另存为 ANSI 编码即可,但更好的做法是删掉重新生成。

这个坑的影响面比想象中大。答辩时评委一般会直接双击打开 logcat.csv 看考勤记录,如果满屏乱码,第一印象就会打折扣。同样的问题也适用于 feature_all.csv,所以两个文件的写入编码务必都用 utf-8-sig。另外,写 CSV 时记得带上 newline="" 参数,否则在 Windows 上每行之间会产生空行,那是由 CSV 库和系统换行符叠加导致的,看起来虽不难看,但会显得代码不够严谨。

6. 把系统从「能跑」变成「答辩稳」:演示视频验证与阈值收口

等到系统在你自己的电脑上能稳定跑通了,别急着收工,先做一轮「答辩模拟」。现场演示最怕的不是算法不行,而是硬件不给面子——会议室光线不足、摄像头被别人占用、投影仪分辨率干扰,任何一个小问题都会让演示当场翻车。我一般会准备一条退路:把演示过程录制成视频,识别代码直接读取视频文件而不是摄像头,这样现场演示时只要双击脚本,画面就会稳定播放,识别结果和实时摄像头模式完全一致。

把摄像头换成视频文件只需要改一行代码:cap = cv2.VideoCapture("demo.mp4")。录制 demo.mp4 时有一个细节,视频分辨率不要太高,1080p 就行,因为识别端还会把帧缩放到 960 宽,源视频再高也是浪费。录制内容建议覆盖两个场景:一段是员工正常打卡,另一段是陌生人尝试打卡被拒绝停留两秒,间隔循环,时长控制在 30 秒左右。演示时评委看到的是连贯的识别过程,你就有了充足的时间讲解原理。

阈值收口这一步同样在答辩前完成。用每个员工的样本跑一遍摸底测试,记录本人和陌生人的置信度区间,把 config.py 里的 CONF_THRESHOLD 调整到两个区间的中间值。我遇到过的情况是:某个员工戴眼镜和不戴眼镜时距离差得很大,阈值取中间值会导致戴眼镜时识别率下降。这时候最优解不是继续调阈值,而是让该员工补采一组戴眼镜的样本,重新训练。样本多样性对 LBPH 的改善是立竿见影的,比任何参数调整都有效。

验证流程建议做一次完整的「四步走」:清空 feature_all.csv 重新采集样本、重新训练、跑识别打卡、打开 logcat.csv 检查记录。四步都走通之后,把 config.py 和两个 CSV 文件备份一份,就算答辩现场代码被误改了,也可以用备份快速还原。从那次之后,我每次打开这套系统,第一件事就是确认虚拟环境激活、第二件事验证摄像头索引、第三件事才跑 main.py,三步少一步都可能翻车。这套流程花不了两分钟,但能挡掉大部分现场事故。这套资源里的源码和文档都是现成的,建议先原样跑通再按自己的需求改,希望帮到你。

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

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

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

立即咨询