以下链接是个人关于FSA-Net(头部姿态估算) 所有见解,如有错误欢迎大家指出,我会第一时间纠正。有兴趣的朋友可以加微信:a944284742相互讨论技术。若是帮助到了你什么,一定要记得点赞!因为这是对我最大的鼓励。
姿态估计1-00:FSA-Net(头部姿态估算)-目录-史上最新无死角讲解
分析代码肯定要有一个好的思路,不能一头就钻进去。这是我第一次接触kreas深度框架,但是其对于我理解代码似乎并没有什么影响,毕竟万变不离其宗,上篇博客说了,无非就是处理数据,构建模型,加载数据,训练模型,保存模型这几个节奏。那么我就开始分析整体结构吧,这是这篇博客的工作。
之前的博客提到,其training_and_testing/FSANET_train.py的核心,在于各个model_type部分,下面是我复制的部分代码:
......
model = FSA_net_Var_Capsule(image_size, num_classes, stage_num, lambda_d, S_set)()
model = FSA_net_noS_Capsule(image_size, num_classes, stage_num, lambda_d, S_set)()
model = FSA_net_NetVLAD(image_size, num_classes, stage_num, lambda_d, S_set)()
......
可以看到,对于每个model_type都构建了一个类,构建类之后,还添加了一个()符号,这里说明什么?说明其构建类之后马上调用了_call_函数。我们选中一个类,如FSA_net_noS_Capsule,一路追踪下去,可以追踪到lib/FSANET_model.py文件中的BaseFSANet类,为什么会追踪到这里,因为他是所有网络结构类的基类,所以要先查看他,类似于砌房子要先打地基一样的道理,但是呢,这个类内部实现的函数还真的挺多的,暂时不去理会,我们先找到其中的def call(self):函数,这是本节的重点。该函数注释如下:
如果看得不是很懂,后面还有带读,不过我觉得聪明的你,看了注释,理解起来应该都是小问题了。注意,下小节我们再深究细节,现在我们只看整体架构即可,就是叫你不要去深究函数的实现过程。
def __call__(self):
logging.debug("Creating model...")
img_inputs = Input(self._input_shape)
# Build various models
# 构建论文中的两个stream网络,输入[b,64,64,3]得到三个[b,8,8,64]
ssr_G_model = self.ssr_G_model_build(img_inputs)
# 根据参数,确定是否构建论文中Scoring模型,输入三个[b,8,8,64],得到一个[b,3*7,64]
if self.is_noS_model:
# 三个[b,8,8,64]输入, 链接到一起成为[b,192,64]输出
ssr_S_model = self.ssr_noS_model_build()
else:
# 如果构建Scoring模型,其有两种方式,分别为使用1x1的卷积,或者通过方差计算
# 三个[b, 8, 8, 64]输入, num_primcaps参数为192,
# 输出[b,21,64]
ssr_S_model = self.ssr_S_model_build(num_primcaps=self.num_primcaps,m_dim=self.m_dim)
# 构建特征压缩模型,或者说胶囊网络,论文中的Feature aggregation操作
# 可以看到,这个函数是没有实现的,也就是由子类去实现,为什么呢?
# 前面的ssr_S_model模型,根据配置不一样,输出结果有两种可能,分别为[b,192,64]与[b,21,64]
# 继承该类的子类在构建网络的的时候,会根据is_noS_model参数实现合适的该函数
# 输出为3个[b,16]的特征向量
ssr_aggregation_model = self.ssr_aggregation_model_build((self.num_primcaps,64))
# 通过Feature aggregation得到三个特征向量,可以选择选择两种方式进行姿态估算
# self.F_shape默认为16,刚好耦合上个模型的输出,即输入为3个[b,16]
# 输出共九个结果,3个[b,3,3] 6个[b,3]
if self.is_fc_model:
# 直接使用对每个特征进行全连接层
ssr_F_Cap_model = self.ssr_FC_model_build(self.F_shape,'ssr_F_Cap_model')
else:
# 对每个特征进行合适的分割之后,再进行全链接
ssr_F_Cap_model = self.ssr_F_model_build(self.F_shape,'ssr_F_Cap_model')
# Wire them up,把模型链接起来,进行整体网络的搭建,即前向传播过程
# img_inputs[b,64,64,3] --> 三个[b,8,8,64]
ssr_G_list = ssr_G_model(img_inputs)
# 三个[b,8,8,64] --> ssr_primcaps[b, 3 * 7, 64]
ssr_primcaps = ssr_S_model(ssr_G_list)
# 根据子类的实现不一样,各自不同,但是输出结果为3个[b,16]
ssr_Cap_list = ssr_aggregation_model(ssr_primcaps)
# 3个[b,16] --> 3个[b,3,3] + 6个[b,3]
# ssr_F_Cap_list前三个形状为pred_s1,pred_s2,pred_s3[b,3,3], 为论文中的p
# ssr_F_Cap_list中间三个为delta_s1,delta_s2,delta_s3形状为[b,3],为论文中的Δ
# ssr_F_Cap_list后面三个为local_s1,local_s2,local_s3,为论文的的η
ssr_F_Cap_list = ssr_F_Cap_model(ssr_Cap_list)
# 根据输入3个[b,3,3] 以及 6个[b,3],进行姿态计算
# 输出pred_pose[b,3], 默认的self.stage_num为[3,3,3], self.lambda_d=1
pred_pose = SSRLayer(s1=self.stage_num[0], s2=self.stage_num[1], s3=self.stage_num[2], lambda_d=self.lambda_d, name="pred_pose")(ssr_F_Cap_list)
return Model(inputs=img_inputs, outputs=pred_pose)
ssr_G_model
首先我们看到其上的代码:
# 构建论文中的两个stream网络,输入[b,64,64,3]得到三个[b,8,8,64]
ssr_G_model = self.ssr_G_model_build(img_inputs)
这个模型的构建,是构建论文的如下部分:
在每个K处,都会获得一个KaTeX parse error: Undefined control sequence: \c at position 17: …\times h\times \̲c̲的特征图。也就是一共或的K个特征图,从论文可以知道,对于该网络结构的实现K=3。
ssr_S_model
# 根据参数,确定是否构建论文中Scoring模型,输入三个[b,8,8,64],得到一个[b,3*7,64]
if self.is_noS_model:
# 三个[b,8,8,64]输入, 链接到一起成为[b,192,64]输出
ssr_S_model = self.ssr_noS_model_build()
else:
# 如果构建Scoring模型,其有两种方式,分别为使用1x1的卷积,或者通过方差计算
# 三个[b, 8, 8, 64]输入, num_primcaps参数为192,
# 输出[b,21,64]
ssr_S_model = self.ssr_S_model_build(num_primcaps=self.num_primcaps,m_dim=self.m_dim)
可以看到,其根据self.is_noS_model参数,构建不同的ssr_S_model模型,也就是如下部分:
从论文中,我们可以知道Scoring function的实现有3中方式,第一种即为什么都做,即调用构建函数
ssr_S_model = self.ssr_noS_model_build()
如果使用1x1的的卷积或者方差方法,则调用:
ssr_S_model = self.ssr_S_model_build(num_primcaps=self.num_primcaps,m_dim=self.m_dim)
也就是该函数中还会有分支存在,再次根据参数是选择1x1的卷积,或者方差的方法去实现Scoring function。
还有一点,就本人猜测,该函数内部已经实现了
红框部分。
ssr_aggregation_model
代码如下:
ssr_aggregation_model = self.ssr_aggregation_model_build((self.num_primcaps,64))
对应论文如下部分:
要注意的是,该函数这里基类是没有实现,是需要子类去实现,因为输入形状大小和前面的网络有关,而前面网络和由传入的参数决定。
ssr_F_Cap_model
# 通过Feature aggregation得到三个特征向量,可以选择选择两种方式进行姿态估算
# self.F_shape默认为16,刚好耦合上个模型的输出,即输入为3个[b,16]
# 输出共九个结果,3个[b,3,3] 6个[b,3]
if self.is_fc_model:
# 直接使用对每个特征进行全连接层
ssr_F_Cap_model = self.ssr_FC_model_build(self.F_shape,'ssr_F_Cap_model')
else:
# 对每个特征进行合适的分割之后,再进行全链接
ssr_F_Cap_model = self.ssr_F_model_build(self.F_shape,'ssr_F_Cap_model')
这里主要是为了获得论文中的 { p ⃗ , η ⃗ , Δ } k = 1 K {\{\vec{p} ,\vec{η},\Delta \}}_{k=1}^K {p,η,Δ}k=1K参数的集合。
SSRLayer
通过上面的介绍,整体的模型已经构建完成,但是整体模型输出的结果为 { p ⃗ , η ⃗ , Δ } k = 1 K {\{\vec{p} ,\vec{η},\Delta \}}_{k=1}^K {p,η,Δ}k=1K,所以最后我们还有使用SSR算法进行处理,得到最后的姿态估计结果,即yaw, pitch, roll。
代码如下:
# 根据输入3个[b,3,3] 以及 6个[b,3],进行姿态计算
# 输出pred_pose[b,3], 默认的self.stage_num为[3,3,3], self.lambda_d=1
pred_pose = SSRLayer(s1=self.stage_num[0], s2=self.stage_num[1], s3=self.stage_num[2], lambda_d=self.lambda_d, name="pred_pose")(ssr_F_Cap_list)
return Model(inputs=img_inputs, outputs=pred_pose)
快把,是不是一下子就和论文对应起来了,从下小结开始,我们就要去深究代码每个细节的实现了,也就上上面构建模型的细节,我们还会见面的对吧,就在下篇博客!羞涩