正文:
1.前言
总结一下,目前为止做了什么:在前面几篇博文中,对数据进行了处理、搭建了神经网络、训练了神经网络并进行了测试。在实际使用中,因为检测的图片可能是整个人的图像,所以需要从这些图像中框出人脸的位置,这也是这个项目的目的。由于神经网络只能对一张含有人脸或者非人脸的图片进行判断,所以在检测一张图片时,需要先进行预处理。一张图片进行预处理以及神经网络的判断输出后,可能会有很多个结果,所以还要进行后处理,找出最优解。
2.预处理
-
滑动窗口
由于一张图片可能有很多个部分组成(人脸、非人脸)。所以需要先对输入的图片进行每部分的截取。滑动金字塔是按照固定的窗口大小在图片中滑动,从而得到很多个窗口,每个窗口都作为神经网络的输入。
上图展示了滑动窗口将一张图片分为很多张子图片,除此之位,窗口还记录了子图片在图片中的位置,最后可用于人脸标记。每张图片都输入到训练好的模型,如果神经网络的输出概率(神经网络softmax输出)大于我们设置的阈值,就可以认为这张子图片是人脸部分,进行位置标记。
下面是程序实现,函数返回子窗口图片和对应的位置。
import math def slid_win(input_img, win_size_h, win_size_w, slid_step,other): """滑动窗口函数""" #输入参数-输入、窗口w、窗口h和步长 #输出滑动图片、滑动坐标(坐标格式:[y1,y2,x1,x2] sli_images = [] locates = [] image_h, image_w, _ = input_img.shape lim_w = math.ceil((image_w-win_size_w)/ slid_step) lim_h = math.ceil((image_h-win_size_h)/slid_step) # print(lim_w, lim_h) for i in range(lim_h): strat_h = slid_step*i end_h = strat_h + win_size_h for j in range(lim_w): start_w = slid_step*j end_w = start_w + win_size_w # print(strat_h, end_h, start_w, end_w) if (end_h< image_h) or (end_w< image_w): part_img = input_img[strat_h:end_h, start_w:end_w] locate = [strat_h, end_h, start_w, end_w] sli_images.append(part_img) locates.append(locate) # print(locate) #添加没有滑动到的区域 if not isinstance(((image_w-win_size_w)/ slid_step), int): #判断横方向是不是为整数 不是标志有余留的边缘 for i in range(lim_h): #遍历纵轴 strat_h = slid_step*i end_h = strat_h + win_size_h start_w = image_w-win_size_w end_w = image_w part_img = input_img[strat_h:end_h, start_w:end_w] locate = [int(strat_h), int(end_h), int(start_w), int(end_w)] sli_images.append(part_img) # print(locate) locates.append(locate) if not isinstance(((image_h-win_size_h)/ slid_step), int):#判断纵方向是不是为整数 不是标志有余留的边缘 for j in range(lim_w): #遍历横轴 start_w = slid_step*j end_w = start_w + win_size_w strat_h = image_h-win_size_h end_h = image_h part_img = input_img[strat_h:end_h, start_w:end_w] locate = [strat_h, end_h, start_w, end_w] sli_images.append(part_img) # print(locate) locates.append(locate) condition1 = not isinstance(((image_w-win_size_w)/ slid_step), int) condition2 = not isinstance(((image_h-win_size_h)/ slid_step), int) if condition1 and condition2: #添加最后一张 start_w = image_w-win_size_w end_w = image_w strat_h = image_h-win_size_h end_h = image_h part_img = input_img[strat_h:end_h, start_w:end_w] locate = [strat_h, end_h, start_w, end_w] sli_images.append(part_img) # print(locate) locates.append(locate) #格式y1 y2 x1 x2 return sli_images, locates
-
图像金字塔
虽然滑动窗口可以把一张图片中的人脸和非人脸分离出来,但是还会存在一个问题,因为滑动窗口的尺寸是固定的,所以会存在窗口大小和人脸大小不匹配的情况。所以需要先对输入的图片进行尺寸变换的操作,使得滑动窗口的大小总能和人脸大小相互匹配。(图片来自于百度图片)
3.后处理
经过了预处理并输入到神经网络,且进行位置标记,得到的图片是这样的。
一点也不美观,也没有达到效果。这主要是由于阈值的设置(达到阈值就框出人脸)、滑动窗口步长和比例因子所造成的。所以这里需要进行非极大值抑制(NMS)处理,下面是NMS的算法思想。
NMS简单的说就是对框进行合并,合并的基准是概率最大对应的窗口。如果一个窗口和概率最大对应的窗口重叠面积足够大,就进行消除,只保留概率值最大所对应的窗口。根据这个思想进行程序编写如下。
def compuet_iou(rect1, rect2):
"""计算两个矩形的IOU"""
#rect是矩形的四个坐标 左上角和右下角坐标 rect = [x1,y1,x2,y2]
area1 = (rect1[3] - rect1[1]) * (rect1[2] - rect1[0])
area2 = (rect2[3] - rect2[1]) * (rect2[2] - rect2[0])
all_area = area1 + area2
x1 = max(rect1[0], rect2[0])
y1 = max(rect1[1], rect2[1])
x2 = min(rect1[2], rect2[2])
y2 = min(rect1[3], rect2[3])
h = max(0, y2 - y1)
w = max(0, x2 - x1)
overlap = h * w
#判断等于1情况
try :
iou = overlap/(all_area - overlap)
except:
iou = 1
else :
iou = overlap/(all_area - overlap)
return iou
def max_prop(locates, props):
"""取最大概率的nms"""
#输入参数:所有超过阈值的滑动窗位置、滑动窗口对应阈值
#输出 算法留下来的框位置
temp_locates = []
temp_prop = []
final_locates = []
return_locates = []
second_locates = []
second_prop = []
for i in range(len(locates)): #遍历当前所有框
temp_locate = [locates[i][2],locates[i][0],locates[i][3],locates[i][1]]
temp_locates.append(temp_locate)
temp_prop.append(props[i])
for j in range(len(locates)):
now_locate = [locates[j][2],locates[j][0],locates[j][3],locates[j][1]]
iou = compuet_iou(temp_locate,now_locate) #计算iou
if iou>0.3 and iou<1: #阈值设置
temp_locates.append(now_locate)
temp_prop.append(props[j])
second_locates.append(temp_locates)
second_prop.append(temp_prop)
temp_locates = [] #清零 下一次储存
temp_prop = []
for k in range(len(second_locates)):
area = []
for l in range(len(second_locates[k])):
area.append(second_prop[k][l]) #存储概率值
max_index = area.index(max(area)) #取最大概率
final_locates.append(second_locates[k][max_index])#取最大概率对应的框
# #整理列表 删除相同元素
for final_locate in final_locates:
if not final_locate in return_locates:
return_locates.append(final_locate)
return return_locates #return_locates #格式[x1,y1,x2,y2]
4.项目总结
至此,整个人脸检测项目就完成了。从数据处理到神经网络搭建、训练与测试,再到一张人脸图片的测试以及一张图片的测试。下面是这个项目的步骤总结。
- 确定数据集 --> 处理得到训练、验证以及测试的图片
- 搭建神经网络 --> 用1中的图片进行训练 --> 得到分类器模型
- 图片输入 --> 图像金字塔 --> 滑动窗口 --> 送入2中得到的分类器模型分类 --> 输出概率值并标记窗口 --> 非极大值抑制 --> 框出人脸
步骤 | 备注 |
---|---|
确定数据集 | FDDB或WIDER FACE或其他 |
处理得到训练、验证以及测试的图片 | 该内容在FDDB数据集处理、WIDER FACE数据集处理、数据集的标签生成中,数据也在其中。 |
搭建神经网络 | 按照 神经网络的搭建的框架进行搭建 |
用1中的图片进行训练 | 设置学习率和训练次数等参数进行训练。训练神经网络中的训练框架为了加快速度设置了比较小的batch、图片和训练次数(实际使用时,并不是使用这么小的batch、图片和训练次数得到模型),可以自主进行选择调节 |
得到分类器模型 | 训练搭建好的神经网络得到的模型。我的模型在这里获取人脸检测模型密码:f3vzk8 |
图片输入 | 选择一张图片或者视频的一帧 |
图像金字塔 | 本篇博文 |
滑动窗口 | 本篇博文 |
送入分类器模型分类 | 用验证神经网络中的图片验证程序进行图片输入,其中的输出为经过softmax后的概率值。(用于本博文中滑动窗口输入,输出的概率值与阈值比较) |
输出概率值并标记窗口 | 输出概率值指的是softmax输出,当这个值超出自己设定的阈值时就进行标记 |
非极大值抑制 | 本篇博文,经过非极大值抑制后的输出就是最终人脸检测结果 |