🌟基于 MTCNN+FaceNet+KNN 的人脸识别完整入门实战(附代码)
👉前置知识可以参考补充文章:https://blog.csdn.net/m0_62059512/article/details/149327064?spm=1011.2415.3001.5331
人脸识别是计算机视觉中的重要应用之一,广泛应用于门禁系统、支付验证、视频监控等场景。
今天我们将用 Python +MTCNN + facenet-pytorch + KNN 构建一个完整的人脸识别系统,从人脸检测、
人脸特征提取、训练分类器到实际识别,全部一步到位!
🧠一、核心原理简介
我们的人脸识别系统包含以下三个步骤:
- 人脸检测(MTCNN):从图像中裁剪出人脸区域。
- 人脸特征提取(FaceNet):将人脸转化为128维向量(embedding)。
- 人脸分类(KNN):根据已知人脸构建KNN分类器,进行识别。
为什么选择这种结构?
- MTCNN:轻量、快速、准确。
- FaceNet(InceptionResnetV1):经典的人脸嵌入模型,效果稳定。
- KNN:简单有效,适合小规模人脸识别系统。
🛠️二、环境准备
📦安装依赖:
pip install facenet-pytorch
pip install scikit-learn
pip install joblib
pip install numpy pillow scipy
📁目录结构:
project/
├── lfw_train/ # 训练集(子文件夹为人名,每人若干张图)
├── test_images/ # 测试图片目录
└── face_recognition_demo.py # 主程序
📄三、完整代码讲解(核心部分)
1️⃣ 模型初始化
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
mtcnn = MTCNN(image_size=160, margin=0, device=device)
resnet = InceptionResnetV1(pretrained='vggface2').eval().to(device)
2️⃣ 提取训练集特征并保存模型
embeddings = []
labels = []
for person_name in os.listdir(data_dir):
person_dir = os.path.join(data_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)
try:
img = Image.open(img_path).convert("RGB")
face = mtcnn(img)
if face is None:
continue
with torch.no_grad():
emb = resnet(face.unsqueeze(0).to(device))
embeddings.append(emb.squeeze().cpu().numpy())
labels.append(person_name)
except Exception as e:
print(f"处理失败: {img_path},错误: {e}")
训练并保存 KNN 模型:
knn = KNeighborsClassifier(n_neighbors=3, weights='distance')
knn.fit(embeddings, labels)
joblib.dump(knn, 'knn_model.pkl')
3️⃣ 识别阶段(测试图片)
knn = joblib.load('knn_model.pkl')
for img_name in os.listdir(test_dir):
img_path = os.path.join(test_dir, img_name)
try:
img = Image.open(img_path).convert("RGB")
face = mtcnn(img)
if face is None:
print(f"未检测到人脸: {img_name}")
continue
with torch.no_grad():
emb = resnet(face.unsqueeze(0).to(device))
embedding = emb.squeeze().cpu().numpy()
pred_label = knn.predict(embedding.reshape(1, -1))[0]
distances = [euclidean(embedding, e) for e in person_embeddings[pred_label]]
avg_distance = np.mean(distances)
if avg_distance < threshold:
print(f"识别为: {pred_label}(距离: {avg_distance:.3f}),可信,源文件: {img_name}")
else:
print(f"识别为未知(距离: {avg_distance:.3f}),不可信,源文件: {img_name}")
except Exception as e:
print(f"处理失败: {img_name},错误: {e}")
📈四、识别效果展示
✅ 测试图是 Obama 的另一张图,系统输出:
识别为: Obama(距离: 0.58),可信!
❌ 测试图是陌生人,系统输出:
识别为未知(距离: 1.13),不可信!
📌五、常见问题 FAQ
1. 多张人脸图片中只取了1张怎么办?
当前设置只处理检测到一张脸,如果有多人图像可考虑:
faces = mtcnn(img, return_prob=False)
2. 阈值怎么调?
建议从 0.8~1.2 之间微调,使用验证集来选择最优值。
3.报错:RuntimeError: Unsupported image type, must be 8bit gray or RGB image?
这是由于 Numpy 的版本太新,与 dlib 不兼容导致的。解决办法:
pip install numpy==1.26.4
将 Numpy 从 2.0.0 降级到 1.26.4 即可解决该错误。
4. 安装 dlib 报错:Failed to build installable wheels for some pyproject.toml based projects?
这是因为 dlib 编译较复杂,推荐直接使用预编译的 .whl
文件安装。步骤如下:
-
下载对应 Python 版本的 dlib 安装包,例如 Python 3.10 使用:
dlib-19.22.99-cp310-cp310-win_amd64.whl -
下载完成后进入该目录,使用 pip 安装:
pip install dlib-19.22.99-cp310-cp310-win_amd64.whl
这样就能解决安装失败的问题,后续安装其他依赖不会报错。
📚六、总结
下一步尝试:
- 加入 SVM / ArcFace 分类器
- 增加数据增强,提高鲁棒性
- 用 GUI / Web 接口包装成实际应用
📎项目完整代码
你可以将完整代码保存为 face_recognition_demo.py,根据本文结构执行即可。
import os
import torch
from facenet_pytorch import MTCNN, InceptionResnetV1
from PIL import Image
import numpy as np
from sklearn.neighbors import KNeighborsClassifier
import joblib
from scipy.spatial.distance import euclidean
# 设备选择
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
# 模型初始化
mtcnn = MTCNN(image_size=160, margin=0, device=device)
resnet = InceptionResnetV1(pretrained='vggface2').eval().to(device)
# 训练集目录
data_dir = "lfw_train"
# 1. 提取训练集特征及标签
embeddings = []
labels = []
for person_name in os.listdir(data_dir):
person_dir = os.path.join(data_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)
try:
img = Image.open(img_path).convert("RGB")
face = mtcnn(img)
if face is None:
continue
with torch.no_grad():
emb = resnet(face.unsqueeze(0).to(device))
embeddings.append(emb.squeeze().cpu().numpy())
labels.append(person_name)
except Exception as e:
print(f"处理失败: {img_path},错误: {e}")
# 2. 训练 KNN 分类器
knn = KNeighborsClassifier(n_neighbors=3, weights='distance')
knn.fit(embeddings, labels)
joblib.dump(knn, 'knn_model.pkl')
print(f"模型训练完成!共提取人脸数:{len(labels)}")
# 3. 按人名整理训练向量,方便距离计算
person_embeddings = {}
for emb, label in zip(embeddings, labels):
person_embeddings.setdefault(label, []).append(emb)
# ------------------------
# 识别阶段代码(测试图片识别)
# ------------------------
test_dir = "test_images"
threshold = 0.9 # 距离阈值,可调
# 加载训练好的 KNN 模型
knn = joblib.load('knn_model.pkl')
for img_name in os.listdir(test_dir):
img_path = os.path.join(test_dir, img_name)
try:
img = Image.open(img_path).convert("RGB")
face = mtcnn(img)
if face is None:
print(f"未检测到人脸: {img_name}")
continue
with torch.no_grad():
emb = resnet(face.unsqueeze(0).to(device))
embedding = emb.squeeze().cpu().numpy()
# 预测标签
pred_label = knn.predict(embedding.reshape(1, -1))[0]
# 计算与该类别所有训练向量的平均距离
distances = [euclidean(embedding, e) for e in person_embeddings[pred_label]]
avg_distance = np.mean(distances)
# 判断是否为本人
if avg_distance < threshold:
print(f"识别为: {pred_label}(距离: {avg_distance:.3f}),可信,源文件: {img_name}")
else:
print(f"识别为未知(距离: {avg_distance:.3f}),不可信,源文件: {img_name}")
except Exception as e:
print(f"处理失败: {img_name},错误: {e}")