说明
本来想自己复现一下facenet的,但是发现facenet已经被做成了python的第三方库,于是自己用了用,发现挺简单的,然后又看了看源码,感觉模型架构实现部分很简单,所以就算了。
想了想,决定新开一个系列,就是快速上手项目,主要目的就是尽可能的简单使用一个项目。
GitHub项目地址
想要自己研读代码的,可以把源码下载到本地:
https://github.com/timesler/facenet-pytorch/
目录结构
经常会刷到如何学习人工智能的视频,总会有人说学习CV先跑一个项目,而这个项目大部分都人脸识别。
我也是最近看了人脸识别相关的论文,才知道原来facenet已经把代码做成了一个python第三方库。难怪大家都说要实现人脸识别,因为它真的简单,并且可以在不需要你训练的情况下,直接拿过来用。
pip install facenet-pytorch
如果你只想要简单了解这个项目,直接安装facenet-pytorch即可,不过,我们这里还是把源码从GitHub上下过来:
https://github.com/timesler/facenet-pytorch/
因为,想要深入了解,最好还是从源码入手,另外源码还提供了几张测试图片可以用。
这里简单对下载后的GitHub项目进行简单的目录结构说明:
虽然你可以直接使用方法:
from facenet_pytorch import InceptionResnetV1
resnet = InceptionResnetV1(pretrained='vggface2')
来自动下载预训练权重,不过这个下载地址其实是在GitHub上,所以注定了下载比较慢。因此我建议你可以自己下载:
https://github.com/timesler/facenet-pytorch/releases/download/v2.2.9/20180402-114759-vggface2.pt
链接:https://pan.baidu.com/s/1TPaz_RO4faazUUg6ljftVA
提取码:dqqi
下载后,需要把下载的文件放入下面的路径中:
C:\Users\用户\.cache\torch\checkpoints
下面对常用的方法进行一定的说明:
该类的常用初始化参数:
参数 | 意义 |
---|---|
image_size | 输出图像大小,默认160 |
margin | 添加到预测框的margin值,默认为0 |
min_face_size | 最小的搜索人脸的大小,默认为20 |
thresholds | 人脸检测阈值,默认为[0.6, 0.7, 0.7] |
factor | 用于创建图像金字塔的缩放因子,默认为0.709 |
post_process | 是否进行后处理,默认为True |
select_largest | 如果为True,返回检测到人脸中最大的,否则返回概率最大的,默认为True |
selection_method | 用何种方式选择人脸,和上面的参数有点类似,默认为None |
keep_all | 如果为True,所有检测到的人脸都返回,默认为False |
device | 指定设备信息,默认为None |
该类的常用方法:
方法 | 参数 | 作用 | 举例 |
---|---|---|---|
前向传播方法 | 图像、保存路径、是否返回概率值 | 基本的运行流程,但是返回人脸的数组 | mtcnn(img.path.True) |
detect | 图像、是否返回人脸关键点 | 与前向传播类似,但是返回预测框坐标和概率值 | mtcnn.detect(img,False) |
# 常见的用法
# 1. 创建对象
resnet = InceptionResnetV1(pretrained='vggface2').eval().to('cuda')
'''
pretrained,即是否启用预训练模型
'''
# 2. 识别人脸
face_embedding = resnet(img)
其实,如果你下载了GitHub上的源码,你也许已经发现了,作者自己提供了测试代码和如何使用这些代码。(下面的代码参考作者提供的测试代码,我只是稍微修改了一些)
这里,我根据作者的提示简单粗暴的来实现一下人脸检测:
# 导入包
from facenet_pytorch import MTCNN
import cv2
import torch
# 指定设备
device = torch.device('cuda:0' if torch.cuda.is_available() else 'cpu')
# 创建对象:这个是作者提供的方法
mtcnn = MTCNN(
image_size=160, margin=0, min_face_size=20,
thresholds=[0.6, 0.7, 0.7], factor=0.709, post_process=True,
device=device
)
# 人脸检测
# 给定一张图片路径
img_path = '../data/test.jpg'
image = cv2.imread(img_path)
# 为了防止图片太大,把它缩放一下,如果图片不大,可以把它注释掉
# image = cv2.resize(image,(500,500))
# 预测
box,prob = mtcnn.detect(image)
# 把结果可视化
cv2.namedWindow('draw')
cv2.rectangle(image,(int(box[0][0]),int(box[0][1])),(int(box[0][2]),int(box[0][3])),(0,0,255))
cv2.imshow('draw',image)
cv2.waitKey(0)
看看预测结果:
同样的,作者也提供了人脸识别的脚本,注意:人脸检测只需要检测到人脸即可,人脸识别不仅需要检测人脸,还需要识别这个人是谁。
那么可以简单的实现一下:
from facenet_pytorch import MTCNN,InceptionResnetV1
import torch
from torch.utils.data import DataLoader
from torchvision import datasets
import numpy as np
import pandas as pd
import os
from PIL import Image, ImageDraw, ImageFont
# 定义加载图像的线程数
workers = 0 if os.name=="nt" else 4
# 定义设备
device = torch.device('cuda:0' if torch.cuda.is_available() else "cpu")
# 创建mtcnn
mtcnn = MTCNN(image_size=160,margin=0,min_face_size=20,thresholds=[0.6,0.7,0.7],
factor=0.709,post_process=True,device=device
)
# 创建resnet
resnet = InceptionResnetV1(pretrained='vggface2').eval().to(device)
# 定义数据加载器的函数
def collate_fn(x):
return x[0]
#将所有的单人照图片放在各自的文件夹中,文件夹名字就是人的名字,存放格式如下::
dataset = datasets.ImageFolder("../data/test_images") #加载数据库
dataset.idx_to_class = {i:c for c,i in dataset.class_to_idx.items()}
loader = DataLoader(dataset,collate_fn=collate_fn,num_workers=workers)
aligned = [] #aligned就是从图像上抠出的人脸,大小是之前定义的image_size=160
names = []
# 将获取的人脸图片保存下来,注意路径
i = 1
for x, y in loader:
path = '../data/test_images_aligned/{}/'.format(dataset.idx_to_class[y]) # 这个是要保存的人脸路径
# 如果要保存识别到的人脸,在save_path参数指明保存路径即可,不保存可以用None
x_aligned, prob = mtcnn(x, return_prob=True,save_path= path+ '/{}.jpg'.format(i))
i = i+1
# 如果有预测的值,放入结果列表中
if x_aligned is not None:
print('Face detected with probability: {:8f}'.format(prob))
aligned.append(x_aligned)
names.append(dataset.idx_to_class[y])
# 把获取的值保存下来
aligned = torch.stack(aligned).to(device)
embeddings = resnet(aligned).detach().cpu() #提取所有人脸的特征向量,每个向量的长度是512
#两两之间计算混淆矩阵
dists = [[(e1 - e2).norm().item() for e2 in embeddings] for e1 in embeddings]
print(names)
print(pd.DataFrame(dists, columns=names, index=names))
torch.save(embeddings,'database.pt') # 当然也可以保存在一个文件
torch.save(names,'names.pt')
#对新的照片进行人脸识别
# mtcnn网络负责检测人脸
mtcnn = MTCNN(keep_all=True, device=device)
resnet = InceptionResnetV1(pretrained='vggface2').eval().to('cuda')
# 加载上面学习到的数据
names = torch.load("./names.pt")
embeddings = torch.load("./database.pt").to('cuda')
def detect_frame(img):
fontStyle = ImageFont.truetype("arial.ttf", 100,encoding="utf-8")
faces = mtcnn(img) # 直接infer所有的faces
#但是这里相当于两次infer,会浪费时间
boxes, _ = mtcnn.detect(img) # 检测出人脸框 返回的是位置
# frame_draw = img.copy()
draw = ImageDraw.Draw(img)
print("检测人脸数目:",len(boxes))
for i,box in enumerate(boxes):
print(box)
draw.rectangle(box.tolist(),outline='white',width=5) # 绘制框
face_embedding = resnet(faces[i].unsqueeze(0).to('cuda'))
#print(face_embedding.size(),'大小')
# 计算距离
probs = [(face_embedding - embeddings[i]).norm().item() for i in range(embeddings.size()[0])]
#print(probs)
# 我们可以认为距离最近的那个就是最有可能的人,但也有可能出问题,数据库中可以存放一个人的多视角多姿态数据,对比的时候可以采用其他方法,如投票机制决定最后的识别人脸
index = probs.index(min(probs)) # 对应的索引就是判断的人脸
name = names[index] # 对应的人脸
draw.text( (int(box[0]),int(box[1])), str(name), fill=(255,0,0),font=fontStyle)
return img
if __name__ == '__main__':
from matplotlib import pyplot as plt
# 人脸检测
img_path = '../data/test.jpg'
img = Image.open(img_path)
frame = detect_frame(img)
plt.imshow(frame)
plt.show()
这个项目主要的特点是简单,容易扩展。
不过,在源码中,项目架构容易实现,但是具体的细节阅读起来仍需一定的基础。如果感兴趣或者想要提高自己阅读代码能力的朋友,可以去看看项目中的model
文件夹。