garmentiq.landmark.detection.model_definition
The HRNet pose model used for landmark detection.
PoseHighResolutionNet maintains high-resolution feature maps throughout the network
rather than recovering resolution at the end, which is what makes it accurate for
precise keypoint localisation.
1"""The HRNet pose model used for landmark detection. 2 3`PoseHighResolutionNet` maintains high-resolution feature maps throughout the network 4rather than recovering resolution at the end, which is what makes it accurate for 5precise keypoint localisation. 6""" 7import os 8import logging 9import torch 10import torch.nn as nn 11import torch.nn.functional as F 12 13 14BN_MOMENTUM = 0.1 15logger = logging.getLogger(__name__) 16 17 18def conv3x3(in_planes, out_planes, stride=1): 19 """ 20 Creates a 3x3 convolutional layer with padding. 21 22 Args: 23 in_planes (int): Number of input channels. 24 out_planes (int): Number of output channels. 25 stride (int, optional): Stride of the convolution. Defaults to 1. 26 27 Returns: 28 nn.Conv2d: 3x3 convolution layer with specified parameters. 29 """ 30 return nn.Conv2d( 31 in_planes, out_planes, kernel_size=3, stride=stride, padding=1, bias=False 32 ) 33 34 35class BasicBlock(nn.Module): 36 """ 37 Basic residual block with two 3x3 convolutional layers. 38 39 Attributes: 40 expansion (int): Expansion factor for output channels (always 1 for BasicBlock). 41 conv1 (nn.Conv2d): First convolutional layer. 42 bn1 (nn.BatchNorm2d): Batch normalization after first conv. 43 conv2 (nn.Conv2d): Second convolutional layer. 44 bn2 (nn.BatchNorm2d): Batch normalization after second conv. 45 downsample (nn.Module or None): Optional downsampling layer for residual connection. 46 stride (int): Stride of the first convolution. 47 """ 48 49 expansion = 1 50 51 def __init__(self, inplanes, planes, stride=1, downsample=None): 52 """ 53 Initializes BasicBlock. 54 55 Args: 56 inplanes (int): Number of input channels. 57 planes (int): Number of output channels. 58 stride (int, optional): Stride of the first convolution. Defaults to 1. 59 downsample (nn.Module or None, optional): Downsampling layer for residual. Defaults to None. 60 """ 61 super(BasicBlock, self).__init__() 62 self.conv1 = conv3x3(inplanes, planes, stride) 63 self.bn1 = nn.BatchNorm2d(planes, momentum=BN_MOMENTUM) 64 self.conv2 = conv3x3(planes, planes) 65 self.bn2 = nn.BatchNorm2d(planes, momentum=BN_MOMENTUM) 66 self.downsample = downsample 67 self.stride = stride 68 69 def forward(self, x): 70 """ 71 Forward pass of the BasicBlock. 72 73 Args: 74 x (torch.Tensor): Input tensor. 75 76 Returns: 77 torch.Tensor: Output tensor after residual addition and activation. 78 """ 79 residual = x 80 81 out = self.conv1(x) 82 out = self.bn1(out) 83 out = F.relu(out, inplace=True) 84 85 out = self.conv2(out) 86 out = self.bn2(out) 87 88 if self.downsample is not None: 89 residual = self.downsample(x) 90 91 out += residual 92 out = F.relu(out, inplace=True) 93 94 return out 95 96 97class Bottleneck(nn.Module): 98 """ 99 Bottleneck residual block with 1x1, 3x3, and 1x1 convolutions. 100 101 Attributes: 102 expansion (int): Expansion factor for output channels (usually 4 for Bottleneck). 103 conv1 (nn.Conv2d): 1x1 convolution reducing channels. 104 bn1 (nn.BatchNorm2d): Batch normalization after conv1. 105 conv2 (nn.Conv2d): 3x3 convolution. 106 bn2 (nn.BatchNorm2d): Batch normalization after conv2. 107 conv3 (nn.Conv2d): 1x1 convolution expanding channels. 108 bn3 (nn.BatchNorm2d): Batch normalization after conv3. 109 downsample (nn.Module or None): Optional downsampling layer for residual connection. 110 stride (int): Stride for the 3x3 convolution. 111 """ 112 113 expansion = 4 114 115 def __init__(self, inplanes, planes, stride=1, downsample=None): 116 """ 117 Initializes Bottleneck block. 118 119 Args: 120 inplanes (int): Number of input channels. 121 planes (int): Number of output channels before expansion. 122 stride (int, optional): Stride for the 3x3 convolution. Defaults to 1. 123 downsample (nn.Module or None, optional): Downsampling layer for residual. Defaults to None. 124 """ 125 super(Bottleneck, self).__init__() 126 self.conv1 = nn.Conv2d(inplanes, planes, kernel_size=1, bias=False) 127 self.bn1 = nn.BatchNorm2d(planes, momentum=BN_MOMENTUM) 128 self.conv2 = nn.Conv2d( 129 planes, planes, kernel_size=3, stride=stride, padding=1, bias=False 130 ) 131 self.bn2 = nn.BatchNorm2d(planes, momentum=BN_MOMENTUM) 132 self.conv3 = nn.Conv2d( 133 planes, planes * self.expansion, kernel_size=1, bias=False 134 ) 135 self.bn3 = nn.BatchNorm2d(planes * self.expansion, momentum=BN_MOMENTUM) 136 self.downsample = downsample 137 self.stride = stride 138 139 def forward(self, x): 140 """ 141 Forward pass of the Bottleneck block. 142 143 Args: 144 x (torch.Tensor): Input tensor. 145 146 Returns: 147 torch.Tensor: Output tensor after residual addition and activation. 148 """ 149 residual = x 150 151 out = self.conv1(x) 152 out = self.bn1(out) 153 out = F.relu(out, inplace=True) 154 155 out = self.conv2(out) 156 out = self.bn2(out) 157 out = F.relu(out, inplace=True) 158 159 out = self.conv3(out) 160 out = self.bn3(out) 161 162 if self.downsample is not None: 163 residual = self.downsample(x) 164 165 out += residual 166 out = F.relu(out, inplace=True) 167 168 return out 169 170 171class HighResolutionModule(nn.Module): 172 """ 173 HighResolutionModule maintains high-resolution representations through multi-branch architecture and fusion. 174 175 This module consists of several parallel branches with residual blocks, and fuse layers to combine 176 features from different resolutions. 177 178 Attributes: 179 num_branches (int): Number of parallel branches. 180 blocks (nn.Module): Residual block class (BasicBlock or Bottleneck). 181 num_blocks (list[int]): Number of residual blocks per branch. 182 num_inchannels (list[int]): Number of input channels for each branch. 183 num_channels (list[int]): Number of channels per branch before expansion. 184 fuse_method (str): Method to fuse multi-branch outputs ('SUM' supported). 185 multi_scale_output (bool): Whether to output multi-scale features. 186 branches (nn.ModuleList): The parallel branches. 187 fuse_layers (nn.ModuleList or None): Layers that fuse features from branches. 188 """ 189 190 def __init__( 191 self, 192 num_branches, 193 blocks, 194 num_blocks, 195 num_inchannels, 196 num_channels, 197 fuse_method, 198 multi_scale_output=True, 199 ): 200 """ 201 Initializes HighResolutionModule. 202 203 Args: 204 num_branches (int): Number of parallel branches. 205 blocks (nn.Module): Residual block class (BasicBlock or Bottleneck). 206 num_blocks (list[int]): Number of residual blocks per branch. 207 num_inchannels (list[int]): Number of input channels for each branch. 208 num_channels (list[int]): Number of channels per branch before expansion. 209 fuse_method (str): Method to fuse multi-branch outputs. 210 multi_scale_output (bool, optional): Output multi-scale features or not. Defaults to True. 211 212 Raises: 213 ValueError: If lengths of inputs do not match num_branches. 214 """ 215 super(HighResolutionModule, self).__init__() 216 self._check_branches( 217 num_branches, blocks, num_blocks, num_inchannels, num_channels 218 ) 219 220 self.num_inchannels = num_inchannels 221 self.fuse_method = fuse_method 222 self.num_branches = num_branches 223 224 self.multi_scale_output = multi_scale_output 225 226 self.branches = self._make_branches( 227 num_branches, blocks, num_blocks, num_channels 228 ) 229 self.fuse_layers = self._make_fuse_layers() 230 231 def _check_branches( 232 self, num_branches, blocks, num_blocks, num_inchannels, num_channels 233 ): 234 """ 235 Validates that lengths of num_blocks, num_inchannels, and num_channels match num_branches. 236 237 Args: 238 num_branches (int): Number of branches. 239 blocks (nn.Module): Block type. 240 num_blocks (list[int]): Number of blocks per branch. 241 num_inchannels (list[int]): Number of input channels per branch. 242 num_channels (list[int]): Number of channels per branch. 243 244 Raises: 245 ValueError: If any length mismatch occurs. 246 """ 247 if num_branches != len(num_blocks): 248 error_msg = "NUM_BRANCHES({}) <> NUM_BLOCKS({})".format( 249 num_branches, len(num_blocks) 250 ) 251 logger.error(error_msg) 252 raise ValueError(error_msg) 253 254 if num_branches != len(num_channels): 255 error_msg = "NUM_BRANCHES({}) <> NUM_CHANNELS({})".format( 256 num_branches, len(num_channels) 257 ) 258 logger.error(error_msg) 259 raise ValueError(error_msg) 260 261 if num_branches != len(num_inchannels): 262 error_msg = "NUM_BRANCHES({}) <> NUM_INCHANNELS({})".format( 263 num_branches, len(num_inchannels) 264 ) 265 logger.error(error_msg) 266 raise ValueError(error_msg) 267 268 def _make_one_branch(self, branch_index, block, num_blocks, num_channels, stride=1): 269 """ 270 Constructs one branch of the module consisting of sequential residual blocks. 271 272 Args: 273 branch_index (int): Index of the branch. 274 block (nn.Module): Residual block class. 275 num_blocks (int): Number of residual blocks in this branch. 276 num_channels (list[int]): Number of channels per branch. 277 stride (int, optional): Stride of the first block. Defaults to 1. 278 279 Returns: 280 nn.Sequential: Sequential container of residual blocks. 281 """ 282 downsample = None 283 if ( 284 stride != 1 285 or self.num_inchannels[branch_index] 286 != num_channels[branch_index] * block.expansion 287 ): 288 downsample = nn.Sequential( 289 nn.Conv2d( 290 self.num_inchannels[branch_index], 291 num_channels[branch_index] * block.expansion, 292 kernel_size=1, 293 stride=stride, 294 bias=False, 295 ), 296 nn.BatchNorm2d( 297 num_channels[branch_index] * block.expansion, momentum=BN_MOMENTUM 298 ), 299 ) 300 301 layers = [] 302 layers.append( 303 block( 304 self.num_inchannels[branch_index], 305 num_channels[branch_index], 306 stride, 307 downsample, 308 ) 309 ) 310 self.num_inchannels[branch_index] = num_channels[branch_index] * block.expansion 311 for i in range(1, num_blocks[branch_index]): 312 layers.append( 313 block(self.num_inchannels[branch_index], num_channels[branch_index]) 314 ) 315 316 return nn.Sequential(*layers) 317 318 def _make_branches(self, num_branches, block, num_blocks, num_channels): 319 """ 320 Constructs all branches for the module. 321 322 Args: 323 num_branches (int): Number of branches. 324 block (nn.Module): Residual block class. 325 num_blocks (list[int]): Number of blocks per branch. 326 num_channels (list[int]): Number of channels per branch. 327 328 Returns: 329 nn.ModuleList: List of branch modules. 330 """ 331 branches = [] 332 333 for i in range(num_branches): 334 branches.append(self._make_one_branch(i, block, num_blocks, num_channels)) 335 336 return nn.ModuleList(branches) 337 338 def _make_fuse_layers(self): 339 """ 340 Constructs layers to fuse multi-resolution branch outputs. 341 342 Returns: 343 nn.ModuleList or None: Fuse layers or None if single branch. 344 """ 345 if self.num_branches == 1: 346 return None 347 348 num_branches = self.num_branches 349 num_inchannels = self.num_inchannels 350 fuse_layers = [] 351 for i in range(num_branches if self.multi_scale_output else 1): 352 fuse_layer = [] 353 for j in range(num_branches): 354 if j > i: 355 fuse_layer.append( 356 nn.Sequential( 357 nn.Conv2d( 358 num_inchannels[j], 359 num_inchannels[i], 360 1, 361 1, 362 0, 363 bias=False, 364 ), 365 nn.BatchNorm2d(num_inchannels[i]), 366 nn.Upsample(scale_factor=2 ** (j - i), mode="nearest"), 367 ) 368 ) 369 elif j == i: 370 fuse_layer.append(None) 371 else: 372 conv3x3s = [] 373 for k in range(i - j): 374 if k == i - j - 1: 375 num_outchannels_conv3x3 = num_inchannels[i] 376 conv3x3s.append( 377 nn.Sequential( 378 nn.Conv2d( 379 num_inchannels[j], 380 num_outchannels_conv3x3, 381 3, 382 2, 383 1, 384 bias=False, 385 ), 386 nn.BatchNorm2d(num_outchannels_conv3x3), 387 ) 388 ) 389 else: 390 num_outchannels_conv3x3 = num_inchannels[j] 391 conv3x3s.append( 392 nn.Sequential( 393 nn.Conv2d( 394 num_inchannels[j], 395 num_outchannels_conv3x3, 396 3, 397 2, 398 1, 399 bias=False, 400 ), 401 nn.BatchNorm2d(num_outchannels_conv3x3), 402 nn.ReLU(True), 403 ) 404 ) 405 fuse_layer.append(nn.Sequential(*conv3x3s)) 406 fuse_layers.append(nn.ModuleList(fuse_layer)) 407 408 return nn.ModuleList(fuse_layers) 409 410 def get_num_inchannels(self): 411 """ 412 Returns the number of input channels for each branch after block expansion. 413 414 Returns: 415 list[int]: Number of input channels per branch. 416 """ 417 return self.num_inchannels 418 419 def forward(self, x): 420 """ 421 Forward pass through the HighResolutionModule. 422 423 Args: 424 x (list[torch.Tensor]): List of input tensors for each branch. 425 426 Returns: 427 list[torch.Tensor]: List of output tensors after multi-branch fusion. 428 """ 429 if self.num_branches == 1: 430 return [self.branches[0](x[0])] 431 432 for i in range(self.num_branches): 433 x[i] = self.branches[i](x[i]) 434 435 x_fuse = [] 436 437 for i in range(len(self.fuse_layers)): 438 y = x[0] if i == 0 else self.fuse_layers[i][0](x[0]) 439 for j in range(1, self.num_branches): 440 if i == j: 441 y = y + x[j] 442 else: 443 y = y + self.fuse_layers[i][j](x[j]) 444 x_fuse.append(F.relu(y, inplace=True)) 445 446 return x_fuse 447 448 449blocks_dict = {"BASIC": BasicBlock, "BOTTLENECK": Bottleneck} 450 451 452class PoseHighResolutionNet(nn.Module): 453 """ 454 High-Resolution Network (HRNet) tailored for garment landmark detection. 455 456 The network maintains high-resolution representations through multiple stages and branches, 457 fusing multi-scale features and finally predicting heatmaps or coordinates for landmarks. 458 459 Attributes: 460 inplanes (int): Initial number of input channels. 461 conv1 (nn.Conv2d): Initial 3x3 convolution. 462 bn1 (nn.BatchNorm2d): Batch normalization after conv1. 463 conv2 (nn.Conv2d): Second 3x3 convolution. 464 bn2 (nn.BatchNorm2d): Batch normalization after conv2. 465 relu (nn.ReLU): ReLU activation. 466 stage1_cfg (dict): Configuration for stage1. 467 stage2_cfg (dict): Configuration for stage2. 468 stage3_cfg (dict): Configuration for stage3. 469 stage4_cfg (dict): Configuration for stage4. 470 transition1 (nn.ModuleList): Transition layers between stages. 471 stage2 (HighResolutionModule): Stage 2 module. 472 transition2 (nn.ModuleList): Transition layers between stages. 473 stage3 (HighResolutionModule): Stage 3 module. 474 transition3 (nn.ModuleList): Transition layers between stages. 475 stage4 (HighResolutionModule): Stage 4 module. 476 final_layer (nn.Conv2d): Final convolution layer to output predictions. 477 target_type (str): Output format type ('gaussian' or 'coordinate'). 478 """ 479 480 def __init__(self, **kwargs): 481 """ 482 Initializes PoseHighResolutionNet with default HRNet configurations. 483 484 Args: 485 target_type (str, optional): Type of target output. Either "gaussian" or "coordinate". Defaults to "gaussian". 486 """ 487 self.inplanes = 64 488 # Hardcoded values from YAML MODEL.EXTRA 489 extra = { 490 "PRETRAINED_LAYERS": [ 491 "conv1", 492 "bn1", 493 "conv2", 494 "bn2", 495 "layer1", 496 "transition1", 497 "stage2", 498 "transition2", 499 "stage3", 500 "transition3", 501 "stage4", 502 ], 503 "FINAL_CONV_KERNEL": 1, 504 "STAGE2": { 505 "NUM_MODULES": 1, 506 "NUM_BRANCHES": 2, 507 "BLOCK": "BASIC", 508 "NUM_BLOCKS": [4, 4], 509 "NUM_CHANNELS": [48, 96], 510 "FUSE_METHOD": "SUM", 511 }, 512 "STAGE3": { 513 "NUM_MODULES": 4, 514 "NUM_BRANCHES": 3, 515 "BLOCK": "BASIC", 516 "NUM_BLOCKS": [4, 4, 4], 517 "NUM_CHANNELS": [48, 96, 192], 518 "FUSE_METHOD": "SUM", 519 }, 520 "STAGE4": { 521 "NUM_MODULES": 3, 522 "NUM_BRANCHES": 4, 523 "BLOCK": "BASIC", 524 "NUM_BLOCKS": [4, 4, 4, 4], 525 "NUM_CHANNELS": [48, 96, 192, 384], 526 "FUSE_METHOD": "SUM", 527 }, 528 } 529 530 self.model_name = "pose_hrnet" 531 self.target_type = "gaussian" 532 533 super(PoseHighResolutionNet, self).__init__() 534 535 # stem net 536 self.conv1 = nn.Conv2d(3, 64, kernel_size=3, stride=2, padding=1, bias=False) 537 self.bn1 = nn.BatchNorm2d(64, momentum=BN_MOMENTUM) 538 self.conv2 = nn.Conv2d(64, 64, kernel_size=3, stride=2, padding=1, bias=False) 539 self.bn2 = nn.BatchNorm2d(64, momentum=BN_MOMENTUM) 540 self.layer1 = self._make_layer(Bottleneck, 64, 4) 541 542 # Stage2 543 self.stage2_cfg = extra["STAGE2"] 544 num_channels = self.stage2_cfg["NUM_CHANNELS"] 545 block = blocks_dict[self.stage2_cfg["BLOCK"]] 546 num_channels = [ 547 num_channels[i] * block.expansion for i in range(len(num_channels)) 548 ] 549 self.transition1 = self._make_transition_layer([256], num_channels) 550 self.stage2, pre_stage_channels = self._make_stage( 551 self.stage2_cfg, num_channels 552 ) 553 554 # Stage3 555 self.stage3_cfg = extra["STAGE3"] 556 num_channels = self.stage3_cfg["NUM_CHANNELS"] 557 block = blocks_dict[self.stage3_cfg["BLOCK"]] 558 num_channels = [ 559 num_channels[i] * block.expansion for i in range(len(num_channels)) 560 ] 561 self.transition2 = self._make_transition_layer(pre_stage_channels, num_channels) 562 self.stage3, pre_stage_channels = self._make_stage( 563 self.stage3_cfg, num_channels 564 ) 565 566 # Stage4 567 self.stage4_cfg = extra["STAGE4"] 568 num_channels = self.stage4_cfg["NUM_CHANNELS"] 569 block = blocks_dict[self.stage4_cfg["BLOCK"]] 570 num_channels = [ 571 num_channels[i] * block.expansion for i in range(len(num_channels)) 572 ] 573 self.transition3 = self._make_transition_layer(pre_stage_channels, num_channels) 574 self.stage4, pre_stage_channels = self._make_stage( 575 self.stage4_cfg, num_channels, multi_scale_output=False 576 ) 577 578 # Final layer 579 self.final_layer = nn.Conv2d( 580 in_channels=pre_stage_channels[0], 581 out_channels=294, # from MODEL.NUM_JOINTS 582 kernel_size=extra["FINAL_CONV_KERNEL"], 583 stride=1, 584 padding=1 if extra["FINAL_CONV_KERNEL"] == 3 else 0, 585 ) 586 587 self.pretrained_layers = extra["PRETRAINED_LAYERS"] 588 589 def _make_transition_layer(self, num_channels_pre_layer, num_channels_cur_layer): 590 """ 591 Creates transition layers to match the number of channels between stages. 592 593 Args: 594 num_channels_pre_layer (list[int]): Channels from previous stage. 595 num_channels_cur_layer (list[int]): Channels for current stage. 596 597 Returns: 598 nn.ModuleList: List of transition layers. 599 """ 600 num_branches_cur = len(num_channels_cur_layer) 601 num_branches_pre = len(num_channels_pre_layer) 602 603 transition_layers = [] 604 for i in range(num_branches_cur): 605 if i < num_branches_pre: 606 if num_channels_cur_layer[i] != num_channels_pre_layer[i]: 607 transition_layers.append( 608 nn.Sequential( 609 nn.Conv2d( 610 num_channels_pre_layer[i], 611 num_channels_cur_layer[i], 612 3, 613 1, 614 1, 615 bias=False, 616 ), 617 nn.BatchNorm2d(num_channels_cur_layer[i]), 618 nn.ReLU(inplace=True), 619 ) 620 ) 621 else: 622 transition_layers.append(None) 623 else: 624 conv3x3s = [] 625 for j in range(i + 1 - num_branches_pre): 626 inchannels = num_channels_pre_layer[-1] 627 outchannels = ( 628 num_channels_cur_layer[i] 629 if j == i - num_branches_pre 630 else inchannels 631 ) 632 conv3x3s.append( 633 nn.Sequential( 634 nn.Conv2d(inchannels, outchannels, 3, 2, 1, bias=False), 635 nn.BatchNorm2d(outchannels), 636 nn.ReLU(inplace=True), 637 ) 638 ) 639 transition_layers.append(nn.Sequential(*conv3x3s)) 640 641 return nn.ModuleList(transition_layers) 642 643 def _make_layer(self, block, planes, blocks, stride=1): 644 """ 645 Creates a layer composed of sequential residual blocks. 646 647 Args: 648 block (nn.Module): Residual block type (BasicBlock or Bottleneck). 649 planes (int): Number of output channels. 650 blocks (int): Number of blocks in this layer. 651 stride (int, optional): Stride for the first block. Defaults to 1. 652 653 Returns: 654 nn.Sequential: Sequential container of residual blocks. 655 """ 656 downsample = None 657 if stride != 1 or self.inplanes != planes * block.expansion: 658 downsample = nn.Sequential( 659 nn.Conv2d( 660 self.inplanes, 661 planes * block.expansion, 662 kernel_size=1, 663 stride=stride, 664 bias=False, 665 ), 666 nn.BatchNorm2d(planes * block.expansion, momentum=BN_MOMENTUM), 667 ) 668 669 layers = [] 670 layers.append(block(self.inplanes, planes, stride, downsample)) 671 self.inplanes = planes * block.expansion 672 for i in range(1, blocks): 673 layers.append(block(self.inplanes, planes)) 674 675 return nn.Sequential(*layers) 676 677 def _make_stage(self, layer_config, num_inchannels, multi_scale_output=True): 678 """ 679 Constructs a stage consisting of one or more HighResolutionModules. 680 681 Args: 682 layer_config (dict): Configuration dictionary for the stage. 683 num_inchannels (list[int]): Number of input channels for each branch. 684 multi_scale_output (bool, optional): Output multi-scale features or not. Defaults to True. 685 686 Returns: 687 tuple: 688 nn.Sequential: Stage module. 689 list[int]: Number of output channels for each branch. 690 """ 691 num_modules = layer_config["NUM_MODULES"] 692 num_branches = layer_config["NUM_BRANCHES"] 693 num_blocks = layer_config["NUM_BLOCKS"] 694 num_channels = layer_config["NUM_CHANNELS"] 695 block = blocks_dict[layer_config["BLOCK"]] 696 fuse_method = layer_config["FUSE_METHOD"] 697 698 modules = [] 699 for i in range(num_modules): 700 # multi_scale_output is only used last module 701 if not multi_scale_output and i == num_modules - 1: 702 reset_multi_scale_output = False 703 else: 704 reset_multi_scale_output = True 705 706 modules.append( 707 HighResolutionModule( 708 num_branches, 709 block, 710 num_blocks, 711 num_inchannels, 712 num_channels, 713 fuse_method, 714 reset_multi_scale_output, 715 ) 716 ) 717 num_inchannels = modules[-1].get_num_inchannels() 718 719 return nn.Sequential(*modules), num_inchannels 720 721 def forward(self, x): 722 """ 723 Forward pass of the PoseHighResolutionNet. 724 725 Args: 726 x (torch.Tensor): Input tensor of shape (batch_size, 3, H, W). 727 728 Returns: 729 torch.Tensor: Output heatmaps or coordinates for landmarks. 730 """ 731 x = self.conv1(x) 732 x = self.bn1(x) 733 x = F.relu(x, inplace=True) 734 x = self.conv2(x) 735 x = self.bn2(x) 736 x = F.relu(x, inplace=True) 737 x = self.layer1(x) 738 739 x_list = [] 740 for i in range(self.stage2_cfg["NUM_BRANCHES"]): 741 if self.transition1[i] is not None: 742 x_list.append(self.transition1[i](x)) 743 else: 744 x_list.append(x) 745 y_list = self.stage2(x_list) 746 747 x_list = [] 748 for i in range(self.stage3_cfg["NUM_BRANCHES"]): 749 if self.transition2[i] is not None: 750 x_list.append(self.transition2[i](y_list[-1])) 751 else: 752 x_list.append(y_list[i]) 753 y_list = self.stage3(x_list) 754 755 x_list = [] 756 for i in range(self.stage4_cfg["NUM_BRANCHES"]): 757 if self.transition3[i] is not None: 758 x_list.append(self.transition3[i](y_list[-1])) 759 else: 760 x_list.append(y_list[i]) 761 y_list = self.stage4(x_list) 762 763 if self.model_name == "pose_hrnet" or "pose_metric_gcn": 764 x = self.final_layer(y_list[0]) 765 else: 766 x = y_list[0] 767 768 if self.target_type == "gaussian": 769 return x 770 771 elif self.target_type == "coordinate": 772 B, C, H, W = x.shape 773 774 """B - cal x,y seperately""" 775 h = F.softmax(x.view(B, C, H * W) * 1, dim=2) 776 h = h.view(B, C, H, W) 777 hx = h.sum(dim=2) # (B, C, W) 778 px = (hx * (torch.arange(W, device=h.device).float().view(1, 1, W))).sum( 779 2, keepdim=True 780 ) 781 hy = h.sum(dim=3) # (B, C, H) 782 py = (hy * (torch.arange(H, device=h.device).float().view(1, 1, H))).sum( 783 2, keepdim=True 784 ) 785 x = torch.cat([px, py], dim=2) 786 return h, x 787 else: 788 raise NotImplementedError(f"{self.target_type} is unknown.")
19def conv3x3(in_planes, out_planes, stride=1): 20 """ 21 Creates a 3x3 convolutional layer with padding. 22 23 Args: 24 in_planes (int): Number of input channels. 25 out_planes (int): Number of output channels. 26 stride (int, optional): Stride of the convolution. Defaults to 1. 27 28 Returns: 29 nn.Conv2d: 3x3 convolution layer with specified parameters. 30 """ 31 return nn.Conv2d( 32 in_planes, out_planes, kernel_size=3, stride=stride, padding=1, bias=False 33 )
Creates a 3x3 convolutional layer with padding.
Arguments:
- in_planes (int): Number of input channels.
- out_planes (int): Number of output channels.
- stride (int, optional): Stride of the convolution. Defaults to 1.
Returns:
nn.Conv2d: 3x3 convolution layer with specified parameters.
36class BasicBlock(nn.Module): 37 """ 38 Basic residual block with two 3x3 convolutional layers. 39 40 Attributes: 41 expansion (int): Expansion factor for output channels (always 1 for BasicBlock). 42 conv1 (nn.Conv2d): First convolutional layer. 43 bn1 (nn.BatchNorm2d): Batch normalization after first conv. 44 conv2 (nn.Conv2d): Second convolutional layer. 45 bn2 (nn.BatchNorm2d): Batch normalization after second conv. 46 downsample (nn.Module or None): Optional downsampling layer for residual connection. 47 stride (int): Stride of the first convolution. 48 """ 49 50 expansion = 1 51 52 def __init__(self, inplanes, planes, stride=1, downsample=None): 53 """ 54 Initializes BasicBlock. 55 56 Args: 57 inplanes (int): Number of input channels. 58 planes (int): Number of output channels. 59 stride (int, optional): Stride of the first convolution. Defaults to 1. 60 downsample (nn.Module or None, optional): Downsampling layer for residual. Defaults to None. 61 """ 62 super(BasicBlock, self).__init__() 63 self.conv1 = conv3x3(inplanes, planes, stride) 64 self.bn1 = nn.BatchNorm2d(planes, momentum=BN_MOMENTUM) 65 self.conv2 = conv3x3(planes, planes) 66 self.bn2 = nn.BatchNorm2d(planes, momentum=BN_MOMENTUM) 67 self.downsample = downsample 68 self.stride = stride 69 70 def forward(self, x): 71 """ 72 Forward pass of the BasicBlock. 73 74 Args: 75 x (torch.Tensor): Input tensor. 76 77 Returns: 78 torch.Tensor: Output tensor after residual addition and activation. 79 """ 80 residual = x 81 82 out = self.conv1(x) 83 out = self.bn1(out) 84 out = F.relu(out, inplace=True) 85 86 out = self.conv2(out) 87 out = self.bn2(out) 88 89 if self.downsample is not None: 90 residual = self.downsample(x) 91 92 out += residual 93 out = F.relu(out, inplace=True) 94 95 return out
Basic residual block with two 3x3 convolutional layers.
Attributes:
- expansion (int): Expansion factor for output channels (always 1 for BasicBlock).
- conv1 (nn.Conv2d): First convolutional layer.
- bn1 (nn.BatchNorm2d): Batch normalization after first conv.
- conv2 (nn.Conv2d): Second convolutional layer.
- bn2 (nn.BatchNorm2d): Batch normalization after second conv.
- downsample (nn.Module or None): Optional downsampling layer for residual connection.
- stride (int): Stride of the first convolution.
52 def __init__(self, inplanes, planes, stride=1, downsample=None): 53 """ 54 Initializes BasicBlock. 55 56 Args: 57 inplanes (int): Number of input channels. 58 planes (int): Number of output channels. 59 stride (int, optional): Stride of the first convolution. Defaults to 1. 60 downsample (nn.Module or None, optional): Downsampling layer for residual. Defaults to None. 61 """ 62 super(BasicBlock, self).__init__() 63 self.conv1 = conv3x3(inplanes, planes, stride) 64 self.bn1 = nn.BatchNorm2d(planes, momentum=BN_MOMENTUM) 65 self.conv2 = conv3x3(planes, planes) 66 self.bn2 = nn.BatchNorm2d(planes, momentum=BN_MOMENTUM) 67 self.downsample = downsample 68 self.stride = stride
Initializes BasicBlock.
Arguments:
- inplanes (int): Number of input channels.
- planes (int): Number of output channels.
- stride (int, optional): Stride of the first convolution. Defaults to 1.
- downsample (nn.Module or None, optional): Downsampling layer for residual. Defaults to None.
70 def forward(self, x): 71 """ 72 Forward pass of the BasicBlock. 73 74 Args: 75 x (torch.Tensor): Input tensor. 76 77 Returns: 78 torch.Tensor: Output tensor after residual addition and activation. 79 """ 80 residual = x 81 82 out = self.conv1(x) 83 out = self.bn1(out) 84 out = F.relu(out, inplace=True) 85 86 out = self.conv2(out) 87 out = self.bn2(out) 88 89 if self.downsample is not None: 90 residual = self.downsample(x) 91 92 out += residual 93 out = F.relu(out, inplace=True) 94 95 return out
Forward pass of the BasicBlock.
Arguments:
- x (torch.Tensor): Input tensor.
Returns:
torch.Tensor: Output tensor after residual addition and activation.
98class Bottleneck(nn.Module): 99 """ 100 Bottleneck residual block with 1x1, 3x3, and 1x1 convolutions. 101 102 Attributes: 103 expansion (int): Expansion factor for output channels (usually 4 for Bottleneck). 104 conv1 (nn.Conv2d): 1x1 convolution reducing channels. 105 bn1 (nn.BatchNorm2d): Batch normalization after conv1. 106 conv2 (nn.Conv2d): 3x3 convolution. 107 bn2 (nn.BatchNorm2d): Batch normalization after conv2. 108 conv3 (nn.Conv2d): 1x1 convolution expanding channels. 109 bn3 (nn.BatchNorm2d): Batch normalization after conv3. 110 downsample (nn.Module or None): Optional downsampling layer for residual connection. 111 stride (int): Stride for the 3x3 convolution. 112 """ 113 114 expansion = 4 115 116 def __init__(self, inplanes, planes, stride=1, downsample=None): 117 """ 118 Initializes Bottleneck block. 119 120 Args: 121 inplanes (int): Number of input channels. 122 planes (int): Number of output channels before expansion. 123 stride (int, optional): Stride for the 3x3 convolution. Defaults to 1. 124 downsample (nn.Module or None, optional): Downsampling layer for residual. Defaults to None. 125 """ 126 super(Bottleneck, self).__init__() 127 self.conv1 = nn.Conv2d(inplanes, planes, kernel_size=1, bias=False) 128 self.bn1 = nn.BatchNorm2d(planes, momentum=BN_MOMENTUM) 129 self.conv2 = nn.Conv2d( 130 planes, planes, kernel_size=3, stride=stride, padding=1, bias=False 131 ) 132 self.bn2 = nn.BatchNorm2d(planes, momentum=BN_MOMENTUM) 133 self.conv3 = nn.Conv2d( 134 planes, planes * self.expansion, kernel_size=1, bias=False 135 ) 136 self.bn3 = nn.BatchNorm2d(planes * self.expansion, momentum=BN_MOMENTUM) 137 self.downsample = downsample 138 self.stride = stride 139 140 def forward(self, x): 141 """ 142 Forward pass of the Bottleneck block. 143 144 Args: 145 x (torch.Tensor): Input tensor. 146 147 Returns: 148 torch.Tensor: Output tensor after residual addition and activation. 149 """ 150 residual = x 151 152 out = self.conv1(x) 153 out = self.bn1(out) 154 out = F.relu(out, inplace=True) 155 156 out = self.conv2(out) 157 out = self.bn2(out) 158 out = F.relu(out, inplace=True) 159 160 out = self.conv3(out) 161 out = self.bn3(out) 162 163 if self.downsample is not None: 164 residual = self.downsample(x) 165 166 out += residual 167 out = F.relu(out, inplace=True) 168 169 return out
Bottleneck residual block with 1x1, 3x3, and 1x1 convolutions.
Attributes:
- expansion (int): Expansion factor for output channels (usually 4 for Bottleneck).
- conv1 (nn.Conv2d): 1x1 convolution reducing channels.
- bn1 (nn.BatchNorm2d): Batch normalization after conv1.
- conv2 (nn.Conv2d): 3x3 convolution.
- bn2 (nn.BatchNorm2d): Batch normalization after conv2.
- conv3 (nn.Conv2d): 1x1 convolution expanding channels.
- bn3 (nn.BatchNorm2d): Batch normalization after conv3.
- downsample (nn.Module or None): Optional downsampling layer for residual connection.
- stride (int): Stride for the 3x3 convolution.
116 def __init__(self, inplanes, planes, stride=1, downsample=None): 117 """ 118 Initializes Bottleneck block. 119 120 Args: 121 inplanes (int): Number of input channels. 122 planes (int): Number of output channels before expansion. 123 stride (int, optional): Stride for the 3x3 convolution. Defaults to 1. 124 downsample (nn.Module or None, optional): Downsampling layer for residual. Defaults to None. 125 """ 126 super(Bottleneck, self).__init__() 127 self.conv1 = nn.Conv2d(inplanes, planes, kernel_size=1, bias=False) 128 self.bn1 = nn.BatchNorm2d(planes, momentum=BN_MOMENTUM) 129 self.conv2 = nn.Conv2d( 130 planes, planes, kernel_size=3, stride=stride, padding=1, bias=False 131 ) 132 self.bn2 = nn.BatchNorm2d(planes, momentum=BN_MOMENTUM) 133 self.conv3 = nn.Conv2d( 134 planes, planes * self.expansion, kernel_size=1, bias=False 135 ) 136 self.bn3 = nn.BatchNorm2d(planes * self.expansion, momentum=BN_MOMENTUM) 137 self.downsample = downsample 138 self.stride = stride
Initializes Bottleneck block.
Arguments:
- inplanes (int): Number of input channels.
- planes (int): Number of output channels before expansion.
- stride (int, optional): Stride for the 3x3 convolution. Defaults to 1.
- downsample (nn.Module or None, optional): Downsampling layer for residual. Defaults to None.
140 def forward(self, x): 141 """ 142 Forward pass of the Bottleneck block. 143 144 Args: 145 x (torch.Tensor): Input tensor. 146 147 Returns: 148 torch.Tensor: Output tensor after residual addition and activation. 149 """ 150 residual = x 151 152 out = self.conv1(x) 153 out = self.bn1(out) 154 out = F.relu(out, inplace=True) 155 156 out = self.conv2(out) 157 out = self.bn2(out) 158 out = F.relu(out, inplace=True) 159 160 out = self.conv3(out) 161 out = self.bn3(out) 162 163 if self.downsample is not None: 164 residual = self.downsample(x) 165 166 out += residual 167 out = F.relu(out, inplace=True) 168 169 return out
Forward pass of the Bottleneck block.
Arguments:
- x (torch.Tensor): Input tensor.
Returns:
torch.Tensor: Output tensor after residual addition and activation.
172class HighResolutionModule(nn.Module): 173 """ 174 HighResolutionModule maintains high-resolution representations through multi-branch architecture and fusion. 175 176 This module consists of several parallel branches with residual blocks, and fuse layers to combine 177 features from different resolutions. 178 179 Attributes: 180 num_branches (int): Number of parallel branches. 181 blocks (nn.Module): Residual block class (BasicBlock or Bottleneck). 182 num_blocks (list[int]): Number of residual blocks per branch. 183 num_inchannels (list[int]): Number of input channels for each branch. 184 num_channels (list[int]): Number of channels per branch before expansion. 185 fuse_method (str): Method to fuse multi-branch outputs ('SUM' supported). 186 multi_scale_output (bool): Whether to output multi-scale features. 187 branches (nn.ModuleList): The parallel branches. 188 fuse_layers (nn.ModuleList or None): Layers that fuse features from branches. 189 """ 190 191 def __init__( 192 self, 193 num_branches, 194 blocks, 195 num_blocks, 196 num_inchannels, 197 num_channels, 198 fuse_method, 199 multi_scale_output=True, 200 ): 201 """ 202 Initializes HighResolutionModule. 203 204 Args: 205 num_branches (int): Number of parallel branches. 206 blocks (nn.Module): Residual block class (BasicBlock or Bottleneck). 207 num_blocks (list[int]): Number of residual blocks per branch. 208 num_inchannels (list[int]): Number of input channels for each branch. 209 num_channels (list[int]): Number of channels per branch before expansion. 210 fuse_method (str): Method to fuse multi-branch outputs. 211 multi_scale_output (bool, optional): Output multi-scale features or not. Defaults to True. 212 213 Raises: 214 ValueError: If lengths of inputs do not match num_branches. 215 """ 216 super(HighResolutionModule, self).__init__() 217 self._check_branches( 218 num_branches, blocks, num_blocks, num_inchannels, num_channels 219 ) 220 221 self.num_inchannels = num_inchannels 222 self.fuse_method = fuse_method 223 self.num_branches = num_branches 224 225 self.multi_scale_output = multi_scale_output 226 227 self.branches = self._make_branches( 228 num_branches, blocks, num_blocks, num_channels 229 ) 230 self.fuse_layers = self._make_fuse_layers() 231 232 def _check_branches( 233 self, num_branches, blocks, num_blocks, num_inchannels, num_channels 234 ): 235 """ 236 Validates that lengths of num_blocks, num_inchannels, and num_channels match num_branches. 237 238 Args: 239 num_branches (int): Number of branches. 240 blocks (nn.Module): Block type. 241 num_blocks (list[int]): Number of blocks per branch. 242 num_inchannels (list[int]): Number of input channels per branch. 243 num_channels (list[int]): Number of channels per branch. 244 245 Raises: 246 ValueError: If any length mismatch occurs. 247 """ 248 if num_branches != len(num_blocks): 249 error_msg = "NUM_BRANCHES({}) <> NUM_BLOCKS({})".format( 250 num_branches, len(num_blocks) 251 ) 252 logger.error(error_msg) 253 raise ValueError(error_msg) 254 255 if num_branches != len(num_channels): 256 error_msg = "NUM_BRANCHES({}) <> NUM_CHANNELS({})".format( 257 num_branches, len(num_channels) 258 ) 259 logger.error(error_msg) 260 raise ValueError(error_msg) 261 262 if num_branches != len(num_inchannels): 263 error_msg = "NUM_BRANCHES({}) <> NUM_INCHANNELS({})".format( 264 num_branches, len(num_inchannels) 265 ) 266 logger.error(error_msg) 267 raise ValueError(error_msg) 268 269 def _make_one_branch(self, branch_index, block, num_blocks, num_channels, stride=1): 270 """ 271 Constructs one branch of the module consisting of sequential residual blocks. 272 273 Args: 274 branch_index (int): Index of the branch. 275 block (nn.Module): Residual block class. 276 num_blocks (int): Number of residual blocks in this branch. 277 num_channels (list[int]): Number of channels per branch. 278 stride (int, optional): Stride of the first block. Defaults to 1. 279 280 Returns: 281 nn.Sequential: Sequential container of residual blocks. 282 """ 283 downsample = None 284 if ( 285 stride != 1 286 or self.num_inchannels[branch_index] 287 != num_channels[branch_index] * block.expansion 288 ): 289 downsample = nn.Sequential( 290 nn.Conv2d( 291 self.num_inchannels[branch_index], 292 num_channels[branch_index] * block.expansion, 293 kernel_size=1, 294 stride=stride, 295 bias=False, 296 ), 297 nn.BatchNorm2d( 298 num_channels[branch_index] * block.expansion, momentum=BN_MOMENTUM 299 ), 300 ) 301 302 layers = [] 303 layers.append( 304 block( 305 self.num_inchannels[branch_index], 306 num_channels[branch_index], 307 stride, 308 downsample, 309 ) 310 ) 311 self.num_inchannels[branch_index] = num_channels[branch_index] * block.expansion 312 for i in range(1, num_blocks[branch_index]): 313 layers.append( 314 block(self.num_inchannels[branch_index], num_channels[branch_index]) 315 ) 316 317 return nn.Sequential(*layers) 318 319 def _make_branches(self, num_branches, block, num_blocks, num_channels): 320 """ 321 Constructs all branches for the module. 322 323 Args: 324 num_branches (int): Number of branches. 325 block (nn.Module): Residual block class. 326 num_blocks (list[int]): Number of blocks per branch. 327 num_channels (list[int]): Number of channels per branch. 328 329 Returns: 330 nn.ModuleList: List of branch modules. 331 """ 332 branches = [] 333 334 for i in range(num_branches): 335 branches.append(self._make_one_branch(i, block, num_blocks, num_channels)) 336 337 return nn.ModuleList(branches) 338 339 def _make_fuse_layers(self): 340 """ 341 Constructs layers to fuse multi-resolution branch outputs. 342 343 Returns: 344 nn.ModuleList or None: Fuse layers or None if single branch. 345 """ 346 if self.num_branches == 1: 347 return None 348 349 num_branches = self.num_branches 350 num_inchannels = self.num_inchannels 351 fuse_layers = [] 352 for i in range(num_branches if self.multi_scale_output else 1): 353 fuse_layer = [] 354 for j in range(num_branches): 355 if j > i: 356 fuse_layer.append( 357 nn.Sequential( 358 nn.Conv2d( 359 num_inchannels[j], 360 num_inchannels[i], 361 1, 362 1, 363 0, 364 bias=False, 365 ), 366 nn.BatchNorm2d(num_inchannels[i]), 367 nn.Upsample(scale_factor=2 ** (j - i), mode="nearest"), 368 ) 369 ) 370 elif j == i: 371 fuse_layer.append(None) 372 else: 373 conv3x3s = [] 374 for k in range(i - j): 375 if k == i - j - 1: 376 num_outchannels_conv3x3 = num_inchannels[i] 377 conv3x3s.append( 378 nn.Sequential( 379 nn.Conv2d( 380 num_inchannels[j], 381 num_outchannels_conv3x3, 382 3, 383 2, 384 1, 385 bias=False, 386 ), 387 nn.BatchNorm2d(num_outchannels_conv3x3), 388 ) 389 ) 390 else: 391 num_outchannels_conv3x3 = num_inchannels[j] 392 conv3x3s.append( 393 nn.Sequential( 394 nn.Conv2d( 395 num_inchannels[j], 396 num_outchannels_conv3x3, 397 3, 398 2, 399 1, 400 bias=False, 401 ), 402 nn.BatchNorm2d(num_outchannels_conv3x3), 403 nn.ReLU(True), 404 ) 405 ) 406 fuse_layer.append(nn.Sequential(*conv3x3s)) 407 fuse_layers.append(nn.ModuleList(fuse_layer)) 408 409 return nn.ModuleList(fuse_layers) 410 411 def get_num_inchannels(self): 412 """ 413 Returns the number of input channels for each branch after block expansion. 414 415 Returns: 416 list[int]: Number of input channels per branch. 417 """ 418 return self.num_inchannels 419 420 def forward(self, x): 421 """ 422 Forward pass through the HighResolutionModule. 423 424 Args: 425 x (list[torch.Tensor]): List of input tensors for each branch. 426 427 Returns: 428 list[torch.Tensor]: List of output tensors after multi-branch fusion. 429 """ 430 if self.num_branches == 1: 431 return [self.branches[0](x[0])] 432 433 for i in range(self.num_branches): 434 x[i] = self.branches[i](x[i]) 435 436 x_fuse = [] 437 438 for i in range(len(self.fuse_layers)): 439 y = x[0] if i == 0 else self.fuse_layers[i][0](x[0]) 440 for j in range(1, self.num_branches): 441 if i == j: 442 y = y + x[j] 443 else: 444 y = y + self.fuse_layers[i][j](x[j]) 445 x_fuse.append(F.relu(y, inplace=True)) 446 447 return x_fuse
HighResolutionModule maintains high-resolution representations through multi-branch architecture and fusion.
This module consists of several parallel branches with residual blocks, and fuse layers to combine features from different resolutions.
Attributes:
- num_branches (int): Number of parallel branches.
- blocks (nn.Module): Residual block class (BasicBlock or Bottleneck).
- num_blocks (list[int]): Number of residual blocks per branch.
- num_inchannels (list[int]): Number of input channels for each branch.
- num_channels (list[int]): Number of channels per branch before expansion.
- fuse_method (str): Method to fuse multi-branch outputs ('SUM' supported).
- multi_scale_output (bool): Whether to output multi-scale features.
- branches (nn.ModuleList): The parallel branches.
- fuse_layers (nn.ModuleList or None): Layers that fuse features from branches.
191 def __init__( 192 self, 193 num_branches, 194 blocks, 195 num_blocks, 196 num_inchannels, 197 num_channels, 198 fuse_method, 199 multi_scale_output=True, 200 ): 201 """ 202 Initializes HighResolutionModule. 203 204 Args: 205 num_branches (int): Number of parallel branches. 206 blocks (nn.Module): Residual block class (BasicBlock or Bottleneck). 207 num_blocks (list[int]): Number of residual blocks per branch. 208 num_inchannels (list[int]): Number of input channels for each branch. 209 num_channels (list[int]): Number of channels per branch before expansion. 210 fuse_method (str): Method to fuse multi-branch outputs. 211 multi_scale_output (bool, optional): Output multi-scale features or not. Defaults to True. 212 213 Raises: 214 ValueError: If lengths of inputs do not match num_branches. 215 """ 216 super(HighResolutionModule, self).__init__() 217 self._check_branches( 218 num_branches, blocks, num_blocks, num_inchannels, num_channels 219 ) 220 221 self.num_inchannels = num_inchannels 222 self.fuse_method = fuse_method 223 self.num_branches = num_branches 224 225 self.multi_scale_output = multi_scale_output 226 227 self.branches = self._make_branches( 228 num_branches, blocks, num_blocks, num_channels 229 ) 230 self.fuse_layers = self._make_fuse_layers()
Initializes HighResolutionModule.
Arguments:
- num_branches (int): Number of parallel branches.
- blocks (nn.Module): Residual block class (BasicBlock or Bottleneck).
- num_blocks (list[int]): Number of residual blocks per branch.
- num_inchannels (list[int]): Number of input channels for each branch.
- num_channels (list[int]): Number of channels per branch before expansion.
- fuse_method (str): Method to fuse multi-branch outputs.
- multi_scale_output (bool, optional): Output multi-scale features or not. Defaults to True.
Raises:
- ValueError: If lengths of inputs do not match num_branches.
411 def get_num_inchannels(self): 412 """ 413 Returns the number of input channels for each branch after block expansion. 414 415 Returns: 416 list[int]: Number of input channels per branch. 417 """ 418 return self.num_inchannels
Returns the number of input channels for each branch after block expansion.
Returns:
list[int]: Number of input channels per branch.
420 def forward(self, x): 421 """ 422 Forward pass through the HighResolutionModule. 423 424 Args: 425 x (list[torch.Tensor]): List of input tensors for each branch. 426 427 Returns: 428 list[torch.Tensor]: List of output tensors after multi-branch fusion. 429 """ 430 if self.num_branches == 1: 431 return [self.branches[0](x[0])] 432 433 for i in range(self.num_branches): 434 x[i] = self.branches[i](x[i]) 435 436 x_fuse = [] 437 438 for i in range(len(self.fuse_layers)): 439 y = x[0] if i == 0 else self.fuse_layers[i][0](x[0]) 440 for j in range(1, self.num_branches): 441 if i == j: 442 y = y + x[j] 443 else: 444 y = y + self.fuse_layers[i][j](x[j]) 445 x_fuse.append(F.relu(y, inplace=True)) 446 447 return x_fuse
Forward pass through the HighResolutionModule.
Arguments:
- x (list[torch.Tensor]): List of input tensors for each branch.
Returns:
list[torch.Tensor]: List of output tensors after multi-branch fusion.
453class PoseHighResolutionNet(nn.Module): 454 """ 455 High-Resolution Network (HRNet) tailored for garment landmark detection. 456 457 The network maintains high-resolution representations through multiple stages and branches, 458 fusing multi-scale features and finally predicting heatmaps or coordinates for landmarks. 459 460 Attributes: 461 inplanes (int): Initial number of input channels. 462 conv1 (nn.Conv2d): Initial 3x3 convolution. 463 bn1 (nn.BatchNorm2d): Batch normalization after conv1. 464 conv2 (nn.Conv2d): Second 3x3 convolution. 465 bn2 (nn.BatchNorm2d): Batch normalization after conv2. 466 relu (nn.ReLU): ReLU activation. 467 stage1_cfg (dict): Configuration for stage1. 468 stage2_cfg (dict): Configuration for stage2. 469 stage3_cfg (dict): Configuration for stage3. 470 stage4_cfg (dict): Configuration for stage4. 471 transition1 (nn.ModuleList): Transition layers between stages. 472 stage2 (HighResolutionModule): Stage 2 module. 473 transition2 (nn.ModuleList): Transition layers between stages. 474 stage3 (HighResolutionModule): Stage 3 module. 475 transition3 (nn.ModuleList): Transition layers between stages. 476 stage4 (HighResolutionModule): Stage 4 module. 477 final_layer (nn.Conv2d): Final convolution layer to output predictions. 478 target_type (str): Output format type ('gaussian' or 'coordinate'). 479 """ 480 481 def __init__(self, **kwargs): 482 """ 483 Initializes PoseHighResolutionNet with default HRNet configurations. 484 485 Args: 486 target_type (str, optional): Type of target output. Either "gaussian" or "coordinate". Defaults to "gaussian". 487 """ 488 self.inplanes = 64 489 # Hardcoded values from YAML MODEL.EXTRA 490 extra = { 491 "PRETRAINED_LAYERS": [ 492 "conv1", 493 "bn1", 494 "conv2", 495 "bn2", 496 "layer1", 497 "transition1", 498 "stage2", 499 "transition2", 500 "stage3", 501 "transition3", 502 "stage4", 503 ], 504 "FINAL_CONV_KERNEL": 1, 505 "STAGE2": { 506 "NUM_MODULES": 1, 507 "NUM_BRANCHES": 2, 508 "BLOCK": "BASIC", 509 "NUM_BLOCKS": [4, 4], 510 "NUM_CHANNELS": [48, 96], 511 "FUSE_METHOD": "SUM", 512 }, 513 "STAGE3": { 514 "NUM_MODULES": 4, 515 "NUM_BRANCHES": 3, 516 "BLOCK": "BASIC", 517 "NUM_BLOCKS": [4, 4, 4], 518 "NUM_CHANNELS": [48, 96, 192], 519 "FUSE_METHOD": "SUM", 520 }, 521 "STAGE4": { 522 "NUM_MODULES": 3, 523 "NUM_BRANCHES": 4, 524 "BLOCK": "BASIC", 525 "NUM_BLOCKS": [4, 4, 4, 4], 526 "NUM_CHANNELS": [48, 96, 192, 384], 527 "FUSE_METHOD": "SUM", 528 }, 529 } 530 531 self.model_name = "pose_hrnet" 532 self.target_type = "gaussian" 533 534 super(PoseHighResolutionNet, self).__init__() 535 536 # stem net 537 self.conv1 = nn.Conv2d(3, 64, kernel_size=3, stride=2, padding=1, bias=False) 538 self.bn1 = nn.BatchNorm2d(64, momentum=BN_MOMENTUM) 539 self.conv2 = nn.Conv2d(64, 64, kernel_size=3, stride=2, padding=1, bias=False) 540 self.bn2 = nn.BatchNorm2d(64, momentum=BN_MOMENTUM) 541 self.layer1 = self._make_layer(Bottleneck, 64, 4) 542 543 # Stage2 544 self.stage2_cfg = extra["STAGE2"] 545 num_channels = self.stage2_cfg["NUM_CHANNELS"] 546 block = blocks_dict[self.stage2_cfg["BLOCK"]] 547 num_channels = [ 548 num_channels[i] * block.expansion for i in range(len(num_channels)) 549 ] 550 self.transition1 = self._make_transition_layer([256], num_channels) 551 self.stage2, pre_stage_channels = self._make_stage( 552 self.stage2_cfg, num_channels 553 ) 554 555 # Stage3 556 self.stage3_cfg = extra["STAGE3"] 557 num_channels = self.stage3_cfg["NUM_CHANNELS"] 558 block = blocks_dict[self.stage3_cfg["BLOCK"]] 559 num_channels = [ 560 num_channels[i] * block.expansion for i in range(len(num_channels)) 561 ] 562 self.transition2 = self._make_transition_layer(pre_stage_channels, num_channels) 563 self.stage3, pre_stage_channels = self._make_stage( 564 self.stage3_cfg, num_channels 565 ) 566 567 # Stage4 568 self.stage4_cfg = extra["STAGE4"] 569 num_channels = self.stage4_cfg["NUM_CHANNELS"] 570 block = blocks_dict[self.stage4_cfg["BLOCK"]] 571 num_channels = [ 572 num_channels[i] * block.expansion for i in range(len(num_channels)) 573 ] 574 self.transition3 = self._make_transition_layer(pre_stage_channels, num_channels) 575 self.stage4, pre_stage_channels = self._make_stage( 576 self.stage4_cfg, num_channels, multi_scale_output=False 577 ) 578 579 # Final layer 580 self.final_layer = nn.Conv2d( 581 in_channels=pre_stage_channels[0], 582 out_channels=294, # from MODEL.NUM_JOINTS 583 kernel_size=extra["FINAL_CONV_KERNEL"], 584 stride=1, 585 padding=1 if extra["FINAL_CONV_KERNEL"] == 3 else 0, 586 ) 587 588 self.pretrained_layers = extra["PRETRAINED_LAYERS"] 589 590 def _make_transition_layer(self, num_channels_pre_layer, num_channels_cur_layer): 591 """ 592 Creates transition layers to match the number of channels between stages. 593 594 Args: 595 num_channels_pre_layer (list[int]): Channels from previous stage. 596 num_channels_cur_layer (list[int]): Channels for current stage. 597 598 Returns: 599 nn.ModuleList: List of transition layers. 600 """ 601 num_branches_cur = len(num_channels_cur_layer) 602 num_branches_pre = len(num_channels_pre_layer) 603 604 transition_layers = [] 605 for i in range(num_branches_cur): 606 if i < num_branches_pre: 607 if num_channels_cur_layer[i] != num_channels_pre_layer[i]: 608 transition_layers.append( 609 nn.Sequential( 610 nn.Conv2d( 611 num_channels_pre_layer[i], 612 num_channels_cur_layer[i], 613 3, 614 1, 615 1, 616 bias=False, 617 ), 618 nn.BatchNorm2d(num_channels_cur_layer[i]), 619 nn.ReLU(inplace=True), 620 ) 621 ) 622 else: 623 transition_layers.append(None) 624 else: 625 conv3x3s = [] 626 for j in range(i + 1 - num_branches_pre): 627 inchannels = num_channels_pre_layer[-1] 628 outchannels = ( 629 num_channels_cur_layer[i] 630 if j == i - num_branches_pre 631 else inchannels 632 ) 633 conv3x3s.append( 634 nn.Sequential( 635 nn.Conv2d(inchannels, outchannels, 3, 2, 1, bias=False), 636 nn.BatchNorm2d(outchannels), 637 nn.ReLU(inplace=True), 638 ) 639 ) 640 transition_layers.append(nn.Sequential(*conv3x3s)) 641 642 return nn.ModuleList(transition_layers) 643 644 def _make_layer(self, block, planes, blocks, stride=1): 645 """ 646 Creates a layer composed of sequential residual blocks. 647 648 Args: 649 block (nn.Module): Residual block type (BasicBlock or Bottleneck). 650 planes (int): Number of output channels. 651 blocks (int): Number of blocks in this layer. 652 stride (int, optional): Stride for the first block. Defaults to 1. 653 654 Returns: 655 nn.Sequential: Sequential container of residual blocks. 656 """ 657 downsample = None 658 if stride != 1 or self.inplanes != planes * block.expansion: 659 downsample = nn.Sequential( 660 nn.Conv2d( 661 self.inplanes, 662 planes * block.expansion, 663 kernel_size=1, 664 stride=stride, 665 bias=False, 666 ), 667 nn.BatchNorm2d(planes * block.expansion, momentum=BN_MOMENTUM), 668 ) 669 670 layers = [] 671 layers.append(block(self.inplanes, planes, stride, downsample)) 672 self.inplanes = planes * block.expansion 673 for i in range(1, blocks): 674 layers.append(block(self.inplanes, planes)) 675 676 return nn.Sequential(*layers) 677 678 def _make_stage(self, layer_config, num_inchannels, multi_scale_output=True): 679 """ 680 Constructs a stage consisting of one or more HighResolutionModules. 681 682 Args: 683 layer_config (dict): Configuration dictionary for the stage. 684 num_inchannels (list[int]): Number of input channels for each branch. 685 multi_scale_output (bool, optional): Output multi-scale features or not. Defaults to True. 686 687 Returns: 688 tuple: 689 nn.Sequential: Stage module. 690 list[int]: Number of output channels for each branch. 691 """ 692 num_modules = layer_config["NUM_MODULES"] 693 num_branches = layer_config["NUM_BRANCHES"] 694 num_blocks = layer_config["NUM_BLOCKS"] 695 num_channels = layer_config["NUM_CHANNELS"] 696 block = blocks_dict[layer_config["BLOCK"]] 697 fuse_method = layer_config["FUSE_METHOD"] 698 699 modules = [] 700 for i in range(num_modules): 701 # multi_scale_output is only used last module 702 if not multi_scale_output and i == num_modules - 1: 703 reset_multi_scale_output = False 704 else: 705 reset_multi_scale_output = True 706 707 modules.append( 708 HighResolutionModule( 709 num_branches, 710 block, 711 num_blocks, 712 num_inchannels, 713 num_channels, 714 fuse_method, 715 reset_multi_scale_output, 716 ) 717 ) 718 num_inchannels = modules[-1].get_num_inchannels() 719 720 return nn.Sequential(*modules), num_inchannels 721 722 def forward(self, x): 723 """ 724 Forward pass of the PoseHighResolutionNet. 725 726 Args: 727 x (torch.Tensor): Input tensor of shape (batch_size, 3, H, W). 728 729 Returns: 730 torch.Tensor: Output heatmaps or coordinates for landmarks. 731 """ 732 x = self.conv1(x) 733 x = self.bn1(x) 734 x = F.relu(x, inplace=True) 735 x = self.conv2(x) 736 x = self.bn2(x) 737 x = F.relu(x, inplace=True) 738 x = self.layer1(x) 739 740 x_list = [] 741 for i in range(self.stage2_cfg["NUM_BRANCHES"]): 742 if self.transition1[i] is not None: 743 x_list.append(self.transition1[i](x)) 744 else: 745 x_list.append(x) 746 y_list = self.stage2(x_list) 747 748 x_list = [] 749 for i in range(self.stage3_cfg["NUM_BRANCHES"]): 750 if self.transition2[i] is not None: 751 x_list.append(self.transition2[i](y_list[-1])) 752 else: 753 x_list.append(y_list[i]) 754 y_list = self.stage3(x_list) 755 756 x_list = [] 757 for i in range(self.stage4_cfg["NUM_BRANCHES"]): 758 if self.transition3[i] is not None: 759 x_list.append(self.transition3[i](y_list[-1])) 760 else: 761 x_list.append(y_list[i]) 762 y_list = self.stage4(x_list) 763 764 if self.model_name == "pose_hrnet" or "pose_metric_gcn": 765 x = self.final_layer(y_list[0]) 766 else: 767 x = y_list[0] 768 769 if self.target_type == "gaussian": 770 return x 771 772 elif self.target_type == "coordinate": 773 B, C, H, W = x.shape 774 775 """B - cal x,y seperately""" 776 h = F.softmax(x.view(B, C, H * W) * 1, dim=2) 777 h = h.view(B, C, H, W) 778 hx = h.sum(dim=2) # (B, C, W) 779 px = (hx * (torch.arange(W, device=h.device).float().view(1, 1, W))).sum( 780 2, keepdim=True 781 ) 782 hy = h.sum(dim=3) # (B, C, H) 783 py = (hy * (torch.arange(H, device=h.device).float().view(1, 1, H))).sum( 784 2, keepdim=True 785 ) 786 x = torch.cat([px, py], dim=2) 787 return h, x 788 else: 789 raise NotImplementedError(f"{self.target_type} is unknown.")
High-Resolution Network (HRNet) tailored for garment landmark detection.
The network maintains high-resolution representations through multiple stages and branches, fusing multi-scale features and finally predicting heatmaps or coordinates for landmarks.
Attributes:
- inplanes (int): Initial number of input channels.
- conv1 (nn.Conv2d): Initial 3x3 convolution.
- bn1 (nn.BatchNorm2d): Batch normalization after conv1.
- conv2 (nn.Conv2d): Second 3x3 convolution.
- bn2 (nn.BatchNorm2d): Batch normalization after conv2.
- relu (nn.ReLU): ReLU activation.
- stage1_cfg (dict): Configuration for stage1.
- stage2_cfg (dict): Configuration for stage2.
- stage3_cfg (dict): Configuration for stage3.
- stage4_cfg (dict): Configuration for stage4.
- transition1 (nn.ModuleList): Transition layers between stages.
- stage2 (HighResolutionModule): Stage 2 module.
- transition2 (nn.ModuleList): Transition layers between stages.
- stage3 (HighResolutionModule): Stage 3 module.
- transition3 (nn.ModuleList): Transition layers between stages.
- stage4 (HighResolutionModule): Stage 4 module.
- final_layer (nn.Conv2d): Final convolution layer to output predictions.
- target_type (str): Output format type ('gaussian' or 'coordinate').
481 def __init__(self, **kwargs): 482 """ 483 Initializes PoseHighResolutionNet with default HRNet configurations. 484 485 Args: 486 target_type (str, optional): Type of target output. Either "gaussian" or "coordinate". Defaults to "gaussian". 487 """ 488 self.inplanes = 64 489 # Hardcoded values from YAML MODEL.EXTRA 490 extra = { 491 "PRETRAINED_LAYERS": [ 492 "conv1", 493 "bn1", 494 "conv2", 495 "bn2", 496 "layer1", 497 "transition1", 498 "stage2", 499 "transition2", 500 "stage3", 501 "transition3", 502 "stage4", 503 ], 504 "FINAL_CONV_KERNEL": 1, 505 "STAGE2": { 506 "NUM_MODULES": 1, 507 "NUM_BRANCHES": 2, 508 "BLOCK": "BASIC", 509 "NUM_BLOCKS": [4, 4], 510 "NUM_CHANNELS": [48, 96], 511 "FUSE_METHOD": "SUM", 512 }, 513 "STAGE3": { 514 "NUM_MODULES": 4, 515 "NUM_BRANCHES": 3, 516 "BLOCK": "BASIC", 517 "NUM_BLOCKS": [4, 4, 4], 518 "NUM_CHANNELS": [48, 96, 192], 519 "FUSE_METHOD": "SUM", 520 }, 521 "STAGE4": { 522 "NUM_MODULES": 3, 523 "NUM_BRANCHES": 4, 524 "BLOCK": "BASIC", 525 "NUM_BLOCKS": [4, 4, 4, 4], 526 "NUM_CHANNELS": [48, 96, 192, 384], 527 "FUSE_METHOD": "SUM", 528 }, 529 } 530 531 self.model_name = "pose_hrnet" 532 self.target_type = "gaussian" 533 534 super(PoseHighResolutionNet, self).__init__() 535 536 # stem net 537 self.conv1 = nn.Conv2d(3, 64, kernel_size=3, stride=2, padding=1, bias=False) 538 self.bn1 = nn.BatchNorm2d(64, momentum=BN_MOMENTUM) 539 self.conv2 = nn.Conv2d(64, 64, kernel_size=3, stride=2, padding=1, bias=False) 540 self.bn2 = nn.BatchNorm2d(64, momentum=BN_MOMENTUM) 541 self.layer1 = self._make_layer(Bottleneck, 64, 4) 542 543 # Stage2 544 self.stage2_cfg = extra["STAGE2"] 545 num_channels = self.stage2_cfg["NUM_CHANNELS"] 546 block = blocks_dict[self.stage2_cfg["BLOCK"]] 547 num_channels = [ 548 num_channels[i] * block.expansion for i in range(len(num_channels)) 549 ] 550 self.transition1 = self._make_transition_layer([256], num_channels) 551 self.stage2, pre_stage_channels = self._make_stage( 552 self.stage2_cfg, num_channels 553 ) 554 555 # Stage3 556 self.stage3_cfg = extra["STAGE3"] 557 num_channels = self.stage3_cfg["NUM_CHANNELS"] 558 block = blocks_dict[self.stage3_cfg["BLOCK"]] 559 num_channels = [ 560 num_channels[i] * block.expansion for i in range(len(num_channels)) 561 ] 562 self.transition2 = self._make_transition_layer(pre_stage_channels, num_channels) 563 self.stage3, pre_stage_channels = self._make_stage( 564 self.stage3_cfg, num_channels 565 ) 566 567 # Stage4 568 self.stage4_cfg = extra["STAGE4"] 569 num_channels = self.stage4_cfg["NUM_CHANNELS"] 570 block = blocks_dict[self.stage4_cfg["BLOCK"]] 571 num_channels = [ 572 num_channels[i] * block.expansion for i in range(len(num_channels)) 573 ] 574 self.transition3 = self._make_transition_layer(pre_stage_channels, num_channels) 575 self.stage4, pre_stage_channels = self._make_stage( 576 self.stage4_cfg, num_channels, multi_scale_output=False 577 ) 578 579 # Final layer 580 self.final_layer = nn.Conv2d( 581 in_channels=pre_stage_channels[0], 582 out_channels=294, # from MODEL.NUM_JOINTS 583 kernel_size=extra["FINAL_CONV_KERNEL"], 584 stride=1, 585 padding=1 if extra["FINAL_CONV_KERNEL"] == 3 else 0, 586 ) 587 588 self.pretrained_layers = extra["PRETRAINED_LAYERS"]
Initializes PoseHighResolutionNet with default HRNet configurations.
Arguments:
- target_type (str, optional): Type of target output. Either "gaussian" or "coordinate". Defaults to "gaussian".
722 def forward(self, x): 723 """ 724 Forward pass of the PoseHighResolutionNet. 725 726 Args: 727 x (torch.Tensor): Input tensor of shape (batch_size, 3, H, W). 728 729 Returns: 730 torch.Tensor: Output heatmaps or coordinates for landmarks. 731 """ 732 x = self.conv1(x) 733 x = self.bn1(x) 734 x = F.relu(x, inplace=True) 735 x = self.conv2(x) 736 x = self.bn2(x) 737 x = F.relu(x, inplace=True) 738 x = self.layer1(x) 739 740 x_list = [] 741 for i in range(self.stage2_cfg["NUM_BRANCHES"]): 742 if self.transition1[i] is not None: 743 x_list.append(self.transition1[i](x)) 744 else: 745 x_list.append(x) 746 y_list = self.stage2(x_list) 747 748 x_list = [] 749 for i in range(self.stage3_cfg["NUM_BRANCHES"]): 750 if self.transition2[i] is not None: 751 x_list.append(self.transition2[i](y_list[-1])) 752 else: 753 x_list.append(y_list[i]) 754 y_list = self.stage3(x_list) 755 756 x_list = [] 757 for i in range(self.stage4_cfg["NUM_BRANCHES"]): 758 if self.transition3[i] is not None: 759 x_list.append(self.transition3[i](y_list[-1])) 760 else: 761 x_list.append(y_list[i]) 762 y_list = self.stage4(x_list) 763 764 if self.model_name == "pose_hrnet" or "pose_metric_gcn": 765 x = self.final_layer(y_list[0]) 766 else: 767 x = y_list[0] 768 769 if self.target_type == "gaussian": 770 return x 771 772 elif self.target_type == "coordinate": 773 B, C, H, W = x.shape 774 775 """B - cal x,y seperately""" 776 h = F.softmax(x.view(B, C, H * W) * 1, dim=2) 777 h = h.view(B, C, H, W) 778 hx = h.sum(dim=2) # (B, C, W) 779 px = (hx * (torch.arange(W, device=h.device).float().view(1, 1, W))).sum( 780 2, keepdim=True 781 ) 782 hy = h.sum(dim=3) # (B, C, H) 783 py = (hy * (torch.arange(H, device=h.device).float().view(1, 1, H))).sum( 784 2, keepdim=True 785 ) 786 x = torch.cat([px, py], dim=2) 787 return h, x 788 else: 789 raise NotImplementedError(f"{self.target_type} is unknown.")
Forward pass of the PoseHighResolutionNet.
Arguments:
- x (torch.Tensor): Input tensor of shape (batch_size, 3, H, W).
Returns:
torch.Tensor: Output heatmaps or coordinates for landmarks.