DAN(Deep Alignment Network),与以往级联神经网络输入的是图像的某一部分不同,DAN各阶段网络的输入均为整张图片。当网络均采用整张图片作为输入时,DAN可以有效的克服头部姿态以及初始化带来的问题,从而得到更好的检测效果。之所以DAN能将整张图片作为输入,是因为其加入了关键点热图(Landmark Heatmaps),关键点热图的使用是本文的重要创新点。
注:速度略差,但想法很好。
创新点:
1)与以往的级联模型不同,网络模型输入是整张人脸图,可获取更多信息。
2)关键点热图。
DAN的基本框架:
迭代处理的框架,可以利用上一帧的预测结果来预测下一帧 Landmark 的位置。
(1)初始
输入灰度图I以及标准关键点模板 S 0 S{_{0}} S0,预测得到新的关键点位置 S 1 S{_{1}} S1。
注: S 0 S{_{0}} S0可取所有训练样本的平均关键点。
其中“FEED FORWARD NN”结构如下,输出136个值用于预测68个关键点:
新的关键点位置会送入 “CONNECTION LAYERS”,该网络结构如下:
首先计算一个 S 1 S{_{1}} S1到 S 0 S{_{0}} S0的相似变换矩阵 T 1 T{_{1}} T1。
注:这里不采用仿射变换,是为了防止局部畸变,而是采用了相似变换。
通过 T 1 T{_{1}} T1我们可以对图像进行矫正得到 T 1 ( I ) T{_{1}}(I) T1(I),同时对关键点进行变换得到关键点热图 H 1 H{_{1}} H1。
特征图 F 1 F{_{1}} F1是通过“FEED FORWARD NN”的fc1层特征进一步得到的。
其中关键点热图通过下式计算得到:(其实就是一个中心衰减,关键点处值最大,越远则值越小)。
其中特征图的计算如下:使用dense layer使fc1层输出单元为3136,使用ReLU激活,reshape为5656,然后上采样到112112,和输入图像一样大。(之所以一开始不直接生成112112,是因为实验发现提升不大但计算量会增大比较多。)目的:人为给CNN增加上一阶段信息。
注:图像的转换使用双线性插值。
(2)初始迭代
每次迭代输入 T N ( I ) T{_{N}}(I) TN(I), H N H{_{N}} HN和 F N F{_{N}} FN,它们的维度均为112112。然后计算新的 S t + 1 S{_{t+1}} St+1。
需要注意的是,由于图像进行了相似变换,因此为了和最初的输入图像相匹配,需要做如下矫正:
IBUG数据集上的第一阶段输出:
从图中发现,DAN要做的“变换”,就是把图片给矫正了。那么DAN对姿态变换具有很好的适应能力,或许就得益于这个“变换”。
实现过程:
数据准备: 扩大1.3倍人脸框裁剪图片,同时计算平均人脸关键点。
输入图像进行灰度化处理
网络学习的是已知标准关键点的偏移量。
第一阶段:
输入灰度图 -> 前向传播 -> 直到fc2层输出136个偏移值,再加上标准的平均关键点来计算损失(L2损失/双眼间距离做归一化)。
第二阶段:
1)计算相似变换矩阵,根据标准关键点和第一阶段输出的关键点。
2)对原始图像和第一阶段输出的关键点应用相似变换。
3)对变换后的输出关键点计算热图
4) 对第一阶段的fc1层增加一个全连接层,输出为56x56,之后reshape为[None,56,56,1],上采样到112x112大小得到特征图。
将变换后的原始图像、热图及特征图进行concat连接,作为第二阶段的输入,不断迭代,输出为fc2。
损失计算:fc2+变换后的关键点来作为最后的预测关键点,因为输入时进行了相似变换,
因此计算损失时,需要对预测的关键点进行反变换回原图。
…综上:可以增加N个阶段,每个阶段都需要关键点对齐操作。
热图的计算:
Python代码:
import os
import numpy as np
import itertools
import cv2
from matplotlib import pyplot as plt
np.set_printoptions(suppress=True,threshold=np.NaN)
HalfSize = 8 #半径的尺寸
IMGSIZE = 112 #图像的尺寸
#定义16*16*2的矩阵,0为y,从上到下[-8,7];1为x,从左到右[-8,7]。
Offsets = np.array(list(itertools.product(range(-HalfSize, HalfSize), \
range(-HalfSize, HalfSize))), dtype=np.int32).reshape((16,16,2))
#限定landmark的值域范围
def clip_by_value(landmark):
h,w = landmark.shape
for i in range(h):
for j in range(w):
if landmark[i][j] <HalfSize:
landmark[i][j] = HalfSize
if landmark[i][j] > (IMGSIZE-1-HalfSize):
landmark[i][j] = (IMGSIZE-1-HalfSize)
return landmark
#针对每个点位,画点热图
def draw_landmark(Point):
iniLandmark = Point.astype(np.int) #对点值取整
locations = Offsets + iniLandmark #移动16*16区域到关键点区域
dxdy = Point - iniLandmark.astype(np.float) #关键点的偏移量
offsetsSubPix = Offsets.astype(np.float) - dxdy #公式中的(x,y)- Si
vals = 1/(1 + np.linalg.norm(offsetsSubPix,axis =2)) #公式H(x,y)
img = draw_point(locations,vals)
return img
#在图像上画点
def draw_point(location,vals):
img = np.zeros((IMGSIZE,IMGSIZE,1),np.float32)
h,w,c = location.shape
for i in range(h):
for j in range(w):
img[location[i][j][0]][location[i][j][1]] = vals[i][j]
return img
if __name__ == '__main__':
aa = '0.162805 0.337997 0.160961 0.434602 *** *** 0.693376'
landmark = np.array(aa.strip().split()).astype(np.float32)*112
land = landmark.reshape((-1,2))[:,::-1] #reshape成68*2,并进行x和y转换
land = clip_by_value(land)
#定义68个点位热图
R = np.zeros((68,112,112,1)).astype(np.float32)
for i in range(68):
R[i] = draw_landmark(land[i])
L = np.zeros((112,112)).astype(np.float32)
#选取68个热图中值最大的进行保留
for i in range(112):
for j in range(112):
maxP = 0
for k in range(68):
if maxP < R[k][i][j][:]:
maxP = R[k][i][j][:]
L[i][j] = maxP
np.save('p2.6.np',L)
plt.imshow(L)
plt.show()
#pic = L.reshape((112,112,1))
#cv2.imshow('aaa',pic)
#cv2.waitKey(0)
最终的效果:
免责声明:本文系网络转载或改编,未找到原创作者,版权归原作者所有。如涉及版权,请联系删