简介:本资源是一份面向高校计算机、人工智能及相关专业本科生的机器学习课程设计与期末大作业实战项目,聚焦人脸识别与性别检测两大核心任务,解决从理论建模到工程落地的完整实践问题。压缩包共14个文件,含6个Python主程序(如video_face_rec.py、photo_lib_sex_rec.py等,分别支撑视频/图像人脸检测、BP神经网络性别识别及模块化测试)、3份PDF报告(含工作报告、最终报告与课堂汇报材料,覆盖研究背景、BP网络设计、实验对比与结果分析)、3个.zbak备份文件及1个README.md说明文档,整体大小仅3.94MB,轻量易部署。已有65人下载学习,项目经导师指导获评97分高分,源码可直接运行,无需修改;配套报告结构规范、图文并茂,清晰呈现数据预处理、特征提取、BP网络训练调参及多场景(静态图+动态视频)验证全过程,是理解传统机器学习在CV任务中应用的优质教学范例。 如果你正在为一门机器学习课程的大作业发愁,大概已经在搜索框里敲过“Python 人脸识别 源码”这类关键词了。这个选题在高校里几乎是“万能选择”:需求明确、可视化效果好、算法链路完整,既能用到图像处理,又能用到机器学习分类模型,还自带很强的演示冲击力。去年我带着学弟完整做过一个同样主题的项目,从选题、数据准备、模型训练到课程报告和答辩,最终拿到了95分以上的课程成绩,这篇就把整套过程拆开讲一遍。文章适合三类人:第一次做图像类机器学习作业、手里只有普通笔记本、需要一个稳妥高分项目的同学,也适合想弄懂PCA特征脸原理而不是只会调包的人。
1. 选题决策:为什么人脸识别是机器学习大作业的最优解
1.1 三类常见选题方向,为什么我推荐人脸识别
机器学习大作业通常有三类主流方向:数据挖掘类(电商用户画像、信贷违约预测)、自然语言处理类(垃圾邮件分类、情感分析)、计算机视觉类(手写数字识别、物体分类、人脸识别)。数据挖掘类最大的问题是demo感弱,你跑完一个准确率,屏幕上一行数字,老师很难直观感受到“机器学习在起作用”;自然语言处理类对中文预处理要求高,分词、停用词、词向量这些步骤很容易让初学者在环境配置上卡一个星期;而计算机视觉类天然带有“看着像AI”的展示效果。
人脸识别在视觉类选题里又是最合适的。手写数字识别(MNIST)已经被写到烂大街,老师看到题目就没有新鲜感;通用物体分类在传统机器学习框架下效果一般,深度学习又超纲。人脸识别却恰好在一个平衡点上:数据量小、类别可控、传统机器学习方法(PCA、LBP、SVM)效果好,而且可以实时摄像头演示,答辩现场的冲击力比其他项目高一个档次。
1.2 这个项目的边界:机器学习,而不是深度学习
很多同学一听到人脸识别,第一反应是CNN、ResNet、FaceNet,然后担心自己笔记本跑不动。这里要明确一个大作业定位问题:课程名称是“机器学习”,不是“深度学习”。大多数本科机器学习课讲到的内容止步于PCA、SVM、决策树、随机森林,如果你直接端出一个预训练好的深度学习模型,老师反而会质疑:这到底是你写的还是搬运的?传统机器学习路线则完全贴合课程知识点,每个环节都能解释清楚“为什么这么做”。
我推荐的主线方案是:OpenCV做人脸检测和图像预处理,PCA(主成分分析)做特征提取和降维,SVM做人脸分类。这套方案在普通笔记本上几分钟就能完成训练,也不需要GPU,代码量和可解释性都非常适合课程报告。后面如果想拔高,可以再用CNN做一个对比实验,但主线必须让老师看到你对课程知识的掌握。
1.3 最终交付物清单
95分以上的项目,交付物绝不是“能跑的代码”这么简单。我给自己定的交付清单包括四部分:完整可运行的源码工程、课程报告、答辩PPT、演示脚本。源码里要包含数据处理、模型训练、评估、实时识别四个模块;报告要覆盖选题背景、相关工作、技术原理、实验设计、结果分析、总结展望;演示脚本要提前设计好“哪张图先点、摄像头识别哪个人、意外情况怎么圆”。
这个清单的好处是:每一步都有明确产出,不会到答辩前一夜才发现缺图表或代码跑不通。尤其课程报告,很多同学最后草草交一两千字,但报告恰恰是分数权重最高的部分。我的经验是,代码决定你的项目下限,报告和答辩决定你的分数上限。
2. 技术栈与方案选型:PCA+SVM是稳赢的经典组合
2.1 环境与依赖版本
先明确技术栈。我用的是Python 3.8 + OpenCV 4.5 + scikit-learn 1.0 + NumPy + Matplotlib,操作系统Windows 10。建议直接用Anaconda建一个独立环境,不要一股脑装到base环境里,不然后面安装OpenCV和scikit-learn版本冲突时你会非常痛苦。
创建环境的命令很简单:
conda create -n face_recognition python=3.8 conda activate face_recognition pip install opencv-python scikit-learn numpy matplotlib之所以选Python 3.8而不是3.11,是因为OpenCV和scikit-learn的预编译包在3.8上的兼容性最成熟,对新手最友好。你不需要最新版本,你需要的是“装完就能跑”的版本。OpenCV用来做人脸检测、图像读取、摄像头调用;scikit-learn提供PCA、SVM、模型评估全套工具;NumPy负责矩阵运算;Matplotlib画特征脸和混淆矩阵。
2.2 LBPH、PCA+SVM、CNN三条路线对比
人脸识别在传统机器学习中有三条常见技术路线,我做过对比实验,才最终确定主线方案。这里直接给结论表格:
| 路线 | 核心原理 | 优点 | 缺点 | 报告可解释性 |
|---|---|---|---|---|
| LBPH | 局部二值模式直方图 | OpenCV内置,代码量极小 | 对光照敏感,算法深度不足 | 低,基本是调库 |
| PCA+SVM | 特征脸降维 + 分类器 | 原理清晰,可写内容多 | 需要自己做数据预处理 | 高 |
| CNN | 卷积神经网络 | 精度高,效果好 | 训练慢,超纲,容易失真 | 中,难解释透彻 |
LBPH最大的问题不是效果差,而是代码太短。你在报告里写“调用cv2.face.LBPHFaceRecognizer_create()完成训练”,原理部分写不了几页,老师一看就知道你没有深入理解。CNN路线的问题在于,如果作业要求是机器学习课,深度学习方法写多了会偏离主题,而且大多数同学对CNN的梯度反向传播解释不清,答辩问两句就露馅。
2.3 最终选型逻辑与报告中的“对比实验”伏笔
最终我选择PCA+SVM作为主方案。原因有三:第一,PCA是机器学习课程中“降维”章节的核心内容,SVM是“分类”章节的核心内容,一个项目串起两个重点,课程报告的知识覆盖度天然够高;第二,特征脸(Eigenface)有非常直观的可视化结果,可以把“抽象的高维空间投影”变成一张张能看懂的“平均脸”和“特征脸”,答辩时老师很有兴趣;第三,SVM在小样本分类任务上表现稳定,几十个人的小数据集完全够用。
同时我在报告里留了一个伏笔:用LBPH和CNN做对比实验,说明不同方法的优缺点。这样既展示了你对多种方案的了解,又不会喧宾夺主。老师看到的是“学生有全局视野,但能抓住主线”,这个印象分很高。
3. 数据集构建与预处理:分数分水岭从这里开始
3.1 公开数据集与自采数据怎么选
人脸识别大作业的数据集有两种来源:公开数据集和自己采集。公开数据集经典的有AT&T(ORL)人脸库、Yale人脸库、Extended Yale B等。ORL有40个人,每人10张不同光照、表情、遮挡的灰度图,尺寸92x112,非常适合机器学习课程实验。耶鲁人脸库有15人,每人11张,包含光照变化和表情变化,也是传统人脸识别论文的常客。
我的建议是:主体使用公开数据集,保证实验可复现,再补充自己采集的小规模数据用于实时演示。完全用自采数据有两个问题:一是采集工作量巨大,每个人要不同角度、不同表情、不同光照,至少20张才够训练;二是自己采集的照片分辨率、背景、光照不统一,预处理难度直线上升。但如果只用公共数据集,摄像头实时识别演示时模型不认识你,现场会很尴尬。所以我的方案是:在公开数据集上完成主要实验和报告图表,再额外建一个5人左右的“自采实时识别数据集”,每人采集30张,用于最后的摄像头演示。
3.2 人脸检测、裁剪与对齐的完整流程
自采数据的流程并不是直接拿照片训练,必须先做人脸检测和裁剪。原始照片里有背景、头发、衣服,这些信息对识别不但没有帮助,反而会干扰模型。人脸检测我用OpenCV的Haar级联分类器,代码非常简洁:
import cv2 face_cascade = cv2.CascadeClassifier( cv2.data.haarcascades + "haarcascade_frontalface_default.xml" ) def detect_and_crop_face(image, target_size=(64, 64)): gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) faces = face_cascade.detectMultiScale( gray, scaleFactor=1.1, minNeighbors=5, minSize=(50, 50) ) if len(faces) == 0: return None x, y, w, h = faces[0] face = gray[y : y + h, x : x + w] return cv2.resize(face, target_size)这里有几个参数值得解释。scaleFactor=1.1表示每次搜索窗口按10%比例缩小,这个值越小检测越慢但召回率越高,对普通人脸识别1.1是稳妥值;minNeighbors=5表示一个候选区域至少要被5个邻近检测窗口确认才算真正人脸,值太小人脸误检会增多,值太大会漏检。裁剪后统一缩放到64x64,是为了让所有训练样本有相同的向量长度,PCA才能处理。
对齐是很多教程忽略的一步。人脸倾斜会显著影响识别准确率。最简单的对齐方法是检测两只眼睛的位置,根据眼睛连线角度做旋转校正。检测眼睛可以继续用OpenCV的眼部级联分类器:
eye_cascade = cv2.CascadeClassifier( cv2.data.haarcascades + "haarcascade_eye.xml" ) def align_face(face): eyes = eye_cascade.detectMultiScale(face) if len(eyes) < 2: return face eyes = sorted(eyes, key=lambda e: e[0]) (x1, y1, _, _), (x2, y2, _, _) = eyes[:2] dx = (x2 + (eyes[1][2] / 2)) - (x1 + (eyes[0][2] / 2)) dy = (y2 + (eyes[1][3] / 2)) - (y1 + (eyes[0][3] / 2)) angle = np.degrees(np.arctan2(dy, dx)) center = (face.shape[1] // 2, face.shape[0] // 2) matrix = cv2.getRotationMatrix2D(center, angle, 1.0) return cv2.warpAffine(face, matrix, (face.shape[1], face.shape[0]))注意,眼检测并不总稳定,sorted后取前两个眼睛框时,如果检测到两个框都在同一只眼睛上,对齐就会错乱。所以我在实际项目里加了判断:双眼中心点横坐标距离小于面宽1/3时,视为检测异常,直接返回原图,不做过校正。这个经验是测试了上百张图后总结出来的。
3.3 数据划分:最容易忽略的数据泄露问题
数据划分是新手最容易出错、也是老师最爱问的细节。很多入门教程直接把所有图片随机打乱,比如40个人每人10张,随机取80%训练、20%测试。这样划分会产生严重的数据泄露:同一个人的照片同时出现在训练集和测试集,测试时模型见过这个人,识别的准确率虚高。
正确做法是按人划分:把40个人的名单随机分成训练组和测试组,比如训练组32人,测试组8人,训练组所有人的照片进训练集,测试组所有人的照片进测试集。这样测试集里的人对模型来说是“完全陌生的人”,才真正检验了模型泛化能力。用scikit-learn的train_test_split实现时,要把y设为人的ID而不是图片索引,先处理人的ID列表再展开成图片样本:
from sklearn.model_selection import train_test_split person_ids = np.array(list(person_dict.keys())) train_people, test_people = train_test_split( person_ids, test_size=0.2, random_state=42, stratify=person_ids )如果你做的是“人脸验证”(判断两张脸是否同一人),数据集划分逻辑又会不同。但在课程大作业的“人脸识别”设定下,按人划分是基本素养,这个细节写进报告会让老师立刻觉得你懂行。
4. 核心算法实现:特征脸、降维与分类器调参
4.1 PCA特征脸的原理与代码落地
PCA特征脸的核心思想可以这样理解:一张64x64的人脸图,按像素展开后是一个4096维的向量。直接在这个维度上做分类,维度太高、噪声太多,而且很多像素之间是相关的。PCA要做的事,是在4096维空间里找一组新坐标轴,使得所有训练图像坐标值的方差尽可能大——方差大的方向就是人脸之间差异最明显的地方。
这些新坐标轴对应回图像尺寸,看起来就是一张张“特征脸”。训练时把每张人脸投影到特征脸张成的低维子空间,得到一个低维特征向量;识别时把新图片也投影到这个空间,再用分类器判断它属于谁。好处是既能压缩数据,又能保留最具判别性的信息。
sklearn实现PCA非常方便:
from sklearn.decomposition import PCA def train_pca(features, n_components=0.95): pca = PCA(n_components=n_components, whiten=True) reduced = pca.fit_transform(features) return pca, reduced这里的n_components=0.95不是固定取95个特征,而是保留95%的方差。对于ORL数据集,4096维原始特征通常只需要几十个主成分就能保留95%以上的信息,降维效果非常显著。whiten=True会对降维后的特征做归一化,让每个维度的方差为1,这能明显提升SVM的训练稳定性和收敛速度。
我在项目里额外写了一个“手写PCA”的片段,用于报告展示原理:
def pca_numpy(X, k): mean_face = np.mean(X, axis=0) X_centered = X - mean_face cov = np.cov(X_centered.T) eigenvalues, eigenvectors = np.linalg.eigh(cov) idx = np.argsort(eigenvalues)[::-1] eigenvectors = eigenvectors[:, idx[:k]] return mean_face, eigenvectors但实际训练和预测我还是用sklearn的PCA,因为手写版本在大矩阵上计算协方差很慢,数值稳定性也不如sklearn。报告里可以两者都出现:原理部分用手写代码讲清数学过程,实验部分用sklearn保证结果可靠。这个操作在答辩时相当加分,因为你证明了自己不是只会调包。
4.2 SVM训练和参数搜索
降维之后就是分类。我选了SVM,因为它在小样本、高维数据上的表现稳定,而且决策边界有清晰的数学解释。核函数选择RBF(径向基函数),因为人脸数据经过PCA投影后依然不是严格线性可分的,RBF核能把样本映射到更高维空间找一个更灵活的边界。
SVM最关键的参数是C和gamma。C控制误分类惩罚,太大容易过拟合,太小容易欠拟合;gamma控制RBF核的“作用范围”,太大容易过拟合,太小会让决策边界过分平滑。这两个参数不能靠猜,我用GridSearchCV做了网格搜索:
from sklearn.svm import SVC from sklearn.model_selection import GridSearchCV param_grid = { "C": [0.1, 1, 10, 50, 100], "gamma": [0.001, 0.0001, 0.00001, 0.000001], "kernel": ["rbf"], } svm = SVC(probability=True, random_state=42) grid = GridSearchCV(svm, param_grid, cv=5, n_jobs=-1, verbose=1) grid.fit(train_features_reduced, train_labels) print("best params:", grid.best_params_)这里有几个值得注意的点。cv=5表示5折交叉验证,把训练集内部再切5份轮流验证,避免参数选择阶段又出现数据泄露。n_jobs=-1是让所有CPU核并行跑,否则十几组参数可能要等很久。对ORL数据集加自采数据,这个搜索过程通常在几分钟内就能跑完。
网格搜索结束后,最好手动再检查一下模型在测试集上的表现。有时候交叉验证分数最高的一组参数并不一定在测试集上表现最好,这时候可以在报告中列出前几组相近参数的结果,说明你做了稳定性分析,而不是只看单一指标。
4.3 评估指标与可视化矩阵
模型训练完,评估阶段不能只输出一个准确率。我做了三件事:准确率、分类报告、混淆矩阵热力图。
from sklearn.metrics import accuracy_score, classification_report, confusion_matrix, ConfusionMatrixDisplay test_features_reduced = pca.transform(test_features) pred = grid.best_estimator_.predict(test_features_reduced) print("accuracy:", accuracy_score(test_labels, pred)) print(classification_report(test_labels, pred))对自采的小数据集,我还会逐类打印出哪些人被混淆了。比如“P01被人认成P03”说明这两个人的特征脸距离太近,那就要检查预处理是否到位、图像里是否有类似的遮挡或角度。混淆矩阵可视化用Matplotlib:
import matplotlib.pyplot as plt cm = confusion_matrix(test_labels, pred) disp = ConfusionMatrixDisplay(confusion_matrix=cm) disp.plot(cmap="Blues") plt.title("Confusion Matrix on Test Set") plt.savefig("report/confusion_matrix.png", dpi=300)这个图直接插到课程报告里,比一百句“准确率达到百分之九十”都有说服力。我还额外画了两张图:PCA保留方差百分比曲线(横轴为主成分数量,纵轴为累计解释方差),以及随机选取几张测试人脸的重构对比图(原图、PCA用10维重构、用50维重构)。这两张图让“降维到底发生了什么”变得可视化,老师看报告的体验会非常不一样。
5. 实时识别与可视化演示:让答辩现场不再尴尬
5.1 摄像头实时识别链路
模型训练完成后,项目还不能止步于“跑出准确率”。我强烈建议加一个摄像头实时识别模块,这是答辩现场的视觉高潮。核心链路是:摄像头读帧 -> 灰度化 -> 人脸检测 -> 裁剪缩放 -> PCA降维 -> SVM预测 -> 在画面上画框并显示姓名。
代码骨架如下:
cap = cv2.VideoCapture(0) model = grid.best_estimator_ while True: ret, frame = cap.read() if not ret: break face = detect_and_crop_face(frame) if face is not None: face_vector = face.reshape(1, -1) reduced = pca.transform(face_vector) prob = model.predict_proba(reduced)[0] pred_idx = np.argmax(prob) name = id_to_name[pred_idx] confidence = prob[pred_idx] cv2.rectangle(frame, (x, y), (x + w, y + h), (0, 255, 0), 2) cv2.putText(frame, f"{name} {confidence:.2f}", (x, y - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.8, (0, 255, 0), 2) cv2.imshow("Face Recognition Demo", frame) if cv2.waitKey(1) & 0xFF == ord("q"): break cap.release() cv2.destroyAllWindows()识别结果显示的是名字而不是ID,答辩现场老师看到自己名字被认出来,视觉效果比“类别ID 5”好得多。我在做ID到姓名的映射时,建立了一个字典:{0: "Alice", 1: "Bob", 2: "Charlie", ...},并在开源码里用中文注释说明如何修改这个映射表。
5.2 中文显示与结果平滑:两个必须处理的细节
这里有两个坑必须提前处理,否则演示很容易翻车。
第一个坑是cv2.putText不支持中文。OpenCV的putText只能画英文字符,你直接传中文姓名会显示成乱码“???”。解决办法是先用PIL(Pillow)把中文画到透明背景图上,再叠加到OpenCV的帧上。简单思路:
from PIL import Image, ImageDraw, ImageFont def draw_chinese_text(frame, text, position, font_path="msyh.ttc", size=24): img_pil = Image.fromarray(cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)) draw = ImageDraw.Draw(img_pil) font = ImageFont.truetype(font_path, size) draw.text(position, text, font=font, fill=(0, 255, 0)) return cv2.cvtColor(np.array(img_pil), cv2.COLOR_RGB2BGR)msyh.ttc是微软雅黑字体,Windows系统里有。实际演示时我是把英文名和中文名一起画,既避开乱码问题,又能保证老师看懂。
第二个坑是识别结果闪烁。摄像头连续帧之间,模型预测结果可能在两三个名字之间跳来跳去,画面看起来非常不稳定。我的解决方案是做一个简单的“最近N帧投票”:维护一个长度为5的队列,只有最新5帧里同一个名字出现3次以上,才更新画面显示的姓名。这个平滑机制虽然简单,但演示效果立刻提升一个档次。
5.3 答辩演示流程设计
演示环节不要只打开摄像头就去认脸,要设计一个有节奏感的流程。我的顺序是:先用训练好的模型识别几张测试图片,展示画像、检测框、识别结果和置信度;再切到摄像头实时识别,请台下一位同学帮忙站到摄像头前,识别出他是谁;如果现场光线不好导致识别失败,不要慌乱,拿出事先准备好的兜底方案——我用小窗口展示了最近三天的离线识别记录截图,证明系统在正常状态下是稳定的。
这个流程的要点是“先离线后在线、先成功再意外”。答辩现场难免出现摄像头驱动问题、光线过暗、人脸检测失败等意外,提前准备好备用演示内容、熟悉“降级预案”,能让你在突发情况时保持从容。我甚至在脚本里写了“如果摄像头完全打不开,就播放提前录好的识别演示视频”这一步,确保项目演示永不出白屏。
6. 课程报告与答辩准备:从源码到95分的最后一公里
6.1 报告结构设计:把项目包装成研究过程
很多同学的课程报告就是“我把代码跑通了,效果还不错”,这种报告拿不到高分。95分以上的报告,看起来必须是一篇“小型研究论文”,而不是代码说明书。我采用的报告结构是:
- 摘要与关键词
- 研究背景与意义:为什么做人脸识别,难点在哪
- 相关工作与技术原理:LBPH、PCA、SVM、CNN各讲清楚
- 系统设计:数据获取、预处理、特征提取、分类识别的整体流程图
- 实验设置与结果:数据集、评价指标、消融对比、特征可视化
- 问题与改进:当前方案局限,以及如何用LDA/Fisherface、深度学习进一步提升
- 总结与心得体会
关键技巧是在“技术原理”部分,不要只写“PCA是主成分分析”。要写清楚数学目标:最大化投影方差、求解协方差矩阵特征值、取前k个特征向量构成投影矩阵。公式不需要多,但要有推导逻辑。这部分加上我自己手写的PCA代码片段,老师一看就知道你是真学了。
6.2 图表比代码更值得花时间
课程报告里,图表的价值远高于大段代码。我做了六类图,每一类都直接支撑一个论点:数据集样本展示图(说明数据多样性和预处理效果)、均值脸和前十张特征脸(直观表现PCA到底学到了什么)、累计解释方差曲线(说明为什么选95%方差)、重构对比图(说明保留主成分数量对重建质量的影响)、混淆矩阵热力图(评估分类效果)、RBF核SVM参数搜索热力图(体现调参过程)。
参数搜索热力图是我特别推荐的“装点门面”图表。把GridSearchCV的结果用matplotlib.imshow画成二维矩阵,横轴是gamma,纵轴是C,颜色表示交叉验证分数。这张图放在报告里,瞬间拉开和普通作业的差距,因为它证明你系统性地做了实验,而不是侥幸调出一个好模型。
6.3 答辩高频问题与回答思路
答辩环节老师高频问题大致有六个。第一个是“PCA为什么能降维”,回答要抓住“寻找方差最大的投影方向”和“去除特征之间的相关性”。第二个是“为什么训练集和测试集要按人划分”,回答要强调泛化能力评估、避免数据泄露。第三个是“为什么你的准确率不是100%”,这个问题其实考察你理不理解模型局限性,可以从光照、表情、遮挡、训练样本量这几个角度回答。
第四个是“如果人脸戴了眼镜或者侧脸怎么办”,这需要说明Haar级联检测的局限性,以及可以如何用更鲁棒的特征(比如LBPH)或增加数据增强来缓解。第五个是“为什么不用深度学习”,回答要说清楚课程范围、传统方法的可解释性、以及对比实验的结论。第六个是“SVM的RBF核参数C和gamma各代表什么”,要能画图解释不同参数下的决策边界变化,不要只背概念。
我陪学弟模拟答辩时发现,老师最喜欢追问的其实是“你这个项目如果换个数据集还能用吗”。回答时要强调:代码的流程是通用的,只需要替换数据目录和重新训练,同时指出不同数据集需要重新调整人脸检测参数和PCA降维维度。这个回答能把问题引向你已经做过的对比实验上,掌握主动权。
7. 踩坑记录与源码工程化组织
7.1 五个真实踩坑经历
第一个坑是中文路径。Windows系统下,OpenCV的imread在路径包含中文时经常返回None,而且不报错。我一开始没注意,训练数据里一张照片都没读到,模型准确率却是0,排查了很久才发现是数据路径问题。解决办法是把项目根目录放在纯英文路径下,数据集命名也全部用英文字符。
第二个坑是训练图片尺寸不一致。ORL数据集本身就是统一尺寸,但自采数据集经过裁剪后如果有人脸检测失败,返回的face是None,程序直接崩掉。我后来在预处理阶段加了一个判断:检测不到人脸就跳过该张图片,并输出日志提醒,而不是让整条流程中断。
第三个坑是GridSearchCV跑出天文数字的组合。我第一次设置参数网格时,C和gamma各取了10个值,还有多个核函数,总共几百个组合,加上5折交叉验证,普通笔记本跑了快两个小时。后来我收敛了网格范围,并先用一个小子集做快速搜索,确认方向后再全量跑。
第四个坑是演示时画面分辨率过低。默认摄像头是640x480,人脸检测如果minSize设置过大,离得远一点就检测不到,画面里人稍微侧移就直接丢框。我做了个交互式滑块调节minSize,现场演示时可以当场调整,避免尴尬。
第五个坑是数据集存储格式混乱。实验做到一半,我发现自己采集的数据里,同一个人的照片有的命名P01_01.jpg,有的命名IMG20230101.jpg,导致标签和文件名对不上。后来我写了一个规范化的目录扫描函数,严格按person_id/sample_index.jpg结构整理数据。这也是工程化习惯的重要性。
7.2 源码目录设计与人机工程
课程大作业的源码不需要多复杂,但目录结构一定要清晰。我最终的目录是这样的:
face_recognition_project/ ├── data/ │ ├── raw/ # 原始数据集(ORL + 自采) │ ├── processed/ # 检测裁剪后的统一尺寸图片 │ └── split/ # 按人划分好的训练/测试索引 ├── models/ # 保存的PCA与SVM模型文件 ├── src/ │ ├── data_preprocess.py # 数据检测、裁剪、对齐、划分 │ ├── train_pca_svm.py # 特征提取、模型训练、网格搜索 │ ├── evaluate.py # 评估、混淆矩阵、特征可视化 │ └── realtime_demo.py # 摄像头与人脸识别演示 ├── report/ # 课程报告与答辩PPT素材 └── README.md # 运行说明每一个Python文件我都写了函数级注释和模块级docstring。不要觉得这是浪费时间,老师打开源码第一眼看到的就是注释是否规范,这直接影响代码分。README里我写了三行:环境配置命令、运行顺序(先预处理->再训练->最后演示)、数据集来源说明。这样不管是谁拿到这份代码,照着README就能跑通,也是“工程化”的体现。
7.3 继续升级的方向
如果时间允许,可以在现有基础上升级四个方向。第一是换用LDA(Fisherface)做人脸特征提取,LDA和PCA的核心区别在于PCA找方差最大的方向、LDA找类别可分性最大的方向,把它作为对比实验写进报告会很有层次。第二是做人脸对齐时用更鲁棒的关键点检测算法,比如dlib的68点模型,能明显改善姿态变化下的识别效果。第三是加一个简单的活体检测逻辑,比如要求用户眨眼或用动作配合,这对应到人脸识别门禁机等真实场景,能体现落地意识。第四是换成CNN或迁移学习做精度对比,但要控制篇幅,明确说明课程主线仍是传统机器学习。
第四个方向如果做,建议用现成框架如PyTorch,而不自己手写网络,节省时间。报告里可以加一句结论:“在自采小数据集上,传统机器学习方法在训练时间和可解释性上仍有明显优势,深度学习方法在精度上略高但在计算资源要求上更高。”这句话既能体现工作量,又不越界。
最后再分享一个小经验。做完这个项目后,我最大的体会是:大作业高分的关键不是“模型有多高级”,而是“每一步选择都有理由”。源码能跑只是底线,能够清晰讲出为什么选PCA、为什么按人划分、为什么SVM参数取这些值,才是拉开分数差距的地方。你手里的课程报告,其实就是在向老师证明:你不是跑通了一个demo,而是完整地做了一次机器学习实验。希望这篇内容能帮你少走弯路,把这次大作业变成一次真正有收获的完整项目实践。
本文还有配套的精品资源,点击获取