OpenSeeFace/model.py

220 lines
7.8 KiB
Python
Raw Normal View History

# This file is not used by the tracking application and currently outdated
2020-01-01 13:55:01 +01:00
import torch
import torch.nn as nn
class DSConv2d(nn.Module):
def __init__(self, in_planes, out_planes, kernels_per_layer=4, groups=1):
super(DSConv2d, self).__init__()
self.conv = nn.Sequential(
nn.Conv2d(in_planes, in_planes * kernels_per_layer, kernel_size=3, padding=1, groups=in_planes),
nn.Conv2d(in_planes * kernels_per_layer, out_planes, kernel_size=1, groups=groups)
)
def forward(self, x):
x = self.conv(x)
return x
class UNetUp(nn.Module):
def __init__(self, in_channels, residual_in_channels, out_channels, size):
super(UNetUp, self).__init__()
self.up = nn.Upsample(size=size, mode='bilinear', align_corners=True)
self.conv = DSConv2d(in_channels + residual_in_channels, out_channels, 1, 1)
def forward(self, x1, x2):
x1 = self.up(x1)
x = torch.cat([x2, x1], dim=1)
x = self.conv(x)
return x
# Copy of torchvision ShuffleNetV2 for type annotation
def channel_shuffle(x, groups: int):
batchsize, num_channels, height, width = x.data.size()
channels_per_group = num_channels // groups
# reshape
x = x.view(batchsize, groups,
channels_per_group, height, width)
x = torch.transpose(x, 1, 2).contiguous()
# flatten
x = x.view(batchsize, -1, height, width)
return x
class InvertedResidual(nn.Module):
def __init__(self, inp, oup, stride):
super(InvertedResidual, self).__init__()
if not (1 <= stride <= 3):
raise ValueError('illegal stride value')
self.stride = stride
branch_features = oup // 2
assert (self.stride != 1) or (inp == branch_features << 1)
self.branch1 = nn.Sequential(
self.depthwise_conv(inp, inp, kernel_size=3, stride=self.stride, padding=1),
nn.BatchNorm2d(inp),
nn.Conv2d(inp, branch_features, kernel_size=1, stride=1, padding=0, bias=False),
nn.BatchNorm2d(branch_features),
nn.ReLU(inplace=True),
)
self.branch2 = nn.Sequential(
nn.Conv2d(inp if (self.stride > 1) else branch_features,
branch_features, kernel_size=1, stride=1, padding=0, bias=False),
nn.BatchNorm2d(branch_features),
nn.ReLU(inplace=True),
self.depthwise_conv(branch_features, branch_features, kernel_size=3, stride=self.stride, padding=1),
nn.BatchNorm2d(branch_features),
nn.Conv2d(branch_features, branch_features, kernel_size=1, stride=1, padding=0, bias=False),
nn.BatchNorm2d(branch_features),
nn.ReLU(inplace=True),
)
@staticmethod
def depthwise_conv(i, o, kernel_size, stride=1, padding=0, bias=False):
return nn.Conv2d(i, o, kernel_size, stride, padding, bias=bias, groups=i)
def forward(self, x):
if self.stride == 1:
x1, x2 = x.chunk(2, dim=1)
out = torch.cat((x1, self.branch2(x2)), dim=1)
else:
out = torch.cat((self.branch1(x), self.branch2(x)), dim=1)
out = channel_shuffle(out, 2)
return out
class ShuffleNetV2(nn.Module):
def __init__(self, stages_repeats, stages_out_channels, num_classes=1000):
super(ShuffleNetV2, self).__init__()
if len(stages_repeats) != 3:
raise ValueError('expected stages_repeats as list of 3 positive ints')
if len(stages_out_channels) != 5:
raise ValueError('expected stages_out_channels as list of 5 positive ints')
self._stage_out_channels = stages_out_channels
input_channels = 3
output_channels = self._stage_out_channels[0]
self.conv1 = nn.Sequential(
nn.Conv2d(input_channels, output_channels, 3, 2, 1, bias=False),
nn.BatchNorm2d(output_channels),
nn.ReLU(inplace=True),
)
input_channels = output_channels
self.maxpool = nn.MaxPool2d(kernel_size=3, stride=2, padding=1)
stage_names = ['stage{}'.format(i) for i in [2, 3, 4]]
for name, repeats, output_channels in zip(
stage_names, stages_repeats, self._stage_out_channels[1:]):
seq = [InvertedResidual(input_channels, output_channels, 2)]
for i in range(repeats - 1):
seq.append(InvertedResidual(output_channels, output_channels, 1))
setattr(self, name, nn.Sequential(*seq))
input_channels = output_channels
output_channels = self._stage_out_channels[-1]
self.conv5 = nn.Sequential(
nn.Conv2d(input_channels, output_channels, 1, 1, 0, bias=False),
nn.BatchNorm2d(output_channels),
nn.ReLU(inplace=True),
)
self.fc = nn.Linear(output_channels, num_classes)
def forward(self, x):
x = self.conv1(x)
x = self.maxpool(x)
x = self.stage2(x)
x = self.stage3(x)
x = self.stage4(x)
x = self.conv5(x)
x = x.mean([2, 3]) # globalpool
x = self.fc(x)
return x
# Facial landmark detection model
class OpenSeeNetSNV2(ShuffleNetV2):
def __init__(self):
super(OpenSeeNetSNV2, self).__init__([4, 8, 4], [24, 116, 232, 464, 1024])
self.up1 = UNetUp(1024, 232, 256, (14,14))
self.up2 = UNetUp(256, 116, 198 * 1, (28,28))
self.group = DSConv2d(198 * 1, 198, kernels_per_layer=4, groups=3)
def _forward_impl(self, x):
x = self.conv1(x)
x = self.maxpool(x)
x = self.stage2(x)
r2 = x
x = self.stage3(x)
r3 = x
x = self.stage4(x)
x = self.conv5(x)
x = self.up1(x, r3)
x = self.up2(x, r2)
x = self.group(x)
return x
def forward(self, x):
return self._forward_impl(x)
# Adaptive Wing Loss with offset layers and emphasis for eyes and eyebrows with 66 landmark points
def AdapWingLoss(pre_hm, gt_hm):
# pre_hm = pre_hm.to('cpu')
# gt_hm = gt_hm.to('cpu')
theta = 0.5
alpha = 2.1
w = 14
e = 1
A = w * (1 / (1 + torch.pow(theta / e, alpha - gt_hm))) * (alpha - gt_hm) * torch.pow(theta / e, alpha - gt_hm - 1) * (1 / e)
C = (theta * A - w * torch.log(1 + torch.pow(theta / e, alpha - gt_hm)))
batch_size = gt_hm.size()[0]
hm_num = gt_hm.size()[1]
mask = torch.zeros_like(gt_hm)
# W = 10
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (3, 3))
first = True
first_mask = None
for i in range(batch_size):
img_list = []
for j in range(hm_num // 3):
hm = np.round(gt_hm[i][j].cpu().numpy() * 255)
img_list.append(hm)
img_merge = cv2.merge(img_list)
img_dilate = cv2.morphologyEx(img_merge, cv2.MORPH_DILATE, kernel)
img_dilate[img_dilate < 51] = 1 # 0*W+1
img_dilate[img_dilate >= 51] = 11 # 1*W+1
img_dilate = np.array(img_dilate, dtype=np.int)
img_dilate = img_dilate.transpose(2, 0, 1)
dilated = torch.from_numpy(img_dilate).float()
if first:
first_mask = dilated
first = False
dilated[17:27] *= 1.2
dilated[17] *= 1.3
dilated[18] *= 1.4
dilated[25] *= 1.4
dilated[26] *= 1.3
dilated[36:47] *= 2.5
mask[i] = torch.cat([dilated, dilated, dilated], 0)
diff_hm = torch.abs(gt_hm - pre_hm)
AWingLoss = A * diff_hm - C
idx = diff_hm < theta
AWingLoss[idx] = w * torch.log(1 + torch.pow(diff_hm / e, alpha - gt_hm))[idx]
AWingLoss *= mask
sum_loss = torch.sum(AWingLoss)
all_pixel = torch.sum(mask)
mean_loss = sum_loss / all_pixel
return first_mask.detach(), mean_loss
# The fast model runs on grayscale 112x112 using the ShuffleNet V2 0.5x configuration with input_channels=1. It was trained using AWL, but without the additional weighting for certain features
.