在人脸识别领域,尤其是在存在复杂背景、多面孔以及不同光照条件下,构建一个能够准确预测人脸的系统是一项极具挑战性的任务。本文将介绍如何构建一个在某些情况下性能超越人类的模型。数据集包含三个类别(由于保密问题,无法共享数据,但会展示数据的样子)。第一类是杰西·艾森伯格(演员),第二类是米拉·库尼斯(流行明星),第三类是任意人。以下是训练(80张图像)和测试数据(1800+张图像)的样子。
本文是数据科学博客大赛的一部分。测试数据和从这些图像中提取的人脸具有极高的复杂性,因为它们包含多张人脸、复杂的背景和许多像素化图像。另一方面,训练数据非常干净,如下所示。在训练和测试数据分布上有很大的差异。需要一种技术,无论需要多少样本,无论训练和测试数据有多大差异,都能很好地泛化。
FaceNet被认为是谷歌开发的最先进模型。它基于初始层,解释FaceNet的完整架构超出了本文的范围。下面给出了FaceNet的架构。FaceNet使用初始模块在块中减少可训练参数的数量。这个模型接受160×160的RGB图像,并为图像生成128大小的嵌入。对于这个实现,将需要一些额外的函数。但在将人脸图像输入FaceNet之前,需要从图像中提取人脸。
detector = dlib.cnn_face_detection_model_v1("../input/pretrained-models-faces/mmod_human_face_detector.dat")
def rect_to_bb(rect):
# 将dlib预测的边界框转换为OpenCV通常使用的格式(x, y, w, h)
x = rect.rect.left()
y = rect.rect.top()
w = rect.rect.right() - x
h = rect.rect.bottom() - y
return (x, y, w, h)
def dlib_corrected(data, data_type='train'):
dim = (160, 160)
data_images = []
if data_type == 'train':
data_labels = []
for cnt in range(0, len(data)):
image = data['img'][cnt]
if image.shape[0] > 1000 and image.shape[1] > 1000:
image = cv2.resize(image, (1000, 1000), interpolation=cv2.INTER_AREA)
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
rects = detector(gray, 1)
sub_images_data = []
for (i, rect) in enumerate(rects):
(x, y, w, h) = rect_to_bb(rect)
clone = image.copy()
if x >= 0 and y >= 0 and w >= 0 and h >= 0:
crop_img = clone[y:y+h, x:x+w]
else:
crop_img = clone.copy()
rgbImg = cv2.resize(crop_img, dim, interpolation=cv2.INTER_AREA)
if data_type == 'train':
sub_images_data = rgbImg.copy()
else:
sub_images_data.append(rgbImg)
if len(rects) == 0:
if data_type == 'train':
sub_images_data = np.empty(dim + (3,))
sub_images_data[:] = np.nan
if data_type == 'test':
nan_images_data = np.empty(dim + (3,))
nan_images_data[:] = np.nan
sub_images_data.append(nan_images_data)
data_images.append(sub_images_data)
if data_type == 'train':
data_labels.append(data['class'][cnt])
if data_type == 'train':
return np.array(data_images), np.array(data_labels)
else:
return np.array(data_images)
DLIB是一个广泛用于人脸检测的模型。在实验中,发现dlib比HAAR产生了更好的结果,尽管注意到仍有改进的空间:如果矩形人脸边界移出图像,取整个图像而不是人脸裁剪。它实现如下:如果(x>=0 and y>=0 and w>=0 and h>=0): crop_img = clone[y:y+h, x:x+w] else: crop_img = clone.copy()。对于测试图像,不是每张图像保存一个人脸,而是保存所有面孔以供预测。
不是使用基于HOG的检测器,而是可以使用基于CNN的检测器。由于这些改进是为与FaceNet一起使用而量身定制的,将定义一个新的校正人脸检测。上述代码块从图像中提取人脸,对于许多图像,有多个面孔,因此需要将所有这些面孔放入一个列表中。对于提取人脸,使用dlib.cnn_face_detection_model_v1,请注意不要向此输入非常大的维度图像,否则会从dlib获得内存错误。如果图像中没有面孔,在这些地方存储NaN。让现在将FaceNet应用于这些数据图像。上述预处理仅适用于测试数据,训练数据已经很干净,可以从上面的图像中看到。一旦从训练数据获得Face嵌入,就为测试数据获得人脸嵌入,但首先,应该使用上述代码块中给出的预处理从测试数据中提取人脸。
def get_embedding(model, face_pixels):
face_pixels = face_pixels.astype('float32')
mean, std = face_pixels.mean(), face_pixels.std()
face_pixels = (face_pixels - mean) / std
samples = expand_dims(face_pixels, axis=0)
yhat = model.predict(samples)
return yhat[0]
model = load_model('../input/pretrained-models-faces/facenet_keras.h5')
svmtrainX = []
for index, face_pixels in enumerate(newTrainX):
embedding = get_embedding(model, face_pixels)
svmtrainX.append(embedding)
生成训练和测试的嵌入后,将使用SVM进行分类。为什么使用SVM,可能会问?凭借丰富的经验,知道SVM + DL-based features可以超越任何其他方法,即使是深度学习方法,当数据量很小的时候。
from sklearn.svm import SVC
from sklearn.pipeline import make_pipeline
from sklearn.naive_bayes import GaussianNB
from sklearn.neural_network import MLPClassifier
from sklearn.preprocessing import StandardScaler, MinMaxScaler, Normalizer
linear_model = make_pipeline(StandardScaler(), SVC(kernel='rbf', C=1.0, gamma=0.01, probability=True))
linear_model.fit(svmtrainX, svmtrainY)
predicitons = []
for i in corrected_test_X:
flag = 0
if len(i) == 1:
embedding = get_embedding(model, i[0])
tmp_output = linear_model.predict([embedding])
predicitons.append(tmp_output[0])
else:
tmp_sub_pred = []
tmp_sub_prob = []
for j in i:
j = j.astype(int)
embedding = get_embedding(model, j)
tmp_output = linear_model.predict([embedding])
tmp_sub_pred.append(tmp_output[0])
tmp_output_prob = linear_model.predict_log_proba([embedding])
tmp_sub_prob.append(np.max(tmp_output_prob[0]))
if 1 in tmp_sub_pred and 2 in tmp_sub_pred:
index_1 = np.where(np.array(tmp_sub_pred) == 1)[0][0]
index_2 = np.where(np.array(tmp_sub_pred) == 2)[0][0]
if tmp_sub_prob[index_1] > tmp_sub_prob[index_2]:
predicitons.append(1)
else:
predicitons.append(2)
elif 1 not in tmp_sub_pred and 2 not in tmp_sub_pred:
predicitons.append(0)
elif 1 in tmp_sub_pred and 2 not in tmp_sub_pred:
predicitons.append(1)
elif 1 not in tmp_sub_pred and 2 in tmp_sub_pred:
predicitons.append(2)