了解了人脸识别与定位原理以后,下面以Celeb-DF数据集中的视频为素材开展实验,对所有视频的人脸都需要进行定位和提取。Celeb-DF包含多个文件夹,包括Celeb-real(原始视频)、Celeb-synthesis(合成视频)、YouTube-real(来自YouTube的原始视频)组成。
实验以Celeb-real中的id0_0001.mp4为例介绍数据预处理的具体工作,下面分步骤进行介绍:
第一步,安装必要的类库。
实验中需要使用moviepy和MTCNN类库。Moviepy可以实现简单的视频提取和处理。实验环境为Python3.x+Keras,首先安装moviepy:
pip install moviepy
MTCNN的代码包含在faceswap-GAN中,可以直接在github上下载该代码,命令如下:
git clone https://github. com/taiji1985/faceswap-GAN.git
第二步,从视频中提取图像。
从视频中提取图像有很多种方案这里采用moviepy的方式。moviepy的VideoFileClip类提供fl_image方法可以逐帧处理图像,并提供回调函数。编写回调函数并在回调函数中将获取的图像保存到文件中(图8-19)。
图8-19 提取的部分图像
fn_input_video='id0_0001.mp4'
class VideoInfo:
def__init__(self):
self. frame=0
pass
pass
info=VideoInfo()
def saveimg(img, info):
info. frame+=1
if info. frame%40==0:
fname='out/img_%03d.jpg'%info.frame
else:
return np. zeros((3,3,3))
plt. imsave(fname, img, format="jpg")
return img
output='dummy.mp4'
info=VideoInfo()
clip1=VideoFileClip(fn_input_video)
clip=clip1.fl_image(lambda img:saveimg(img, info))
clip. write_videofile(output, audio=False, verbose=False)
clip1. reader.close()
上面代码中每40帧提取一幅图片的设定是为了加速实验的进行,实际测试时可以根据需求设定,如每5帧提取一幅图像。
如果代码提示ffmpeg没有找到,可以使用下面的代码下载。
import imageio
imageio. plugins.ffmpeg.download()
第三步,人脸定位和提取。
下面选取以从视频中提取的一帧图像为例,演示如何进行图像中的人脸定位和提取。如果需要对所有提取的图像做人脸区域提取,读者可以写一个循环自行实现。
首先,需要创建MTCNN的网络结构,并加载预先训练好的参数。
import keras. backend as K
from detector. face_detector import MTCNNFaceDetector
detector=MTCNNFaceDetector(sess=K.get_session()
,model_path="./mtcnn_weights/")
该代码执行时会爆出一些warning,这些可以忽略。代码仅需执行一次,多次执行会引发错误。
接下来就可以用以识别图像中的人脸了。下面以图像img_040.jpg为例。
img_path='out/img_040.jpg'
detec_threshold=0.9
threshold=[0.7,0.8,detec_threshold]#three steps's threshold
factor=0.709#scale factor
input_img=plt.imread(img_path)
print(input_img. shape)
plt. imshow(input_img)
faces, pnts=detector.detect_face(input_img,
threshold=detec_threshold, use_auto_downscaling=False)
print(faces)
print(pnts)
执行结果如图8-20所示。detect_face函数返回的是一个检测到的人脸的数组。这个案例中只检测到一个人脸,在其他图像中有可能检出多个人脸。每个人脸有一个五元组表示,包括人脸的边界框和置信度。边界框包括边界矩形的左上角和右下角。置信度在本次实验中为0.9999938,即非常确信这是一张人脸(图8-20)。
图8-20 截取人脸的参数
使用边界框就可以将人脸切出。下面for语句对所有的人脸数据进行了遍历,使用numpy的向量切片功能切出对应区域,图像显示如图8-21所示。
import os
from pathlib import Path
from converter. landmarks_alignment import∗
save_path='out'
for idx,(x0,y1,x1,y0,conf_score)in enumerate(faces):
det_face_im=input_img[int(x0):int(x1),int(y0):int(y1),:]
#get src/tar landmarks
src_landmarks=get_src_landmarks(x0,x1,y0,y1,pnts)
tar_landmarks=get_tar_landmarks(det_face_im)
#align detected face
aligned_det_face_im=landmarks_match_mtcnn(det_face_im, src_landmarks, tar_landmarks)
Path(os. path.join(f"{save_path}","rgb")).mkdir(parents=True, exist_ok=True)
fname=os.path.join(f"{save_path}","rgb",f"frame4_face{str(idx)}.jpg")
print(fname)
plt. imsave(fname, det_face_im, format="png")
plt. imshow(det_face_im)
图8-21 截取的人脸图像
第四步,图像放缩。
从视频中提取的头像尺寸不一,需要缩放为统一的尺寸。下面代码借助Opencv完成这一任务。
import opencv
im2=cv2.resize(det_face_im,(128,128))(www.xing528.com)
plt. imshow(im2)
第五步,数据批量处理。
上面的实验给出了从视频中提取图像和从图像中提取人脸的方法。使用上述方法对数据集中所有的人脸进行处理后将所有人脸放缩为128×128,并组成numpy的四维数组,数组维度定义为(样本编号,长,宽,通道)。根据视频所在的目录,将提取的人脸的标签定义为:Celeb-real对应0,Celeb-synthesis对应1,YouTube-real对应0,构成一个标签数组。将这两个数组使用np.save保存为npy文件,为后续的实验提供基础。
此步骤代码烦琐,主要采用若干循环实现,略去不写,读者可自行实现。
需要指出的是,在预处理图像的时候需要考虑到数据平衡的问题,即训练集中正例和反例的数量一致。然而原始视频和合成视频在数据集中本身不平衡,这就要求在原始视频和合成视频中采用不同的采样频率(间隔若干帧获取一副图像的间隔帧数)。
第六步,数据扩充。
为了避免网络学习识别人脸的种类(是甲的脸,还是乙的脸),使用下面的函数对数据进行旋转处理,不仅避免网络训练受到图像内容的影响,而且扩充了数据集。
import cv2
defkuochong(imgs, labels):
print("kuochong……")
n=imgs.shape[0]
I=[]
L=[]
foriinrange(n):
img=imgs[i]
b=labels[i]
#print(img. shape)
I. append(img)
L. append(b)
foriin[-0,1]:
z=cv2.flip(img,0)
#print('flip',z. shape)
I. append(z)
L. append(b)
img90=img
foriinrange(3):
img90=np.rot90(img90)
#print('rot90',img90. shape)
I. append(img90)
L. append(b)
I=np.array(I)
L=np.array(L)
iflen(L. shape)==1:
L=np.expand_dims(L,1)
returnI, L
第七步,设置训练集和测试集。
下面将要把预处理生成的数据集划分为训练集和测试集。下面代码列出了两个主要函数:shuffle用以打乱数据集,video_level_split用以将训练集和测试集分开,可以保证同一个视频生成的图像不会同时存在于测试集和训练集中,避免网络根据人脸相似度进行分类,进而无法学到可以分辨是否合成的特征。其中使用的helper为一个包含了各种绘图函数的帮助类,可以使用curl从谷歌下载。
!curl-L-ohelper. py"https://drive.google.com/uc?export=download&id=1G2Njh5br22EY7-SvoEtz00fZItaedcKz"
importhelper
importimportlib
importlib. reload(helper)
defshuffle(imgs, labels):
globalrandom_ret
q=np.random.RandomState(seed=11).permutation(len(imgs))
random_ret=q
n_imgs=imgs[q]
n_labels=labels[q]
iflen(n_labels. shape)==1:
n_labels=np.expand_dims(n_labels,1)returnn_imgs, n_labels
defvideo_level_split(imgs, labels, filenames):
n=imgs.shape[0]//2
f=int(n∗9/10)
train_img_first=imgs[:f]
train_label_first=labels[:f]
train_filenames_first=filenames[:f]
test_img_first=imgs[f+1:n]
test_label_first=labels[f+1:n]
test_filenames_first=filenames[f+1:n]
train_img_second=imgs[n:n+f]
train_label_second=labels[n:n+f]
train_filenames_second=filenames[n:n+f]
test_img_second=imgs[n+f+1:]
test_label_second=labels[n+f+1:]
test_filenames_second=filenames[n+f+1:]
train_img=np.concatenate((train_img_first, train_img_second),axis=0)
train_label=np.concatenate((train_label_first, train_label_second),axis=0)
train_filenames=np.concatenate((train_filenames_first, train_filenames_sec-ond),axis=0)
test_img=np.concatenate((test_img_first, test_img_second),axis=0)
test_label=np.concatenate((test_label_first, test_label_second),axis=0)
test_filenames=np.concatenate((test_filenames_first, test_filenames_second),axis=0)
return train_img, train_label, test_img, test_label, train_filenames, test_filenames
利用video_level_split进行训练集和测试集的分离,随后扩充训练集,最后随机打乱。随机打乱的技巧可以一定程度上减小随机梯度下降之类的优化算法停留于极小值处的可能。
#n_imgs, n_labels=shuffle(imgs, labels)
train_imgs, train_labels, test_imgs, test_labels, train_filenames, test_filenames=video_level_split(imgs, labels, fnames)
train_imgs, train_labels=kuochong(train_imgs, train_labels)
train_imgs, train_labels=shuffle(train_imgs, train_labels)
对处理后的图像打印其前4张,效果如图8-22所示。
图8-22 预处理后的示例图像
免责声明:以上内容源自网络,版权归原作者所有,如有侵犯您的原创版权请告知,我们将尽快删除相关内容。