基于Tensorflow框架的人脸检测总结附代码(持续更新)
基于Tensorflow框架的人脸匹配总结附代码(持续更新)
基于Tensorflow框架的人脸对齐、人脸关键点检测总结附代码(持续更新)
基于Tensorflow框架的人脸活体检测、人脸属性总结附代码(持续更新)
最近利用人脸开源数据库WIDER FACE、64_CASIA-FaceV5、CelebA、300W_LP以及自己做的一个近200张照片的私人数据集复现了人脸检测、人脸匹配、人脸对齐、人脸关键点检测、活体检测、人脸属性等功能,并且将其集成在微信小程序中,以上所有内容将用大概5篇博客来总结。所有数据集下载链接附在下方,所有完整代码附在GitHub链接
WIDER FACE 数据集,包括32203个图像和393703个人脸图像,其中在尺度、姿势、遮挡、表情、装扮、光照等均有所不同。
LFW数据集,是目前人脸识别的常用测试集,其中提供的人脸图片均来源于生活中的自然场景,因此识别难度会增大,尤其由于多姿态、光照、表情、年龄、遮挡等因素影响导致即使同一人的照片差别也很大。并且有些照片中可能不止一个人脸出现,对这些多人脸图像仅选择中心坐标的人脸作为目标,其他区域的视为背景干扰。LFW数据集共有13233张人脸图像,每张图像均给出对应的人名,共有5749人,且绝大部分人仅有一张图片。每张图片的尺寸为250X250,绝大部分为彩色图像,但也存在少许黑白人脸图片。
64_CASIA-FaceV5 该数据集包含了来自500个人的2500张亚洲人脸图片。
CelebA 包含10,177个名人身份的202,599张人脸图片,每张图片都做好了特征标记,包含人脸bbox标注框、5个人脸特征点坐标以及40个属性标记,广泛用于人脸相关的计算机视觉训练任务,可用于人脸属性标识训练、人脸检测训练以及landmark标记等。
300W_LP 该数据集是3DDFA团队基于现有的AFW,IBUG,HEPEP,FLWP等2D人脸对齐数据集,通过3DMM拟合得到的3DMM标注,并对姿态,光照,色彩等进行变化以及对原始图像进行flip(镜像)得到的一个大姿态3D人脸对齐数据集。
文章目录
*
- 1. 人脸检测业务介绍
- 2. 人脸检测方法介绍
- 3. 人脸检测遇到的问题以及解决方法
- 4. 基于Tensorflow + SSD编程实现
; 1. 人脸检测业务介绍
人脸检测:实际上是在包含人脸的图像中,能够精确识别人脸位置,并用检测框(矩形框)标记,检测框位置通常由(x,y,w,h)四个参数控制,(x,y)为检测框左上角位置坐标,(w,h)代表矩形框的大小;另外也可以由(x1,y1,x2,y2)来表示,(x1,y1)表示矩形框的左上角坐标,(x2,y2)表示矩形框的右下角坐标。人脸检测问题实际上属于目标检测问题,因此,与目标检测方法互通,同时检测人脸
也是图像检测以及图像分割的问题,在得到人脸之后,方便进行接下来的人脸关键点定位以及人脸属性分析等问题。
人脸检测评价指标:
(1)检测率、误报率
检测率:正确检出人脸数量 / 真实人脸数量 ; 误报率: 错误检测 / 总人脸数量
每一个标记只允许有一个检测与之相对应(使用NMS,非极大值抑制)且重复检测视为错误检测
(2)ROC曲线、PR曲线
ROC曲线: ROC的全名叫做Receiver Operating Characteristic,中文名字叫"受试者工作特征曲线",其主要分析工具是一个画在二维平面上的曲线——ROC 曲线。平面的横坐标是false positive rate(FPR),纵坐标是true positive rate(TPR)。对某个分类器而言,我们可以根据其在测试样本上的表现得到一个TPR和FPR点对。这样,此分类器就可以映射成ROC平面上的一个点。调整这个分类器分类时候使用的阈值,我们就可以得到一个经过(0, 0),(1, 1)的曲线,这就是此分类器的ROC曲线。 什么是ROC曲线?为什么要使用ROC?以及 AUC的计算
PR曲线:PR曲线中的P代表的是precision(精准率),R代表的是recall(召回率),其代表的是精准率与召回率的关系,一般情况下,将recall设置为横坐标,precision设置为纵坐标。 PR曲线详解
(3)速度:FPS(帧率)
FPS:刷新率,帧率越大,画面越流畅;帧率越小,画面越有跳动感。
一些概念的定义
(4)IOU交并比:简单理解为 A∩B / A∪B
; 2. 人脸检测方法介绍
(1)传统人脸检测方法
VJ框架:利用了积分图特征,使用Adaboost分类器辨别是否为人脸,计算量偏大,参数多。走近人脸检测(2)——VJ人脸检测器及其发展
DPM:最早出现于是应用在目标检测当中,属于HOG算法(纹理特征)的改进,利用手工设计+分类器(SVM、Adaboost、随机森林)模型实现功能。DPM目标检测算法(毕业论文节选)
Casecade:JDA算法(级联人脸检测模型)主要进行边缘分布和条件分布的自适应,同时适配两个分布,然后非常精巧地规到了一个优化目标里。用弱分类器迭代,最后达到了很好的效果,具体详解参考:上手实践ICCV2013的JDA(Joint Distribution Adaptation)方法
《小王爱迁移》系列之二:联合分布适配(JDA)方法简介
(2)从粗粒度到细粒度的级联模型
Cascade CNN:使用尺度级联的方式检测人脸,通过不同尺寸的图片输入来达到较优效果,具体详解参考: 人脸检测——CascadeCNN
Faceness-Net:通过根据面部部位的空间结构和排列来对面部部位的反应进行评分,从而从新的角度寻找面部。考虑到仅部分可见面部的挑战性情况,精心制定了评分机制。根据这一点,网络可以检测到严重遮挡和不受约束的姿势变化下的人脸,这是大多数现有人脸检测方法的主要困难和瓶颈。
详解参考:人脸检测概述(不是人脸识别)
MTCNN/ICC-CNN:使用多尺度级联方式,同时融合人脸+关键点多任务,主题框架类似于cascade。总体可分为P-Net、R-Net、和O-Net三层网络结构。
详解参考:MTCNN工作原理
(3)通用目标检测算法 + 基于人脸问题的优化
随着目标检测算法的不断创新优化,各种基础网络层出不穷:Face RCNN → SSH/RSA → SFD/DSF/AFD → PyramidBox → FaceBoxes → FaceGAN
上述方法部分简介参考博客:
Face R-CNN
PyramidBox 人脸检测算法
人脸检测:Faceboxes(IJCB2017)
FACEGAN: Facial Attribute Controllable rEenactment GAN
; 3. 人脸检测遇到的问题以及解决方法
(1)人脸可能出现在图像中的任何一个位置;
解决方法:由于人脸可能出现在图像的任何位置,在检测时用固定大小的窗口对图像从上到下、从左到右扫描,判断窗口里的子图像是否为人脸,这称为滑动窗口技术(sliding window)。
(2)人脸可能有不同的大小;
解决方法:提取多尺度特征以及对尺度不敏感的特征,同时考虑Anchor使用;
(3)人脸在图像中可能有不同的视角和姿态;
解决方法:解决低头、侧脸、抬头下的人脸数据预处理问题,对算法进行拆解,对其主干网络、Anchor进行优化,同时挑选合适的loss达到提高性能的效果。
(4)人脸可能被部分遮挡;
解决方法:人群密集时采集的数据,首先选择合适的图像预处理手段,对算法进行拆解后,同样选择合适的主干网络进行优化,选择合适的Anchor参数,使用NMS等目标检测算法。
(5)小人脸检测问题
解决方法:解决感受野问题,进行数据增强处理,提取小尺度特征,择取合适的loss,以及选用改进的NMS算法等。
人脸检测算法综述
格灵深瞳:人脸识别最新进展以及工业级大规模人脸识别实践探讨 | 公开课笔记...
; 4. 基于Tensorflow + SSD编程实现
(1)SSD模型介绍
SSD模型是一种one-stage 方法,属于端对端的训练,直接回归目标类别和位置,主干网络为VGGNet,将VGGNet的最后两层替换为卷积层,之后执行多次下采样,得到不同尺度特征图,根据特征图进行目标区域定位并作为检测层的输入,同时在检测层中定义Default bounding boxes,然后完成对类别分数、偏移量的预测。
Anchor:特征图作为检测层的输入,在对其进行处理时,Anchor为特征图上的每一个点,每个点有C个通道,之后将Anchor映射到原图中,用以计算回归以及分类。
Default box:特征图(m _n)上存在m * n个单元,每个单元便是一个Anchor,且每个单元生成多个固定尺度和不同长宽比的区域便是box;假设特征图有m_n个单元,每个单元对应 k 个default box,每个default box预测 c 个类别概率分布和4个坐标,则会生成 k(c+4)(m)n个输出值。
样本构造*:
正样本:根据真实GT box 找到最匹配的piror box 放入候选正样本集,与GTbox满足IOU > 0.5;
负样本:难例挖掘(OHEM),正负样本比为1:3;
SSD(Single Shot MultiBox Detector)模型介绍
SSD模型详解
(2)人脸数据清洗以及数据打包
WIDER FACE数据集标注有很多标签,其中有部分标签在做人脸检测过程中时不需要的,因此在此要有一个数据清洗的过程,同时呢在数据集处理结束后要将数据进行打包,方便后续模型的训练。
数据清洗 关键代码如下:
rootdir = "E:/Python/widerface"
gtfile = "E:/Python/widerface/wider_face_split/wider_face_test_filelist.txt";
im_folder = "E:/Python/widerface/WIDER_test/images";
fwrite = open("E:/Python/widerface/Main/test.txt", "w")
with open(gtfile, "r") as gt:
while(True):
gt_con = gt.readline()[:-1]
if gt_con is None or gt_con == "":
break
im_path = im_folder + "/" + gt_con;
print(im_path)
im_data = cv2.imread(im_path)
if im_data is None:
continue
sc = max(im_data.shape)
im_data_tmp = numpy.zeros([sc, sc, 3], dtype=numpy.uint8)
off_w = (sc - im_data.shape[1]) // 2
off_h = (sc - im_data.shape[0]) // 2
im_data_tmp[off_h:im_data.shape[0]+off_h, off_w:im_data.shape[1]+off_w, ...] = im_data
im_data = im_data_tmp
numbox = int(gt.readline())
bboxes = []
for i in range(numbox):
line = gt.readline()
infos = line.split(" ")
for j in range(infos.__len__() - 1):
infos[j] = int(infos[j])
if infos[2] * 80 < im_data.shape[1] or infos[3] * 80 < im_data.shape[0]:
continue
bbox = (infos[0] + off_w, infos[1] + off_h, infos[2], infos[3])
bboxes.append(bbox)
filename = gt_con.replace("/", "_")
fwrite.write(filename.split(".")[0] + "\n")
cv2.imwrite("{}/JPEGImages/{}".format(rootdir, filename), im_data)
xmlpath = "{}/Annotations/{}.xml".format(rootdir, filename.split(".")[0])
writexml(filename, im_data, bboxes, xmlpath)
fwrite.close()
在对数据进行打包时,利用了models-master中dataset_tools工具中create_pascal_tf_record.py文件,在进行微调之后,观察下面列出代码为数据打包的内容
example = tf.train.Example(features=tf.train.Features(feature={
'image/height': dataset_util.int64_feature(height),
'image/width': dataset_util.int64_feature(width),
'image/filename': dataset_util.bytes_feature(
data['filename'].encode('utf8')),
'image/source_id': dataset_util.bytes_feature(
data['filename'].encode('utf8')),
'image/key/sha256': dataset_util.bytes_feature(key.encode('utf8')),
'image/encoded': dataset_util.bytes_feature(encoded_jpg),
'image/format': dataset_util.bytes_feature('jpeg'.encode('utf8')),
'image/object/bbox/xmin': dataset_util.float_list_feature(xmin),
'image/object/bbox/xmax': dataset_util.float_list_feature(xmax),
'image/object/bbox/ymin': dataset_util.float_list_feature(ymin),
'image/object/bbox/ymax': dataset_util.float_list_feature(ymax),
'image/object/class/text': dataset_util.bytes_list_feature(classes_text),
'image/object/class/label': dataset_util.int64_list_feature(classes),
'image/object/difficult': dataset_util.int64_list_feature(difficult_obj),
'image/object/truncated': dataset_util.int64_list_feature(truncated),
'image/object/view': dataset_util.bytes_list_feature(poses),
}))
return example
(3)人脸模型训练、调试与测试
模型训练使用models-master中的model_main.py文件,同时使用ssd_resnet_v1_fpn_feature_extractor.py文件进行训练;
修改ssd_resnet50_v1_fpn_shared_box_predictor_640x640_face_sync.config配置文件,代码如下:
train_config: {
batch_size: 12
sync_replicas: true
startup_delay_steps: 0
replicas_to_aggregate: 8
num_steps: 100000
data_augmentation_options {
random_horizontal_flip {
}
}
data_augmentation_options {
random_crop_image {
min_object_covered: 0.0
min_aspect_ratio: 0.75
max_aspect_ratio: 3.0
min_area: 0.75
max_area: 1.0
overlap_thresh: 0.0
}
}
optimizer {
momentum_optimizer: {
learning_rate: {
cosine_decay_learning_rate {
learning_rate_base: .04
total_steps: 1000 00
warmup_learning_rate: .013333
warmup_steps: 2000
}
}
momentum_optimizer_value: 0.9
}
use_moving_average: false
}
max_number_of_boxes: 100
unpad_groundtruth_tensors: false
}
model_main.py中的核心代码:
def extract_features(self, preprocessed_inputs):
"""Extract features from preprocessed inputs.
Args:
preprocessed_inputs: a [batch, height, width, channels] float tensor
representing a batch of images.
Returns:
feature_maps: a list of tensors where the ith tensor has shape
[batch, height_i, width_i, depth_i]
"""
preprocessed_inputs = shape_utils.check_min_image_dim(
129, preprocessed_inputs)
with tf.variable_scope(
self._resnet_scope_name, reuse=self._reuse_weights) as scope:
with slim.arg_scope(resnet_v1.resnet_arg_scope()):
with (slim.arg_scope(self._conv_hyperparams_fn())
if self._override_base_feature_extractor_hyperparams else
context_manager.IdentityContextManager()):
_, image_features = self._resnet_base_fn(
inputs=ops.pad_to_multiple(preprocessed_inputs,
self._pad_to_multiple),
num_classes=None,
is_training=None,
global_pool=False,
output_stride=None,
store_non_strided_activations=True,
min_base_depth=self._min_depth,
depth_multiplier=self._depth_multiplier,
scope=scope)
image_features = self._filter_features(image_features)
depth_fn = lambda d: max(int(d * self._depth_multiplier), self._min_depth)
with slim.arg_scope(self._conv_hyperparams_fn()):
with tf.variable_scope(self._fpn_scope_name,
reuse=self._reuse_weights):
base_fpn_max_level = min(self._fpn_max_level, 5)
feature_block_list = []
for level in range(self._fpn_min_level, base_fpn_max_level + 1):
feature_block_list.append('block{}'.format(level - 1))
fpn_features = feature_map_generators.fpn_top_down_feature_maps(
[(key, image_features[key]) for key in feature_block_list],
depth=depth_fn(self._additional_layer_depth),
use_native_resize_op=self._use_native_resize_op)
feature_maps = []
for level in range(self._fpn_min_level, base_fpn_max_level + 1):
feature_maps.append(
fpn_features['top_down_block{}'.format(level - 1)])
last_feature_map = fpn_features['top_down_block{}'.format(
base_fpn_max_level - 1)]
for i in range(base_fpn_max_level, self._fpn_max_level):
last_feature_map = slim.conv2d(
last_feature_map,
num_outputs=depth_fn(self._additional_layer_depth),
kernel_size=[3, 3],
stride=2,
padding='SAME',
scope='bottom_up_block{}'.format(i))
feature_maps.append(last_feature_map)
return feature_maps
之后利用models-master → research → object_detection →export_inference_graph.py文件将模型转化为pb文件,后续调试、测试代码为test_model.py以及相关代码,GitHub
(4)人脸检测模型优化改进策略
更好的学习策略;
更好的损失函数;
更好的泛化技术;
更好的预测方案;
参考博客:调参:深度学习模型24种优化策略
总结:此部分代码基于tensorflow接口而进行训练、调试、测试等步骤,在之后模型训练中会改用其他方式。
Original: https://blog.csdn.net/weixin_46236212/article/details/122570929
Author: 问言
Title: 基于Tensorflow框架的人脸检测总结附代码(持续更新)
相关阅读
Title: Speaker Diarization
Speaker Diarization(声纹分割聚类、说话人日志),解决的问题是"who spoke when",即给定一个包含多人交替说话的语音,需要判断每个时间点是谁在说话。
技术流程框架
; 1、语音检测
利用语音检测模型(如VAD),将音频帧逐帧分为语音(speech,即有人说话)和非语音(non-speech,即无人说话)两个类别。其中非语音可能包括静音、噪音、音乐等。
2、语音分割/说话人转换检测
对于一段语音音频,分割的目标是 分割后的每段音频只有一个说话人。有两种方法可以把整段语音切分为多个小段:
(1)定长切分
通常可以把每段长度设为 0.5秒 ~ 2秒 之间。例如将窗口长度设置为1.0s, 然后将整段语音音频按每1.0s为一个片段进行切分, 其中, 两个相邻片段之间的重叠时长为0.5s。
优点:简单、不需要模型;
缺点:片段太长时,可能会包含说话人转换点;判断太短时会导致说话人声纹信息不足,识别准确率下降。
(2)说话人转换检测模型(Speaker Change Detection,SCD)
训练说话人转换检测模型(Speaker Change Detection,SCD),以SCD预测的转换点进行切分。注:SCD只判断转换点,但并不知道转换后的说话人是哪个(说话人数量>2时)。所以SCD后还是需要后续的聚类步骤。
SCD和聚类的区别如下:
SCD有两种,如下图所示,
缺点:SCD的准确率严重影响声纹分割聚类整个系统的效果。
; 3、声纹嵌入码
使用预训练好的 声纹识别模型, 将声纹识别模型中的声纹特征提取模块单独取出, 然后将上一步骤切分好的所有音频片段依次放入声纹提取模块进行处理, 通过声纹提取模块, 提取出各个音频片段的声纹向量信息(如i-vector、x-vector等)
将音频各个片段的声纹向量进行拼接, 拼接为一个矩阵, 矩阵的横坐标为时间维度, 单位为一个窗口的大小, 纵坐标为声纹向量的维度, 拼接后的矩阵我们称为该音频的声纹向量矩阵。
4、聚类分析
聚类作用
输入大量片段的声纹嵌入码,聚类算法将会输出每个片段对应的聚类标签,这个聚类标签就可以作为说话人身份标记。
一般先构建相似度矩阵,然后再做聚类(聚类算法的输入是相似度矩阵)。
音频的相似度矩阵用于描述该音频各个片段之间的相似度, 我们使用该音频的 声纹向量矩阵的转置 * 声纹向量矩阵 得到音频的相似度矩阵, 该相似度矩阵中, 每个元素的值代表了该行的索引对应的音频片段声纹信息和该列的索引对应的音频片段声纹信息的相似度。
得到相似度矩阵之后, 就可以将相似度矩阵进行聚类, 来对各个音频片段进行聚类, 片段之间相似度符合设定阈值的两个片段聚为一类。
常用的聚类算法有AgglomerativeClustering(层次聚类)、SpectralClustering(谱聚类)、KMeans。
5、二次分割
基于聚类结果获得的分割结果在分界线附近会有歧义,比如两个类重叠的区域应该划分到哪个类,二次分割的目的就是在前面聚类结果的基础上进行更精细的切分,获得更精确的结果。
以上是传统的聚类方法,此外还有监督式聚类方法(不再详细介绍):
参考:
1、https://zhuanlan.zhihu.com/p/338656027
2、https://zhuanlan.zhihu.com/p/394397963
3、https://www.bilibili.com/video/BV1Ky4y1876H
; 评价指标(Diarization Error Rate)
一般是对模型输出结果尝试各种说话人的排列,最后选效果最好的说话人分配方法计算Diarization Error Rate (DER,较常用)作为评价指标。
(还有其他的评价指标:词分割聚类错误率WDER、雅卡尔错误率JER)
上图中Reference是ground truth,Hypothesis是Speaker Diarization预测出来的结果,其中,
Miss : 属于说话人A的时长, 但系统没有分到说话人A音频中;
False Alarm : 被系统误分到说话人A音频中, 但实际不属于说话人A的时长;
Overlap : 被系统分为说话人A和说话人B同时说话, 但实际没有同时说话的时长;
Confusion : 被系统分为说话人A的, 但是实际属于说话人B的时长;
Reference Length : 是整条音频的总时长。
DER的计算公式如下:
通常False Alarm和Miss都来自于语音检测或者语音分割,与声纹嵌入码、聚类分析过程无关,Overlap在大部分论文中都忽略掉了,Confusion比较重要,是由声纹分割聚类带来的错误,因此有时也只考虑这一项用来计算DER。
DER代码实现
1、github-simpleDER
上述链接主页有调用方法说明,主要实现部分在文件der.py中的函数DER(ref, hyp),其中ref和hyp分别是ground truth和模型输出的文件,文件格式为一系列分割好的语音段(speaker, start, end),对应(说话人id,开始时间,结束时间)。
该函数计算DER的公式为:
der = (union_total_length - optimal_match_overlap) / ref_total_length
union_total_length
:将ref和hyp的所有语音段取并集之后,计算并集的总长度。
optimal_match_overlap
:可以理解为ref和hyp中匹配正确的总语音时长。
ref_total_length
:ref总时长。
union_total_length
- optimal_match_overlap
的结果就是模型判断错误的语音时长,包括MISS、False Alarm和Confusion。
def DER(ref, hyp):
"""Compute Diarization Error Rate.
Args:
ref: a list of tuples for the ground truth, where each tuple is
(speaker, start, end) of type (string, float, float)
hyp: a list of tuples for the diarization result hypothesis, same type
as `ref`
Returns:
a float number for the Diarization Error Rate
"""
check_input(ref)
check_input(hyp)
ref_total_length = compute_total_length(ref)
cost_matrix = build_cost_matrix(ref, hyp)
row_index, col_index = optimize.linear_sum_assignment(-cost_matrix)
optimal_match_overlap = cost_matrix[row_index, col_index].sum()
union_total_length = compute_merged_total_length(ref, hyp)
der = (union_total_length - optimal_match_overlap) / ref_total_length
return der
2、speechbrain.utils.DER module(推荐)
使用speechbrain.utils.DER来根据 ref和sys的rttm文件直接计算DER,官方介绍以及使用样例,调用输出:
MS:Miss
FA :False Alarms
SER :Speaker Error Rates,也是Confusion
DER:Diarization Error Rates.
注:这个DER module的源代码来自:https://github.com/nryant/dscore(具体是通过调用脚本scorelib/md-eval-22.pl来实现的,该脚本的计算公式可以参考本链接)
参考:
1、https://github.com/wq2012/SimpleDER
2、https://github.com/nryant/dscore
3、https://speechbrain.readthedocs.io/en/latest/API/speechbrain.utils.DER.html
4、http://www.xavieranguera.com/phdthesis/node108.htmlOriginal: https://blog.csdn.net/weixin_44070509/article/details/123774888
Author: 44070509
Title: Speaker Diarization