Initial snapshot: hand motion pipeline (Dyn-HaMR + dex-retargeting + SPIDER)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,16 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
from .classfication_loss import BCELoss
|
||||
from .heatmap_loss import AdaptiveWingLoss
|
||||
from .mesh_loss import GANLoss, MeshLoss
|
||||
from .mse_loss import JointsMSELoss, JointsOHKMMSELoss
|
||||
from .multi_loss_factory import AELoss, HeatmapLoss, MultiLossFactory
|
||||
from .regression_loss import (BoneLoss, L1Loss, MPJPELoss, MSELoss,
|
||||
SemiSupervisionLoss, SmoothL1Loss, SoftWingLoss,
|
||||
WingLoss)
|
||||
|
||||
__all__ = [
|
||||
'JointsMSELoss', 'JointsOHKMMSELoss', 'HeatmapLoss', 'AELoss',
|
||||
'MultiLossFactory', 'MeshLoss', 'GANLoss', 'SmoothL1Loss', 'WingLoss',
|
||||
'MPJPELoss', 'MSELoss', 'L1Loss', 'BCELoss', 'BoneLoss',
|
||||
'SemiSupervisionLoss', 'SoftWingLoss', 'AdaptiveWingLoss'
|
||||
]
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
import torch.nn as nn
|
||||
import torch.nn.functional as F
|
||||
|
||||
from ..builder import LOSSES
|
||||
|
||||
|
||||
@LOSSES.register_module()
|
||||
class BCELoss(nn.Module):
|
||||
"""Binary Cross Entropy loss."""
|
||||
|
||||
def __init__(self, use_target_weight=False, loss_weight=1.):
|
||||
super().__init__()
|
||||
self.criterion = F.binary_cross_entropy
|
||||
self.use_target_weight = use_target_weight
|
||||
self.loss_weight = loss_weight
|
||||
|
||||
def forward(self, output, target, target_weight=None):
|
||||
"""Forward function.
|
||||
|
||||
Note:
|
||||
- batch_size: N
|
||||
- num_labels: K
|
||||
|
||||
Args:
|
||||
output (torch.Tensor[N, K]): Output classification.
|
||||
target (torch.Tensor[N, K]): Target classification.
|
||||
target_weight (torch.Tensor[N, K] or torch.Tensor[N]):
|
||||
Weights across different labels.
|
||||
"""
|
||||
|
||||
if self.use_target_weight:
|
||||
assert target_weight is not None
|
||||
loss = self.criterion(output, target, reduction='none')
|
||||
if target_weight.dim() == 1:
|
||||
target_weight = target_weight[:, None]
|
||||
loss = (loss * target_weight).mean()
|
||||
else:
|
||||
loss = self.criterion(output, target)
|
||||
|
||||
return loss * self.loss_weight
|
||||
@@ -0,0 +1,86 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
|
||||
from ..builder import LOSSES
|
||||
|
||||
|
||||
@LOSSES.register_module()
|
||||
class AdaptiveWingLoss(nn.Module):
|
||||
"""Adaptive wing loss. paper ref: 'Adaptive Wing Loss for Robust Face
|
||||
Alignment via Heatmap Regression' Wang et al. ICCV'2019.
|
||||
|
||||
Args:
|
||||
alpha (float), omega (float), epsilon (float), theta (float)
|
||||
are hyper-parameters.
|
||||
use_target_weight (bool): Option to use weighted MSE loss.
|
||||
Different joint types may have different target weights.
|
||||
loss_weight (float): Weight of the loss. Default: 1.0.
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
alpha=2.1,
|
||||
omega=14,
|
||||
epsilon=1,
|
||||
theta=0.5,
|
||||
use_target_weight=False,
|
||||
loss_weight=1.):
|
||||
super().__init__()
|
||||
self.alpha = float(alpha)
|
||||
self.omega = float(omega)
|
||||
self.epsilon = float(epsilon)
|
||||
self.theta = float(theta)
|
||||
self.use_target_weight = use_target_weight
|
||||
self.loss_weight = loss_weight
|
||||
|
||||
def criterion(self, pred, target):
|
||||
"""Criterion of wingloss.
|
||||
|
||||
Note:
|
||||
batch_size: N
|
||||
num_keypoints: K
|
||||
|
||||
Args:
|
||||
pred (torch.Tensor[NxKxHxW]): Predicted heatmaps.
|
||||
target (torch.Tensor[NxKxHxW]): Target heatmaps.
|
||||
"""
|
||||
H, W = pred.shape[2:4]
|
||||
delta = (target - pred).abs()
|
||||
|
||||
A = self.omega * (
|
||||
1 / (1 + torch.pow(self.theta / self.epsilon, self.alpha - target))
|
||||
) * (self.alpha - target) * (torch.pow(
|
||||
self.theta / self.epsilon,
|
||||
self.alpha - target - 1)) * (1 / self.epsilon)
|
||||
C = self.theta * A - self.omega * torch.log(
|
||||
1 + torch.pow(self.theta / self.epsilon, self.alpha - target))
|
||||
|
||||
losses = torch.where(
|
||||
delta < self.theta,
|
||||
self.omega *
|
||||
torch.log(1 +
|
||||
torch.pow(delta / self.epsilon, self.alpha - target)),
|
||||
A * delta - C)
|
||||
|
||||
return torch.mean(losses)
|
||||
|
||||
def forward(self, output, target, target_weight):
|
||||
"""Forward function.
|
||||
|
||||
Note:
|
||||
batch_size: N
|
||||
num_keypoints: K
|
||||
|
||||
Args:
|
||||
output (torch.Tensor[NxKxHxW]): Output heatmaps.
|
||||
target (torch.Tensor[NxKxHxW]): Target heatmaps.
|
||||
target_weight (torch.Tensor[NxKx1]):
|
||||
Weights across different joint types.
|
||||
"""
|
||||
if self.use_target_weight:
|
||||
loss = self.criterion(output * target_weight.unsqueeze(-1),
|
||||
target * target_weight.unsqueeze(-1))
|
||||
else:
|
||||
loss = self.criterion(output, target)
|
||||
|
||||
return loss * self.loss_weight
|
||||
@@ -0,0 +1,340 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
|
||||
from ..builder import LOSSES
|
||||
from ..utils.geometry import batch_rodrigues
|
||||
|
||||
|
||||
def perspective_projection(points, rotation, translation, focal_length,
|
||||
camera_center):
|
||||
"""This function computes the perspective projection of a set of 3D points.
|
||||
|
||||
Note:
|
||||
- batch size: B
|
||||
- point number: N
|
||||
|
||||
Args:
|
||||
points (Tensor([B, N, 3])): A set of 3D points
|
||||
rotation (Tensor([B, 3, 3])): Camera rotation matrix
|
||||
translation (Tensor([B, 3])): Camera translation
|
||||
focal_length (Tensor([B,])): Focal length
|
||||
camera_center (Tensor([B, 2])): Camera center
|
||||
|
||||
Returns:
|
||||
projected_points (Tensor([B, N, 2])): Projected 2D
|
||||
points in image space.
|
||||
"""
|
||||
|
||||
batch_size = points.shape[0]
|
||||
K = torch.zeros([batch_size, 3, 3], device=points.device)
|
||||
K[:, 0, 0] = focal_length
|
||||
K[:, 1, 1] = focal_length
|
||||
K[:, 2, 2] = 1.
|
||||
K[:, :-1, -1] = camera_center
|
||||
|
||||
# Transform points
|
||||
points = torch.einsum('bij,bkj->bki', rotation, points)
|
||||
points = points + translation.unsqueeze(1)
|
||||
|
||||
# Apply perspective distortion
|
||||
projected_points = points / points[:, :, -1].unsqueeze(-1)
|
||||
|
||||
# Apply camera intrinsics
|
||||
projected_points = torch.einsum('bij,bkj->bki', K, projected_points)
|
||||
projected_points = projected_points[:, :, :-1]
|
||||
return projected_points
|
||||
|
||||
|
||||
@LOSSES.register_module()
|
||||
class MeshLoss(nn.Module):
|
||||
"""Mix loss for 3D human mesh. It is composed of loss on 2D joints, 3D
|
||||
joints, mesh vertices and smpl parameters (if any).
|
||||
|
||||
Args:
|
||||
joints_2d_loss_weight (float): Weight for loss on 2D joints.
|
||||
joints_3d_loss_weight (float): Weight for loss on 3D joints.
|
||||
vertex_loss_weight (float): Weight for loss on 3D verteices.
|
||||
smpl_pose_loss_weight (float): Weight for loss on SMPL
|
||||
pose parameters.
|
||||
smpl_beta_loss_weight (float): Weight for loss on SMPL
|
||||
shape parameters.
|
||||
img_res (int): Input image resolution.
|
||||
focal_length (float): Focal length of camera model. Default=5000.
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
joints_2d_loss_weight,
|
||||
joints_3d_loss_weight,
|
||||
vertex_loss_weight,
|
||||
smpl_pose_loss_weight,
|
||||
smpl_beta_loss_weight,
|
||||
img_res,
|
||||
focal_length=5000):
|
||||
|
||||
super().__init__()
|
||||
# Per-vertex loss on the mesh
|
||||
self.criterion_vertex = nn.L1Loss(reduction='none')
|
||||
|
||||
# Joints (2D and 3D) loss
|
||||
self.criterion_joints_2d = nn.SmoothL1Loss(reduction='none')
|
||||
self.criterion_joints_3d = nn.SmoothL1Loss(reduction='none')
|
||||
|
||||
# Loss for SMPL parameter regression
|
||||
self.criterion_regr = nn.MSELoss(reduction='none')
|
||||
|
||||
self.joints_2d_loss_weight = joints_2d_loss_weight
|
||||
self.joints_3d_loss_weight = joints_3d_loss_weight
|
||||
self.vertex_loss_weight = vertex_loss_weight
|
||||
self.smpl_pose_loss_weight = smpl_pose_loss_weight
|
||||
self.smpl_beta_loss_weight = smpl_beta_loss_weight
|
||||
self.focal_length = focal_length
|
||||
self.img_res = img_res
|
||||
|
||||
def joints_2d_loss(self, pred_joints_2d, gt_joints_2d, joints_2d_visible):
|
||||
"""Compute 2D reprojection loss on the joints.
|
||||
|
||||
The loss is weighted by joints_2d_visible.
|
||||
"""
|
||||
conf = joints_2d_visible.float()
|
||||
loss = (conf *
|
||||
self.criterion_joints_2d(pred_joints_2d, gt_joints_2d)).mean()
|
||||
return loss
|
||||
|
||||
def joints_3d_loss(self, pred_joints_3d, gt_joints_3d, joints_3d_visible):
|
||||
"""Compute 3D joints loss for the examples that 3D joint annotations
|
||||
are available.
|
||||
|
||||
The loss is weighted by joints_3d_visible.
|
||||
"""
|
||||
conf = joints_3d_visible.float()
|
||||
if len(gt_joints_3d) > 0:
|
||||
gt_pelvis = (gt_joints_3d[:, 2, :] + gt_joints_3d[:, 3, :]) / 2
|
||||
gt_joints_3d = gt_joints_3d - gt_pelvis[:, None, :]
|
||||
pred_pelvis = (pred_joints_3d[:, 2, :] +
|
||||
pred_joints_3d[:, 3, :]) / 2
|
||||
pred_joints_3d = pred_joints_3d - pred_pelvis[:, None, :]
|
||||
return (
|
||||
conf *
|
||||
self.criterion_joints_3d(pred_joints_3d, gt_joints_3d)).mean()
|
||||
return pred_joints_3d.sum() * 0
|
||||
|
||||
def vertex_loss(self, pred_vertices, gt_vertices, has_smpl):
|
||||
"""Compute 3D vertex loss for the examples that 3D human mesh
|
||||
annotations are available.
|
||||
|
||||
The loss is weighted by the has_smpl.
|
||||
"""
|
||||
conf = has_smpl.float()
|
||||
loss_vertex = self.criterion_vertex(pred_vertices, gt_vertices)
|
||||
loss_vertex = (conf[:, None, None] * loss_vertex).mean()
|
||||
return loss_vertex
|
||||
|
||||
def smpl_losses(self, pred_rotmat, pred_betas, gt_pose, gt_betas,
|
||||
has_smpl):
|
||||
"""Compute SMPL parameters loss for the examples that SMPL parameter
|
||||
annotations are available.
|
||||
|
||||
The loss is weighted by has_smpl.
|
||||
"""
|
||||
conf = has_smpl.float()
|
||||
gt_rotmat = batch_rodrigues(gt_pose.view(-1, 3)).view(-1, 24, 3, 3)
|
||||
loss_regr_pose = self.criterion_regr(pred_rotmat, gt_rotmat)
|
||||
loss_regr_betas = self.criterion_regr(pred_betas, gt_betas)
|
||||
loss_regr_pose = (conf[:, None, None, None] * loss_regr_pose).mean()
|
||||
loss_regr_betas = (conf[:, None] * loss_regr_betas).mean()
|
||||
return loss_regr_pose, loss_regr_betas
|
||||
|
||||
def project_points(self, points_3d, camera):
|
||||
"""Perform orthographic projection of 3D points using the camera
|
||||
parameters, return projected 2D points in image plane.
|
||||
|
||||
Note:
|
||||
- batch size: B
|
||||
- point number: N
|
||||
|
||||
Args:
|
||||
points_3d (Tensor([B, N, 3])): 3D points.
|
||||
camera (Tensor([B, 3])): camera parameters with the
|
||||
3 channel as (scale, translation_x, translation_y)
|
||||
|
||||
Returns:
|
||||
Tensor([B, N, 2]): projected 2D points \
|
||||
in image space.
|
||||
"""
|
||||
batch_size = points_3d.shape[0]
|
||||
device = points_3d.device
|
||||
cam_t = torch.stack([
|
||||
camera[:, 1], camera[:, 2], 2 * self.focal_length /
|
||||
(self.img_res * camera[:, 0] + 1e-9)
|
||||
],
|
||||
dim=-1)
|
||||
camera_center = camera.new_zeros([batch_size, 2])
|
||||
rot_t = torch.eye(
|
||||
3, device=device,
|
||||
dtype=points_3d.dtype).unsqueeze(0).expand(batch_size, -1, -1)
|
||||
joints_2d = perspective_projection(
|
||||
points_3d,
|
||||
rotation=rot_t,
|
||||
translation=cam_t,
|
||||
focal_length=self.focal_length,
|
||||
camera_center=camera_center)
|
||||
return joints_2d
|
||||
|
||||
def forward(self, output, target):
|
||||
"""Forward function.
|
||||
|
||||
Args:
|
||||
output (dict): dict of network predicted results.
|
||||
Keys: 'vertices', 'joints_3d', 'camera',
|
||||
'pose'(optional), 'beta'(optional)
|
||||
target (dict): dict of ground-truth labels.
|
||||
Keys: 'vertices', 'joints_3d', 'joints_3d_visible',
|
||||
'joints_2d', 'joints_2d_visible', 'pose', 'beta',
|
||||
'has_smpl'
|
||||
|
||||
Returns:
|
||||
dict: dict of losses.
|
||||
"""
|
||||
losses = {}
|
||||
|
||||
# Per-vertex loss for the shape
|
||||
pred_vertices = output['vertices']
|
||||
|
||||
gt_vertices = target['vertices']
|
||||
has_smpl = target['has_smpl']
|
||||
loss_vertex = self.vertex_loss(pred_vertices, gt_vertices, has_smpl)
|
||||
losses['vertex_loss'] = loss_vertex * self.vertex_loss_weight
|
||||
|
||||
# Compute loss on SMPL parameters, if available
|
||||
if 'pose' in output.keys() and 'beta' in output.keys():
|
||||
pred_rotmat = output['pose']
|
||||
pred_betas = output['beta']
|
||||
gt_pose = target['pose']
|
||||
gt_betas = target['beta']
|
||||
loss_regr_pose, loss_regr_betas = self.smpl_losses(
|
||||
pred_rotmat, pred_betas, gt_pose, gt_betas, has_smpl)
|
||||
losses['smpl_pose_loss'] = \
|
||||
loss_regr_pose * self.smpl_pose_loss_weight
|
||||
losses['smpl_beta_loss'] = \
|
||||
loss_regr_betas * self.smpl_beta_loss_weight
|
||||
|
||||
# Compute 3D joints loss
|
||||
pred_joints_3d = output['joints_3d']
|
||||
gt_joints_3d = target['joints_3d']
|
||||
joints_3d_visible = target['joints_3d_visible']
|
||||
loss_joints_3d = self.joints_3d_loss(pred_joints_3d, gt_joints_3d,
|
||||
joints_3d_visible)
|
||||
losses['joints_3d_loss'] = loss_joints_3d * self.joints_3d_loss_weight
|
||||
|
||||
# Compute 2D reprojection loss for the 2D joints
|
||||
pred_camera = output['camera']
|
||||
gt_joints_2d = target['joints_2d']
|
||||
joints_2d_visible = target['joints_2d_visible']
|
||||
pred_joints_2d = self.project_points(pred_joints_3d, pred_camera)
|
||||
|
||||
# Normalize keypoints to [-1,1]
|
||||
# The coordinate origin of pred_joints_2d is
|
||||
# the center of the input image.
|
||||
pred_joints_2d = 2 * pred_joints_2d / (self.img_res - 1)
|
||||
# The coordinate origin of gt_joints_2d is
|
||||
# the top left corner of the input image.
|
||||
gt_joints_2d = 2 * gt_joints_2d / (self.img_res - 1) - 1
|
||||
loss_joints_2d = self.joints_2d_loss(pred_joints_2d, gt_joints_2d,
|
||||
joints_2d_visible)
|
||||
losses['joints_2d_loss'] = loss_joints_2d * self.joints_2d_loss_weight
|
||||
|
||||
return losses
|
||||
|
||||
|
||||
@LOSSES.register_module()
|
||||
class GANLoss(nn.Module):
|
||||
"""Define GAN loss.
|
||||
|
||||
Args:
|
||||
gan_type (str): Support 'vanilla', 'lsgan', 'wgan', 'hinge'.
|
||||
real_label_val (float): The value for real label. Default: 1.0.
|
||||
fake_label_val (float): The value for fake label. Default: 0.0.
|
||||
loss_weight (float): Loss weight. Default: 1.0.
|
||||
Note that loss_weight is only for generators; and it is always 1.0
|
||||
for discriminators.
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
gan_type,
|
||||
real_label_val=1.0,
|
||||
fake_label_val=0.0,
|
||||
loss_weight=1.0):
|
||||
super().__init__()
|
||||
self.gan_type = gan_type
|
||||
self.loss_weight = loss_weight
|
||||
self.real_label_val = real_label_val
|
||||
self.fake_label_val = fake_label_val
|
||||
|
||||
if self.gan_type == 'vanilla':
|
||||
self.loss = nn.BCEWithLogitsLoss()
|
||||
elif self.gan_type == 'lsgan':
|
||||
self.loss = nn.MSELoss()
|
||||
elif self.gan_type == 'wgan':
|
||||
self.loss = self._wgan_loss
|
||||
elif self.gan_type == 'hinge':
|
||||
self.loss = nn.ReLU()
|
||||
else:
|
||||
raise NotImplementedError(
|
||||
f'GAN type {self.gan_type} is not implemented.')
|
||||
|
||||
@staticmethod
|
||||
def _wgan_loss(input, target):
|
||||
"""wgan loss.
|
||||
|
||||
Args:
|
||||
input (Tensor): Input tensor.
|
||||
target (bool): Target label.
|
||||
|
||||
Returns:
|
||||
Tensor: wgan loss.
|
||||
"""
|
||||
return -input.mean() if target else input.mean()
|
||||
|
||||
def get_target_label(self, input, target_is_real):
|
||||
"""Get target label.
|
||||
|
||||
Args:
|
||||
input (Tensor): Input tensor.
|
||||
target_is_real (bool): Whether the target is real or fake.
|
||||
|
||||
Returns:
|
||||
(bool | Tensor): Target tensor. Return bool for wgan, \
|
||||
otherwise, return Tensor.
|
||||
"""
|
||||
|
||||
if self.gan_type == 'wgan':
|
||||
return target_is_real
|
||||
target_val = (
|
||||
self.real_label_val if target_is_real else self.fake_label_val)
|
||||
return input.new_ones(input.size()) * target_val
|
||||
|
||||
def forward(self, input, target_is_real, is_disc=False):
|
||||
"""
|
||||
Args:
|
||||
input (Tensor): The input for the loss module, i.e., the network
|
||||
prediction.
|
||||
target_is_real (bool): Whether the targe is real or fake.
|
||||
is_disc (bool): Whether the loss for discriminators or not.
|
||||
Default: False.
|
||||
|
||||
Returns:
|
||||
Tensor: GAN loss value.
|
||||
"""
|
||||
target_label = self.get_target_label(input, target_is_real)
|
||||
if self.gan_type == 'hinge':
|
||||
if is_disc: # for discriminators in hinge-gan
|
||||
input = -input if target_is_real else input
|
||||
loss = self.loss(1 + input).mean()
|
||||
else: # for generators in hinge-gan
|
||||
loss = -input.mean()
|
||||
else: # other gan types
|
||||
loss = self.loss(input, target_label)
|
||||
|
||||
# loss_weight is always 1.0 for discriminators
|
||||
return loss if is_disc else loss * self.loss_weight
|
||||
@@ -0,0 +1,153 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
|
||||
from ..builder import LOSSES
|
||||
|
||||
|
||||
@LOSSES.register_module()
|
||||
class JointsMSELoss(nn.Module):
|
||||
"""MSE loss for heatmaps.
|
||||
|
||||
Args:
|
||||
use_target_weight (bool): Option to use weighted MSE loss.
|
||||
Different joint types may have different target weights.
|
||||
loss_weight (float): Weight of the loss. Default: 1.0.
|
||||
"""
|
||||
|
||||
def __init__(self, use_target_weight=False, loss_weight=1.):
|
||||
super().__init__()
|
||||
self.criterion = nn.MSELoss()
|
||||
self.use_target_weight = use_target_weight
|
||||
self.loss_weight = loss_weight
|
||||
|
||||
def forward(self, output, target, target_weight):
|
||||
"""Forward function."""
|
||||
batch_size = output.size(0)
|
||||
num_joints = output.size(1)
|
||||
|
||||
heatmaps_pred = output.reshape(
|
||||
(batch_size, num_joints, -1)).split(1, 1)
|
||||
heatmaps_gt = target.reshape((batch_size, num_joints, -1)).split(1, 1)
|
||||
|
||||
loss = 0.
|
||||
|
||||
for idx in range(num_joints):
|
||||
heatmap_pred = heatmaps_pred[idx].squeeze(1)
|
||||
heatmap_gt = heatmaps_gt[idx].squeeze(1)
|
||||
if self.use_target_weight:
|
||||
loss += self.criterion(heatmap_pred * target_weight[:, idx],
|
||||
heatmap_gt * target_weight[:, idx])
|
||||
else:
|
||||
loss += self.criterion(heatmap_pred, heatmap_gt)
|
||||
|
||||
return loss / num_joints * self.loss_weight
|
||||
|
||||
|
||||
@LOSSES.register_module()
|
||||
class CombinedTargetMSELoss(nn.Module):
|
||||
"""MSE loss for combined target.
|
||||
CombinedTarget: The combination of classification target
|
||||
(response map) and regression target (offset map).
|
||||
Paper ref: Huang et al. The Devil is in the Details: Delving into
|
||||
Unbiased Data Processing for Human Pose Estimation (CVPR 2020).
|
||||
|
||||
Args:
|
||||
use_target_weight (bool): Option to use weighted MSE loss.
|
||||
Different joint types may have different target weights.
|
||||
loss_weight (float): Weight of the loss. Default: 1.0.
|
||||
"""
|
||||
|
||||
def __init__(self, use_target_weight, loss_weight=1.):
|
||||
super().__init__()
|
||||
self.criterion = nn.MSELoss(reduction='mean')
|
||||
self.use_target_weight = use_target_weight
|
||||
self.loss_weight = loss_weight
|
||||
|
||||
def forward(self, output, target, target_weight):
|
||||
batch_size = output.size(0)
|
||||
num_channels = output.size(1)
|
||||
heatmaps_pred = output.reshape(
|
||||
(batch_size, num_channels, -1)).split(1, 1)
|
||||
heatmaps_gt = target.reshape(
|
||||
(batch_size, num_channels, -1)).split(1, 1)
|
||||
loss = 0.
|
||||
num_joints = num_channels // 3
|
||||
for idx in range(num_joints):
|
||||
heatmap_pred = heatmaps_pred[idx * 3].squeeze()
|
||||
heatmap_gt = heatmaps_gt[idx * 3].squeeze()
|
||||
offset_x_pred = heatmaps_pred[idx * 3 + 1].squeeze()
|
||||
offset_x_gt = heatmaps_gt[idx * 3 + 1].squeeze()
|
||||
offset_y_pred = heatmaps_pred[idx * 3 + 2].squeeze()
|
||||
offset_y_gt = heatmaps_gt[idx * 3 + 2].squeeze()
|
||||
if self.use_target_weight:
|
||||
heatmap_pred = heatmap_pred * target_weight[:, idx]
|
||||
heatmap_gt = heatmap_gt * target_weight[:, idx]
|
||||
# classification loss
|
||||
loss += 0.5 * self.criterion(heatmap_pred, heatmap_gt)
|
||||
# regression loss
|
||||
loss += 0.5 * self.criterion(heatmap_gt * offset_x_pred,
|
||||
heatmap_gt * offset_x_gt)
|
||||
loss += 0.5 * self.criterion(heatmap_gt * offset_y_pred,
|
||||
heatmap_gt * offset_y_gt)
|
||||
return loss / num_joints * self.loss_weight
|
||||
|
||||
|
||||
@LOSSES.register_module()
|
||||
class JointsOHKMMSELoss(nn.Module):
|
||||
"""MSE loss with online hard keypoint mining.
|
||||
|
||||
Args:
|
||||
use_target_weight (bool): Option to use weighted MSE loss.
|
||||
Different joint types may have different target weights.
|
||||
topk (int): Only top k joint losses are kept.
|
||||
loss_weight (float): Weight of the loss. Default: 1.0.
|
||||
"""
|
||||
|
||||
def __init__(self, use_target_weight=False, topk=8, loss_weight=1.):
|
||||
super().__init__()
|
||||
assert topk > 0
|
||||
self.criterion = nn.MSELoss(reduction='none')
|
||||
self.use_target_weight = use_target_weight
|
||||
self.topk = topk
|
||||
self.loss_weight = loss_weight
|
||||
|
||||
def _ohkm(self, loss):
|
||||
"""Online hard keypoint mining."""
|
||||
ohkm_loss = 0.
|
||||
N = len(loss)
|
||||
for i in range(N):
|
||||
sub_loss = loss[i]
|
||||
_, topk_idx = torch.topk(
|
||||
sub_loss, k=self.topk, dim=0, sorted=False)
|
||||
tmp_loss = torch.gather(sub_loss, 0, topk_idx)
|
||||
ohkm_loss += torch.sum(tmp_loss) / self.topk
|
||||
ohkm_loss /= N
|
||||
return ohkm_loss
|
||||
|
||||
def forward(self, output, target, target_weight):
|
||||
"""Forward function."""
|
||||
batch_size = output.size(0)
|
||||
num_joints = output.size(1)
|
||||
if num_joints < self.topk:
|
||||
raise ValueError(f'topk ({self.topk}) should not '
|
||||
f'larger than num_joints ({num_joints}).')
|
||||
heatmaps_pred = output.reshape(
|
||||
(batch_size, num_joints, -1)).split(1, 1)
|
||||
heatmaps_gt = target.reshape((batch_size, num_joints, -1)).split(1, 1)
|
||||
|
||||
losses = []
|
||||
for idx in range(num_joints):
|
||||
heatmap_pred = heatmaps_pred[idx].squeeze(1)
|
||||
heatmap_gt = heatmaps_gt[idx].squeeze(1)
|
||||
if self.use_target_weight:
|
||||
losses.append(
|
||||
self.criterion(heatmap_pred * target_weight[:, idx],
|
||||
heatmap_gt * target_weight[:, idx]))
|
||||
else:
|
||||
losses.append(self.criterion(heatmap_pred, heatmap_gt))
|
||||
|
||||
losses = [loss.mean(dim=1).unsqueeze(dim=1) for loss in losses]
|
||||
losses = torch.cat(losses, dim=1)
|
||||
|
||||
return self._ohkm(losses) * self.loss_weight
|
||||
+281
@@ -0,0 +1,281 @@
|
||||
# ------------------------------------------------------------------------------
|
||||
# Adapted from https://github.com/HRNet/HigherHRNet-Human-Pose-Estimation
|
||||
# Original licence: Copyright (c) Microsoft, under the MIT License.
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
|
||||
from ..builder import LOSSES
|
||||
|
||||
|
||||
def _make_input(t, requires_grad=False, device=torch.device('cpu')):
|
||||
"""Make zero inputs for AE loss.
|
||||
|
||||
Args:
|
||||
t (torch.Tensor): input
|
||||
requires_grad (bool): Option to use requires_grad.
|
||||
device: torch device
|
||||
|
||||
Returns:
|
||||
torch.Tensor: zero input.
|
||||
"""
|
||||
inp = torch.autograd.Variable(t, requires_grad=requires_grad)
|
||||
inp = inp.sum()
|
||||
inp = inp.to(device)
|
||||
return inp
|
||||
|
||||
|
||||
@LOSSES.register_module()
|
||||
class HeatmapLoss(nn.Module):
|
||||
"""Accumulate the heatmap loss for each image in the batch.
|
||||
|
||||
Args:
|
||||
supervise_empty (bool): Whether to supervise empty channels.
|
||||
"""
|
||||
|
||||
def __init__(self, supervise_empty=True):
|
||||
super().__init__()
|
||||
self.supervise_empty = supervise_empty
|
||||
|
||||
def forward(self, pred, gt, mask):
|
||||
"""Forward function.
|
||||
|
||||
Note:
|
||||
- batch_size: N
|
||||
- heatmaps weight: W
|
||||
- heatmaps height: H
|
||||
- max_num_people: M
|
||||
- num_keypoints: K
|
||||
|
||||
Args:
|
||||
pred (torch.Tensor[N,K,H,W]):heatmap of output.
|
||||
gt (torch.Tensor[N,K,H,W]): target heatmap.
|
||||
mask (torch.Tensor[N,H,W]): mask of target.
|
||||
"""
|
||||
assert pred.size() == gt.size(
|
||||
), f'pred.size() is {pred.size()}, gt.size() is {gt.size()}'
|
||||
|
||||
if not self.supervise_empty:
|
||||
empty_mask = (gt.sum(dim=[2, 3], keepdim=True) > 0).float()
|
||||
loss = ((pred - gt)**2) * empty_mask.expand_as(
|
||||
pred) * mask[:, None, :, :].expand_as(pred)
|
||||
else:
|
||||
loss = ((pred - gt)**2) * mask[:, None, :, :].expand_as(pred)
|
||||
loss = loss.mean(dim=3).mean(dim=2).mean(dim=1)
|
||||
return loss
|
||||
|
||||
|
||||
@LOSSES.register_module()
|
||||
class AELoss(nn.Module):
|
||||
"""Associative Embedding loss.
|
||||
|
||||
`Associative Embedding: End-to-End Learning for Joint Detection and
|
||||
Grouping <https://arxiv.org/abs/1611.05424v2>`_.
|
||||
"""
|
||||
|
||||
def __init__(self, loss_type):
|
||||
super().__init__()
|
||||
self.loss_type = loss_type
|
||||
|
||||
def singleTagLoss(self, pred_tag, joints):
|
||||
"""Associative embedding loss for one image.
|
||||
|
||||
Note:
|
||||
- heatmaps weight: W
|
||||
- heatmaps height: H
|
||||
- max_num_people: M
|
||||
- num_keypoints: K
|
||||
|
||||
Args:
|
||||
pred_tag (torch.Tensor[KxHxW,1]): tag of output for one image.
|
||||
joints (torch.Tensor[M,K,2]): joints information for one image.
|
||||
"""
|
||||
tags = []
|
||||
pull = 0
|
||||
for joints_per_person in joints:
|
||||
tmp = []
|
||||
for joint in joints_per_person:
|
||||
if joint[1] > 0:
|
||||
tmp.append(pred_tag[joint[0]])
|
||||
if len(tmp) == 0:
|
||||
continue
|
||||
tmp = torch.stack(tmp)
|
||||
tags.append(torch.mean(tmp, dim=0))
|
||||
pull = pull + torch.mean((tmp - tags[-1].expand_as(tmp))**2)
|
||||
|
||||
num_tags = len(tags)
|
||||
if num_tags == 0:
|
||||
return (
|
||||
_make_input(torch.zeros(1).float(), device=pred_tag.device),
|
||||
_make_input(torch.zeros(1).float(), device=pred_tag.device))
|
||||
elif num_tags == 1:
|
||||
return (_make_input(
|
||||
torch.zeros(1).float(), device=pred_tag.device), pull)
|
||||
|
||||
tags = torch.stack(tags)
|
||||
|
||||
size = (num_tags, num_tags)
|
||||
A = tags.expand(*size)
|
||||
B = A.permute(1, 0)
|
||||
|
||||
diff = A - B
|
||||
|
||||
if self.loss_type == 'exp':
|
||||
diff = torch.pow(diff, 2)
|
||||
push = torch.exp(-diff)
|
||||
push = torch.sum(push) - num_tags
|
||||
elif self.loss_type == 'max':
|
||||
diff = 1 - torch.abs(diff)
|
||||
push = torch.clamp(diff, min=0).sum() - num_tags
|
||||
else:
|
||||
raise ValueError('Unknown ae loss type')
|
||||
|
||||
push_loss = push / ((num_tags - 1) * num_tags) * 0.5
|
||||
pull_loss = pull / (num_tags)
|
||||
|
||||
return push_loss, pull_loss
|
||||
|
||||
def forward(self, tags, joints):
|
||||
"""Accumulate the tag loss for each image in the batch.
|
||||
|
||||
Note:
|
||||
- batch_size: N
|
||||
- heatmaps weight: W
|
||||
- heatmaps height: H
|
||||
- max_num_people: M
|
||||
- num_keypoints: K
|
||||
|
||||
Args:
|
||||
tags (torch.Tensor[N,KxHxW,1]): tag channels of output.
|
||||
joints (torch.Tensor[N,M,K,2]): joints information.
|
||||
"""
|
||||
pushes, pulls = [], []
|
||||
joints = joints.cpu().data.numpy()
|
||||
batch_size = tags.size(0)
|
||||
for i in range(batch_size):
|
||||
push, pull = self.singleTagLoss(tags[i], joints[i])
|
||||
pushes.append(push)
|
||||
pulls.append(pull)
|
||||
return torch.stack(pushes), torch.stack(pulls)
|
||||
|
||||
|
||||
@LOSSES.register_module()
|
||||
class MultiLossFactory(nn.Module):
|
||||
"""Loss for bottom-up models.
|
||||
|
||||
Args:
|
||||
num_joints (int): Number of keypoints.
|
||||
num_stages (int): Number of stages.
|
||||
ae_loss_type (str): Type of ae loss.
|
||||
with_ae_loss (list[bool]): Use ae loss or not in multi-heatmap.
|
||||
push_loss_factor (list[float]):
|
||||
Parameter of push loss in multi-heatmap.
|
||||
pull_loss_factor (list[float]):
|
||||
Parameter of pull loss in multi-heatmap.
|
||||
with_heatmap_loss (list[bool]):
|
||||
Use heatmap loss or not in multi-heatmap.
|
||||
heatmaps_loss_factor (list[float]):
|
||||
Parameter of heatmap loss in multi-heatmap.
|
||||
supervise_empty (bool): Whether to supervise empty channels.
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
num_joints,
|
||||
num_stages,
|
||||
ae_loss_type,
|
||||
with_ae_loss,
|
||||
push_loss_factor,
|
||||
pull_loss_factor,
|
||||
with_heatmaps_loss,
|
||||
heatmaps_loss_factor,
|
||||
supervise_empty=True):
|
||||
super().__init__()
|
||||
|
||||
assert isinstance(with_heatmaps_loss, (list, tuple)), \
|
||||
'with_heatmaps_loss should be a list or tuple'
|
||||
assert isinstance(heatmaps_loss_factor, (list, tuple)), \
|
||||
'heatmaps_loss_factor should be a list or tuple'
|
||||
assert isinstance(with_ae_loss, (list, tuple)), \
|
||||
'with_ae_loss should be a list or tuple'
|
||||
assert isinstance(push_loss_factor, (list, tuple)), \
|
||||
'push_loss_factor should be a list or tuple'
|
||||
assert isinstance(pull_loss_factor, (list, tuple)), \
|
||||
'pull_loss_factor should be a list or tuple'
|
||||
|
||||
self.num_joints = num_joints
|
||||
self.num_stages = num_stages
|
||||
self.ae_loss_type = ae_loss_type
|
||||
self.with_ae_loss = with_ae_loss
|
||||
self.push_loss_factor = push_loss_factor
|
||||
self.pull_loss_factor = pull_loss_factor
|
||||
self.with_heatmaps_loss = with_heatmaps_loss
|
||||
self.heatmaps_loss_factor = heatmaps_loss_factor
|
||||
|
||||
self.heatmaps_loss = \
|
||||
nn.ModuleList(
|
||||
[
|
||||
HeatmapLoss(supervise_empty)
|
||||
if with_heatmaps_loss else None
|
||||
for with_heatmaps_loss in self.with_heatmaps_loss
|
||||
]
|
||||
)
|
||||
|
||||
self.ae_loss = \
|
||||
nn.ModuleList(
|
||||
[
|
||||
AELoss(self.ae_loss_type) if with_ae_loss else None
|
||||
for with_ae_loss in self.with_ae_loss
|
||||
]
|
||||
)
|
||||
|
||||
def forward(self, outputs, heatmaps, masks, joints):
|
||||
"""Forward function to calculate losses.
|
||||
|
||||
Note:
|
||||
- batch_size: N
|
||||
- heatmaps weight: W
|
||||
- heatmaps height: H
|
||||
- max_num_people: M
|
||||
- num_keypoints: K
|
||||
- output_channel: C C=2K if use ae loss else K
|
||||
|
||||
Args:
|
||||
outputs (list(torch.Tensor[N,C,H,W])): outputs of stages.
|
||||
heatmaps (list(torch.Tensor[N,K,H,W])): target of heatmaps.
|
||||
masks (list(torch.Tensor[N,H,W])): masks of heatmaps.
|
||||
joints (list(torch.Tensor[N,M,K,2])): joints of ae loss.
|
||||
"""
|
||||
heatmaps_losses = []
|
||||
push_losses = []
|
||||
pull_losses = []
|
||||
for idx in range(len(outputs)):
|
||||
offset_feat = 0
|
||||
if self.heatmaps_loss[idx]:
|
||||
heatmaps_pred = outputs[idx][:, :self.num_joints]
|
||||
offset_feat = self.num_joints
|
||||
heatmaps_loss = self.heatmaps_loss[idx](heatmaps_pred,
|
||||
heatmaps[idx],
|
||||
masks[idx])
|
||||
heatmaps_loss = heatmaps_loss * self.heatmaps_loss_factor[idx]
|
||||
heatmaps_losses.append(heatmaps_loss)
|
||||
else:
|
||||
heatmaps_losses.append(None)
|
||||
|
||||
if self.ae_loss[idx]:
|
||||
tags_pred = outputs[idx][:, offset_feat:]
|
||||
batch_size = tags_pred.size()[0]
|
||||
tags_pred = tags_pred.contiguous().view(batch_size, -1, 1)
|
||||
|
||||
push_loss, pull_loss = self.ae_loss[idx](tags_pred,
|
||||
joints[idx])
|
||||
push_loss = push_loss * self.push_loss_factor[idx]
|
||||
pull_loss = pull_loss * self.pull_loss_factor[idx]
|
||||
|
||||
push_losses.append(push_loss)
|
||||
pull_losses.append(pull_loss)
|
||||
else:
|
||||
push_losses.append(None)
|
||||
pull_losses.append(None)
|
||||
|
||||
return heatmaps_losses, push_losses, pull_losses
|
||||
+448
@@ -0,0 +1,448 @@
|
||||
# Copyright (c) OpenMMLab. All rights reserved.
|
||||
import math
|
||||
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
import torch.nn.functional as F
|
||||
|
||||
from ..builder import LOSSES
|
||||
|
||||
|
||||
@LOSSES.register_module()
|
||||
class SmoothL1Loss(nn.Module):
|
||||
"""SmoothL1Loss loss.
|
||||
|
||||
Args:
|
||||
use_target_weight (bool): Option to use weighted MSE loss.
|
||||
Different joint types may have different target weights.
|
||||
loss_weight (float): Weight of the loss. Default: 1.0.
|
||||
"""
|
||||
|
||||
def __init__(self, use_target_weight=False, loss_weight=1.):
|
||||
super().__init__()
|
||||
self.criterion = F.smooth_l1_loss
|
||||
self.use_target_weight = use_target_weight
|
||||
self.loss_weight = loss_weight
|
||||
|
||||
def forward(self, output, target, target_weight=None):
|
||||
"""Forward function.
|
||||
|
||||
Note:
|
||||
- batch_size: N
|
||||
- num_keypoints: K
|
||||
- dimension of keypoints: D (D=2 or D=3)
|
||||
|
||||
Args:
|
||||
output (torch.Tensor[N, K, D]): Output regression.
|
||||
target (torch.Tensor[N, K, D]): Target regression.
|
||||
target_weight (torch.Tensor[N, K, D]):
|
||||
Weights across different joint types.
|
||||
"""
|
||||
if self.use_target_weight:
|
||||
assert target_weight is not None
|
||||
loss = self.criterion(output * target_weight,
|
||||
target * target_weight)
|
||||
else:
|
||||
loss = self.criterion(output, target)
|
||||
|
||||
return loss * self.loss_weight
|
||||
|
||||
|
||||
@LOSSES.register_module()
|
||||
class WingLoss(nn.Module):
|
||||
"""Wing Loss. paper ref: 'Wing Loss for Robust Facial Landmark Localisation
|
||||
with Convolutional Neural Networks' Feng et al. CVPR'2018.
|
||||
|
||||
Args:
|
||||
omega (float): Also referred to as width.
|
||||
epsilon (float): Also referred to as curvature.
|
||||
use_target_weight (bool): Option to use weighted MSE loss.
|
||||
Different joint types may have different target weights.
|
||||
loss_weight (float): Weight of the loss. Default: 1.0.
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
omega=10.0,
|
||||
epsilon=2.0,
|
||||
use_target_weight=False,
|
||||
loss_weight=1.):
|
||||
super().__init__()
|
||||
self.omega = omega
|
||||
self.epsilon = epsilon
|
||||
self.use_target_weight = use_target_weight
|
||||
self.loss_weight = loss_weight
|
||||
|
||||
# constant that smoothly links the piecewise-defined linear
|
||||
# and nonlinear parts
|
||||
self.C = self.omega * (1.0 - math.log(1.0 + self.omega / self.epsilon))
|
||||
|
||||
def criterion(self, pred, target):
|
||||
"""Criterion of wingloss.
|
||||
|
||||
Note:
|
||||
- batch_size: N
|
||||
- num_keypoints: K
|
||||
- dimension of keypoints: D (D=2 or D=3)
|
||||
|
||||
Args:
|
||||
pred (torch.Tensor[N, K, D]): Output regression.
|
||||
target (torch.Tensor[N, K, D]): Target regression.
|
||||
"""
|
||||
delta = (target - pred).abs()
|
||||
losses = torch.where(
|
||||
delta < self.omega,
|
||||
self.omega * torch.log(1.0 + delta / self.epsilon), delta - self.C)
|
||||
return torch.mean(torch.sum(losses, dim=[1, 2]), dim=0)
|
||||
|
||||
def forward(self, output, target, target_weight=None):
|
||||
"""Forward function.
|
||||
|
||||
Note:
|
||||
- batch_size: N
|
||||
- num_keypoints: K
|
||||
- dimension of keypoints: D (D=2 or D=3)
|
||||
|
||||
Args:
|
||||
output (torch.Tensor[N, K, D]): Output regression.
|
||||
target (torch.Tensor[N, K, D]): Target regression.
|
||||
target_weight (torch.Tensor[N,K,D]):
|
||||
Weights across different joint types.
|
||||
"""
|
||||
if self.use_target_weight:
|
||||
assert target_weight is not None
|
||||
loss = self.criterion(output * target_weight,
|
||||
target * target_weight)
|
||||
else:
|
||||
loss = self.criterion(output, target)
|
||||
|
||||
return loss * self.loss_weight
|
||||
|
||||
|
||||
@LOSSES.register_module()
|
||||
class SoftWingLoss(nn.Module):
|
||||
"""Soft Wing Loss 'Structure-Coherent Deep Feature Learning for Robust Face
|
||||
Alignment' Lin et al. TIP'2021.
|
||||
|
||||
loss =
|
||||
1. |x| , if |x| < omega1
|
||||
2. omega2*ln(1+|x|/epsilon) + B, if |x| >= omega1
|
||||
|
||||
Args:
|
||||
omega1 (float): The first threshold.
|
||||
omega2 (float): The second threshold.
|
||||
epsilon (float): Also referred to as curvature.
|
||||
use_target_weight (bool): Option to use weighted MSE loss.
|
||||
Different joint types may have different target weights.
|
||||
loss_weight (float): Weight of the loss. Default: 1.0.
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
omega1=2.0,
|
||||
omega2=20.0,
|
||||
epsilon=0.5,
|
||||
use_target_weight=False,
|
||||
loss_weight=1.):
|
||||
super().__init__()
|
||||
self.omega1 = omega1
|
||||
self.omega2 = omega2
|
||||
self.epsilon = epsilon
|
||||
self.use_target_weight = use_target_weight
|
||||
self.loss_weight = loss_weight
|
||||
|
||||
# constant that smoothly links the piecewise-defined linear
|
||||
# and nonlinear parts
|
||||
self.B = self.omega1 - self.omega2 * math.log(1.0 + self.omega1 /
|
||||
self.epsilon)
|
||||
|
||||
def criterion(self, pred, target):
|
||||
"""Criterion of wingloss.
|
||||
|
||||
Note:
|
||||
batch_size: N
|
||||
num_keypoints: K
|
||||
dimension of keypoints: D (D=2 or D=3)
|
||||
|
||||
Args:
|
||||
pred (torch.Tensor[N, K, D]): Output regression.
|
||||
target (torch.Tensor[N, K, D]): Target regression.
|
||||
"""
|
||||
delta = (target - pred).abs()
|
||||
losses = torch.where(
|
||||
delta < self.omega1, delta,
|
||||
self.omega2 * torch.log(1.0 + delta / self.epsilon) + self.B)
|
||||
return torch.mean(torch.sum(losses, dim=[1, 2]), dim=0)
|
||||
|
||||
def forward(self, output, target, target_weight=None):
|
||||
"""Forward function.
|
||||
|
||||
Note:
|
||||
batch_size: N
|
||||
num_keypoints: K
|
||||
dimension of keypoints: D (D=2 or D=3)
|
||||
|
||||
Args:
|
||||
output (torch.Tensor[N, K, D]): Output regression.
|
||||
target (torch.Tensor[N, K, D]): Target regression.
|
||||
target_weight (torch.Tensor[N, K, D]):
|
||||
Weights across different joint types.
|
||||
"""
|
||||
if self.use_target_weight:
|
||||
assert target_weight is not None
|
||||
loss = self.criterion(output * target_weight,
|
||||
target * target_weight)
|
||||
else:
|
||||
loss = self.criterion(output, target)
|
||||
|
||||
return loss * self.loss_weight
|
||||
|
||||
|
||||
@LOSSES.register_module()
|
||||
class MPJPELoss(nn.Module):
|
||||
"""MPJPE (Mean Per Joint Position Error) loss.
|
||||
|
||||
Args:
|
||||
use_target_weight (bool): Option to use weighted MSE loss.
|
||||
Different joint types may have different target weights.
|
||||
loss_weight (float): Weight of the loss. Default: 1.0.
|
||||
"""
|
||||
|
||||
def __init__(self, use_target_weight=False, loss_weight=1.):
|
||||
super().__init__()
|
||||
self.use_target_weight = use_target_weight
|
||||
self.loss_weight = loss_weight
|
||||
|
||||
def forward(self, output, target, target_weight=None):
|
||||
"""Forward function.
|
||||
|
||||
Note:
|
||||
- batch_size: N
|
||||
- num_keypoints: K
|
||||
- dimension of keypoints: D (D=2 or D=3)
|
||||
|
||||
Args:
|
||||
output (torch.Tensor[N, K, D]): Output regression.
|
||||
target (torch.Tensor[N, K, D]): Target regression.
|
||||
target_weight (torch.Tensor[N,K,D]):
|
||||
Weights across different joint types.
|
||||
"""
|
||||
|
||||
if self.use_target_weight:
|
||||
assert target_weight is not None
|
||||
loss = torch.mean(
|
||||
torch.norm((output - target) * target_weight, dim=-1))
|
||||
else:
|
||||
loss = torch.mean(torch.norm(output - target, dim=-1))
|
||||
|
||||
return loss * self.loss_weight
|
||||
|
||||
|
||||
@LOSSES.register_module()
|
||||
class L1Loss(nn.Module):
|
||||
"""L1Loss loss ."""
|
||||
|
||||
def __init__(self, use_target_weight=False, loss_weight=1.):
|
||||
super().__init__()
|
||||
self.criterion = F.l1_loss
|
||||
self.use_target_weight = use_target_weight
|
||||
self.loss_weight = loss_weight
|
||||
|
||||
def forward(self, output, target, target_weight=None):
|
||||
"""Forward function.
|
||||
|
||||
Note:
|
||||
- batch_size: N
|
||||
- num_keypoints: K
|
||||
|
||||
Args:
|
||||
output (torch.Tensor[N, K, 2]): Output regression.
|
||||
target (torch.Tensor[N, K, 2]): Target regression.
|
||||
target_weight (torch.Tensor[N, K, 2]):
|
||||
Weights across different joint types.
|
||||
"""
|
||||
if self.use_target_weight:
|
||||
assert target_weight is not None
|
||||
loss = self.criterion(output * target_weight,
|
||||
target * target_weight)
|
||||
else:
|
||||
loss = self.criterion(output, target)
|
||||
|
||||
return loss * self.loss_weight
|
||||
|
||||
|
||||
@LOSSES.register_module()
|
||||
class MSELoss(nn.Module):
|
||||
"""MSE loss for coordinate regression."""
|
||||
|
||||
def __init__(self, use_target_weight=False, loss_weight=1.):
|
||||
super().__init__()
|
||||
self.criterion = F.mse_loss
|
||||
self.use_target_weight = use_target_weight
|
||||
self.loss_weight = loss_weight
|
||||
|
||||
def forward(self, output, target, target_weight=None):
|
||||
"""Forward function.
|
||||
|
||||
Note:
|
||||
- batch_size: N
|
||||
- num_keypoints: K
|
||||
|
||||
Args:
|
||||
output (torch.Tensor[N, K, 2]): Output regression.
|
||||
target (torch.Tensor[N, K, 2]): Target regression.
|
||||
target_weight (torch.Tensor[N, K, 2]):
|
||||
Weights across different joint types.
|
||||
"""
|
||||
if self.use_target_weight:
|
||||
assert target_weight is not None
|
||||
loss = self.criterion(output * target_weight,
|
||||
target * target_weight)
|
||||
else:
|
||||
loss = self.criterion(output, target)
|
||||
|
||||
return loss * self.loss_weight
|
||||
|
||||
|
||||
@LOSSES.register_module()
|
||||
class BoneLoss(nn.Module):
|
||||
"""Bone length loss.
|
||||
|
||||
Args:
|
||||
joint_parents (list): Indices of each joint's parent joint.
|
||||
use_target_weight (bool): Option to use weighted bone loss.
|
||||
Different bone types may have different target weights.
|
||||
loss_weight (float): Weight of the loss. Default: 1.0.
|
||||
"""
|
||||
|
||||
def __init__(self, joint_parents, use_target_weight=False, loss_weight=1.):
|
||||
super().__init__()
|
||||
self.joint_parents = joint_parents
|
||||
self.use_target_weight = use_target_weight
|
||||
self.loss_weight = loss_weight
|
||||
|
||||
self.non_root_indices = []
|
||||
for i in range(len(self.joint_parents)):
|
||||
if i != self.joint_parents[i]:
|
||||
self.non_root_indices.append(i)
|
||||
|
||||
def forward(self, output, target, target_weight=None):
|
||||
"""Forward function.
|
||||
|
||||
Note:
|
||||
- batch_size: N
|
||||
- num_keypoints: K
|
||||
- dimension of keypoints: D (D=2 or D=3)
|
||||
|
||||
Args:
|
||||
output (torch.Tensor[N, K, D]): Output regression.
|
||||
target (torch.Tensor[N, K, D]): Target regression.
|
||||
target_weight (torch.Tensor[N, K-1]):
|
||||
Weights across different bone types.
|
||||
"""
|
||||
output_bone = torch.norm(
|
||||
output - output[:, self.joint_parents, :],
|
||||
dim=-1)[:, self.non_root_indices]
|
||||
target_bone = torch.norm(
|
||||
target - target[:, self.joint_parents, :],
|
||||
dim=-1)[:, self.non_root_indices]
|
||||
if self.use_target_weight:
|
||||
assert target_weight is not None
|
||||
loss = torch.mean(
|
||||
torch.abs((output_bone * target_weight).mean(dim=0) -
|
||||
(target_bone * target_weight).mean(dim=0)))
|
||||
else:
|
||||
loss = torch.mean(
|
||||
torch.abs(output_bone.mean(dim=0) - target_bone.mean(dim=0)))
|
||||
|
||||
return loss * self.loss_weight
|
||||
|
||||
|
||||
@LOSSES.register_module()
|
||||
class SemiSupervisionLoss(nn.Module):
|
||||
"""Semi-supervision loss for unlabeled data. It is composed of projection
|
||||
loss and bone loss.
|
||||
|
||||
Paper ref: `3D human pose estimation in video with temporal convolutions
|
||||
and semi-supervised training` Dario Pavllo et al. CVPR'2019.
|
||||
|
||||
Args:
|
||||
joint_parents (list): Indices of each joint's parent joint.
|
||||
projection_loss_weight (float): Weight for projection loss.
|
||||
bone_loss_weight (float): Weight for bone loss.
|
||||
warmup_iterations (int): Number of warmup iterations. In the first
|
||||
`warmup_iterations` iterations, the model is trained only on
|
||||
labeled data, and semi-supervision loss will be 0.
|
||||
This is a workaround since currently we cannot access
|
||||
epoch number in loss functions. Note that the iteration number in
|
||||
an epoch can be changed due to different GPU numbers in multi-GPU
|
||||
settings. So please set this parameter carefully.
|
||||
warmup_iterations = dataset_size // samples_per_gpu // gpu_num
|
||||
* warmup_epochs
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
joint_parents,
|
||||
projection_loss_weight=1.,
|
||||
bone_loss_weight=1.,
|
||||
warmup_iterations=0):
|
||||
super().__init__()
|
||||
self.criterion_projection = MPJPELoss(
|
||||
loss_weight=projection_loss_weight)
|
||||
self.criterion_bone = BoneLoss(
|
||||
joint_parents, loss_weight=bone_loss_weight)
|
||||
self.warmup_iterations = warmup_iterations
|
||||
self.num_iterations = 0
|
||||
|
||||
@staticmethod
|
||||
def project_joints(x, intrinsics):
|
||||
"""Project 3D joint coordinates to 2D image plane using camera
|
||||
intrinsic parameters.
|
||||
|
||||
Args:
|
||||
x (torch.Tensor[N, K, 3]): 3D joint coordinates.
|
||||
intrinsics (torch.Tensor[N, 4] | torch.Tensor[N, 9]): Camera
|
||||
intrinsics: f (2), c (2), k (3), p (2).
|
||||
"""
|
||||
while intrinsics.dim() < x.dim():
|
||||
intrinsics.unsqueeze_(1)
|
||||
f = intrinsics[..., :2]
|
||||
c = intrinsics[..., 2:4]
|
||||
_x = torch.clamp(x[:, :, :2] / x[:, :, 2:], -1, 1)
|
||||
if intrinsics.shape[-1] == 9:
|
||||
k = intrinsics[..., 4:7]
|
||||
p = intrinsics[..., 7:9]
|
||||
|
||||
r2 = torch.sum(_x[:, :, :2]**2, dim=-1, keepdim=True)
|
||||
radial = 1 + torch.sum(
|
||||
k * torch.cat((r2, r2**2, r2**3), dim=-1),
|
||||
dim=-1,
|
||||
keepdim=True)
|
||||
tan = torch.sum(p * _x, dim=-1, keepdim=True)
|
||||
_x = _x * (radial + tan) + p * r2
|
||||
_x = f * _x + c
|
||||
return _x
|
||||
|
||||
def forward(self, output, target):
|
||||
losses = dict()
|
||||
|
||||
self.num_iterations += 1
|
||||
if self.num_iterations <= self.warmup_iterations:
|
||||
return losses
|
||||
|
||||
labeled_pose = output['labeled_pose']
|
||||
unlabeled_pose = output['unlabeled_pose']
|
||||
unlabeled_traj = output['unlabeled_traj']
|
||||
unlabeled_target_2d = target['unlabeled_target_2d']
|
||||
intrinsics = target['intrinsics']
|
||||
|
||||
# projection loss
|
||||
unlabeled_output = unlabeled_pose + unlabeled_traj
|
||||
unlabeled_output_2d = self.project_joints(unlabeled_output, intrinsics)
|
||||
loss_proj = self.criterion_projection(unlabeled_output_2d,
|
||||
unlabeled_target_2d, None)
|
||||
losses['proj_loss'] = loss_proj
|
||||
|
||||
# bone loss
|
||||
loss_bone = self.criterion_bone(unlabeled_pose, labeled_pose, None)
|
||||
losses['bone_loss'] = loss_bone
|
||||
|
||||
return losses
|
||||
Reference in New Issue
Block a user