-
说明:
这里对目前为止接触到的人脸数据集的清洗进行一个汇总。
此文中把人脸数据集分为三种类型(本分类仅在此文中有效):
1. 野数据集
这里的野数据集指的是从互联网上爬取出的原始人脸数据集,它的特点是:脏、乱、杂 。数据分布不均匀、尺寸不一、存在年龄跨度较大现象,图像格式命名错误且存在较多的冗余、杂质,但优点是类别数目大。使用前需要做深度清洗的工作。
野数据集诸如目前收集的:Chinese-Face, Japanese-Face, Korean-Face数据集。
2. 常用开源数据集
这里的常用开源数据集指的是业内公开常用的数据集,这种数据集由于其尺寸统一和pose较全的特点,可以直接作为训练集或者测试集来使用,但也存在较少的杂质与冗余人脸图像,需要做中度清洗。常用开源数据集有:CASIAWeb-Face, VGG2, MS1M, Asian-celeb等数据集。
3. 二次清洗数据集
这里是指在常用开源数据集的基础上又进行了一次处理。主要是为了保证开源数据集的多样性与类别均衡性。但此类数据集仍然存在小部分的异类人脸图像以及类别分布不均的情况,需要稍作冗余与异类清洗即可。
二次清洗数据集有:DeepGlint的两个数据集train-celebrity和train-msra。
清洗原因:
数据清洗是数据准备的第一步,数据准备是人脸识别模型训练的首要步骤,也是给卷积神经网络提供比较干净的人脸图像数据进行学习并训练以便得到可用的、符合特定应用场景的、识别精度较高的人脸识别模型。
由于爬取得到的人脸数据图像中都会存在噪声图像与无关图像,无论前期从互联网上爬取时工作做的如何疏而不漏,都不能保证最终获取的人脸数据一定符合训练的要求,而掺杂的‘脏’数据往往是影响人脸识别模型训练最终结果的关键。这就需要人工在得到原始数据后对数据完成清洗、增广、提高数据质量等工作。但随着数据量的增加,人工无法满足当下数据清洗的工作提供给后续的开发人员,为了节省时间和提高处理精度,于是编写了清洗工具便于更高效的处理。以下是对清洗过程的介绍。
清洗思路:
主要是通过人脸检测和识别算法检测并清洗同一个类别下的人脸照片中掺杂的噪声图像与无关图像,并在同一类别下的人脸图像中计算两两之间的人脸特征余弦距离,再根据余弦距离求其相似度去除冗余图像,即筛掉异类与冗余,同时保证数据的均衡性与多样性。
各类数据集清洗方法介绍:
在做人脸数据清洗之前需要对将要清洗的数据集进行评估,根据评估结果了解该数据集。包括其数据分布可视化以及数据质量评估两个步骤。根据数据分布可视化可以了解数据的分布情况,便于设置数据均衡阈值。根据数据质量评估结果,便于设置人脸质量清洗阈值。
数据分布可视化采用Matplotlib来实现,在可视化之前,需要得到数据类别数目以及人脸图像数据量的csv文件,可以使用shell得到csv文件。
csv文件生成代码:
#!/bin/bash
find ./ -type d |while read line
do
pwd=${PWD}
cd $line
echo -e "${line#*/},`ls | wc -l`"
cd $pwd
done
数据分布可视化python代码:
asian_log = pd.read_csv("list2.csv")
plt.figure()
ax = plt.subplot(2,2,1)
ax.set_title("Distribution Curve")
Num = range(len(asian_log['name']))
ax.scatter(Num,asian_log['count'])
ax.set_xlabel('class')
ax.set_ylabel('class count')
#plt.legend(loc='upper left')
plt.legend(loc='upper right')
plt.show()
野数据集的清洗方法:
>> 针对于该类数据集建立便于处理的数据分布形式。 如下图所示,箭头表示包含关系,数据集下包含类别文件夹,每个类别下包括该类别的人脸图像。
整理函数如下:
def fileset(path):
if os.path.isdir(path):
lists = os.listdir(path)
#print(lists)
for i in lists:
#print(i)
path = path + '/' + i
if os.path.isfile(image_path):
save_name = image_path.split('/')[7]
save_path = image_file_dir + '/' + save_name
print(image_path)
#print(image_path == save_path)
save_image = save_path + '/' + i
print(save_image)
if image_path != save_image :
shutil.move(image_path, save_path)
if image_path == save_image:
print('This image already exists!')
>> 删除空文件夹以及将图像信息为空的进行删除
def delete_empty_dir(path):
if os.path.isdir(path):
lists = os.listdir(path)
for item in lists:
if item!='System Volume Information':
delete_empty_dir(os.path.join(path, item))
if not os.listdir(path):
os.rmdir(path)
print('移除空目录:' + path)
def delete_empty_image(path):
if os.path.isdir(path):
lists1 = os.listdir(path)
for i in lists1:
image_path = path + '/' + i
print(image_path)
save_name = image_path.split('/')[7]
print(save_name)
save_path = image_file_dir + '/' + save_name + '/'
#print(save_path)
if os.path.isdir(image_path):
lists2 = os.listdir(image_path)
L1 = len(lists2)
print(L1)
for j in range(L1):
print(j)
img_name = lists2[j]
print(img_name)
with open(save_path + '{}'.format(str(img_name)),'rb') as fp:
data = fp.read()
if(len(data) != 0):
pass
else:
os.remove(save_path + '{}'.format(str(img_name)))
>> 图片格式校验,是将非法命名格式的图像进行纠正。
def format_convert(path):
if os.path.isdir(path):
file_lists = os.listdir(path)
print('file_lists:',file_lists)
for i in file_lists:
print('i:',i)
image_path = path + '/' + i
print('file_path:',image_path)
if os.path.isdir(image_path):
print('pass')
pass
elif os.path.isfile(image_path):
print('Judging image format type:')
true_type = imghdr.what(image_path)
print(true_type,image_path)
image_type = image_path.split('.')[1]
image_name = image_path.split('.')[0]
print('image_type:',image_type)
if image_type == true_type:
print('The image format type is right!\n',image_path)
pass
elif true_type == None:
print('This is not a image!\n','Delete:',image_path)
os.remove(image_path)
else:
print('The image format type is wrong!\n','Do:')
old_image_name = image_path
new_image_name = image_name + '.' + true_type
newName = image_path.replace(old_image_name,new_image_name)
print(image_path,'---->',newName)
os.rename(os.path.join(i, image_path),os.path.join(i,newName))
print('Done.')
数量均衡化处理
说明:
清洗工作基于caffe平台使用了人脸检测模型MTCNN以及a、b、c三种人脸识别模型,人脸识别模型的选取可以根据训练或测试所使用的图像尺寸大小来选择。
a) caffe face 图像尺寸为:112*96
b) face net 图像尺寸为:160*160
c) insight face 图像尺寸为:112*112
以上步骤主要针对于野数据集清洗的第一步,下面是所有类型的数据集需要操作的步骤。
>> 删除异类人脸图像,洗掉脏数据:
def List_ordering(distances,class_face_num):
ordering_lists=[]
k=range(len(distances))
for i in k:
if distances[i][0]<0.5:
ordering_lists+=[distances[i]]
return ordering_lists
if __name__ == "__main__":
# face_model = './facenet_model/myfacenet.prototxt' ####160*160 ###几个人脸识别模型的切换
# face_weights = './facenet_model/myfacenet.caffemodel'
# face_model = './caffe_caffemodel/face_deploy.prototxt'###112*96
# face_weights = './caffe_caffemodel/face_model.caffemodel'
face_model = './insight_caffemodel/model.prototxt' ###112*112
face_weights = './insight_caffemodel/face_model.caffemodel'
net = caffe.Net(face_model, face_weights, caffe.TEST)
transformer = caffe.io.Transformer({'data': net.blobs['data'].data.shape})
transformer.set_transpose('data', (2, 0, 1))
file_lists = os.listdir(image_file_dir)
class_num_lists = []
for dir in file_lists:
path = image_file_dir + '/' + dir
class_face_num, faces_lists, image_name_lists, names_lists, image_files_lists,class_features_lists=Get_faces(path,net)
print('image_name_lists:', image_name_lists)
distances=Distance_calculation(class_features_lists)
orig_mean_num = pd.DataFrame(columns=None, data=distances)
orig_mean_num.to_csv(path_or_buf=path + '/orig_test10.csv', encoding='gbk')
# print('len(distances:)',len(distances))
ordering_lists=List_ordering(distances,class_face_num)
# print('ordering_lists:', ordering_lists)
mean_num = pd.DataFrame(columns=None, data=ordering_lists)
mean_num.to_csv(path_or_buf=path + '/test10.csv', encoding='gbk')
>> 保证数据均衡性,这里有两种数据均衡的方法:
- 先设置图像数量阈值进行数据划分,再裁出满足人脸质量阈值的人脸。
- 直接使用人脸检测算法和人脸识别算法并设置均衡阈值,得到最终均衡后的人脸数据集。
第一种方法比较容易处理,方便调试,但操作流程较为麻烦,第二种方法速度太慢,但流程简单,是直接出结果。
第一种方法介绍:
>> 按图像数量阈值进行数据划分:
if DB_FLAG == 'Celebrity':
file_lists=os.listdir(image_file_dir)
class_num_lists = []
total_class_num=len(file_lists)
for dir in file_lists:
path=image_file_dir+'/'+dir
class_count+=1
progress = (class_count / total_class_num) * 100
print('This DS has been processed by %.2f%%' % progress)
class_face_num,faces_lists,image_name_lists,names_lists,image_files_lists=Get_faces(path)
print('class_face_num1:', class_face_num)
print('class_count:', class_count)
#print('faces_lists:',faces_lists)
class_num_lists += [class_face_num]
if class_face_num >= 10: ##设置图像数量阈值进行选取类别,并另存为
print('Save this file directly!')
for i in range(class_face_num):
image_file_path = image_files_lists[i]
name = image_file_path.split('/')[8]
names = image_file_path.split('/')[9]
save_to_file = './celebrity10++_aligned_112*112/' + name + '/' ##新建文件夹将选取出的类别图像得以保存
print(save_to_file)
mkdir(save_to_file)
# for j in range(len(faces_lists)):
cv2.imwrite(save_to_file + '%s' % names, faces_lists[i])
del i
elif class_face_num==0:
print('This file has no faces!')
else:
pass
#print('class_num_lists:', class_num_lists)
del dir,total_class_num,file_lists
>> 设置人脸图像质量阈值和图像尺寸,裁出符合条件的人脸
def generate_celebrity_db_aligned():
mtcnn_model_path = "./mtcnn/model"
detect_threshhold = 0.90
quality = 0
multi_faces = False
caffe.set_mode_gpu()
image_files = []
names = []
image_files, names = utility.get_imagefiles_from_dir(db_image_file_dir,True)
print(len(image_files))
print(image_files[0:10])
facedetector = utility.initFaceDetector(mtcnn_model_path)
minsize = facedetector[0]
PNet = facedetector[1]
RNet = facedetector[2]
ONet = facedetector[3]
threshold = facedetector[4]
factor = facedetector[5]
faces = []
for j in range(len(image_files)):
im_file = image_files[j]
im = cv2.imread(im_file)
faces = utility.crop_and_align(im, minsize, PNet, RNet, ONet, threshold, factor, detect_threshhold, quality, multi_faces)
if len(faces) == 1:
save_to_file = './db_aligned_test/' + names[j] + '/'
utility.mkdir(save_to_file)
save_branch2 = im_file.split('_')[1]
cv2.imwrite(save_to_file + names[j] + save_branch2, faces[0])
elif len(faces) == 0:
print('cannot found face in file %s' % im_file)
elif len(faces) > 1:
print('too many faces in file %s' % im_file)
if j % 50 == 0:
print('processed %d pics' % j)
第二种方法介绍:
>> 直接输出处理结果
if DB_FLAG == 'Celebrity':
file_lists=os.listdir(image_file_dir)
class_num_lists = []
total_class_num=len(file_lists)
for dir in file_lists:
path=image_file_dir+'/'+dir
class_count+=1
progress = (class_count / total_class_num) * 100
print('This DS has been processed by %.2f%%' % progress)
class_face_num,faces_lists,image_name_lists,names_lists,image_files_lists=Get_faces(path)
print('class_face_num1:', class_face_num)
print('class_count:', class_count)
#print('faces_lists:',faces_lists)
class_num_lists += [class_face_num]
if class_face_num >= 10:
pass
elif class_face_num ==0:
print('This file has no faces!')
elif class_face_num < 10:
print('Save this file directly!')
print('class_face_num:',class_face_num)
for i in range(class_face_num):
# print('####################################################################')
image_file_path = image_files_lists[i]
name = image_file_path.split('/')[8]
names = image_file_path.split('/')[9]
save_to_file = './celebrity_val_aligned_112*112/' + name + '/'
print('save_to_file:',save_to_file)
mkdir(save_to_file)
# for j in range(len(faces_lists)):
cv2.imwrite(save_to_file + '%s' % names, faces_lists[i])
# del i
save_class_count1+=1
print('save_class_count1:',save_class_count1)
if save_class_count1==7500:
print('full!')
break
# print('class_num_lists:', class_num_lists)
del dir,total_class_num,file_lists
保证数据多样性
如果不单为了保证数据的均衡性,还保留其数据的多样性,那么还需做以下操作:
注:保留其多样性的代码只完成了一部分,思路连同代码一并给出。
思路: 利用神经网络的全连接层FC输出得到的人脸特征feature,通过计算单个类别下的人脸图像两两之间的余弦距离,在保证相同类别的前提下判断其距离大小,距离越大,认为其人脸差异性越大,距离越小,则相反,此种方法不仅可以保留其数据的多样性,同时还过滤掉了冗余的图像,且方法得以验证。
余弦距离计算的代码实现:
def dist(a,b):
score = np.dot(a,b)/(np.linalg.norm(a)*np.linalg.norm(b))
distance = 1 - score
return distance
人脸特征提取的代码实现:
def extract_feature_512(net,transformer,img):
# img read from cv2.imread
img = (img - 127.5) / 128
net.blobs['data'].data[...] = transformer.preprocess('data',img) #将图片载入到blob中
net.forward()
feature = np.float64(net.blobs['fc5'].data[0])
return feature
同一类别下,计算两两距离的代码实现:
def Distance_calculation(class_features_lists):
distances=[]
k = range(len(class_features_lists))
for i in k:
# print('i:',i)
K = range(i + 1, len(class_features_lists))
for j in K:
# print('j:',j)
distance=dist(class_features_lists[i],class_features_lists[j])
# print('distance:',distance)
if distance:
distances += [[distance,[i,j]]]
orig_mean_num = pd.DataFrame(columns=None, data=distances)
orig_mean_num.to_csv('./orig_test10.csv', encoding='gbk')
return distances