目录
这里我们陈述人脸识别的问题。然后对现有的用于人脸识别的AI库做一个简短的回顾。我们使用Keras FaceNet库。然后我们开发用于人脸识别的Python代码并使用创建的人脸数据库测试算法。
介绍
人脸识别是人工智能(AI)的一个领域,现代深度学习(DL)方法在过去十年中取得了巨大成功。最好的人脸识别系统可以以与人类相同的精度识别图像和视频中的人物,甚至更好。
我们关于这个主题的系列文章分为两个部分:
- 人脸检测,客户端应用程序检测图像或视频源中的人脸,对齐检测到的人脸图片,并将它们提交给服务器。
- 人脸识别(这部分),服务器端应用程序进行人脸识别。
在本系列的这一部分中,我们将讨论人脸识别问题,并将之前开发的人脸检测器与人脸识别器结合起来。然后,我们将在Docker容器中实现人脸识别,并添加一个Web API,用于将检测到的人脸传输到运行识别的服务器。此外,我们将考虑在Kubernetes中运行人脸识别的某些方面。最后,我们将讨论从头开始构建人脸识别系统。
我们假设您熟悉DNN、Python、Keras和TensorFlow。
人脸识别
人脸识别可以描述为在人脸数据库中找到与被识别的人最相似的人。在上一篇文章(本系列前半部分的最后一篇)中,我们创建了一个包含15人的数据库。在本系列的这一半中,我们将更详细地考虑人脸识别任务,并开发一种使用预训练DNN模型识别视频源中人物的算法。
我们有一个人脸数据库,其中包含人的样本图像——每个人一张照片。当在图像或视频中检测到人脸时,检测器会生成每个检测到的人脸的图片。我们必须确定那张照片中的人脸是否属于我们数据库中的一个人。有两种可能的情况:
- 检测到的人脸属于数据库中的某个人,在这种情况下,我们必须指定此人的ID(例如,他们的姓名)。
- 检测到的人脸属于一个未知的人,在这种情况下,我们必须说明这一事实。
人脸识别模型
所有现代最先进的人脸识别方法都使用DNN模型进行人脸识别。模型可以具有不同的架构,并且可以在不同的人脸数据库上进行训练。但是,他们使用类似的方法来实现目标。首先,使用DNN模型作为特征提取器来获取人脸图像的嵌入。然后使用这些嵌入来确定人脸的相似程度,这被称为两个人脸图像之间的“距离”——距离越小,人脸越相似。通过评估检测到的人脸与数据库中所有人脸之间的距离,我们可以找到最相似的人。
开发和训练用于人脸识别的DNN模型并非易事。幸运的是,有许多免费的预训练模型和库可以实现最先进的DNN架构以进行人脸识别。例如:
上述每种模型都有其优点和缺点。最佳选择取决于具体情况。在本系列中,我们将使用FaceNet预训练模型有两个原因:
- 该模型经过训练可以直接优化人脸嵌入,无需中间层。
- 我们只需几行Python代码就可以运行识别算法,因为该模型是在Keras中实现的。
人脸识别码
是时候为我们的人脸识别器编写代码了。基于DNN的人脸识别器必须至少实现两个功能:一个是从一张人脸图像中提取嵌入,另一个是评估两个人脸图像中嵌入之间的距离。以下是我们如何基于FaceNet DNN模型对识别器进行编码:
class FaceNetRec:
def __init__(self, model, min_distance):
self.model = load_model(model)
self.min_distance = min_distance
def get_model(self):
return self.model
def embeddings(self, f_img):
r_img = cv2.resize(f_img, (160, 160), cv2.INTER_AREA)
arr = r_img.astype('float32')
arr = (arr-127.5)/127.5
samples = np.expand_dims(arr, axis=0)
embds = self.model.predict(samples)
return embds[0]
def eval_distance(self, embds1, embds2):
dist = distance.cosine(embds1, embds2)
return dist
def img_distance(self, f_img1, f_img2):
embds1 = self.embeddings(f_img1)
embds2 = self.embeddings(f_img2)
dist = self.eval_distance(embds1, embds2)
return dist
def match(self, embds1, embds2):
dist = self.eval_distance(embds1, embds2)
return dist <= self.min_distance
def img_match(self, f_img1, f_img2):
embds1 = self.embeddings(f_img1)
embds2 = self.embeddings(f_img2)
return self.match(embds1, embds2)
def recognize(self, embds, f_db):
minfd = 2.0
indx = -1
f_data = f_db.get_data();
for (i, data) in enumerate(f_data):
(name, embds_i, p_img) = data
dist = self.eval_distance(embds, embds_i)
if (dist<minfd) and (dist<self.min_distance):
indx = i
minfd = dist
if indx>=0:
(name, embds_i, p_img) = f_data[indx]
return (name, minfd, p_img)
return None
def img_recognize(self, f_img, f_db):
embds = self.embeddings(f_img)
return self.recognize(embds, f_db)
类的构造函数从model参数指定的文件加载Keras模型。该min_distance参数指定将面部图像识别为属于特定人所需的最小距离值。该embeddings和eval_distance方法来实现上述功能。我们使用所谓的余弦距离来评估嵌入的相似性。
我们想提到的另一种方法是recognize。该方法将检测到的人脸和f_db对象的嵌入作为输入。该对象按照本文(参考“创建数据库”部分)文章中的描述创建人脸数据库。
class FaceDB:
def __init__(self):
self.clear()
def clear(self):
self.f_data = []
def load(self, db_path, rec):
self.clear()
files = FileUtils.get_files(db_path)
for (i, fname) in enumerate(files):
f_img = cv2.imread(fname, cv2.IMREAD_UNCHANGED)
embds = rec.embeddings(f_img)
f = os.path.basename(fname)
p_name = os.path.splitext(f)[0]
data = (p_name, embds, f_img)
self.f_data.append(data)
def get_data(self):
return self.f_data
该类的load方法搜索由db_path参数指定的文件夹中的所有文件,为其找到的所有图像创建嵌入,并为数据库中的每个人构建名称-嵌入-图像三元组。该recognize方法使用来自人脸数据库的数据,评估所有人的嵌入与检测到的人脸之间的距离。最小距离表示数据库中最相似的人。请注意,如果此值大于指定的阈值,则结果将为None,表示未知人脸。
结合人脸识别器和人脸检测器
我们现在可以将开发的类与本文(参考“人脸检测”部分)中描述的MTCNN视频人脸检测器相结合。
class VideoFR:
def __init__(self, detector, rec, f_db):
self.detector = detector
self.rec = rec
self.f_db = f_db
def process(self, video, align=False):
detection_num = 0;
rec_num = 0
capture = cv2.VideoCapture(video)
img = None
dname = 'AI face recognition'
cv2.namedWindow(dname, cv2.WINDOW_NORMAL)
cv2.resizeWindow(dname, 960, 720)
frame_count = 0
dt = 0
if align:
fa = Face_Align_Mouth(160)
# Capture all frames
while(True):
(ret, frame) = capture.read()
if frame is None:
break
frame_count = frame_count+1
faces = self.detector.detect(frame)
f_count = len(faces)
detection_num += f_count
names = None
if (f_count>0) and (not (self.f_db is None)):
t1 = time.time()
names = [None]*f_count
for (i, face) in enumerate(faces):
if align:
(f_cropped, f_img) = fa.align(frame, face)
else:
(f_cropped, f_img) = self.detector.extract(frame, face)
if (not (f_img is None)) and (not f_img.size==0):
embds = self.rec.embeddings(f_img)
data = self.rec.recognize(embds, self.f_db)
if not (data is None):
rec_num += 1
(name, dist, p_photo) = data
conf = 1.0 - dist
names[i] = (name, conf)
t2 = time.time()
dt = dt + (t2-t1)
if len(faces)>0:
Utils.draw_faces(faces, (0, 0, 255), frame, True, True, names)
# Display the resulting frame
cv2.imshow(dname,frame)
if cv2.waitKey(1) & 0xFF == ord('q'):
break
capture.release()
cv2.destroyAllWindows()
if dt>0:
fps = detection_num/dt
else:
fps = 0
return (detection_num, rec_num, fps)
在初始化时,该VideoFR类接收MTCNN人脸检测器、人脸识别器和人脸数据库。然后将这些组件组合成一个人脸识别管道。
测试识别器
我们现在可以使用以下代码对视频文件进行识别:
m_file = r"C:\PI_FR\net\facenet_keras.h5"
rec = FaceNetRec(m_file, 0.5)
print("Recognizer loaded.")
print(rec.get_model().inputs)
print(rec.get_model().outputs)
db_path = r"C:\PI_FR\db"
f_db = FaceDB()
f_db.load(db_path, rec)
d = MTCNN_Detector(50, 0.95)
vr = VideoFR(d, rec, f_db)
v_file = r"C:\PI_FR\video\5_3.mp4"
(f_count, rec_count, fps) = vr.process(v_file, True)
print("Face detections: "+str(f_count))
print("Face recognitions: "+str(rec_count))
print("FPS: "+str(fps))
请注意,当一个人被识别时,我们会在输出中绘制他们的名字以及识别的置信度(相似度分数)。如果一个人没有被识别出来,我们就得出人脸检测的置信度。从上面的测试结果可以看出,我们的人脸识别算法在视频文件上运行良好。它在大多数帧中正确识别了数据库中的两个人(Lena和Marat),并且在整个视频中都没有识别出未知的人。
让我们对另外两个视频片段进行测试。
在第一个视频中,我们得到了100%正确的结果。在第二个视频中,算法未能识别出其中一个人:它错误地命名了一个不在数据库中的人。
这展示了人脸识别系统的一个常见问题。当一个人是已知的(他的脸在数据库中)时,系统会通过分配最大的相似度值来正确识别他们。但是,当检测到的人未知时,算法仍然可以在数据库中找到一个与检测到的人长得有点像的人。
我们可以通过min_distance在初始化识别器时更改参数来解决上述问题。请注意,被错误识别为已知人物的女性的置信度永远不会大于0.65。因此,如果我们将相似度(距离)的最小值设置为 0.35,则不会再次发生错误识别。
下一步
现在我们有了一个很好的人脸识别系统。在下一篇文章中,我们将为这个系统创建一个Docker容器。
https://www.codeproject.com/Articles/5305695/Introduction-to-Face-Identification