基于 MTCNN+FaceNet+KNN 的人脸识别完整入门实战(附代码)

🌟基于 MTCNN+FaceNet+KNN 的人脸识别完整入门实战(附代码)

👉前置知识可以参考补充文章:https://blog.csdn.net/m0_62059512/article/details/149327064?spm=1011.2415.3001.5331

人脸识别是计算机视觉中的重要应用之一,广泛应用于门禁系统、支付验证、视频监控等场景。
今天我们将用 Python +MTCNN + facenet-pytorch + KNN 构建一个完整的人脸识别系统,从人脸检测、
人脸特征提取、训练分类器到实际识别,全部一步到位!

🧠一、核心原理简介

我们的人脸识别系统包含以下三个步骤:

  1. 人脸检测(MTCNN):从图像中裁剪出人脸区域。
  2. 人脸特征提取(FaceNet):将人脸转化为128维向量(embedding)。
  3. 人脸分类(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 文件安装。步骤如下:

  1. 下载对应 Python 版本的 dlib 安装包,例如 Python 3.10 使用:
    dlib-19.22.99-cp310-cp310-win_amd64.whl

  2. 下载完成后进入该目录,使用 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}")

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值