简介:这份资源是面向高校学生与Python初学者的机器学习人脸识别系统完整项目包,可直接用于期末大作业、课程设计或自学练手。项目以Python实现人脸检测与识别核心流程,并附带一份完整报告文档,帮助读者理解算法思路与工程结构。压缩包共4个文件,包含2个py源码文件、1份pdf报告和1份md说明文档,整体约631KB,体积轻便,下载后简单部署即可运行。源码中带有详细注释,新手也能看懂,适合作为课程设计参考或二次开发基础。目前已有283人学习下载,说明其在同类作业资源中具有一定认可度。读者可获得可运行的人脸识别系统源码、配套报告文档以及清晰的代码注释,既能直接用于作业提交,也能借此掌握机器学习在人脸识别中的实际应用方式,对提升项目实践能力有较高参考价值。
1. 从一份期末大作业说起:人脸识别系统到底难在哪
每年期末季,GitHub 和各类资源站上「Python 机器学习人脸识别系统源码+报告」的检索量都会暴涨。很多人拿到压缩包,解压、装依赖、跑main.py,然后卡在摄像头打不开、识别率惨不忍睹、报告不知道怎么写这三个坑里。这份标题背后其实是一套完整的工程链路:数据采集、人脸检测、特征提取、分类器训练、GUI 集成、实验报告撰写。它适合两类人——一类是赶期末大作业的学生,需要一套能跑通、能改、能写进报告的完整方案;另一类是想入门机器学习落地的新手,想找一个有明确输入输出、能快速看到效果的项目练手。
人脸识别听起来玄乎,拆开看无非是「找到脸 → 对齐脸 → 把脸变成一串数字 → 比对数字」。真正让人翻车的地方不在算法本身,而在数据质量、环境配置和参数调优。下面我按实际做项目的顺序,把这套系统从零到跑通讲清楚,包括每一步的命令、参数含义和常见报错。
2. 技术选型:为什么用 OpenCV + face_recognition 而不是从零训 CNN
2.1 三条主流路线的取舍
做期末大作业级别的人脸识别,常见有三条路:一是从零训练 CNN,二是用 OpenCV 的 LBPH 算法,三是用 dlib/face_recognition 这类预训练模型做特征比对。从零训 CNN 需要几千张标注人脸和 GPU,期末周期内基本不现实;LBPH 简单但对光照和角度极其敏感,稍微换个方向就认不出;face_recognition 底层是 dlib 的 ResNet 人脸特征提取,单张图编码成 128 维向量,比对欧氏距离即可,精度够用、代码量少、CPU 也能跑。
我一般推荐 face_recognition + OpenCV 的组合。OpenCV 负责摄像头读取、图像预处理和 GUI 显示,face_recognition 负责检测和编码。这样分工清晰,报告里也好写:检测用 HOG 或 CNN,编码用 ResNet,分类用距离阈值。
2.2 环境搭建:Python 版本和依赖安装的坑
Python 版本建议 3.8~3.10,太新的版本 dlib 编译容易失败。Windows 上装 dlib 是最容易翻车的一步,因为需要 CMake 和 Visual Studio 的 C++ 编译工具。最省事的做法是直接装预编译的 wheel。
# 建议先建虚拟环境,避免污染全局 python -m venv face_env # Windows 激活 face_env\Scripts\activate # Linux/Mac 激活 source face_env/bin/activate # 安装核心依赖,指定版本避免兼容问题 pip install opencv-python==4.8.1.78 pip install face_recognition==1.3.0 pip install numpy==1.24.3 pip install pillow==10.0.1opencv-python负责图像和摄像头操作,face_recognition封装了 dlib 的检测和编码接口,numpy做向量运算,pillow处理中文路径下的图片读写。版本号不是随便写的:numpy 1.24 以上和旧版 dlib 有 ABI 冲突,opencv 4.8 在 Windows 上对 DirectShow 摄像头支持更稳。
如果pip install face_recognition卡在编译 dlib,Windows 用户可以去下载对应 Python 版本的 dlib wheel 文件手动安装,再装 face_recognition。Linux 用户先sudo apt install cmake build-essential再装。这一步没有后悔药,装不上后面全白搭。
2.3 项目目录结构怎么组织
一份能写进报告的源码,目录结构要清晰,答辩时老师一眼能看懂。我一般这样分:
face_project/ ├── dataset/ # 每人一个子文件夹,存人脸样本 │ ├── person_01/ │ └── person_02/ ├── encodings/ # 存放训练好的 128 维特征 │ └── face_encodings.pkl ├── src/ │ ├── collect_faces.py # 采集样本 │ ├── train_model.py # 训练编码 │ ├── recognize.py # 实时识别 │ └── gui.py # 图形界面 ├── report/ # 实验报告 └── requirements.txtdataset下每人一个文件夹,文件夹名就是标签名。encodings存 pickle 文件,避免每次启动都重新编码。src放四个脚本,职责单一,报告里可以逐个截图说明。
3. 数据采集与预处理:样本质量决定识别上限
3.1 用 OpenCV 采集人脸样本的完整脚本
数据采集是整个系统最容易被忽视、却最影响结果的环节。很多人随便拍几张就开跑,结果识别率上不去,回头怪算法。我的经验是:每人至少 20 张,覆盖正脸、左右偏转、戴眼镜/不戴眼镜、不同光照。
import cv2 import os # 采集参数 person_name = "person_01" save_dir = os.path.join("dataset", person_name) os.makedirs(save_dir, exist_ok=True) # 加载 OpenCV 自带的人脸检测器 detector = cv2.CascadeClassifier( cv2.data.haarcascades + "haarcascade_frontalface_default.xml" ) cap = cv2.VideoCapture(0) # 0 表示默认摄像头 count = 0 max_count = 30 # 每人采集 30 张 while count < max_count: ret, frame = cap.read() if not ret: print("摄像头读取失败,检查设备占用") break 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 = cv2.resize(gray[y:y+h, x:x+w], (200, 200)) cv2.imwrite(f"{save_dir}/{count}.jpg", face_img) cv2.rectangle(frame, (x, y), (x+w, y+h), (0, 255, 0), 2) cv2.imshow("Collecting", frame) if cv2.waitKey(1) & 0xFF == ord('q'): break cap.release() cv2.destroyAllWindows()detectMultiScale的三个参数要理解:第一个是灰度图,第二个1.3是每次缩放比例,越小检测越慢但越全,第三个5是邻域阈值,越大误检越少但漏检越多。采集时保存的是灰度图并统一缩放到 200×200,这样后续训练不用再做尺寸归一化。max_count设 30 是经验值,太少模型不稳,太多采集时间长且冗余。
3.2 样本清洗:哪些图必须删掉
采集完不要直接训练,先人工过一遍。以下几类图必须删:检测框明显偏了只框到半张脸的、光照过曝或全黑的、模糊到看不清五官的、同一人重复度极高的。这些脏数据会让编码向量偏移,导致同一个人被识别成不同人。
清洗可以用一个简单脚本批量看图和删除,也可以直接在文件夹里肉眼过。我一般会写个脚本把所有人脸拼成一张大图,一眼扫过去,有问题的记下文件名删掉。
import cv2 import os import numpy as np def make_contact_sheet(folder, cols=6, thumb=100): files = [f for f in os.listdir(folder) if f.endswith(".jpg")] rows = (len(files) + cols - 1) // cols sheet = np.zeros((rows * thumb, cols * thumb, 3), dtype=np.uint8) for i, f in enumerate(files): img = cv2.imread(os.path.join(folder, f)) img = cv2.resize(img, (thumb, thumb)) r, c = divmod(i, cols) sheet[r*thumb:(r+1)*thumb, c*thumb:(c+1)*thumb] = img cv2.imwrite("contact_sheet.jpg", sheet) make_contact_sheet("dataset/person_01")拼图后打开contact_sheet.jpg,异常样本一目了然。这一步花十分钟,能省掉后面调参的两小时。
3.3 数据增强要不要做
期末作业级别,如果每人样本超过 20 张且角度光照有变化,可以不做增强。如果样本少,可以做水平翻转、轻微旋转、亮度调整。但要注意:人脸识别对翻转敏感,左右翻转后的人脸和原图编码差异较大,可能被当成两个人。所以增强只建议做亮度和小角度旋转,不要做翻转。
import cv2 import numpy as np def augment_brightness(img, factor=1.2): hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV) hsv[:, :, 2] = np.clip(hsv[:, :, 2] * factor, 0, 255) return cv2.cvtColor(hsv, cv2.COLOR_HSV2BGR) def augment_rotate(img, angle=10): h, w = img.shape[:2] M = cv2.getRotationMatrix2D((w/2, h/2), angle, 1.0) return cv2.warpAffine(img, M, (w, h))亮度因子控制在 0.8~1.2,旋转角度控制在 ±15 度以内。超过这个范围,生成的人脸就不自然了,反而引入噪声。
4. 训练与识别:128 维编码怎么算、阈值怎么定
4.1 批量编码:把每张脸变成 128 个数字
face_recognition 的核心是face_encodings函数,它接收一张 RGB 图片,返回一个 128 维的 numpy 数组。这个数组就是人脸的「数字指纹」。训练阶段就是把 dataset 下每个人的所有图片都编码,然后按人分组存起来。
import face_recognition import os import pickle import numpy as np dataset_dir = "dataset" known_encodings = [] known_names = [] for person_name in os.listdir(dataset_dir): person_dir = os.path.join(dataset_dir, person_name) if not os.path.isdir(person_dir): continue for img_name in os.listdir(person_dir): img_path = os.path.join(person_dir, img_name) # face_recognition 需要 RGB 格式 image = face_recognition.load_image_file(img_path) # 每张图可能检测到多张脸,取第一张 encodings = face_recognition.face_encodings(image) if len(encodings) == 0: print(f"未检测到人脸,跳过:{img_path}") continue known_encodings.append(encodings[0]) known_names.append(person_name) # 保存编码和标签 data = {"encodings": known_encodings, "names": known_names} with open("encodings/face_encodings.pkl", "wb") as f: pickle.dump(data, f) print(f"共编码 {len(known_encodings)} 张人脸,涉及 {len(set(known_names))} 个人")load_image_file读进来是 RGB,而 OpenCV 读进来是 BGR,两者不能混用,这是新手最常见的翻车点。face_encodings默认用 HOG 模型检测,速度快但小脸容易漏;如果图片里人脸较小,可以加参数model="cnn",但需要 GPU 才快。编码结果用 pickle 存,下次识别直接加载,不用重新跑。
4.2 识别逻辑:欧氏距离和阈值 0.6 的由来
识别时,把摄像头拍到的人脸编码,和已知编码逐一算欧氏距离。距离越小越像。face_recognition 官方建议阈值 0.6,低于 0.6 认为是同一个人。这个值是 dlib 在 LFW 数据集上调出来的平衡点,不是随便定的。
import face_recognition import cv2 import pickle import numpy as np # 加载已知编码 with open("encodings/face_encodings.pkl", "rb") as f: data = pickle.load(f) cap = cv2.VideoCapture(0) # 降采样比例,加快处理速度 scale = 0.25 while True: ret, frame = cap.read() if not ret: break # 缩小图像加速检测 small_frame = cv2.resize(frame, (0, 0), fx=scale, fy=scale) 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 (top, right, bottom, left), face_encoding in zip(face_locations, face_encodings): # 距离比对 distances = face_recognition.face_distance(data["encodings"], face_encoding) best_idx = np.argmin(distances) name = "Unknown" if distances[best_idx] < 0.6: name = data["names"][best_idx] # 坐标还原到原图尺寸 top, right, bottom, left = [int(v / scale) for v in (top, right, bottom, left)] cv2.rectangle(frame, (left, top), (right, bottom), (0, 255, 0), 2) cv2.putText(frame, name, (left, top - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.9, (0, 255, 0), 2) cv2.imshow("Recognition", frame) if cv2.waitKey(1) & 0xFF == ord('q'): break cap.release() cv2.destroyAllWindows()scale = 0.25是性能关键。原图 640×480 直接检测,每帧要几百毫秒,画面卡顿;缩小到 1/4 后,检测速度提升十几倍,坐标再乘回去即可。face_distance返回的是待识别编码和所有已知编码的距离数组,argmin取最近的那个。阈值 0.6 可以调:调到 0.5 更严格,误认少但可能拒真;调到 0.7 更宽松,认得多但可能把别人认成你。报告里可以做一个阈值对比实验,用表格展示不同阈值下的准确率和误识率。
4.3 阈值调优实验怎么做
拿一部分样本做测试集,分别用 0.4、0.5、0.6、0.7 跑一遍,统计正确识别数和错误识别数。表格大概长这样:
| 阈值 | 正确识别 | 误识别 | 拒识 | 准确率 |
|---|---|---|---|---|
| 0.4 | 18 | 0 | 12 | 60% |
| 0.5 | 24 | 1 | 5 | 80% |
| 0.6 | 28 | 2 | 0 | 93% |
| 0.7 | 29 | 5 | 0 | 85% |
这张表放进报告,比单纯说「用了 0.6」有说服力得多。数据自己跑,不要编。
5. 避坑与排查:那些让系统跑不起来的细节
5.1 摄像头打不开或黑屏
现象:cap.read()返回 False,窗口一片黑。原因通常是摄像头被其他程序占用,或者 OpenCV 后端选错。Windows 上可以换cv2.CAP_DSHOW后端:cv2.VideoCapture(0, cv2.CAP_DSHOW)。Linux 上检查/dev/video0权限,把当前用户加入video组。虚拟机里没有 USB 直通的话,摄像头根本传不进来,这种情况换物理机跑。
5.2 中文路径导致图片读取失败
现象:load_image_file报错或返回 None。原因是 OpenCV 和 dlib 对中文路径支持不好。解决办法是用 numpy 读文件再解码,或者干脆把 dataset 路径改成全英文。我一般直接要求项目路径不含中文,省事。
5.3 识别总是 Unknown
现象:明明采集了样本,识别时全是 Unknown。先检查编码文件里names和encodings数量是否一致,再检查采集时保存的是灰度图而识别时用的是 RGB 图——两者编码分布不同,距离自然大。采集和识别必须用同一种颜色空间。另外,如果采集时人脸框偏了,编码质量差,也会导致距离偏大。
5.4 帧率低、画面卡顿
现象:识别窗口一卡一卡。原因是每帧都在做全图检测和编码。解决:降采样(前面说的 scale)、跳帧处理(每 3 帧检测一次,中间帧沿用上次结果)、或者把检测和识别放到独立线程。期末作业级别,降采样加跳帧就够了。
5.5 多人脸时标签错乱
现象:画面里两个人,标签张冠李戴。原因是face_locations和face_encodings的顺序虽然对应,但如果你在循环里做了排序或过滤,顺序就乱了。保持两者用同一个列表推导式生成,不要分开处理。
6. 报告怎么写才不像流水账:实验设计比代码更值分
6.1 报告结构:从问题到验证的闭环
很多人的报告就是贴代码加截图,老师一看就知道是凑的。好的报告应该是一个完整的实验叙事:要解决什么问题、选了哪些方案、为什么选这个、怎么验证、结果如何、有什么局限。我一般按这个框架写:引言(人脸识别的应用场景和本次目标)、相关工作(LBPH、CNN、dlib 三种方案对比)、系统设计(模块图、数据流)、实验设置(数据集规模、硬件环境、参数配置)、实验结果(准确率、阈值对比、耗时)、讨论(失败案例分析)、结论。
6.2 用混淆矩阵和耗时数据撑起实验部分
光说准确率 93% 太单薄。做一个混淆矩阵,展示每个人被正确识别和误识别成谁的次数。再测一下单帧检测耗时、编码耗时、比对耗时,用表格列出来。这些数据用脚本就能跑出来,不需要额外工作,但报告的专业度立刻不一样。
import time import face_recognition import cv2 img = face_recognition.load_image_file("dataset/person_01/1.jpg") t0 = time.time() locations = face_recognition.face_locations(img) t1 = time.time() encodings = face_recognition.face_encodings(img, locations) t2 = time.time() print(f"检测耗时:{(t1-t0)*1000:.1f} ms") print(f"编码耗时:{(t2-t1)*1000:.1f} ms")把不同分辨率下的耗时都测一遍,画个折线图,报告里就是实打实的实验数据。
6.3 失败案例比成功案例更有说服力
挑几张识别错误的图放进报告,分析原因:是光照太暗、角度太偏、还是训练样本里没有类似姿态。然后给出改进方向,比如增加对应姿态的样本、调整阈值、换用 CNN 检测模型。这种自我批判的部分,恰恰是老师最看重的——说明你真的理解了系统的边界,而不是跑通就完事。
6.4 我踩过的那些坑
最后说几个我自己的血泪教训。第一,不要等到答辩前一天才跑通,dlib 编译可能卡你一整晚。第二,采集样本时多拍几组不同光照的,别在同一个位置拍 30 张,那样训练出来的编码方差极小,换到答辩教室的灯光下直接失灵。第三,报告里的数据一定要自己跑出来,网上抄的准确率经不起追问。第四,GUI 用 tkinter 就够了,别上 PyQt,期末作业没必要,还容易打包出问题。这套方案从采集到识别,认真做三天能跑通,报告再花两天打磨,足够应付绝大多数期末要求。希望帮到你。
本文还有配套的精品资源,点击获取