diff --git a/docs/zh_cn/get_started.md b/docs/zh_cn/get_started.md index 58681bad..3ef5785c 100644 --- a/docs/zh_cn/get_started.md +++ b/docs/zh_cn/get_started.md @@ -37,7 +37,7 @@ conda install pytorch torchvision cpuonly -c pytorch ### 最佳实践 -**步骤 0.** 使用 [MIM](https://github.com/open-mmlab/mim) 安装 [MMEngine](https://github.com/open-mmlab/mmengine)、 [MMCV](https://github.com/open-mmlab/mmcv) 和 [MMDetection](https://github.com/open-mmlab/mmdetection)。 +**步骤 0.** 使用 [MIM](https://github.com/open-mmlab/mim) 安装 [MMEngine](https://github.com/open-mmlab/mmengine)、 [MMCV](https://github.com/open-mmlab/mmcv) 和 [MMDetection](https://github.com/open-mmlab/mmdetection) 。 ```shell pip install -U openmim @@ -52,7 +52,7 @@ pip install -r requirements/albu.txt a. 在 MMCV-v2.x 中,`mmcv-full` 改名为 `mmcv`,如果你想安装不包含 CUDA 算子精简版,可以通过 `mim install mmcv-lite>=2.0.0rc1` 来安装。 -b. 如果使用 albumentations,我们建议使用 pip install -r requirements/albu.txt 或者 pip install -U albumentations --no-binary qudida,albumentations 进行安装。 如果简单地使用 pip install albumentations==1.0.1 进行安装,则会同时安装 opencv-python-headless(即便已经安装了 opencv-python 也会再次安装)。我们建议在安装 albumentations 后检查环境,以确保没有同时安装 opencv-python 和 opencv-python-headless,因为同时安装可能会导致一些问题。更多细节请参考官方文档。 +b. 如果使用 albumentations,我们建议使用 pip install -r requirements/albu.txt 或者 pip install -U albumentations --no-binary qudida,albumentations 进行安装。 如果简单地使用 pip install albumentations==1.0.1 进行安装,则会同时安装 opencv-python-headless(即便已经安装了 opencv-python 也会再次安装)。我们建议在安装 albumentations 后检查环境,以确保没有同时安装 opencv-python 和 opencv-python-headless,因为同时安装可能会导致一些问题。更多细节请参考 [官方文档](https://albumentations.ai/docs/getting_started/installation/#note-on-opencv-dependencies) 。 **步骤 1.** 安装 MMYOLO diff --git a/mmyolo/models/dense_heads/yolov6_head.py b/mmyolo/models/dense_heads/yolov6_head.py index e13a3a0c..fae04a92 100644 --- a/mmyolo/models/dense_heads/yolov6_head.py +++ b/mmyolo/models/dense_heads/yolov6_head.py @@ -132,6 +132,15 @@ class YOLOv6HeadModule(BaseModule): kernel_size=1)) def forward(self, x: torch.Tensor) -> torch.Tensor: + """Forward features from the upstream network. + + Args: + x (Tuple[Tensor]): Features from the upstream network, each is + a 4D-tensor. + Returns: + Tuple[List]: A tuple of multi-level classification scores, bbox + predictions. + """ assert len(x) == self.num_levels return multi_apply(self.forward_single, x, self.stems, self.cls_convs, self.cls_preds, self.reg_convs, self.reg_preds) @@ -219,8 +228,29 @@ class YOLOv6Head(YOLOv5Head): self, cls_scores: Sequence[Tensor], bbox_preds: Sequence[Tensor], - objectnesses: Sequence[Tensor], batch_gt_instances: Sequence[InstanceData], batch_img_metas: Sequence[dict], batch_gt_instances_ignore: OptInstanceList = None) -> dict: + """Calculate the loss based on the features extracted by the detection + head. + + Args: + cls_scores (Sequence[Tensor]): Box scores for each scale level, + each is a 4D-tensor, the channel number is + num_priors * num_classes. + bbox_preds (Sequence[Tensor]): Box energies / deltas for each scale + level, each is a 4D-tensor, the channel number is + num_priors * 4. + batch_gt_instances (list[:obj:`InstanceData`]): Batch of + gt_instance. It usually includes ``bboxes`` and ``labels`` + attributes. + batch_img_metas (list[dict]): Meta information of each image, e.g., + image size, scaling factor, etc. + batch_gt_instances_ignore (list[:obj:`InstanceData`], optional): + Batch of gt_instances_ignore. It includes ``bboxes`` attribute + data that is ignored during training and testing. + Defaults to None. + Returns: + dict[str, Tensor]: A dictionary of losses. + """ raise NotImplementedError('Not implemented yet!') diff --git a/mmyolo/models/necks/base_yolo_neck.py b/mmyolo/models/necks/base_yolo_neck.py index 066d4c3b..e488b9c2 100644 --- a/mmyolo/models/necks/base_yolo_neck.py +++ b/mmyolo/models/necks/base_yolo_neck.py @@ -74,26 +74,32 @@ class BaseYOLONeck(BaseModule, metaclass=ABCMeta): @abstractmethod def build_reduce_layer(self, idx: int): + """build reduce layer.""" pass @abstractmethod def build_upsample_layer(self, idx: int): + """build upsample layer.""" pass @abstractmethod def build_top_down_layer(self, idx: int): + """build top down layer.""" pass @abstractmethod def build_downsample_layer(self, idx: int): + """build downsample layer.""" pass @abstractmethod def build_bottom_up_layer(self, idx: int): + """build bottom up layer.""" pass @abstractmethod def build_out_layer(self, idx: int): + """build out layer.""" pass def _freeze_all(self): diff --git a/mmyolo/models/necks/yolov5_pafpn.py b/mmyolo/models/necks/yolov5_pafpn.py index 0abfd043..79942f5c 100644 --- a/mmyolo/models/necks/yolov5_pafpn.py +++ b/mmyolo/models/necks/yolov5_pafpn.py @@ -64,6 +64,14 @@ class YOLOv5PAFPN(BaseYOLONeck): m.reset_parameters() def build_reduce_layer(self, idx: int) -> nn.Module: + """build reduce layer. + + Args: + idx (int): layer idx. + + Returns: + nn.Module: The reduce layer. + """ if idx == 2: layer = ConvModule( make_divisible(self.in_channels[idx], self.widen_factor), @@ -77,9 +85,18 @@ class YOLOv5PAFPN(BaseYOLONeck): return layer def build_upsample_layer(self, *args, **kwargs) -> nn.Module: + """build upsample layer.""" return nn.Upsample(scale_factor=2, mode='nearest') def build_top_down_layer(self, idx: int): + """build top down layer. + + Args: + idx (int): layer idx. + + Returns: + nn.Module: The top down layer. + """ if idx == 1: return CSPLayer( make_divisible(self.in_channels[idx - 1] * 2, @@ -111,6 +128,14 @@ class YOLOv5PAFPN(BaseYOLONeck): act_cfg=self.act_cfg)) def build_downsample_layer(self, idx: int) -> nn.Module: + """build downsample layer. + + Args: + idx (int): layer idx. + + Returns: + nn.Module: The downsample layer. + """ return ConvModule( make_divisible(self.in_channels[idx], self.widen_factor), make_divisible(self.in_channels[idx], self.widen_factor), @@ -121,6 +146,14 @@ class YOLOv5PAFPN(BaseYOLONeck): act_cfg=self.act_cfg) def build_bottom_up_layer(self, idx: int) -> nn.Module: + """build bottom up layer. + + Args: + idx (int): layer idx. + + Returns: + nn.Module: The bottom up layer. + """ return CSPLayer( make_divisible(self.in_channels[idx] * 2, self.widen_factor), make_divisible(self.in_channels[idx + 1], self.widen_factor), @@ -130,4 +163,5 @@ class YOLOv5PAFPN(BaseYOLONeck): act_cfg=self.act_cfg) def build_out_layer(self, *args, **kwargs) -> nn.Module: + """build out layer.""" return nn.Identity() diff --git a/mmyolo/models/necks/yolov6_pafpn.py b/mmyolo/models/necks/yolov6_pafpn.py index fc145af1..54f22d0a 100644 --- a/mmyolo/models/necks/yolov6_pafpn.py +++ b/mmyolo/models/necks/yolov6_pafpn.py @@ -60,6 +60,14 @@ class YOLOv6RepPAFPN(BaseYOLONeck): init_cfg=init_cfg) def build_reduce_layer(self, idx: int) -> nn.Module: + """build reduce layer. + + Args: + idx (int): layer idx. + + Returns: + nn.Module: The reduce layer. + """ if idx == 2: layer = ConvModule( in_channels=make_divisible(self.in_channels[idx], @@ -76,6 +84,14 @@ class YOLOv6RepPAFPN(BaseYOLONeck): return layer def build_upsample_layer(self, idx: int) -> nn.Module: + """build upsample layer. + + Args: + idx (int): layer idx. + + Returns: + nn.Module: The upsample layer. + """ return nn.ConvTranspose2d( in_channels=make_divisible(self.out_channels[idx - 1], self.widen_factor), @@ -86,6 +102,14 @@ class YOLOv6RepPAFPN(BaseYOLONeck): bias=True) def build_top_down_layer(self, idx: int) -> nn.Module: + """build top down layer. + + Args: + idx (int): layer idx. + + Returns: + nn.Module: The top down layer. + """ layer0 = RepStageBlock( in_channels=make_divisible( self.out_channels[idx - 1] + self.in_channels[idx - 1], @@ -109,6 +133,14 @@ class YOLOv6RepPAFPN(BaseYOLONeck): return nn.Sequential(layer0, layer1) def build_downsample_layer(self, idx: int) -> nn.Module: + """build downsample layer. + + Args: + idx (int): layer idx. + + Returns: + nn.Module: The downsample layer. + """ return ConvModule( in_channels=make_divisible(self.out_channels[idx], self.widen_factor), @@ -121,6 +153,14 @@ class YOLOv6RepPAFPN(BaseYOLONeck): act_cfg=self.act_cfg) def build_bottom_up_layer(self, idx: int) -> nn.Module: + """build bottom up layer. + + Args: + idx (int): layer idx. + + Returns: + nn.Module: The bottom up layer. + """ return RepStageBlock( in_channels=make_divisible(self.out_channels[idx] * 2, self.widen_factor), @@ -130,6 +170,7 @@ class YOLOv6RepPAFPN(BaseYOLONeck): block=self.block) def build_out_layer(self, *args, **kwargs) -> nn.Module: + """build out layer.""" return nn.Identity() def init_weights(self): diff --git a/mmyolo/models/necks/yolox_pafpn.py b/mmyolo/models/necks/yolox_pafpn.py index 2936c73e..765a1ba4 100644 --- a/mmyolo/models/necks/yolox_pafpn.py +++ b/mmyolo/models/necks/yolox_pafpn.py @@ -57,6 +57,14 @@ class YOLOXPAFPN(BaseYOLONeck): init_cfg=init_cfg) def build_reduce_layer(self, idx: int) -> nn.Module: + """build reduce layer. + + Args: + idx (int): layer idx. + + Returns: + nn.Module: The reduce layer. + """ if idx == 2: layer = ConvModule( self.in_channels[idx], @@ -70,9 +78,18 @@ class YOLOXPAFPN(BaseYOLONeck): return layer def build_upsample_layer(self, *args, **kwargs) -> nn.Module: + """build upsample layer.""" return nn.Upsample(scale_factor=2, mode='nearest') def build_top_down_layer(self, idx: int) -> nn.Module: + """build top down layer. + + Args: + idx (int): layer idx. + + Returns: + nn.Module: The top down layer. + """ if idx == 1: return CSPLayer( self.in_channels[idx - 1] * 2, @@ -98,6 +115,14 @@ class YOLOXPAFPN(BaseYOLONeck): act_cfg=self.act_cfg)) def build_downsample_layer(self, idx: int) -> nn.Module: + """build downsample layer. + + Args: + idx (int): layer idx. + + Returns: + nn.Module: The downsample layer. + """ return ConvModule( self.in_channels[idx], self.in_channels[idx], @@ -108,6 +133,14 @@ class YOLOXPAFPN(BaseYOLONeck): act_cfg=self.act_cfg) def build_bottom_up_layer(self, idx: int) -> nn.Module: + """build bottom up layer. + + Args: + idx (int): layer idx. + + Returns: + nn.Module: The bottom up layer. + """ return CSPLayer( self.in_channels[idx] * 2, self.in_channels[idx + 1], @@ -117,6 +150,14 @@ class YOLOXPAFPN(BaseYOLONeck): act_cfg=self.act_cfg) def build_out_layer(self, idx: int) -> nn.Module: + """build out layer. + + Args: + idx (int): layer idx. + + Returns: + nn.Module: The out layer. + """ return ConvModule( self.in_channels[idx], self.out_channels, diff --git a/mmyolo/models/task_modules/coders/yolov5_bbox_coder.py b/mmyolo/models/task_modules/coders/yolov5_bbox_coder.py index 49d1c0d2..bab5f0e0 100644 --- a/mmyolo/models/task_modules/coders/yolov5_bbox_coder.py +++ b/mmyolo/models/task_modules/coders/yolov5_bbox_coder.py @@ -9,13 +9,20 @@ from mmyolo.registry import TASK_UTILS @TASK_UTILS.register_module() class YOLOv5BBoxCoder(BaseBBoxCoder): + """YOLOv5 BBox coder. + + This decoder decodes pred bboxes (delta_x, delta_x, w, h) to bboxes (tl_x, + tl_y, br_x, br_y). + """ def encode(self, **kwargs): + """Encode deltas between bboxes and ground truth boxes.""" pass def decode(self, priors: torch.Tensor, pred_bboxes: torch.Tensor, stride: Union[torch.Tensor, int]) -> torch.Tensor: - """Apply transformation `pred_bboxes` to `decoded_bboxes`. + """Decode regression results (delta_x, delta_x, w, h) to bboxes (tl_x, + tl_y, br_x, br_y). Args: priors (torch.Tensor): Basic boxes or points, e.g. anchors. diff --git a/mmyolo/models/task_modules/coders/yolox_bbox_coder.py b/mmyolo/models/task_modules/coders/yolox_bbox_coder.py index b4af953b..02c898d8 100644 --- a/mmyolo/models/task_modules/coders/yolox_bbox_coder.py +++ b/mmyolo/models/task_modules/coders/yolox_bbox_coder.py @@ -9,13 +9,20 @@ from mmyolo.registry import TASK_UTILS @TASK_UTILS.register_module() class YOLOXBBoxCoder(BaseBBoxCoder): + """YOLOX BBox coder. + + This decoder decodes pred bboxes (delta_x, delta_x, w, h) to bboxes (tl_x, + tl_y, br_x, br_y). + """ def encode(self, **kwargs): + """Encode deltas between bboxes and ground truth boxes.""" pass def decode(self, priors: torch.Tensor, pred_bboxes: torch.Tensor, stride: Union[torch.Tensor, int]) -> torch.Tensor: - """Apply transformation `pred_bboxes` to `decoded_bboxes`. + """Decode regression results (delta_x, delta_x, w, h) to bboxes (tl_x, + tl_y, br_x, br_y). Args: priors (torch.Tensor): Basic boxes or points, e.g. anchors. diff --git a/mmyolo/testing/__init__.py b/mmyolo/testing/__init__.py new file mode 100644 index 00000000..b6d7a010 --- /dev/null +++ b/mmyolo/testing/__init__.py @@ -0,0 +1,4 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from ._utils import get_detector_cfg + +__all__ = ['get_detector_cfg'] diff --git a/mmyolo/testing/_utils.py b/mmyolo/testing/_utils.py new file mode 100644 index 00000000..9ccf2fe0 --- /dev/null +++ b/mmyolo/testing/_utils.py @@ -0,0 +1,53 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import copy +from os.path import dirname, exists, join + +import numpy as np +from mmengine.config import Config + + +def _get_config_directory(): + """Find the predefined detector config directory.""" + try: + # Assume we are running in the source mmyolo repo + repo_dpath = dirname(dirname(dirname(__file__))) + except NameError: + # For IPython development when this __file__ is not defined + import mmyolo + repo_dpath = dirname(dirname(mmyolo.__file__)) + config_dpath = join(repo_dpath, 'configs') + if not exists(config_dpath): + raise Exception('Cannot find config path') + return config_dpath + + +def _get_config_module(fname): + """Load a configuration as a python module.""" + config_dpath = _get_config_directory() + config_fpath = join(config_dpath, fname) + config_mod = Config.fromfile(config_fpath) + return config_mod + + +def get_detector_cfg(fname): + """Grab configs necessary to create a detector. + + These are deep copied to allow for safe modification of parameters without + influencing other tests. + """ + config = _get_config_module(fname) + model = copy.deepcopy(config.model) + return model + + +def _rand_bboxes(rng, num_boxes, w, h): + """Randomly generate a specified number of bboxes.""" + cx, cy, bw, bh = rng.rand(num_boxes, 4).T + + tl_x = ((cx * w) - (w * bw / 2)).clip(0, w) + tl_y = ((cy * h) - (h * bh / 2)).clip(0, h) + br_x = ((cx * w) + (w * bw / 2)).clip(0, w) + br_y = ((cy * h) + (h * bh / 2)).clip(0, h) + + bboxes = np.vstack([tl_x, tl_y, br_x, br_y]).T + return bboxes diff --git a/tests/test_models/test_detectors/test_yolo_detector.py b/tests/test_models/test_detectors/test_yolo_detector.py index 4a7a3088..c89a1ded 100644 --- a/tests/test_models/test_detectors/test_yolo_detector.py +++ b/tests/test_models/test_detectors/test_yolo_detector.py @@ -5,10 +5,11 @@ from unittest import TestCase import torch from mmdet.structures import DetDataSample -from mmdet.testing import demo_mm_inputs, get_detector_cfg +from mmdet.testing import demo_mm_inputs from mmengine.logging import MessageHub from parameterized import parameterized +from mmyolo.testing import get_detector_cfg from mmyolo.utils import register_all_modules