Download generate_proposal.py from gatilin/WeDetect-Ref-4B-Base: direct link, hf CLI and curl.
- Browser
- Download file 47.5 kB
-
https://huggingface.co/gatilin/WeDetect-Ref-4B-Base/resolve/main/generate_proposal.py
- Command line
-
hf download hf://gatilin/WeDetect-Ref-4B-Base/generate_proposal.py
-
curl -L -o generate_proposal.py https://huggingface.co/gatilin/WeDetect-Ref-4B-Base/resolve/main/generate_proposal.py
47.5 kB
| import argparse | |
| import torch | |
| from torch import nn | |
| from torch.nn import Parameter | |
| import torch.nn.functional as F | |
| import math | |
| from torch import Tensor | |
| import numpy as np | |
| from typing import Tuple, Union, List, Optional | |
| from torch.nn.modules.utils import _pair | |
| import torchvision | |
| from PIL import Image | |
| import json | |
| import tqdm | |
| from vis import plot_bounding_boxes | |
| def letterbox( | |
| img: Image.Image, | |
| new_shape=(640, 640), | |
| color=(114, 114, 114), | |
| auto=False, | |
| scale_fill=False, | |
| scale_up=True | |
| ): | |
| """ | |
| 将 PIL 图像进行 letterbox 处理,用于 YOLOv5 推理前的预处理 | |
| 来源:YOLOv5 utils/augmentations.py | |
| 参数: | |
| img (PIL.Image): 输入图像 | |
| new_shape (tuple): 模型输入尺寸 (height, width) | |
| color (tuple): 填充颜色 (R, G, B) | |
| auto (bool): 是否自动选择最小步长(如 YOLOv5 的 auto-anchor) | |
| scale_fill (bool): 是否拉伸填充(不保持比例) | |
| scale_up (bool): 是否放大图像 | |
| 返回: | |
| image (PIL.Image): letterbox 后的图像 | |
| ratio (float): 缩放比例 | |
| (dw, dh) (float, float): 左/右 和 上/下 填充的一半(用于还原 bbox) | |
| """ | |
| shape = img.size # (w, h) | |
| new_shape = (new_shape[1], new_shape[0]) # YOLOv5 使用 (w, h) | |
| # 计算缩放比例 | |
| r = min(new_shape[0] / shape[0], new_shape[1] / shape[1]) | |
| if not scale_up: # 只缩小,不放大 | |
| r = min(r, 1.0) | |
| # 计算缩放后的新尺寸 | |
| new_unpad = (int(round(shape[0] * r)), int(round(shape[1] * r))) | |
| # 缩放图像 | |
| img_resized = img.resize(new_unpad, Image.Resampling.BILINEAR) | |
| # 填充颜色 | |
| top, left = 0, 0 | |
| # if auto: # YOLOv5 自动模式:保证尺寸是 32 的倍数 | |
| # dw, dh = new_shape[0] - new_unpad[0], new_shape[1] - new_unpad[1] | |
| # dw %= 32 | |
| # dh %= 32 | |
| # elif scale_fill: | |
| # # 拉伸填充(不推荐,破坏宽高比) | |
| # img_resized = img.resize(new_shape, Image.Resampling.BILINEAR) | |
| # dw, dh = 0, 0 | |
| # new_unpad = new_shape | |
| # else: | |
| # 正常 letterbox:居中填充 | |
| dw, dh = new_shape[0] - new_unpad[0], new_shape[1] - new_unpad[1] | |
| left = dw // 2 | |
| top = dh // 2 | |
| # 创建新图像并填充背景色 | |
| img_letterboxed = Image.new("RGB", new_shape, color) | |
| img_letterboxed.paste(img_resized, (left, top)) | |
| # 计算填充量(用于还原检测框) | |
| ratio = r | |
| dw /= 2 # 左侧填充 | |
| dh /= 2 # 上侧填充 | |
| return img_letterboxed, ratio, (dw, dh) | |
| def filter_scores_and_topk(scores, score_thr, topk, results=None): | |
| """Filter results using score threshold and topk candidates. | |
| Args: | |
| scores (Tensor): The scores, shape (num_bboxes, K). | |
| score_thr (float): The score filter threshold. | |
| topk (int): The number of topk candidates. | |
| results (dict or list or Tensor, Optional): The results to | |
| which the filtering rule is to be applied. The shape | |
| of each item is (num_bboxes, N). | |
| Returns: | |
| tuple: Filtered results | |
| - scores (Tensor): The scores after being filtered, \ | |
| shape (num_bboxes_filtered, ). | |
| - labels (Tensor): The class labels, shape \ | |
| (num_bboxes_filtered, ). | |
| - anchor_idxs (Tensor): The anchor indexes, shape \ | |
| (num_bboxes_filtered, ). | |
| - filtered_results (dict or list or Tensor, Optional): \ | |
| The filtered results. The shape of each item is \ | |
| (num_bboxes_filtered, N). | |
| """ | |
| valid_mask = scores > score_thr | |
| scores = scores[valid_mask] | |
| valid_idxs = torch.nonzero(valid_mask) | |
| num_topk = min(topk, valid_idxs.size(0)) | |
| # torch.sort is actually faster than .topk (at least on GPUs) | |
| scores, idxs = scores.sort(descending=True) | |
| scores = scores[:num_topk] | |
| topk_idxs = valid_idxs[idxs[:num_topk]] | |
| keep_idxs, labels = topk_idxs.unbind(dim=1) | |
| filtered_results = None | |
| if results is not None: | |
| if isinstance(results, dict): | |
| filtered_results = {k: v[keep_idxs] for k, v in results.items()} | |
| elif isinstance(results, list): | |
| filtered_results = [result[keep_idxs] for result in results] | |
| elif isinstance(results, torch.Tensor): | |
| filtered_results = results[keep_idxs] | |
| else: | |
| raise NotImplementedError(f'Only supports dict or list or Tensor, ' | |
| f'but get {type(results)}.') | |
| return scores, labels, keep_idxs, filtered_results | |
| ################################################# | |
| # ConvNext Backbone | |
| ################################################# | |
| class Block(nn.Module): | |
| r"""ConvNeXt Block. There are two equivalent implementations: | |
| (1) DwConv -> LayerNorm (channels_first) -> 1x1 Conv -> GELU -> 1x1 Conv; all in (N, C, H, W) | |
| (2) DwConv -> Permute to (N, H, W, C); LayerNorm (channels_last) -> Linear -> GELU -> Linear; Permute back | |
| We use (2) as we find it slightly faster in PyTorch | |
| Args: | |
| dim (int): Number of input channels. | |
| drop_path (float): Stochastic depth rate. Default: 0.0 | |
| layer_scale_init_value (float): Init value for Layer Scale. Default: 1e-6. | |
| """ | |
| def __init__(self, dim, drop_path=0.0, layer_scale_init_value=1e-6): | |
| super().__init__() | |
| self.dwconv = nn.Conv2d( | |
| dim, dim, kernel_size=7, padding=3, groups=dim | |
| ) # depthwise conv | |
| self.norm = LayerNorm(dim, eps=1e-6) | |
| self.pwconv1 = nn.Linear( | |
| dim, 4 * dim | |
| ) # pointwise/1x1 convs, implemented with linear layers | |
| self.act = nn.GELU() | |
| self.pwconv2 = nn.Linear(4 * dim, dim) | |
| self.gamma = ( | |
| nn.Parameter(layer_scale_init_value * torch.ones((dim)), requires_grad=True) | |
| if layer_scale_init_value > 0 | |
| else None | |
| ) | |
| def forward(self, x: torch.Tensor): | |
| input = x | |
| x = self.dwconv(x) | |
| x = x.permute(0, 2, 3, 1) # (N, C, H, W) -> (N, H, W, C) | |
| x = self.norm(x) | |
| x = self.pwconv1(x) | |
| x = self.act(x) | |
| x = self.pwconv2(x) | |
| if self.gamma is not None: | |
| x = self.gamma * x | |
| x = x.permute(0, 3, 1, 2) # (N, H, W, C) -> (N, C, H, W) | |
| x = input + x | |
| return x | |
| class LayerNorm(nn.Module): | |
| r"""LayerNorm that supports two data formats: channels_last (default) or channels_first. | |
| The ordering of the dimensions in the inputs. channels_last corresponds to inputs with | |
| shape (batch_size, height, width, channels) while channels_first corresponds to inputs | |
| with shape (batch_size, channels, height, width). | |
| """ | |
| def __init__(self, normalized_shape, eps=1e-6, data_format="channels_last"): | |
| super().__init__() | |
| self.weight = nn.Parameter(torch.ones(normalized_shape)) | |
| self.bias = nn.Parameter(torch.zeros(normalized_shape)) | |
| self.eps = eps | |
| self.data_format = data_format | |
| if self.data_format not in ["channels_last", "channels_first"]: | |
| raise NotImplementedError | |
| self.normalized_shape = (normalized_shape,) | |
| def forward(self, x: torch.Tensor): | |
| if self.data_format == "channels_last": | |
| return F.layer_norm( | |
| x, self.normalized_shape, self.weight, self.bias, self.eps | |
| ) | |
| elif self.data_format == "channels_first": | |
| u = x.mean(1, keepdim=True) | |
| s = (x - u).pow(2).mean(1, keepdim=True) | |
| x = (x - u) / torch.sqrt(s + self.eps) | |
| x = self.weight[:, None, None] * x + self.bias[:, None, None] | |
| return x | |
| class ConvNeXt(nn.Module): | |
| r"""ConvNeXt | |
| A PyTorch impl of : `A ConvNet for the 2020s` - | |
| https://arxiv.org/pdf/2201.03545.pdf | |
| Args: | |
| in_chans (int): Number of input image channels. Default: 3 | |
| num_classes (int): Number of classes for classification head. Default: 1000 | |
| depths (tuple(int)): Number of blocks at each stage. Default: [3, 3, 9, 3] | |
| dims (int): Feature dimension at each stage. Default: [96, 192, 384, 768] | |
| drop_path_rate (float): Stochastic depth rate. Default: 0. | |
| layer_scale_init_value (float): Init value for Layer Scale. Default: 1e-6. | |
| head_init_scale (float): Init scaling value for classifier weights and biases. Default: 1. | |
| """ | |
| def __init__( | |
| self, | |
| model_name | |
| ): | |
| super().__init__() | |
| if model_name == "base": | |
| depths = [3, 3, 27, 3] | |
| dims = [128, 256, 512, 1024] | |
| if model_name == "large": | |
| depths = [3, 3, 27, 3] | |
| dims = [192, 384, 768, 1536] | |
| if model_name == "small": | |
| depths = [3, 3, 27, 3] | |
| dims = [96, 192, 384, 768] | |
| self.downsample_layers = ( | |
| nn.ModuleList() | |
| ) # stem and 3 intermediate downsampling conv layers | |
| stem = nn.Sequential( | |
| nn.Conv2d(3, dims[0], kernel_size=4, stride=4), | |
| LayerNorm(dims[0], eps=1e-6, data_format="channels_first"), | |
| ) | |
| self.downsample_layers.append(stem) | |
| for i in range(3): | |
| downsample_layer = nn.Sequential( | |
| LayerNorm(dims[i], eps=1e-6, data_format="channels_first"), | |
| nn.Conv2d(dims[i], dims[i + 1], kernel_size=2, stride=2), | |
| ) | |
| self.downsample_layers.append(downsample_layer) | |
| self.stages = ( | |
| nn.ModuleList() | |
| ) # 4 feature resolution stages, each consisting of multiple residual blocks | |
| dp_rates = [x.item() for x in torch.linspace(0, 0.0, sum(depths))] | |
| cur = 0 | |
| for i in range(4): | |
| stage = nn.Sequential( | |
| *[ | |
| Block( | |
| dim=dims[i], | |
| drop_path=dp_rates[cur + j], | |
| layer_scale_init_value=1e-6, | |
| ) | |
| for j in range(depths[i]) | |
| ] | |
| ) | |
| self.stages.append(stage) | |
| cur += depths[i] | |
| def forward(self, x): | |
| outputs = [] | |
| c1 = self.downsample_layers[0](x) | |
| c1 = self.stages[0](c1) | |
| outputs.append(c1) | |
| c2 = self.downsample_layers[1](c1) | |
| c2 = self.stages[1](c2) | |
| outputs.append(c2) | |
| c3 = self.downsample_layers[2](c2) | |
| c3 = self.stages[2](c3) | |
| outputs.append(c3) | |
| c4 = self.downsample_layers[3](c3) | |
| c4 = self.stages[3](c4) | |
| outputs.append(c4) | |
| return (c1, c2, c3, c4) | |
| ################################################# | |
| # neck | |
| ################################################# | |
| activation_table = {'relu':nn.ReLU(), | |
| 'silu':nn.SiLU(), | |
| 'hardswish':nn.Hardswish() | |
| } | |
| class ConvModule_torch(nn.Module): | |
| '''A combination of Conv + BN + Activation''' | |
| def __init__(self, in_channels, out_channels, kernel_size, stride, activation_type, padding=None, groups=1, bias=False): | |
| super().__init__() | |
| if padding is None: | |
| padding = kernel_size // 2 | |
| self.conv = nn.Conv2d( | |
| in_channels, | |
| out_channels, | |
| kernel_size=kernel_size, | |
| stride=stride, | |
| padding=padding, | |
| groups=groups, | |
| bias=bias, | |
| ) | |
| self.bn = nn.BatchNorm2d(out_channels) | |
| if activation_type is not None: | |
| self.act = activation_table.get(activation_type) | |
| self.activation_type = activation_type | |
| def forward(self, x): | |
| if self.activation_type is None: | |
| return self.bn(self.conv(x)) | |
| return self.act(self.bn(self.conv(x))) | |
| def forward_fuse(self, x): | |
| if self.activation_type is None: | |
| return self.conv(x) | |
| return self.act(self.conv(x)) | |
| class ConvBNReLU(nn.Module): | |
| '''Conv and BN with ReLU activation''' | |
| def __init__(self, in_channels, out_channels, kernel_size=3, stride=1, padding=None, groups=1, bias=False): | |
| super().__init__() | |
| self.block = ConvModule_torch(in_channels, out_channels, kernel_size, stride, 'relu', padding, groups, bias) | |
| def forward(self, x): | |
| return self.block(x) | |
| class ConvBNSiLU(nn.Module): | |
| '''Conv and BN with SiLU activation''' | |
| def __init__(self, in_channels, out_channels, kernel_size=3, stride=1, padding=None, groups=1, bias=False): | |
| super().__init__() | |
| self.block = ConvModule_torch(in_channels, out_channels, kernel_size, stride, 'silu', padding, groups, bias) | |
| def forward(self, x): | |
| return self.block(x) | |
| class RepBlock(nn.Module): | |
| ''' | |
| RepBlock is a stage block with rep-style basic block | |
| ''' | |
| def __init__(self, in_channels, out_channels, block, basic_block, n=1): | |
| super().__init__() | |
| self.conv1 = BottleRep(in_channels, out_channels, basic_block=basic_block, weight=True) | |
| n = n // 2 | |
| self.block = nn.Sequential(*(BottleRep(out_channels, out_channels, basic_block=basic_block, weight=True) for _ in range(n - 1))) if n > 1 else None | |
| def forward(self, x): | |
| x = self.conv1(x) | |
| if self.block is not None: | |
| x = self.block(x) | |
| return x | |
| class BottleRep(nn.Module): | |
| def __init__(self, in_channels, out_channels, basic_block, weight=False): | |
| super().__init__() | |
| self.conv1 = basic_block(in_channels, out_channels) | |
| self.conv2 = basic_block(out_channels, out_channels) | |
| if in_channels != out_channels: | |
| self.shortcut = False | |
| else: | |
| self.shortcut = True | |
| if weight: | |
| self.alpha = Parameter(torch.ones(1)) | |
| else: | |
| self.alpha = 1.0 | |
| def forward(self, x): | |
| outputs = self.conv1(x) | |
| outputs = self.conv2(outputs) | |
| return outputs + self.alpha * x if self.shortcut else outputs | |
| class BepC3(nn.Module): | |
| '''CSPStackRep Block''' | |
| def __init__(self, in_channels, out_channels, n=1, e=0.5): | |
| super().__init__() | |
| c_ = int(out_channels * e) # hidden channels | |
| self.cv1 = ConvBNReLU(in_channels, c_, 1, 1) | |
| self.cv2 = ConvBNReLU(in_channels, c_, 1, 1) | |
| self.cv3 = ConvBNReLU(2 * c_, out_channels, 1, 1) | |
| self.cv1 = ConvBNSiLU(in_channels, c_, 1, 1) | |
| self.cv2 = ConvBNSiLU(in_channels, c_, 1, 1) | |
| self.cv3 = ConvBNSiLU(2 * c_, out_channels, 1, 1) | |
| self.m = RepBlock(in_channels=c_, out_channels=c_, n=n, block=BottleRep, basic_block=ConvBNSiLU) | |
| def forward(self, x): | |
| return self.cv3(torch.cat((self.m(self.cv1(x)), self.cv2(x)), dim=1)) | |
| class Transpose(nn.Module): | |
| '''Normal Transpose, default for upsampling''' | |
| def __init__(self, in_channels, out_channels, kernel_size=2, stride=2): | |
| super().__init__() | |
| self.upsample_transpose = torch.nn.ConvTranspose2d( | |
| in_channels=in_channels, | |
| out_channels=out_channels, | |
| kernel_size=kernel_size, | |
| stride=stride, | |
| bias=True | |
| ) | |
| def forward(self, x): | |
| return self.upsample_transpose(x) | |
| class BiFusion(nn.Module): | |
| '''BiFusion Block in PAN''' | |
| def __init__(self, in_channels, out_channels): | |
| super().__init__() | |
| self.cv1 = ConvBNReLU(in_channels[0], out_channels, 1, 1) | |
| self.cv2 = ConvBNReLU(in_channels[1], out_channels, 1, 1) | |
| self.cv3 = ConvBNReLU(out_channels * 3, out_channels, 1, 1) | |
| self.upsample = Transpose( | |
| in_channels=out_channels, | |
| out_channels=out_channels, | |
| ) | |
| self.downsample = ConvBNReLU( | |
| in_channels=out_channels, | |
| out_channels=out_channels, | |
| kernel_size=3, | |
| stride=2 | |
| ) | |
| def forward(self, x): | |
| x0 = self.upsample(x[0]) | |
| x1 = self.cv1(x[1]) | |
| x2 = self.downsample(self.cv2(x[2])) | |
| return self.cv3(torch.cat((x0, x1, x2), dim=1)) | |
| class CSPRepBiFPANNeck(nn.Module): | |
| """ | |
| CSPRepBiFPANNeck module. | |
| """ | |
| def __init__(self, scale_factor): | |
| super().__init__() | |
| channels_list=[64, 128, 256, 512, 1024, 256, 128, 128, 256, 256, 512] | |
| num_repeats=[1, 6, 12, 18, 6, 12, 12, 12, 12] | |
| csp_e=float(1)/2 | |
| assert channels_list is not None | |
| assert num_repeats is not None | |
| stage_block = BepC3 | |
| self.reduce_layer0 = ConvBNReLU( | |
| in_channels=int(channels_list[4] * scale_factor), # 1024 | |
| out_channels=int(channels_list[5] * scale_factor), # 256 | |
| kernel_size=1, | |
| stride=1 | |
| ) | |
| self.Bifusion0 = BiFusion( | |
| in_channels=[int(channels_list[3] * scale_factor), int(channels_list[2] * scale_factor)], # 512, 256 | |
| out_channels=int(channels_list[5] * scale_factor), # 256 | |
| ) | |
| self.Rep_p4 = stage_block( | |
| in_channels=int(channels_list[5] * scale_factor), # 256 | |
| out_channels=int(channels_list[5] * scale_factor), # 256 | |
| n=num_repeats[5], | |
| e=csp_e, | |
| ) | |
| self.reduce_layer1 = ConvBNReLU( | |
| in_channels=int(channels_list[5] * scale_factor), # 256 | |
| out_channels=int(channels_list[6] * scale_factor), # 128 | |
| kernel_size=1, | |
| stride=1 | |
| ) | |
| self.Bifusion1 = BiFusion( | |
| in_channels=[int(channels_list[2] * scale_factor), int(channels_list[1] * scale_factor)], # 256, 128 | |
| out_channels=int(channels_list[6] * scale_factor), # 128 | |
| ) | |
| self.Rep_p3 = stage_block( | |
| in_channels=int(channels_list[6] * scale_factor), # 128 | |
| out_channels=int(channels_list[6] * scale_factor), # 128 | |
| n=num_repeats[6], | |
| e=csp_e, | |
| ) | |
| self.downsample2 = ConvBNReLU( | |
| in_channels=int(channels_list[6] * scale_factor), # 128 | |
| out_channels=int(channels_list[7] * scale_factor), # 128 | |
| kernel_size=3, | |
| stride=2 | |
| ) | |
| self.Rep_n3 = stage_block( | |
| in_channels=int(channels_list[6] * scale_factor) + int(channels_list[7] * scale_factor), # 128 + 128 | |
| out_channels=int(channels_list[8] * scale_factor), # 256 | |
| n=num_repeats[7], | |
| e=csp_e, | |
| ) | |
| self.downsample1 = ConvBNReLU( | |
| in_channels=int(channels_list[8] * scale_factor), # 256 | |
| out_channels=int(channels_list[9] * scale_factor), # 256 | |
| kernel_size=3, | |
| stride=2 | |
| ) | |
| self.Rep_n4 = stage_block( | |
| in_channels=int(channels_list[5] * scale_factor) + int(channels_list[9] * scale_factor), # 256 + 256 | |
| out_channels=int(channels_list[10] * scale_factor), # 512 | |
| n=num_repeats[8], | |
| e=csp_e, | |
| ) | |
| def forward(self, input): | |
| (x3, x2, x1, x0) = input | |
| fpn_out0 = self.reduce_layer0(x0) | |
| f_concat_layer0 = self.Bifusion0([fpn_out0, x1, x2]) | |
| f_out0 = self.Rep_p4(f_concat_layer0) | |
| fpn_out1 = self.reduce_layer1(f_out0) | |
| f_concat_layer1 = self.Bifusion1([fpn_out1, x2, x3]) | |
| pan_out2 = self.Rep_p3(f_concat_layer1) | |
| down_feat1 = self.downsample2(pan_out2) | |
| p_concat_layer1 = torch.cat([down_feat1, fpn_out1], 1) | |
| pan_out1 = self.Rep_n3(p_concat_layer1) | |
| down_feat0 = self.downsample1(pan_out1) | |
| p_concat_layer2 = torch.cat([down_feat0, fpn_out0], 1) | |
| pan_out0 = self.Rep_n4(p_concat_layer2) | |
| outputs = [pan_out2, pan_out1, pan_out0] | |
| return outputs | |
| ################################################# | |
| # head | |
| ################################################# | |
| class BNContrastiveHead(nn.Module): | |
| """ Batch Norm Contrastive Head for YOLO-World | |
| using batch norm instead of l2-normalization | |
| Args: | |
| embed_dims (int): embed dim of text and image features | |
| norm_cfg (dict): normalization params | |
| """ | |
| def __init__(self, | |
| embed_dims: int, | |
| use_einsum: bool = True) -> None: | |
| super().__init__() | |
| self.norm = nn.BatchNorm2d(embed_dims, momentum=0.03, eps=0.001) | |
| self.bias = nn.Parameter(torch.zeros([])) | |
| # use -1.0 is more stable | |
| self.logit_scale = nn.Parameter(-1.0 * torch.ones([])) | |
| self.use_einsum = use_einsum | |
| def forward(self, x: Tensor, w: Tensor) -> Tensor: | |
| """Forward function of contrastive learning.""" | |
| x = self.norm(x) | |
| w = F.normalize(w, dim=-1, p=2) | |
| if self.use_einsum: | |
| x = torch.einsum('bchw,bkc->bkhw', x, w) | |
| else: | |
| batch, channel, height, width = x.shape | |
| _, k, _ = w.shape | |
| x = x.permute(0, 2, 3, 1) # bchw->bhwc | |
| x = x.reshape(batch, -1, channel) # bhwc->b(hw)c | |
| w = w.permute(0, 2, 1) # bkc->bck | |
| x = torch.matmul(x, w) | |
| x = x.reshape(batch, height, width, k) | |
| x = x.permute(0, 3, 1, 2) | |
| x = x * self.logit_scale.exp() + self.bias | |
| return x | |
| class YOLOWorldHeadModule(nn.Module): | |
| """Head Module for YOLO-World | |
| Args: | |
| embed_dims (int): embed dim for text feautures and image features | |
| use_bn_head (bool): use batch normalization head | |
| """ | |
| def __init__(self, | |
| embed_dims: int, | |
| in_channels, | |
| use_bn_head: bool = False, | |
| use_einsum: bool = True, | |
| freeze_all: bool = False, ) -> None: | |
| self.embed_dims = embed_dims | |
| self.use_bn_head = use_bn_head | |
| self.use_einsum = use_einsum | |
| self.freeze_all = freeze_all | |
| self.in_channels = in_channels | |
| self.reg_max = 16 | |
| super().__init__() | |
| self._init_layers() | |
| def _init_layers(self) -> None: | |
| """initialize conv layers in YOLOv8 head.""" | |
| # Init decouple head | |
| self.cls_preds = nn.ModuleList() | |
| self.reg_preds = nn.ModuleList() | |
| self.cls_contrasts = nn.ModuleList() | |
| cls_out_channels = 256 | |
| self.featmap_strides = [8, 16, 32] | |
| self.num_levels = len(self.in_channels) | |
| reg_out_channels = max( | |
| (16, self.in_channels[0] // 4, self.reg_max * 4)) | |
| for i in range(self.num_levels): | |
| self.reg_preds.append( | |
| nn.Sequential( | |
| nn.Conv2d(in_channels=self.in_channels[i], | |
| out_channels=reg_out_channels, | |
| kernel_size=3, | |
| stride=1, | |
| padding=1, | |
| bias=False,), | |
| nn.BatchNorm2d(reg_out_channels, momentum=0.03, eps=0.001), | |
| nn.SiLU(), | |
| nn.Conv2d(in_channels=reg_out_channels, | |
| out_channels=reg_out_channels, | |
| kernel_size=3, | |
| stride=1, | |
| padding=1, | |
| bias=False,), | |
| nn.BatchNorm2d(reg_out_channels, momentum=0.03, eps=0.001), | |
| nn.SiLU(), | |
| nn.Conv2d(in_channels=reg_out_channels, | |
| out_channels=4 * self.reg_max, | |
| kernel_size=1))) | |
| self.cls_preds.append( | |
| nn.Sequential( | |
| nn.Conv2d(in_channels=self.in_channels[i], | |
| out_channels=cls_out_channels, | |
| kernel_size=3, | |
| stride=1, | |
| padding=1, | |
| bias=False,), | |
| nn.BatchNorm2d(cls_out_channels, momentum=0.03, eps=0.001), | |
| nn.SiLU(), | |
| nn.Conv2d(in_channels=cls_out_channels, | |
| out_channels=cls_out_channels, | |
| kernel_size=3, | |
| stride=1, | |
| padding=1, | |
| bias=False,), | |
| nn.BatchNorm2d(cls_out_channels, momentum=0.03, eps=0.001), | |
| nn.SiLU(), | |
| nn.Conv2d(in_channels=cls_out_channels, | |
| out_channels=self.embed_dims, | |
| kernel_size=1))) | |
| self.cls_contrasts.append(BNContrastiveHead(self.embed_dims, use_einsum=self.use_einsum)) | |
| proj = torch.arange(self.reg_max, dtype=torch.float) | |
| self.register_buffer('proj', proj, persistent=False) | |
| def forward(self, img_feats: Tuple[Tensor], | |
| txt_feats: Tensor) -> Tuple[List]: | |
| """Forward features from the upstream network.""" | |
| assert len(img_feats) == self.num_levels | |
| txt_feats = [txt_feats for _ in range(self.num_levels)] | |
| outputs = [] | |
| for i in range(self.num_levels): | |
| outputs.append( | |
| self.forward_single(img_feats[i], txt_feats[i], | |
| self.cls_preds[i], self.reg_preds[i], | |
| self.cls_contrasts[i])) | |
| return tuple(outputs) | |
| def forward_single(self, img_feat: Tensor, txt_feat: Tensor, | |
| cls_pred: nn.ModuleList, reg_pred: nn.ModuleList, | |
| cls_contrast: nn.ModuleList) -> Tuple: | |
| """Forward feature of a single scale level.""" | |
| b, _, h, w = img_feat.shape | |
| cls_embed = cls_pred(img_feat) | |
| cls_logit = cls_contrast(cls_embed, txt_feat) | |
| bbox_dist_preds = reg_pred(img_feat) | |
| if self.reg_max > 1: | |
| bbox_dist_preds = bbox_dist_preds.reshape( | |
| [-1, 4, self.reg_max, h * w]).permute(0, 3, 1, 2) | |
| # TODO: The get_flops script cannot handle the situation of | |
| # matmul, and needs to be fixed later | |
| # bbox_preds = bbox_dist_preds.softmax(3).matmul(self.proj) | |
| bbox_preds = bbox_dist_preds.softmax(3).matmul( | |
| self.proj.view([-1, 1])).squeeze(-1) | |
| bbox_preds = bbox_preds.transpose(1, 2).reshape(b, -1, h, w) | |
| else: | |
| bbox_preds = bbox_dist_preds | |
| if self.training: | |
| return cls_logit, bbox_preds, bbox_dist_preds | |
| else: | |
| return cls_logit, bbox_preds | |
| ################################################# | |
| # prior generator | |
| ################################################# | |
| class MlvlPointGenerator: | |
| """Standard points generator for multi-level (Mlvl) feature maps in 2D | |
| points-based detectors. | |
| Args: | |
| strides (list[int] | list[tuple[int, int]]): Strides of anchors | |
| in multiple feature levels in order (w, h). | |
| offset (float): The offset of points, the value is normalized with | |
| corresponding stride. Defaults to 0.5. | |
| """ | |
| def __init__(self, | |
| strides: Union[List[int], List[Tuple[int, int]]], | |
| offset: float = 0.5) -> None: | |
| self.strides = [_pair(stride) for stride in strides] | |
| self.offset = offset | |
| def num_levels(self) -> int: | |
| """int: number of feature levels that the generator will be applied""" | |
| return len(self.strides) | |
| def num_base_priors(self) -> List[int]: | |
| """list[int]: The number of priors (points) at a point | |
| on the feature grid""" | |
| return [1 for _ in range(len(self.strides))] | |
| def _meshgrid(self, | |
| x: Tensor, | |
| y: Tensor, | |
| row_major: bool = True) -> Tuple[Tensor, Tensor]: | |
| yy, xx = torch.meshgrid(y, x) | |
| if row_major: | |
| # warning .flatten() would cause error in ONNX exporting | |
| # have to use reshape here | |
| return xx.reshape(-1), yy.reshape(-1) | |
| else: | |
| return yy.reshape(-1), xx.reshape(-1) | |
| def grid_priors(self, | |
| featmap_sizes: List[Tuple], | |
| dtype: torch.dtype = torch.float32, | |
| device = 'cuda', | |
| with_stride: bool = False) -> List[Tensor]: | |
| """Generate grid points of multiple feature levels. | |
| Args: | |
| featmap_sizes (list[tuple]): List of feature map sizes in | |
| multiple feature levels, each size arrange as | |
| as (h, w). | |
| dtype (:obj:`dtype`): Dtype of priors. Defaults to torch.float32. | |
| device (str | torch.device): The device where the anchors will be | |
| put on. | |
| with_stride (bool): Whether to concatenate the stride to | |
| the last dimension of points. | |
| Return: | |
| list[torch.Tensor]: Points of multiple feature levels. | |
| The sizes of each tensor should be (N, 2) when with stride is | |
| ``False``, where N = width * height, width and height | |
| are the sizes of the corresponding feature level, | |
| and the last dimension 2 represent (coord_x, coord_y), | |
| otherwise the shape should be (N, 4), | |
| and the last dimension 4 represent | |
| (coord_x, coord_y, stride_w, stride_h). | |
| """ | |
| assert self.num_levels == len(featmap_sizes) | |
| multi_level_priors = [] | |
| for i in range(self.num_levels): | |
| priors = self.single_level_grid_priors( | |
| featmap_sizes[i], | |
| level_idx=i, | |
| dtype=dtype, | |
| device=device, | |
| with_stride=with_stride) | |
| multi_level_priors.append(priors) | |
| return multi_level_priors | |
| def single_level_grid_priors(self, | |
| featmap_size: Tuple[int], | |
| level_idx: int, | |
| dtype: torch.dtype = torch.float32, | |
| device = 'cuda', | |
| with_stride: bool = False) -> Tensor: | |
| """Generate grid Points of a single level. | |
| Note: | |
| This function is usually called by method ``self.grid_priors``. | |
| Args: | |
| featmap_size (tuple[int]): Size of the feature maps, arrange as | |
| (h, w). | |
| level_idx (int): The index of corresponding feature map level. | |
| dtype (:obj:`dtype`): Dtype of priors. Defaults to torch.float32. | |
| device (str | torch.device): The device the tensor will be put on. | |
| Defaults to 'cuda'. | |
| with_stride (bool): Concatenate the stride to the last dimension | |
| of points. | |
| Return: | |
| Tensor: Points of single feature levels. | |
| The shape of tensor should be (N, 2) when with stride is | |
| ``False``, where N = width * height, width and height | |
| are the sizes of the corresponding feature level, | |
| and the last dimension 2 represent (coord_x, coord_y), | |
| otherwise the shape should be (N, 4), | |
| and the last dimension 4 represent | |
| (coord_x, coord_y, stride_w, stride_h). | |
| """ | |
| feat_h, feat_w = featmap_size | |
| stride_w, stride_h = self.strides[level_idx] | |
| shift_x = (torch.arange(0, feat_w, device=device) + | |
| self.offset) * stride_w | |
| # keep featmap_size as Tensor instead of int, so that we | |
| # can convert to ONNX correctly | |
| shift_x = shift_x.to(dtype) | |
| shift_y = (torch.arange(0, feat_h, device=device) + | |
| self.offset) * stride_h | |
| # keep featmap_size as Tensor instead of int, so that we | |
| # can convert to ONNX correctly | |
| shift_y = shift_y.to(dtype) | |
| shift_xx, shift_yy = self._meshgrid(shift_x, shift_y) | |
| if not with_stride: | |
| shifts = torch.stack([shift_xx, shift_yy], dim=-1) | |
| else: | |
| # use `shape[0]` instead of `len(shift_xx)` for ONNX export | |
| stride_w = shift_xx.new_full((shift_xx.shape[0], ), | |
| stride_w).to(dtype) | |
| stride_h = shift_xx.new_full((shift_yy.shape[0], ), | |
| stride_h).to(dtype) | |
| shifts = torch.stack([shift_xx, shift_yy, stride_w, stride_h], | |
| dim=-1) | |
| all_points = shifts.to(device) | |
| return all_points | |
| def valid_flags(self, | |
| featmap_sizes: List[Tuple[int, int]], | |
| pad_shape: Tuple[int], | |
| device = 'cuda') -> List[Tensor]: | |
| """Generate valid flags of points of multiple feature levels. | |
| Args: | |
| featmap_sizes (list(tuple)): List of feature map sizes in | |
| multiple feature levels, each size arrange as | |
| as (h, w). | |
| pad_shape (tuple(int)): The padded shape of the image, | |
| arrange as (h, w). | |
| device (str | torch.device): The device where the anchors will be | |
| put on. | |
| Return: | |
| list(torch.Tensor): Valid flags of points of multiple levels. | |
| """ | |
| assert self.num_levels == len(featmap_sizes) | |
| multi_level_flags = [] | |
| for i in range(self.num_levels): | |
| point_stride = self.strides[i] | |
| feat_h, feat_w = featmap_sizes[i] | |
| h, w = pad_shape[:2] | |
| valid_feat_h = min(int(np.ceil(h / point_stride[1])), feat_h) | |
| valid_feat_w = min(int(np.ceil(w / point_stride[0])), feat_w) | |
| flags = self.single_level_valid_flags((feat_h, feat_w), | |
| (valid_feat_h, valid_feat_w), | |
| device=device) | |
| multi_level_flags.append(flags) | |
| return multi_level_flags | |
| def single_level_valid_flags(self, | |
| featmap_size: Tuple[int, int], | |
| valid_size: Tuple[int, int], | |
| device = 'cuda') -> Tensor: | |
| """Generate the valid flags of points of a single feature map. | |
| Args: | |
| featmap_size (tuple[int]): The size of feature maps, arrange as | |
| as (h, w). | |
| valid_size (tuple[int]): The valid size of the feature maps. | |
| The size arrange as as (h, w). | |
| device (str | torch.device): The device where the flags will be | |
| put on. Defaults to 'cuda'. | |
| Returns: | |
| torch.Tensor: The valid flags of each points in a single level \ | |
| feature map. | |
| """ | |
| feat_h, feat_w = featmap_size | |
| valid_h, valid_w = valid_size | |
| assert valid_h <= feat_h and valid_w <= feat_w | |
| valid_x = torch.zeros(feat_w, dtype=torch.bool, device=device) | |
| valid_y = torch.zeros(feat_h, dtype=torch.bool, device=device) | |
| valid_x[:valid_w] = 1 | |
| valid_y[:valid_h] = 1 | |
| valid_xx, valid_yy = self._meshgrid(valid_x, valid_y) | |
| valid = valid_xx & valid_yy | |
| return valid | |
| def sparse_priors(self, | |
| prior_idxs: Tensor, | |
| featmap_size: Tuple[int], | |
| level_idx: int, | |
| dtype: torch.dtype = torch.float32, | |
| device = 'cuda') -> Tensor: | |
| """Generate sparse points according to the ``prior_idxs``. | |
| Args: | |
| prior_idxs (Tensor): The index of corresponding anchors | |
| in the feature map. | |
| featmap_size (tuple[int]): feature map size arrange as (w, h). | |
| level_idx (int): The level index of corresponding feature | |
| map. | |
| dtype (obj:`torch.dtype`): Date type of points. Defaults to | |
| ``torch.float32``. | |
| device (str | torch.device): The device where the points is | |
| located. | |
| Returns: | |
| Tensor: Anchor with shape (N, 2), N should be equal to | |
| the length of ``prior_idxs``. And last dimension | |
| 2 represent (coord_x, coord_y). | |
| """ | |
| height, width = featmap_size | |
| x = (prior_idxs % width + self.offset) * self.strides[level_idx][0] | |
| y = ((prior_idxs // width) % height + | |
| self.offset) * self.strides[level_idx][1] | |
| prioris = torch.stack([x, y], 1).to(dtype) | |
| prioris = prioris.to(device) | |
| return prioris | |
| def distance2bbox( | |
| points: Tensor, | |
| distance: Tensor, | |
| max_shape = None | |
| ) -> Tensor: | |
| """Decode distance prediction to bounding box. | |
| Args: | |
| points (Tensor): Shape (B, N, 2) or (N, 2). | |
| distance (Tensor): Distance from the given point to 4 | |
| boundaries (left, top, right, bottom). Shape (B, N, 4) or (N, 4) | |
| max_shape (Union[Sequence[int], Tensor, Sequence[Sequence[int]]], | |
| optional): Maximum bounds for boxes, specifies | |
| (H, W, C) or (H, W). If priors shape is (B, N, 4), then | |
| the max_shape should be a Sequence[Sequence[int]] | |
| and the length of max_shape should also be B. | |
| Returns: | |
| Tensor: Boxes with shape (N, 4) or (B, N, 4) | |
| """ | |
| x1 = points[..., 0] - distance[..., 0] | |
| y1 = points[..., 1] - distance[..., 1] | |
| x2 = points[..., 0] + distance[..., 2] | |
| y2 = points[..., 1] + distance[..., 3] | |
| bboxes = torch.stack([x1, y1, x2, y2], -1) | |
| if max_shape is not None: | |
| if bboxes.dim() == 2 and not torch.onnx.is_in_onnx_export(): | |
| # speed up | |
| bboxes[:, 0::2].clamp_(min=0, max=max_shape[1]) | |
| bboxes[:, 1::2].clamp_(min=0, max=max_shape[0]) | |
| return bboxes | |
| if not isinstance(max_shape, torch.Tensor): | |
| max_shape = x1.new_tensor(max_shape) | |
| max_shape = max_shape[..., :2].type_as(x1) | |
| if max_shape.ndim == 2: | |
| assert bboxes.ndim == 3 | |
| assert max_shape.size(0) == bboxes.size(0) | |
| min_xy = x1.new_tensor(0) | |
| max_xy = torch.cat([max_shape, max_shape], | |
| dim=-1).flip(-1).unsqueeze(-2) | |
| bboxes = torch.where(bboxes < min_xy, min_xy, bboxes) | |
| bboxes = torch.where(bboxes > max_xy, max_xy, bboxes) | |
| return bboxes | |
| class SimpleYOLOWorldDetector(nn.Module): | |
| """Implementation of YOLO World Series""" | |
| def __init__(self, | |
| backbone_size, | |
| prompt_dim=768, | |
| num_prompts=512, | |
| num_proposals=300) -> None: | |
| super().__init__() | |
| self.backbone = ConvNeXt(backbone_size) | |
| if backbone_size == 'base': | |
| scale_factor = 1.0 | |
| in_channels = [128, 256, 512] | |
| self.img_size = (640, 640) | |
| self.grid_size = [6400, 1600, 400] | |
| elif backbone_size == 'large': | |
| scale_factor = 1.5 | |
| in_channels = [192, 384, 768] | |
| self.img_size = (1280, 1280) | |
| self.grid_size = [6400*4, 1600*4, 400*4] | |
| self.num_proposals = num_proposals | |
| self.neck = CSPRepBiFPANNeck(scale_factor) | |
| self.bbox_head = YOLOWorldHeadModule(embed_dims=prompt_dim, in_channels=in_channels, use_bn_head=True, use_einsum=True) | |
| embeddings = nn.functional.normalize(torch.randn( | |
| (num_prompts, prompt_dim)), dim=-1) | |
| self.embeddings = nn.Parameter(embeddings) | |
| self.prior_generator = MlvlPointGenerator(strides=[8, 16, 32], offset=0.5) | |
| def forward(self, image_paths: List[str], rescale=True): | |
| inputs = [] | |
| ratios = [] | |
| offsets = [] | |
| ori_shapes = [] | |
| for image_path in image_paths: | |
| if type(image_path) is str: | |
| img = Image.open(image_path).convert("RGB") | |
| else: | |
| img = image_path | |
| width, height = img.size | |
| ori_shape = (height, width) | |
| ori_shapes.append(ori_shape) | |
| img, ratio, offset = letterbox(img, self.img_size) | |
| img = torch.tensor(np.array(img)).permute(2, 0, 1).to(self.embeddings.data.dtype) # HWC, RGB | |
| img = img / 255.0 | |
| inputs.append(img) | |
| ratios.append(ratio) | |
| offsets.append(offset) | |
| inputs = torch.stack(inputs, dim=0).to(self.embeddings.device) | |
| img_feats = self.backbone(inputs) | |
| img_feats = self.neck(img_feats) | |
| results = self.head_predict(img_feats) | |
| for i in range(len(results)): | |
| # print(results[i]['bboxes']) | |
| results[i]['bboxes'] -= results[i]['bboxes'].new_tensor([ | |
| offsets[i][0], offsets[i][1], offsets[i][0], offsets[i][1] | |
| ]) | |
| # print(results[i]['bboxes']) | |
| if rescale: | |
| results[i]['bboxes'] /= ratios[i] | |
| results[i]['bboxes'][:, 0::2] = results[i]['bboxes'][:, 0::2].clamp_(0, ori_shapes[i][1]) | |
| results[i]['bboxes'][:, 1::2] = results[i]['bboxes'][:, 1::2].clamp_(0, ori_shapes[i][0]) | |
| # print(results[i]['bboxes']) | |
| return results | |
| def head_module_forward_single( | |
| self, | |
| img_feat: Tensor, | |
| cls_pred: nn.ModuleList, | |
| reg_pred: nn.ModuleList, | |
| cls_contrast: nn.ModuleList, | |
| ): | |
| module=self.bbox_head | |
| b, _, h, w = img_feat.shape | |
| cls_embed = cls_pred(img_feat) | |
| cls_embed = cls_contrast.norm(cls_embed) | |
| cls_logits = torch.einsum('bchw,kc->bkhw', cls_embed, self.embeddings) | |
| cls_logits = cls_logits * cls_contrast.logit_scale.exp() + cls_contrast.bias | |
| bbox_dist_preds = reg_pred(img_feat) | |
| if module.reg_max > 1: | |
| bbox_dist_preds = bbox_dist_preds.reshape( | |
| [-1, 4, module.reg_max, h * w] | |
| ).permute(0, 3, 1, 2) | |
| # TODO: The get_flops script cannot handle the situation of | |
| # matmul, and needs to be fixed later | |
| # bbox_preds = bbox_dist_preds.softmax(3).matmul(self.proj) | |
| bbox_preds = ( | |
| bbox_dist_preds.softmax(3).matmul(module.proj.view([-1, 1])).squeeze(-1) | |
| ) | |
| bbox_preds = bbox_preds.transpose(1, 2).reshape(b, -1, h, w) | |
| else: | |
| bbox_preds = bbox_dist_preds | |
| return cls_embed, bbox_preds,cls_logits | |
| def head_predict(self, img_feats): | |
| bbox_embed, bbox_preds, cls_scores = [], [], [] | |
| for i in range(len(img_feats)): | |
| box_embed, bbox_pred, cls_score = self.head_module_forward_single( | |
| img_feats[i], | |
| self.bbox_head.cls_preds[i], | |
| self.bbox_head.reg_preds[i], | |
| self.bbox_head.cls_contrasts[i], | |
| ) | |
| bbox_embed.append(box_embed) | |
| bbox_preds.append(bbox_pred) | |
| cls_scores.append(cls_score) | |
| # objectness = None, with_objectness=False, multi_label=False | |
| txt_channel = bbox_embed[0].shape[1] | |
| num_imgs = bbox_embed[0].shape[0] | |
| featmap_sizes = [x.shape[2:] for x in bbox_preds] | |
| mlvl_priors = self.prior_generator.grid_priors( | |
| featmap_sizes, dtype=bbox_embed[0].dtype, device=bbox_embed[0].device | |
| ) | |
| flatten_priors = torch.cat(mlvl_priors) | |
| mlvl_strides = [ | |
| flatten_priors.new_full((featmap_size.numel(),), stride) | |
| for featmap_size, stride in zip(featmap_sizes, self.bbox_head.featmap_strides) | |
| ] | |
| flatten_stride = torch.cat(mlvl_strides) | |
| flatten_bbox_embed = [ | |
| x.permute(0, 2, 3, 1).reshape(num_imgs, -1, txt_channel) for x in bbox_embed | |
| ] | |
| flatten_cls_scores = [ | |
| cls_score.permute(0, 2, 3, 1).reshape(num_imgs, -1, | |
| cls_scores[0].shape[1]) | |
| for cls_score in cls_scores | |
| ] | |
| flatten_cls_scores = torch.cat(flatten_cls_scores, dim=1).sigmoid() | |
| flatten_bbox_preds = [ | |
| bbox_pred.permute(0, 2, 3, 1).reshape(num_imgs, -1, 4) | |
| for bbox_pred in bbox_preds | |
| ] | |
| flatten_bbox_embed = torch.cat(flatten_bbox_embed, dim=1) | |
| flatten_bbox_preds = torch.cat(flatten_bbox_preds, dim=1) | |
| flatten_bbox_preds = flatten_bbox_preds * flatten_stride[None, :, None] | |
| flatten_decoded_bbox = distance2bbox( | |
| flatten_priors[None], flatten_bbox_preds | |
| ) | |
| results_list = [] | |
| for bbox, embed,scores in zip( | |
| flatten_decoded_bbox, flatten_bbox_embed,flatten_cls_scores | |
| ): | |
| original_scores = scores.clone() | |
| scores, labels, keep_idxs, _ = filter_scores_and_topk( | |
| scores, 0.0, 30000) | |
| # if len(scores) < 50: | |
| # scores, labels, keep_idxs, _ = filter_scores_and_topk( | |
| # original_scores, 0.0001, 50) | |
| bbox = bbox[keep_idxs] | |
| embed = embed[keep_idxs] | |
| idx = torchvision.ops.batched_nms(bbox.float(), scores.float(), labels, 0.7)[:self.num_proposals] | |
| bbox = bbox[idx] | |
| embed = embed[idx] | |
| results_list.append({ | |
| 'bboxes': bbox, | |
| 'embeddings': embed, | |
| 'scores': scores[idx], | |
| }) | |
| return results_list | |
| if __name__ == "__main__": | |
| parser = argparse.ArgumentParser() | |
| parser.add_argument('--wedetect_uni_checkpoint', type=str, default='') | |
| parser.add_argument('--image', type=str, default='') | |
| parser.add_argument('--score_thre', type=float, default=0.1) | |
| parser.add_argument('--num_proposals', type=int, default=300) | |
| parser.add_argument('--visualize', action='store_true') | |
| args = parser.parse_args() | |
| # load detection model | |
| model_size = 'base' if 'base' in args.wedetect_uni_checkpoint else 'large' | |
| model = SimpleYOLOWorldDetector(backbone_size=model_size, prompt_dim=768, num_prompts=256, num_proposals=args.num_proposals) | |
| checkpoint = torch.load(args.wedetect_uni_checkpoint, map_location='cpu') | |
| # backbone | |
| keys = list(checkpoint.keys()) | |
| for key in keys: | |
| if 'backbone' in key: | |
| new_key = key.replace('backbone.image_model.model.', 'backbone.') | |
| checkpoint[new_key] = checkpoint.pop(key) | |
| # head | |
| keys = list(checkpoint.keys()) | |
| for key in keys: | |
| if 'bbox_head' in key: | |
| new_key = key.replace('bbox_head.head_module.', 'bbox_head.') | |
| new_key = new_key.replace('0.2.', '0.6.') | |
| new_key = new_key.replace('1.2.', '1.6.') | |
| new_key = new_key.replace('2.2.', '2.6.') | |
| new_key = new_key.replace('1.bn', '4') | |
| new_key = new_key.replace('1.conv', '3') | |
| new_key = new_key.replace('0.bn', '1') | |
| new_key = new_key.replace('0.conv', '0') | |
| checkpoint[new_key] = checkpoint.pop(key) | |
| msg = model.load_state_dict(checkpoint, strict=False) | |
| print(msg) | |
| model = model.cuda() | |
| model.eval() | |
| with torch.no_grad(): | |
| outputs = model([args.image]) | |
| pred_bboxes = outputs[0]['bboxes'].float().cpu() | |
| pred_scores = outputs[0]['scores'].float().cpu() | |
| if args.score_thre > 0: | |
| mask = pred_scores > args.score_thre | |
| pred_bboxes = pred_bboxes[mask] | |
| pred_scores = pred_scores[mask] | |
| if args.visualize: | |
| image = Image.open(args.image) | |
| pred_image = plot_bounding_boxes(image, pred_bboxes.cpu().tolist()) | |
| pred_image.save("pred.png") # 你可以自定义保存路径和文件名 | |