Files
2026-04-20 10:09:13 +01:00

915 lines
34 KiB
Python

#!/usr/bin/env python3
"""Facial landmarks extractor for faceswap.pnt_y
Code adapted and modified from:
https://github.com/1adrianb/face-alignment
"""
from __future__ import annotations
import logging
import typing as T
from dataclasses import dataclass
import numpy as np
import torch
from torch import nn
from torch.nn import functional as F
from lib.utils import get_module_objects, GetModel
from plugins.extract.base import ExtractPlugin
from . import hrnet_defaults as cfg
from .dark_decoder import Dark
if T.TYPE_CHECKING:
import numpy.typing as npt
logger = logging.getLogger(__name__)
# pylint:disable=duplicate-code
@dataclass
class HRNetStageConfig:
"""Configuration settings for each stage of HRNet"""
num_modules: int
num_branches: int
num_blocks: list[int]
num_channels: list[int]
block: T.Literal["ATTENTION", "BLOCK", "BOTTLENECK"]
class HRNet(ExtractPlugin):
"""HRNet Face alignment"""
def __init__(self) -> None:
super().__init__(input_size=256, # if cfg.weights() == "standard" else 512,
batch_size=cfg.batch_size(),
is_rgb=True,
dtype="float32",
scale=(0, 1))
self._stage_configs = self._get_stage_configs()
self._hm_size = 64 # if cfg.weights() == "standard" else 128
self.model: HighResolutionNet
self.realign_centering = "legacy"
self._mean = np.array([0.485, 0.456, 0.406], dtype=np.float32)
self._std = np.array([0.229, 0.224, 0.225], dtype=np.float32)
self._dark = Dark(68, self._hm_size) if cfg.dark_decoder() else None
@classmethod
def _get_stage_configs(cls) -> tuple[HRNetStageConfig, HRNetStageConfig, HRNetStageConfig]:
"""Obtain the model configuration for the chosen weights
Returns
-------
The model configuration
"""
# weights = cfg.weights()
weights = "standard"
if weights == "standard":
retval = (HRNetStageConfig(num_modules=1,
num_branches=2,
num_blocks=[4, 4],
num_channels=[18, 36],
block="BLOCK"),
HRNetStageConfig(num_modules=4,
num_branches=3,
num_blocks=[4, 4, 4],
num_channels=[18, 36, 72],
block="ATTENTION"),
HRNetStageConfig(num_modules=3,
num_branches=4,
num_blocks=[4, 4, 4, 4],
num_channels=[18, 36, 72, 144],
block="ATTENTION"))
else:
retval = (HRNetStageConfig(num_modules=1,
num_branches=2,
num_blocks=[4, 4],
num_channels=[32, 64],
block="BLOCK"),
HRNetStageConfig(num_modules=4,
num_branches=3,
num_blocks=[4, 4, 4],
num_channels=[32, 64, 128],
block="ATTENTION"),
HRNetStageConfig(num_modules=3,
num_branches=4,
num_blocks=[4, 4, 4, 4],
num_channels=[32, 64, 128, 256],
block="ATTENTION"))
logger.debug("[HRNet] using config for weights '%s': %s", weights, retval)
return retval
def load_model(self) -> HighResolutionNet:
"""Load the HRNet model
Returns
-------
The loaded HRNet model
"""
# version = 2 if cfg.weights() == "standard" else 3
version = 2
weights = GetModel(f"hrnet_landmark_v{version}.pth", 34).model_path
assert isinstance(weights, str)
model = T.cast(HighResolutionNet, self.load_torch_model(
HighResolutionNet(num_joints=68,
final_conv_kernel=1,
stage_2_config=self._stage_configs[0],
stage_3_config=self._stage_configs[1],
stage_4_config=self._stage_configs[2]),
weights))
return model
def pre_process(self, batch: np.ndarray) -> np.ndarray:
"""Format the ROI faces detection boxes for prediction
Parameters
----------
batch
The batch of face detection bounding boxes as (bs, l, t, r, b)
Returns
-------
The face detection bounding boxes formatted to take an image patch for prediction
"""
heights = batch[:, 3] - batch[:, 1]
widths = batch[:, 2] - batch[:, 0]
ctr_x = np.rint((batch[:, 0] + batch[:, 2]) * 0.5).astype("int32")
ctr_y = np.rint((batch[:, 1] + batch[:, 3]) * 0.5).astype("int32")
size = np.maximum(widths, heights) * 1.25
half = np.rint(size * 0.5).astype("int32")
retval = np.empty((batch.shape[0], 4), dtype=np.int32)
retval[:, 0] = ctr_x - half
retval[:, 1] = ctr_y - half
retval[:, 2] = ctr_x + half
retval[:, 3] = ctr_y + half
return retval
def process(self, batch: np.ndarray) -> np.ndarray:
"""Predict the 68 point landmarks
Parameters
----------
batch
The batch to feed into the aligner
Returns
-------
The predictions from the aligner
"""
batch -= self._mean
batch /= self._std
return self.from_torch(batch.transpose(0, 3, 1, 2))
def _get_predictions(self, scores: npt.NDArray[np.float32]) -> npt.NDArray[np.float32]:
"""Convert the score maps from the model into predictions
Parameters
----------
scores
The score maps from the model
Returns
-------
Predictions from the score maps
"""
batch_size, num_points, size = scores.shape[:3]
scores_r = scores.reshape((batch_size, num_points, -1))
max_val: npt.NDArray[np.float32] = scores_r.max(axis=2)
idx: npt.NDArray[np.int64] = scores_r.argmax(axis=2)
retval = np.empty((batch_size, num_points, 2), dtype=np.float32)
retval[:, :, 0] = (idx - 1) % size + 1
retval[:, :, 1] = np.floor((idx - 1) / size) + 1
mask = max_val[..., None] > 0.
retval *= mask
return retval
def post_process(self, batch: np.ndarray) -> np.ndarray: # pylint:disable=too-many-locals
"""Process the output from the model
Parameters
----------
batch
The predictions from the aligner
Returns
-------
The final landmarks in 0-1 space
"""
if self._dark is not None:
return self._dark(batch) / self._hm_size
batch_size, num_points, height, width = batch.shape
assert height == width, "Heatmaps must be square"
resolution = height
coords = self._get_predictions(batch)
pnt_x = coords[..., 0].astype(np.int32)
pnt_y = coords[..., 1].astype(np.int32)
mask = (pnt_x > 1) & (pnt_x < resolution) & (pnt_y > 1) & (pnt_y < resolution)
pnt_x = np.clip(pnt_x, 2, resolution - 1)
pnt_y = np.clip(pnt_x, 2, resolution - 1)
idx_batch = np.arange(batch_size)[:, None]
idx_pnt = np.arange(num_points)[None, :]
delta_x = (batch[idx_batch, idx_pnt, pnt_y - 1, pnt_x] -
batch[idx_batch, idx_pnt, pnt_y - 1, pnt_x - 2])
delta_y = (batch[idx_batch, idx_pnt, pnt_y, pnt_x - 1] -
batch[idx_batch, idx_pnt, pnt_y - 2, pnt_x - 1])
diff = np.stack([delta_x, delta_y], axis=-1)
coords += (np.sign(diff) * 0.25 * mask[..., None]) + 0.5
coords /= resolution
return coords
class BasicBlock(nn.Module):
""" Basic block for HRNet
Parameters
----------
in_channels
The number of in channels
out_channels
The number of out channels
stride
The stride for the first 3x3 conv block. Default: 1
downsample
The module to use for downsampling or ``None`` for no downsample. Default: ``None``
"""
expansion = 1
def __init__(self,
in_channels: int,
out_channels: int,
stride: int = 1,
downsample: nn.Module | None = None) -> None:
super().__init__()
self.conv1 = nn.Conv2d(in_channels, out_channels, 3, stride=stride, padding=1, bias=False)
self.bn1 = nn.BatchNorm2d(out_channels, momentum=0.01)
self.relu = nn.ReLU(inplace=True)
self.conv2 = nn.Conv2d(in_channels, out_channels, 3, stride=1, padding=1, bias=False)
self.bn2 = nn.BatchNorm2d(out_channels, momentum=0.01)
self.downsample = downsample
self.stride = stride
def forward(self, inputs: torch.Tensor) -> torch.Tensor:
"""Forward pass through HRNet's basic block
Parameters
----------
inputs
Input to the conv block
Returns
-------
Output from the conv block
"""
residual = inputs
out = self.conv1(inputs)
out = self.bn1(out)
out = self.relu(out)
out = self.conv2(out)
out = self.bn2(out)
if self.downsample is not None:
residual = self.downsample(inputs)
out += residual
out = self.relu(out)
return out
class BasicBlockAttention(nn.Module): # pylint:disable=too-many-instance-attributes
""" Custom Basic block for HRNet with Attention
Parameters
----------
in_channels
The number of in channels
out_channels
The number of out channels
stride
The stride for the first 3x3 conv block. Default: 1
downsample
The module to use for downsampling or ``None`` for no downsample. Default: ``None``
"""
expansion = 1
def __init__(self, in_channels, out_channels, stride=1, downsample=None):
super().__init__()
self.conv1 = nn.Conv2d(in_channels, out_channels, 3, stride=stride, padding=1, bias=False)
self.bn1 = nn.BatchNorm2d(out_channels, momentum=0.01)
self.relu = nn.ReLU(inplace=True)
self.conv2 = nn.Conv2d(in_channels, out_channels, 3, stride=1, padding=1, bias=False)
self.bn2 = nn.BatchNorm2d(out_channels, momentum=0.01)
self.downsample = downsample
self.att1 = nn.Sequential(nn.Conv2d(in_channels,
max(1, out_channels // 4),
1,
stride=1,
bias=False),
nn.BatchNorm2d(out_channels // 4),
nn.ReLU(inplace=True))
self.att2 = nn.Conv2d(max(1, out_channels // 4),
max(1, out_channels // 4),
3,
stride=1,
padding=1,
bias=False)
self.att_bn1 = nn.BatchNorm2d(max(1, out_channels // 4), momentum=0.01)
self.att3 = nn.Conv2d(max(1, out_channels // 4), out_channels, 1, 1, bias=False)
self.att_bn2 = nn.BatchNorm2d(out_channels, momentum=0.01)
def forward(self, inputs: torch.Tensor) -> torch.Tensor:
"""Forward pass through HRNet's basic block with attention
Parameters
----------
inputs
Input to the conv block
Returns
-------
Output from the conv block
"""
residual = inputs
out = self.conv1(inputs)
out = self.bn1(out)
out = self.relu(out)
out = self.conv2(out)
out = self.bn2(out)
att = self.att1(out)
att = self.att2(att)
att = self.att_bn1(att)
att = self.relu(att)
att = self.att3(att)
att = self.att_bn2(att)
att = torch.sigmoid(att)
out = out * att
if self.downsample is not None:
residual = self.downsample(inputs)
out += residual
out = self.relu(out)
return out
class Bottleneck(nn.Module):
""" Bottleneck for HRNet
Parameters
----------
in_channels
The number of in channels
out_channels
The number of out channels
stride
The stride for the first 3x3 conv block. Default: 1
downsample
The module to use for downsampling or ``None`` for no downsample. Default: ``None``
"""
expansion = 4
def __init__(self,
in_channels: int,
out_channels: int,
stride: int = 1,
downsample: nn.Module | None = None) -> None:
super().__init__()
self.conv1 = nn.Conv2d(in_channels, out_channels, 1, bias=False)
self.bn1 = nn.BatchNorm2d(out_channels, momentum=0.01)
self.conv2 = nn.Conv2d(out_channels, out_channels, 3, stride=stride, padding=1, bias=False)
self.bn2 = nn.BatchNorm2d(out_channels, momentum=0.01)
self.conv3 = nn.Conv2d(out_channels, out_channels * self.expansion, 1, bias=False)
self.bn3 = nn.BatchNorm2d(out_channels * self.expansion, momentum=0.01)
self.relu = nn.ReLU(inplace=True)
self.downsample = downsample
self.stride = stride
def forward(self, inputs: torch.Tensor) -> torch.Tensor:
"""Forward pass through HRNet's basic block
Parameters
----------
inputs
Input to the conv block
Returns
-------
Output from the conv block
"""
residual = inputs
out = self.conv1(inputs)
out = self.bn1(out)
out = self.relu(out)
out = self.conv2(out)
out = self.bn2(out)
out = self.relu(out)
out = self.conv3(out)
out = self.bn3(out)
if self.downsample is not None:
residual = self.downsample(inputs)
out += residual
out = self.relu(out)
return out
class HighResolutionModule(nn.Module):
""" High Resolution Module for HRNet
Parameters
----------
num_branches
The number of branches to use
blocks
The block object to use
num_blocks
The number of blocks in each branch
num_in_channels
The number of input channels in each branch
num_channels
The number of channels in each branch
multi_scale_output
``True`` to output multi-scaled
"""
def __init__(self,
num_branches: int,
block: type[BasicBlockAttention] | type[BasicBlock] | type[Bottleneck],
num_blocks: list[int],
num_in_channels: list[int],
num_channels: list[int],
multi_scale_output: bool = True) -> None:
super().__init__()
self._check_branches(num_branches,
num_blocks,
num_in_channels,
num_channels)
self.num_in_channels = num_in_channels
self.num_branches = num_branches
self.multi_scale_output = multi_scale_output
self.branches = nn.ModuleList(self._make_one_branch(i,
block,
num_blocks[i],
num_channels[i])
for i in range(num_branches))
self.fuse_layers = self._make_fuse_layers()
self.relu = nn.ReLU(inplace=True)
def _check_branches(self,
num_branches: int,
num_blocks: list[int],
num_in_channels: list[int],
num_channels: list[int]) -> None:
"""Check that the branch configuration is valid
Parameters
----------
num_branches
The number of branches to use
num_blocks
The number of blocks in each branch
num_in_channels
The number of input channels in each branch
num_channels
The number of channels in each branch
Raises
------
ValueError
On an invalid configuration
"""
if num_branches != len(num_blocks):
raise ValueError(f"NUM_BRANCHES({num_branches}) <> NUM_BLOCKS({len(num_blocks)})")
if num_branches != len(num_channels):
raise ValueError(f"NUM_BRANCHES({num_branches}) <> NUM_CHANNELS({len(num_channels)})")
if num_branches != len(num_in_channels):
raise ValueError(f"NUM_BRANCHES({num_branches}) <> "
f"NUM_IN_CHANNELS({len(num_in_channels)})")
def _make_one_branch(self,
branch_index: int,
block: type[BasicBlockAttention | BasicBlock | Bottleneck],
num_blocks: int,
num_channels: int,
stride: int = 1) -> nn.Sequential:
""" Make a single branch
Parameters
----------
branch_index
The index of the branch to make
block
The block object to use
num_blocks
The number of blocks in each branch
num_in_channels
The number of input channels in each branch
num_channels
The number of channels in each branch
multi_scale_output
``True`` to output multi-scaled
Returns
-------
The sequential modules for the branch
"""
downsample = None
if stride != 1 or self.num_in_channels[branch_index] != num_channels * block.expansion:
downsample = nn.Sequential(nn.Conv2d(self.num_in_channels[branch_index],
num_channels * block.expansion,
1,
stride=stride,
bias=False),
nn.BatchNorm2d(num_channels * block.expansion,
momentum=0.01))
layers = []
layers.append(block(self.num_in_channels[branch_index],
num_channels,
stride,
downsample=downsample))
self.num_in_channels[branch_index] = num_channels * block.expansion
for _ in range(1, num_blocks):
layers.append(block(self.num_in_channels[branch_index], num_channels))
return nn.Sequential(*layers)
def _make_fuse_layers(self) -> nn.ModuleList | None:
"""Make the fuse layers for the HR Module
Returns
-------
The fuse layers module list or ``None`` if layers are not to be fused
"""
if self.num_branches == 1:
return None
num_branches = self.num_branches
num_in_channels = self.num_in_channels
fuse_layers = []
for i in range(num_branches if self.multi_scale_output else 1):
fuse_layer = []
for j in range(num_branches):
if j > i:
fuse_layer.append(nn.Sequential(nn.Conv2d(num_in_channels[j],
num_in_channels[i],
1,
stride=1,
padding=0,
bias=False),
nn.BatchNorm2d(num_in_channels[i],
momentum=0.01)))
elif j == i:
fuse_layer.append(None) # type:ignore[arg-type]
else:
conv3x3s = []
for k in range(i - j):
if k == i - j - 1:
num_out_channels_conv3x3 = num_in_channels[i]
conv3x3s.append(nn.Sequential(nn.Conv2d(num_in_channels[j],
num_out_channels_conv3x3,
3,
stride=2,
padding=1,
bias=False),
nn.BatchNorm2d(num_out_channels_conv3x3,
momentum=0.01)))
else:
num_out_channels_conv3x3 = num_in_channels[j]
conv3x3s.append(nn.Sequential(nn.Conv2d(num_in_channels[j],
num_out_channels_conv3x3,
3,
stride=2,
padding=1,
bias=False),
nn.BatchNorm2d(num_out_channels_conv3x3,
momentum=0.01),
nn.ReLU(inplace=True)))
fuse_layer.append(nn.Sequential(*conv3x3s))
fuse_layers.append(nn.ModuleList(fuse_layer))
return nn.ModuleList(fuse_layers)
def get_num_in_channels(self) -> list[int]:
"""Obtain the number of input channels to the module
Returns
-------
The number of input channels to the module
"""
return self.num_in_channels
def forward(self, inputs: list[torch.Tensor]) -> list[torch.Tensor]:
"""Forward pass through the HR Module
Parameters
----------
inputs
Input to the HR Module
Returns
-------
Output from the HR Module
"""
x = inputs
if self.num_branches == 1:
return [self.branches[0](x[0])]
assert self.fuse_layers is not None
for i in range(self.num_branches):
x[i] = self.branches[i](x[i])
x_fuse = []
for i, fuse_layer in enumerate(T.cast(list[nn.ModuleList], self.fuse_layers)):
y = x[0] if i == 0 else fuse_layer[0](x[0])
for j in range(1, self.num_branches):
if i == j:
y = y + x[j]
elif j > i:
y = y + F.interpolate(fuse_layer[j](x[j]),
size=[x[i].shape[2], x[i].shape[3]],
mode="bilinear")
else:
y = y + fuse_layer[j](x[j])
x_fuse.append(self.relu(y))
return x_fuse
class HighResolutionNet(nn.Module): # pylint:disable=too-many-instance-attributes
"""The HRNet Landmark Detection model
Parameters
----------
num_joints
The number of joints in the model
final_conv_kernel
Kernel size of the final convolution
stage_2_config
Configuration settings for stage 2 layers
stage_3_config
Configuration settings for stage 3 layers
stage_4_config
Configuration settings for stage 4 layers
"""
def __init__(self,
num_joints: int,
final_conv_kernel: int,
stage_2_config: HRNetStageConfig,
stage_3_config: HRNetStageConfig,
stage_4_config: HRNetStageConfig) -> None:
self.in_channels = 64
super().__init__()
self.stage_2_config = stage_2_config
self.stage_3_config = stage_3_config
self.stage_4_config = stage_4_config
# stem net
self.conv1 = nn.Conv2d(3, 64, kernel_size=3, stride=2, padding=1,
bias=False)
self.bn1 = nn.BatchNorm2d(64, momentum=0.01)
self.conv2 = nn.Conv2d(64, 64, kernel_size=3, stride=2, padding=1,
bias=False)
self.bn2 = nn.BatchNorm2d(64, momentum=0.01)
self.relu = nn.ReLU(inplace=True)
self.sf = nn.Softmax(dim=1)
self.layer1 = self._make_layer(Bottleneck, 64, 64, 4)
_blocks: dict[str, type[BasicBlockAttention | BasicBlock | Bottleneck]] = {
"ATTENTION": BasicBlockAttention, "BLOCK": BasicBlock, "BOTTLENECK": Bottleneck}
num_channels = stage_2_config.num_channels
block = _blocks[stage_2_config.block]
num_channels = [num_channels[i] * block.expansion for i in range(len(num_channels))]
self.transition1 = self._make_transition_layer([256], num_channels)
self.stage2, pre_stage_channels = self._make_stage(block, stage_2_config, num_channels)
num_channels = stage_3_config.num_channels
block = _blocks[stage_3_config.block]
num_channels = [num_channels[i] * block.expansion for i in range(len(num_channels))]
self.transition2 = self._make_transition_layer(pre_stage_channels, num_channels)
self.stage3, pre_stage_channels = self._make_stage(block, stage_3_config, num_channels)
num_channels = stage_4_config.num_channels
block = _blocks[stage_4_config.block]
num_channels = [num_channels[i] * block.expansion for i in range(len(num_channels))]
self.transition3 = self._make_transition_layer(pre_stage_channels, num_channels)
self.stage4, pre_stage_channels = self._make_stage(block,
stage_4_config,
num_channels,
multi_scale_output=True)
final_inp_channels = sum(pre_stage_channels)
self.head = nn.Sequential(nn.Conv2d(final_inp_channels,
final_inp_channels,
1,
stride=1,
padding=1 if final_conv_kernel == 3 else 0),
nn.BatchNorm2d(final_inp_channels, momentum=0.01),
nn.ReLU(inplace=True),
nn.Conv2d(final_inp_channels,
num_joints,
final_conv_kernel,
stride=1,
padding=1 if final_conv_kernel == 3 else 0))
def _make_transition_layer(self,
num_channels_pre_layer: list[int],
num_channels_cur_layer: list[int]) -> nn.ModuleList:
"""Make an HRNet transition layer
Parameters
----------
num_channels_pre_layer
The number of channels from the previous layer
num_channels_cur_layer
The number of channels from the current layer
Returns
-------
The transition layer module list
"""
num_branches_cur = len(num_channels_cur_layer)
num_branches_pre = len(num_channels_pre_layer)
transition_layers = []
for i in range(num_branches_cur):
if i < num_branches_pre:
if num_channels_cur_layer[i] != num_channels_pre_layer[i]:
transition_layers.append(nn.Sequential(
nn.Conv2d(num_channels_pre_layer[i],
num_channels_cur_layer[i],
3,
stride=1,
padding=1,
bias=False),
nn.BatchNorm2d(num_channels_cur_layer[i], momentum=0.01),
nn.ReLU(inplace=True)))
else:
transition_layers.append(None) # type:ignore[arg-type]
continue
conv3x3s = []
for j in range(i + 1 - num_branches_pre):
in_channels = num_channels_pre_layer[-1]
out_channels = (num_channels_cur_layer[i] if j == i - num_branches_pre
else in_channels)
conv3x3s.append(nn.Sequential(
nn.Conv2d(in_channels, out_channels, 3, stride=2, padding=1, bias=False),
nn.BatchNorm2d(out_channels, momentum=0.01),
nn.ReLU(inplace=True)))
transition_layers.append(nn.Sequential(*conv3x3s))
return nn.ModuleList(transition_layers)
def _make_layer(self,
block: type[BasicBlockAttention | BasicBlock | Bottleneck],
in_channels: int,
out_channels: int,
blocks: int,
stride: int = 1) -> nn.Sequential:
"""Make an HRNet layer
Parameters
----------
block
The type of block to use for the layer
in_channels
The number of input channels
out_channels
The number of output channels
blocks
The number of blocks
stride
The stride size. Default: 1
Returns
-------
The sequential layer
"""
downsample = None
if stride != 1 or in_channels != out_channels * block.expansion:
downsample = nn.Sequential(nn.Conv2d(in_channels,
out_channels * block.expansion,
1,
stride=stride,
bias=False),
nn.BatchNorm2d(out_channels * block.expansion,
momentum=0.01))
layers = []
layers.append(block(in_channels, out_channels, stride, downsample))
in_channels = out_channels * block.expansion
for _ in range(1, blocks):
layers.append(block(in_channels, out_channels))
return nn.Sequential(*layers)
def _make_stage(self,
block: type[BasicBlockAttention | BasicBlock | Bottleneck],
layer_config: HRNetStageConfig,
num_in_channels: list[int],
multi_scale_output: bool = True) -> tuple[nn.Sequential, list[int]]:
"""Make a stage for HRNet
Parameters
----------
block
The type of block to use for the layer
layer_config
The configuration for the stage
num_in_channels
The input channels for the stage
multi_scale_output
``True`` to output multi scale
Returns
-------
sequential
The stage Sequential Modules
num_in_channels
The number of input channels from the final module
"""
num_modules = layer_config.num_modules
num_branches = layer_config.num_branches
num_blocks = layer_config.num_blocks
num_channels = layer_config.num_channels
modules: list[HighResolutionModule] = []
for i in range(num_modules):
if not multi_scale_output and i == num_modules - 1:
reset_multi_scale_output = False
else:
reset_multi_scale_output = True
modules.append(HighResolutionModule(num_branches,
block,
num_blocks,
num_in_channels,
num_channels,
reset_multi_scale_output))
num_in_channels = modules[-1].get_num_in_channels()
return nn.Sequential(*modules), num_in_channels
def forward(self, inputs: torch.Tensor) -> torch.Tensor:
"""Forward pass through HRNet
Parameters
----------
inputs
Input to HRNet
Returns
-------
Output from HRNet
"""
x = self.conv1(inputs)
x = self.bn1(x)
x = self.relu(x)
x = self.conv2(x)
x = self.bn2(x)
x = self.relu(x)
x = self.layer1(x)
x_list = [self.transition1[i](x) if self.transition1[i] is not None else x
for i in range(self.stage_2_config.num_branches)]
y_list = self.stage2(x_list)
x_list = [self.transition2[i](y_list[-1]) if self.transition2[i] is not None
else y_list[i]
for i in range(self.stage_3_config.num_branches)]
y_list = self.stage3(x_list)
x_list = [self.transition3[i](y_list[-1]) if self.transition3[i] is not None
else y_list[i]
for i in range(self.stage_4_config.num_branches)]
x = self.stage4(x_list)
# Head Part
height, width = x[0].size(2), x[0].size(3)
x1 = F.interpolate(x[1], size=(height, width), mode="bilinear", align_corners=False)
x2 = F.interpolate(x[2], size=(height, width), mode="bilinear", align_corners=False)
x3 = F.interpolate(x[3], size=(height, width), mode="bilinear", align_corners=False)
x = torch.cat([x[0], x1, x2, x3], 1)
x = self.head(x)
return x
__all__ = get_module_objects(__name__)