前言
在实际应用中,由于各种因素的影响,采集到的人脸图像可能存在不同的问题,由于摄像机角度不同、人动作不一样,使得过滤后的人脸还是不满足我们进行特征提取的最佳状 态(训练集中大部分都是正面正脸图片),所以我们还需要对人脸进行对齐和矫正。为了解决这些问题,人脸矫正成为了人脸识别技术中不可或缺的一环。
在本文中将着重讲解人脸识别过程中的人脸矫正步骤。通过了解人脸矫正的过程和原理,我们将能够更好地理解人脸识别系统是如何处理和利用人脸图像的。
人脸检测及五官定位
这里可以参考本人之前写的博客:一起来学MediaPipe(一)人脸及五官定位检测 通过mediapipe 库可以轻松解决人脸的定位以及人脸内的五官定位六个部位(双耳2 + 双眼2 + 鼻子1 + 嘴心1)。
解析坐标
在mediapipe库中,mp_face_detection.FaceDetection函数用于检测图像或视频中的人脸,并返回人脸的坐标信息。函数返回的坐标信息的讲解如下:
- LocationData类:mp_face_detection.FaceDetection函数返回一个列表,其中每个元素都是LocationData类的实例。LocationData类包含了表示人脸位置和边界框的属性。
- relative_bounding_box:表示检测到的人脸边界框的相对位置。它是一个四维浮点数的元组 (x, y, width, height),其中 (x, y) 是边界框左上角相对于整个图像宽度和高度的比例,width 和 height 是边界框相对于整个图像的宽度和高度的比例。
- score:表示检测到的人脸的置信度分数。该分数范围从 0 到 1,表示对检测结果的置信程度,值越高表示置信度越高。
坐标信息解读:通过使用 relative_bounding_box 的值,可以解读人脸的位置和边界框。其中的x 和 y 表示边界框左上角相对于整个图像的位置。
例如,如果 x = 0.2,y = 0.3,则边界框的左上角位于图像宽度的 20% 处,高度的 30% 处。width 和 height 表示边界框的宽度和高度相对于整个图像的比例。例如,如果 width = 0.4,height = 0.6,则边界框的宽度为图像宽度的 40%,高度为图像高度的 60%。可以通过将相对坐标与实际图像的宽度和高度相乘,将相对坐标转换为实际坐标。例如,如果图像的宽度为 800 像素,高度为 600 像素,则将 x 乘以 800,y 乘以 600,width 乘以 800,height 乘以 600,就可以得到实际坐标。
博客:一起来学MediaPipe(一)人脸及五官定位检测 中detection返回信息如下:
shell
复制代码
label_id: 0 score: 0.5956658720970154 location_data { format: RELATIVE_BOUNDING_BOX relative_bounding_box { xmin: 0.23118790984153748 ymin: 0.09999196231365204 width: 0.5988736152648926 height: 0.7984580993652344 } relative_keypoints { x: 0.39731642603874207 y: 0.3905108571052551 } relative_keypoints { x: 0.6470986008644104 y: 0.36055833101272583 } relative_keypoints { x: 0.5388920307159424 y: 0.6023311018943787 } relative_keypoints { x: 0.5485179424285889 y: 0.740068256855011 } relative_keypoints { x: 0.2765348255634308 y: 0.40228205919265747 } relative_keypoints { x: 0.7757663130760193 y: 0.3326093554496765 } }
检测函数
定义一个函数进行处理人脸并返回人脸坐标以及五官坐标,其中box_info返回的是人脸(xmin,ymin,width, height),facial返回的是五官的定位坐标信息
shell
复制代码
def get_face_info(image): # To improve performance, optionally mark the image as not writeable to # pass by reference. image.flags.writeable = False image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB) results = face_detection.process(image) # Draw the face detection annotations on the image. image.flags.writeable = True image = cv2.cvtColor(image, cv2.COLOR_RGB2BGR) box_info, facial = None, None if results.detections: for detection in results.detections: box_info = detection.location_data.relative_bounding_box facial = detection.location_data.relative_keypoints return cv2.flip(image, 1), box_info, facial
人脸对齐矫正
人脸对齐矫正用于将人脸图像中的关键点对齐到特定位置,以达到标准化或者更好的分析和比较效果。下面是人脸对齐矫正的基本原理:
- 关键点检测:首先,需要通过算法来检测人脸图像中的关键点,例如眼睛、鼻子、嘴巴等。常用的关键点检测算法包括人工标注、Haar级联分类器、深度学习模型等。
- 刚性变换:一旦检测到关键点,接下来的目标是将关键点对齐到一个标准的位置。这通常涉及到应用刚性变换,包括平移、旋转和缩放等操作,以使得关键点对应的人脸特征在图像中的位置和大小与标准位置相匹配。
- 仿射变换:在某些情况下,刚性变换可能无法完全解决对齐问题,特别是当人脸姿态发生较大变化时。这时,需要使用更灵活的仿射变换,可以通过调整关键点之间的距离、角度和比例来进一步改善对齐效果。
- 插值和融合:在完成对齐变换后,可能会产生一些空白区域或者重叠区域。为了消除这些问题,常常使用插值技术来填充空白区域,同时可以通过融合操作来平滑处理重叠区域。
在上述矫正的过程中有两种方案可供备选:一种是使用标准点进行矫正;另一种是采用深度学习方法进行矫正。这二者各有优缺点,在本文中我将主要就讲述第一种方法。
基于标准参考特征点进行旋转平移矫正
基于标准参考特征点进行旋转平移矫正是一种常用的人脸对齐方法。该方法基于一组预定义的标准参考特征点,通过计算目标人脸的特征点与标准参考特征点之间的差异,来进行旋转和平移变换,以实现人脸对齐的目的。下面是详细的步骤:
通过以上步骤,基于标准参考特征点进行旋转平移矫正可以使目标人脸图像的关键点与预定义的标准特征点对齐。这种对齐方法可以有效地纠正人脸图像中的姿态变化,使得后续的人脸分析和处理更加准确和可靠。定义下面函数通过多个关键点进行定位矫正获取矫正后的图像:
shell
复制代码
# 旋转矩阵 def transformation_from_points(points1, points2): points1 = points1.astype(np.float64) points2 = points2.astype(np.float64) c1 = np.mean(points1, axis=0) c2 = np.mean(points2, axis=0) points1 -= c1 points2 -= c2 s1 = np.std(points1) s2 = np.std(points2) points1 /= s1 points2 /= s2 U, S, Vt = np.linalg.svd(points1.T * points2) R = (U * Vt).T M = np.vstack([ np.hstack(((s2 / s1) * R, c2.T - (s2 / s1) * R * c1.T)), np.asmatrix([0., 0., 1.])]) return M # 人脸对齐 def face_alignment(src_landmark6, image_numpy): h, w, c = image_numpy.shape # 5点对齐后的基准点 dst_landmark6 = np.asmatrix([[283, 162], # 左眼 [405, 165],# 右眼 [345, 233],# 鼻子 [341, 293], # 嘴心 [212, 187], # 左耳 [463, 195]]) # 右耳 # 计算旋转矩阵 M = transformation_from_points(src_landmark6, dst_landmark6) # 旋转图像 align_image = cv2.warpAffine(image_numpy, M[:2], (w, h)) return align_image
坐标解析函数
在基于标准参考特征点进行旋转平移矫正函数中可以看见关键点的坐标以及数据格式,对人脸检测函数get_face_info 中获取的坐标进行解析组织,可如下所示:
shell
复制代码
def CoordinateTransformation(w, h, img_box, facial): xmin, ymin, width, height = int(img_box.xmin * h), \ int(img_box.ymin * w), \ int(img_box.width * h), \ int(img_box.height * w) # 左眼 key_0_x = int(facial[0].x * h) key_0_y = int(img_fa[0].y * w) # 右眼 key_1_x = int(facial[1].x * h) key_1_y = int(facial[1].y * w) # 鼻子 key_2_x = int(facial[2].x * h) key_2_y = int(facial[2].y * w) # 嘴巴 key_3_x = int(facial[3].x * h) key_3_y = int(facial[3].y * w) # 左耳 key_4_x = int(facial[4].x * h) key_4_y = int(facial[4].y * w) # 右耳 key_5_x = int(facial[5].x * h) key_5_y = int(facial[5].y * w) keys = np.asmatrix([[key_0_x, key_0_y], [key_1_x, key_1_y], [key_2_x, key_2_y], [key_3_x, key_3_y], [key_4_x, key_4_y], [key_5_x, key_5_y]]) return xmin, ymin, width, height, keys
完整流程
上面我们定义完成了所有的函数,下面我将讲述如何串联起来进行对齐矫正。大体的思路如下所述:
- 获取一张标准人脸头像,使用get_face_info获取五官位置并记录填写到矩阵中;
- 调取摄像头并获取人脸;
- 使用get_face_info获取五官位置并进行矫正,此时或者矫正后的图像:align_image
- 将矫正后的图像align_image再次使用get_face_info函数进行定位人脸区域;
- 获取get_face_info函数返回人脸位置信息并ROI处理
shell
复制代码
if __name__ == "__main__": mp_face_detection = mp.solutions.face_detection mp_drawing = mp.solutions.drawing_utils src_img = cv2.imread("wa.jpg") # 读取原始图像 src_img_copy = src_img.copy() w, h, _ = src_img.shape # 获取原始图像尺寸 _, box, fa = get_face_info(src_img) # 对原始图像中获取翻转后的尺寸 、 人脸坐标集 、 五官坐标集 xmin, ymin, width, height, keys = CoordinateTransformation(w, h, box, fa) # 转换各种坐标集格式便于读取 align_image, _ = face_alignment(keys, src_img) align_img, align_box, align_fa = get_face_info(align_image) align_w, align_h, _ = align_img.shape align_xmin, align_ymin, align_width, align_height, _ = CoordinateTransformation(align_w, align_h, align_box, align_fa) # 转换各种坐标集格式便于读取 face_roi = align_image[align_ymin: align_ymin + align_width, align_xmin:align_xmin + align_height] # # 使用人脸坐标对人脸部分裁剪出来 face_roi = cv2.resize(face_roi, (150, 150)) src_img_copy[0:150, 0:150] = face_roi # cv2.imwrite("T.png", src_img_copy) cv2.imshow('MediaPipe Face Detection', src_img_copy) cv2.waitKey(0)