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