前言:
centernet可实现目标检测,人体姿态估计,3D检测,本文针对人体姿态估计代码进行解读。文章核心思想,使用热力图表示物体关键点及中心点,及使用中心点到关键点的矢量对检测结果进行双重保障。
1、数据集
数据集使用coco2017中的person_keypoints_{}2017.json,目标检测使用的是image_info_test-dev2017.json,同一个coco数据集有不同类型的标注文件,根据标注文件中的不同图片id可以在同一个图片文件夹中找到图片。初始化dataloader时会调用coco_hp.py中的init()函数,用于后面制作真值格式所要用的一些值进行一些初始化;在for iter_id, batch in enumerate(data_loader):中调用multi_pose.py的getitem()函数,按照需要的字段及格式从annotation中取出真值
coco_hp.py
在init函数中定义一些 a、在datasets/sample/multi_pose.py中会用到的变量及值;b、读取标注文件的路径及名称
multi_pose.py 制作真值
a、根据图片及标注文件路径进行读取
b、在getitem中将标注文件中的真值处理成我们需要的数据存储方式,
c、做了一些数据增强操作,如翻转及仿射变换 及色彩变化(光照、对比度、饱和度)其中色彩变化用到的参数self._data_rng, inp, self._eig_val, self._eig_vec均在coco_hp.py中定义
d、将图片除255使得图片值变为0到1之间的数值 e、归一化,减均值,除方差
ret = {'input': inp, 'hm': hm, 'reg_mask': reg_mask, 'ind': ind, 'wh': wh
'hps': kps, 'hps_mask': kps_mask}
#图片,热力图,int_float回归mask,index,物体宽高,关键点热力图,关键点
inp:图片通过cv2.imread()读取图片,并对图片进行除255,减均值除方差操作
hm : 建立一个和输入图片同样宽高的数组,里面的数值是物体中心点的热力图。热力图是怎么来的呢?1、通过物体宽高算的高斯半径,根据高斯半径,物体中心点,hm及函数draw_gaussian画出属于这个物体的热力图。
reg_mask:#第k个物体的偏差mask=1,初始值为0 reg为物体中心点int到float的偏差
ind:中心点所在的宽高拉成1维后的索引
wh:物体宽高
kps:中心点到关键点间的距离
kps_mask:初始值为0,在有关键点的地方值为1,作用kps*kps_mask与output[kps]*kps_mask做差值,如果检测结果在应该有关键点的地方没有关键点,那output[kps]*kps_mask会很小,则与kps*kps_mask差值会很大
dense_kps:密集的kps。【17,2,128,128】每一个关键点生成一个[2,128,128]的矩阵。如果当前物体中心点高斯半径大于heatmap当前物体中心点高斯半径范围内的值(heatmap当前物体中心点是在这一步之后画的,所以如果两个物体相距较远,一般是大于,如果两个中心点,相距较近,可能有小于的情况)
大于的部分:
矩阵中物体中心点高斯半径范围内存放 pt-ct_int的x,y减delta(5,-4,-3,-2,-1,0,1,2,3,4,5……17)
小于的部分:
使用原来dense_kps这一中心点附近的值
dense_hps_mask:中心点的高斯热力图
如果有dense_kps,则删除原有hps及hps_mask.del ret['hps'], ret['hps_mask']
reg:中心点float到中心点int的误差
hm_hp:关键点绘画的高斯热力图
hp_offset:关键点float到关键点int的误差
hp_ind:关键点所在的宽高拉成1维后的索引
hp_mask:有关键点的地方值为1
2、模型
根据opt.py配置文件中配置的--arch,决定本任务使用哪种网络结构作为backbone,共有“res_18 | res_101 | resdcn_18 | resdcn_101 |''dlav0_34 | dla_34 | hourglass'”七种。
以resnet101为例,在main.py中,调用了createmodel,在createmodel中又调用了_model_factory及getmodel。在_model_factory中传入opt中写的模型名称,通过_model_factory的getmodel调用真正的构建模型方法,如get_pose_net。在get_pose_net中构建PoseResNet类的实例,构建网络模型,同时在get_pose_net中对模型参数进行初始化。
初始化:如果模型层是卷积,使用正态分布初始化,如果是偏置,使用常数0初始化;如果是BN层,weight使用常数1初始化,bias使用0初始化……
def init_weights(self, num_layers, pretrained=True):
if pretrained:
# print('=> init resnet deconv weights from normal distribution')
for _, m in self.deconv_layers.named_modules():
if isinstance(m, nn.ConvTranspose2d):
# print('=> init {}.weight as normal(0, 0.001)'.format(name))
# print('=> init {}.bias as 0'.format(name))
nn.init.normal_(m.weight, std=0.001)
if self.deconv_with_bias:
nn.init.constant_(m.bias, 0)
elif isinstance(m, nn.BatchNorm2d):
# print('=> init {}.weight as 1'.format(name))
# print('=> init {}.bias as 0'.format(name))
nn.init.constant_(m.weight, 1)
nn.init.constant_(m.bias, 0)
……
3、训练
在for epoch中开始循环训练。使用trainer.train(),MutiposeTrainer继承BaseTrainer,BaseTrainer.train.调用runepoch,在runepoch中主要调用model_with_loss(batch)得到模型的检测结果与loss
得到模型结果与计算loss
得到模型结果与loss使用这一句话
output, loss, loss_stats = model_with_loss(batch)
loss调用 MultiPoseTrainer中的__get_losses,在该函数中又调用 MultiPoseLoss类完成整个loss的计算。ModelWithLoss中在BaseTrainer中初始化得到模型及配置参数opt,调用ModelWithLoss类,在该类中对模型进行输入,并将得到的结果送入loss得到loss。
class BaseTrainer(object):
def __init__(
self, opt, model, optimizer=None):
self.opt = opt
self.optimizer = optimizer
self.loss_stats, self.loss = self._get_losses(opt)
self.model_with_loss = ModelWithLoss(model, self.loss)
class ModelWithLoss(torch.nn.Module):
def __init__(self, model, loss):
super(ModelWithLoss, self).__init__()
self.model = model
self.loss = loss
def forward(self, batch):
outputs = self.model(batch['input'])
loss, loss_stats = self.loss(outputs, batch)
return outputs[-1], loss, loss_stats
class MultiPoseLoss(torch.nn.Module):
def __init__(self, opt):
super(MultiPoseLoss, self).__init__()
self.crit = FocalLoss()
self.crit_hm_hp = torch.nn.MSELoss() if opt.mse_loss else FocalLoss()
self.crit_kp = RegWeightedL1Loss() if not opt.dense_hp else \
torch.nn.L1Loss(reduction='sum')
self.crit_reg = RegL1Loss() if opt.reg_loss == 'l1' else \
RegLoss() if opt.reg_loss == 'sl1' else None
self.opt = opt
def forward(self, outputs, batch):
…………………………………………
………………………………
loss = opt.hm_weight * hm_loss + opt.wh_weight * wh_loss + \
opt.off_weight * off_loss + opt.hp_weight * hp_loss + \
opt.hm_hp_weight * hm_hp_loss + opt.off_weight * hp_offset_loss
loss_stats = {'loss': loss, 'hm_loss': hm_loss, 'hp_loss': hp_loss,
'hm_hp_loss': hm_hp_loss, 'hp_offset_loss': hp_offset_loss,
'wh_loss': wh_loss, 'off_loss': off_loss}
return loss, loss_stats
class MultiPoseTrainer(BaseTrainer):
def __init__(self, opt, model, optimizer=None):
super(MultiPoseTrainer, self).__init__(opt, model, optimizer=optimizer)
def _get_losses(self, opt):
loss_states = ['loss', 'hm_loss', 'hp_loss', 'hm_hp_loss',
'hp_offset_loss', 'wh_loss', 'off_loss']
loss = MultiPoseLoss(opt)
return loss_states, loss
计算heatmap使用focalloss
def _neg_loss(pred, gt):
''' Modified focal loss. Exactly the same as CornerNet.
Runs faster and costs a little bit more memory
Arguments:
pred (batch x c x h x w)
gt_regr (batch x c x h x w)
'''
pos_inds = gt.eq(1).float()#gt等于1的index 正样本
neg_inds = gt.lt(1).float() #less than 小于1的index 副样本
neg_weights = torch.pow(1 - gt, 4) #(1-y)^4
loss = 0
pos_loss = torch.log(pred) * torch.pow(1 - pred, 2) * pos_inds
neg_loss = torch.log(1 - pred) * torch.pow(pred, 2) * neg_weights * neg_inds
num_pos = pos_inds.float().sum()
pos_loss = pos_loss.sum()
neg_loss = neg_loss.sum()
if num_pos == 0:
loss = loss - neg_loss #当没有正样本时只取负样本的损失函数
else:
loss = loss - (pos_loss + neg_loss) / num_pos # 有正样本时,正负样本损失相加除正样本个数
return loss
4、decode