目录
在这里,我们将简要说明如何在Raspberry Pi上安装MTCNN、TensorFlow和Keras。然后我们在视频文件上启动人脸检测以测试性能,然后简要说明如何在实时模式下运行检测。最后,我们看看如何使用运动检测器加快检测速度。
介绍
人脸识别是人工智能(AI)的一个领域,深度学习(DL)在过去十年中取得了巨大成功。最好的人脸识别系统可以以与人类相同的精度识别图像和视频中的人物,甚至更好。人脸识别的两个主要基础阶段是人员验证和身份识别。
在本系列文章的前半部分(当前)中,我们将:
- 讨论现有的AI人脸检测方法并开发程序来运行预训练的DNN模型
- 考虑面部对齐并使用面部标志实现一些对齐算法
- 在Raspberry Pi设备上运行人脸检测DNN,探索其性能,并考虑可能的方法来更快地运行它,以及实时检测人脸
- 创建一个简单的人脸数据库并用从图像或视频中提取的人脸填充它
我们假设您熟悉DNN、Python、Keras 和 TensorFlow。
在上一篇文章中,我们实现了基于现代AI方法的通用面部识别系统的第一阶段——面部检测。在本文中,我们将讨论管道的第二阶段——面部对齐——基于OpenCV库中的透视变换函数。这不是强制性阶段。但是,它可以提高识别精度,并且一直用于现实生活中的面部识别软件。
对齐算法
简而言之,人脸对齐是一种将从图像或视频帧中提取的人脸图片转换为指定方向、位置和比例的算法。在人脸识别必须在现实生活中的图像和框架中,人脸可以在各种角度和各种比例因子下出现。下图显示了相对于相机视图处于不同位置的同一张脸。
1 – 脸向右旋转
2 – 脸向一侧倾斜
3 – 脸向左旋转
4 – 脸向下倾斜
人脸对齐的目标是将人脸拟合到相同大小的图像中,并标准化人脸视图:将人脸居中于图像内;相对于图像缩放面部,依此类推。
面部对齐可以使用各种算法,具体取决于有关面部及其位置的可用信息。我们将使用MTCNN模型,因为我们知道人脸的边界框在2D图像中并且有五个面部标志位置(关键点)。我们需要开发一种转换,使用面部的关键点将其带入标准视图。
幸运的是,我们不需要重新发明轮子。在计算机视觉中,这种变换被称为透视变换,用OpenCV库实现起来相对容易。我们只需要在源图像中选择四个点,在目标图像中选择四个匹配点。
看看下面的图片。在左侧,我们绘制了使用MTCNN算法找到的关键点。在右侧,我们显示了可以根据三个关键点的已知坐标计算的三个点:1、2 和 3。
点6位于点1和点2之间。点7和8与关键点1和2一起形成平行四边形。点1和7之间的距离与点2和8之间的距离相同,是点6和3之间距离的两倍。所以点3是平行四边形的中心。
算法的实现
让我们使用平行四边形的四个角来实现透视变换算法:
class Face_Align:
def __init__(self, size):
self.size = size
def align_point(self, point, M):
(x, y) = point
p = np.float32([[[x, y]]])
p = cv2.perspectiveTransform(p, M)
return (int(p[0][0][0]), int(p[0][0][1]))
def align(self, frame, face):
(x1, y1, w, h) = face['box']
(l_eye, r_eye, nose, mouth_l, mouth_r) = Utils.get_keypoints(face)
(pts1, pts2) = self.get_perspective_points(l_eye, r_eye, nose, mouth_l, mouth_r)
s = self.size
M = cv2.getPerspectiveTransform(pts1, pts2)
dst = cv2.warpPerspective(frame, M, (s, s))
f_aligned = copy.deepcopy(face)
f_aligned['box'] = (0, 0, s, s)
f_img = dst
l_eye = self.align_point(l_eye, M)
r_eye = self.align_point(r_eye, M)
nose = self.align_point(nose, M)
mouth_l = self.align_point(mouth_l, M)
mouth_r = self.align_point(mouth_r, M)
f_aligned = Utils.set_keypoints(f_aligned, (l_eye, r_eye, nose, mouth_l, mouth_r))
return (f_aligned, f_img)
class Face_Align_Nose(Face_Align):
def get_perspective_points(self, l_eye, r_eye, nose, mouth_l, mouth_r):
(xl, yl) = l_eye
(xr, yr) = r_eye
(xn, yn) = nose
(xm, ym) = ( 0.5*(xl+xr), 0.5*(yl+yr) )
(dx, dy) = (xn-xm, yn-ym)
(xl2, yl2) = (xl+2.0*dx, yl+2.0*dy)
(xr2, yr2) = (xr+2.0*dx, yr+2.0*dy)
s = self.size
pts1 = np.float32([[xl, yl], [xr, yr], [xr2, yr2], [xl2, yl2]])
pts2 = np.float32([[s*0.25, s*0.25], [s*0.75, s*0.25], [s*0.75, s*0.75], [s*0.25,s*0.75]])
return (pts1, pts2)
上面的代码包含两个类。基类Face_Align使用OpenCV库中的两个函数:getPerspectiveTransform和warpPerspective实现转换算法。第一个函数计算变换矩阵,第二个函数用矩阵变换图像。计算透视图矩阵的点必须由get_perspective_points方法提供。方法的具体实现是在继承的类Face_Align_Nose中实现的。
向检测器添加人脸对齐
现在我们可以将人脸对齐算法添加到上一篇文章中描述的视频人脸检测器中:
class VideoFD:
def __init__(self, detector):
self.detector = detector
def detect(self, video, save_path = None, align = False, draw_points = False):
detection_num = 0;
capture = cv2.VideoCapture(video)
img = None
dname = 'AI face detection'
cv2.namedWindow(dname, cv2.WINDOW_NORMAL)
cv2.resizeWindow(dname, 960, 720)
frame_count = 0
dt = 0
face_num = 0
if align:
fa = Face_Align_Nose(160)
# Capture all frames
while(True):
(ret, frame) = capture.read()
if frame is None:
break
frame_count = frame_count+1
t1 = time.time()
faces = self.detector.detect(frame)
t2 = time.time()
p_count = len(faces)
detection_num += p_count
dt = dt + (t2-t1)
if (not (save_path is None)) and (len(faces)>0) :
f_base = os.path.basename(video)
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):
if draw_points:
Utils.draw_faces([f_cropped], (255, 0, 0), f_img, draw_points, False)
face_num = face_num+1
dfname = os.path.join(save_path, f_base + ("_%06d" % face_num) + ".png")
cv2.imwrite(dfname, f_img)
if len(faces)>0:
Utils.draw_faces(faces, (0, 0, 255), frame)
if not (save_path is None):
dfname = os.path.join(save_path, f_base + ("_%06d" % face_num) + "_frame.png")
cv2.imwrite(dfname, frame)
# Display the resulting frame
cv2.imshow(dname,frame)
if cv2.waitKey(1) & 0xFF == ord('q'):
break
capture.release()
cv2.destroyAllWindows()
fps = frame_count/dt
return (detection_num, fps)
让我们对视频文件启动人脸检测:
d = MTCNN_Detector(50, 0.95)
vd = VideoFD(d)
v_file = r"C:\PI_FR\video\5_2.mp4"
save_path = r"C:\PI_FR\detect"
(f_count, fps) = vd.detect(v_file, save_path, True, False)
print("Face detections: "+str(f_count))
print("FPS: "+str(fps))
在下图中,我们显示了相同位置的人脸,但现在它们与使用我们的算法对齐。
您可以看到现在所有图片的大小都相同。然而,对齐算法并没有做得很好。当人脸向左或向右旋转时,算法会失败并严重扭曲人脸。这是因为鼻尖与其他点不在同一平面上。当面旋转到一边时,点3不再是平行四边形的中心。
修改对齐算法
让我们尝试使用其他人脸地标修改对齐算法。在下图中,我们引入了一个新点——9。
点9计算为点4和5之间的中间点。我们仍然使用四个点——1、2、8、7——作为透视变换的基础。但是现在点7和8的计算是基于点9的坐标。我们假设点6和9位于平行四边形的中线上。下面的代码实现了这个算法:
class Face_Align_Mouth(Face_Align):
def get_perspective_points(self, l_eye, r_eye, nose, mouth_l, mouth_r):
(xl, yl) = l_eye
(xr, yr) = r_eye
(xml, yml) = mouth_l
(xmr, ymr) = mouth_r
(xn, yn) = ( 0.5*(xl+xr), 0.5*(yl+yr) )
(xm, ym) = ( 0.5*(xml+xmr), 0.5*(yml+ymr) )
(dx, dy) = (xm-xn, ym-yn)
(xl2, yl2) = (xl+1.1*dx, yl+1.1*dy)
(xr2, yr2) = (xr+1.1*dx, yr+1.1*dy)
s = self.size
pts1 = np.float32([[xl, yl], [xr, yr], [xr2, yr2], [xl2, yl2]])
pts2 = np.float32([[s*0.3, s*0.3], [s*0.7, s*0.3], [s*0.7, s*0.75], [s*0.3, s*0.75]])
return (pts1, pts2)
在视频检测器中使用新类Face_Align_Mouth并使用相同的视频文件启动它,我们得到以下对齐的人脸。
您会看到现在没有面部扭曲,并且所有面部都正确对齐。请注意,并非所有对齐的人脸看起来都在镜头前。例如,旋转后的面看起来仍然略微旋转。不幸的是,我们无法在2D图像中重建旋转人脸的前视图。
尽管如此,相对于最终图像,所有面部关键点现在都具有相同的坐标。这正是我们想要的对齐算法——将面部归一化为相同的图像坐标。这种方法确保从数据库中的人脸提取的特征和通过相机输入的人脸属于同一空间。
下一步
在本系列的下一篇文章中,我们将在Raspberry Pi设备上运行我们的面部检测器。
https://www.codeproject.com/Articles/5306640/Hybrid-Edge-AI-Face-Alignment