pytorch / pytorch/vision

Ability to add extra custom roi-heads to generalizedRCNN models

Open
#2,229 4 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

module: models needs discussion topic: object detection
Dominant language
Python
Stars
17.9k
Forks
7.3k
Avg merge
1d 15h
Merged PRs (30d)
13

Description

🚀 Feature

This feature would allow adding custom RoI heads to any existing GeneralizedRCNN model.

Motivation

While the current functionalities of existing GeneralizedRCNN models are great, one might want to make extra predictions (like, e.g. the number of sides of an object) per detection, without having to alter the underlying torchvision code.

Pitch

The idea would be to be able to provide an extensions class (inheriting from RoIHeads), that preserves all the current behaviour but also exposes in the forward pass all the necessary elements (proposals, matched_idxs, labels) for an extra head to compute its own predictions.

Alternatives

EDIT

The alternative below was my initial idea. However, in the meantime I have a found a far simpler solution, which can be found on the first comment of this thread. As such, please feel free to ignore the alternative described here.

END OF EDIT

So far I have the current proposal:

  • Allow for passing to the constructor of GeneralizedRCNN models (faster_rcnn, mask_rcnn, keypoint_rcnn) a custom transform (similar to GeneralizedRCNNTransform, probably inheriting from it) that handles any necessary transformations to be done to the extra heads' targets (this custom transform might not even be necessary, depending on the extra heads).
  • Allow for passing to the constructor of GeneralizedRCNN models (faster_rcnn, mask_rcnn, keypoint_rcnn) an instance of a RoiHeadsExtensions, that would inherit from RoIHeads, preserving all its current behaviour but also exposing in the forward pass all the necessary elements (proposals, matched_idxs, labels) for an extra head to compute its own predictions.

Example (for faster_rcnn):

def __init__(self, backbone, num_classes=None,
                 # transform parameters
                 min_size=800, max_size=1333,
                 image_mean=None, image_std=None,

                 transform=None, # NEW

                 # RPN parameters
                 rpn_anchor_generator=None, rpn_head=None,
                 rpn_pre_nms_top_n_train=2000, rpn_pre_nms_top_n_test=1000,
                 rpn_post_nms_top_n_train=2000, rpn_post_nms_top_n_test=1000,
                 rpn_nms_thresh=0.7,
                 rpn_fg_iou_thresh=0.7, rpn_bg_iou_thresh=0.3,
                 rpn_batch_size_per_image=256, rpn_positive_fraction=0.5,
                 # Box parameters
                 box_roi_pool=None, box_head=None, box_predictor=None,
                 box_score_thresh=0.05, box_nms_thresh=0.5, box_detections_per_img=100,
                 box_fg_iou_thresh=0.5, box_bg_iou_thresh=0.5,
                 box_batch_size_per_image=512, box_positive_fraction=0.25,
                 bbox_reg_weights=None,

                 # RoI heads extensions # NEW
                 roi_heads_extensions=None):

Adapting existing code to allow for a custom transform would be as simple as changing, in faster_rcnn, from:

if image_mean is None:
    image_mean = [0.485, 0.456, 0.406]
if image_std is None:
    image_std = [0.229, 0.224, 0.225]
transform = GeneralizedRCNNTransform(min_size, max_size, image_mean, image_std)

super(FasterRCNN, self).__init__(backbone, rpn, roi_heads, transform)

to:

if transform is None:
            if image_mean is None:
                image_mean = [0.485, 0.456, 0.406]
            if image_std is None:
                image_std = [0.229, 0.224, 0.225]
            transform = GeneralizedRCNNTransform(min_size, max_size, image_mean, image_std)

super(FasterRCNN, self).__init__(backbone, rpn, roi_heads, transform)

As for creating the RoiHeadsExtensions class it would be necessary to change the RoiHeads class in the following way:

  • add, at construction time, an internal parameter that identifies if extensions exist, and by default is false.
self.has_extensions = False
  • change the return of forward from:
return result, losses

to

if self.has_extensions:
            return result, losses, (proposals, matched_idxs, labels)
return result, losses

thus allowing the extensions to access the proposals, matched_idxs and labels.

Now, the RoiHeadsExtensions class itself would simply hold the extra heads and mimick RoiHeads as much as possible. So far I had in mind something like:

class RoIHeadsExtensions(RoIHeads):
    # Note that depending on your extensions, you might have to create your own GeneralizedRCNNTransform.

    def __init__(self, extensions):
        # type: (List[CustomRoIHead])
        self.extensions = extensions
        super(RoIHeads, self).__init__()


    def add_base(self, roi_heads):
        # type: (RoIHeads)

        self.has_extensions = True

        self.box_similarity   = roi_heads.box_similarity # ISSUE EDIT -> 'roi_heads.box_ops.box_iou' was wrong!
        self.proposal_matcher = roi_heads.proposal_matcher
        self.fg_bg_sampler    = roi_heads.fg_bg_sampler
        self.box_coder        = roi_heads.box_coder

        self.box_roi_pool  = roi_heads.box_roi_pool
        self.box_head      = roi_heads.box_head
        self.box_predictor = roi_heads.box_predictor

        self.score_thresh       = roi_heads.score_thresh
        self.nms_thresh         = roi_heads.nms_thresh
        self.detections_per_img = roi_heads.detections_per_img

        has_mask = roi_heads.has_mask()
        self.mask_roi_pool  = roi_heads.mask_roi_pool if has_mask else None
        self.mask_head      = roi_heads.mask_head if has_mask else None
        self.mask_predictor = roi_heads.mask_predictor if has_mask else None

        has_keypoint = roi_heads.has_keypoint()
        self.keypoint_roi_pool  = roi_heads.keypoint_roi_pool if has_keypoint else None
        self.keypoint_head      = roi_heads.keypoint_head if has_keypoint else None
        self.keypoint_predictor = roi_heads.keypoint_predictor if has_keypoint else None


    def forward(self, features, proposals, image_shapes, targets=None):
        # type: (Dict[str, Tensor], List[Tensor], List[Tuple[int, int]], Optional[List[Dict[str, Tensor]]])
        """
        Arguments:
            features (List[Tensor])
            proposals (List[Tensor[N, 4]])
            image_shapes (List[Tuple[H, W]])
            targets (List[Dict])
        """
        result, losses, values_for_extension = super(RoIHeadsExtensions, self).forward(features, proposals, image_shapes, targets)

        for extension in self.extensions:
            extension.forward(result, losses, features, image_shapes, targets, values_for_extension) # ISSUE EDIT -> was missing image_shapes!

        return result, losses

Which would get updated in fasterrcnn by simply adding

if roi_heads_extensions:
    roi_heads_extensions.add_base(roi_heads)
    roi_heads = roi_heads_extensions

to the end of

roi_heads = RoIHeads(
    # Box
    box_roi_pool, box_head, box_predictor,
    box_fg_iou_thresh, box_bg_iou_thresh,
    box_batch_size_per_image, box_positive_fraction,
    bbox_reg_weights,
    box_score_thresh, box_nms_thresh, box_detections_per_img)

yielding:

roi_heads = RoIHeads(
    # Box
    box_roi_pool, box_head, box_predictor,
    box_fg_iou_thresh, box_bg_iou_thresh,
    box_batch_size_per_image, box_positive_fraction,
    bbox_reg_weights,
    box_score_thresh, box_nms_thresh, box_detections_per_img)
if roi_heads_extensions:
    roi_heads_extensions.add_base(roi_heads)
    roi_heads = roi_heads_extensions

As far as I see, this would preserve the existing behaviour of all 3 models and would require minimal changes to mask_rcnn and keypoint_rcnn (just its own parameters and the call to super(), i.e. faster_rcnn), some also small, albeit larger, changes to faster_rcnn (that still preserve its current behaviour) and some more significant changes to roi_heads.py, that nonetheless still preserve its current behaviour.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start by reading torchvision/models/detection/roi_heads.py and the Faster R-CNN, Mask R-CNN, and Keypoint R-CNN entry points mentioned in the issue. Trace how RoI heads are constructed and how their forward results and losses are consumed. Done means custom RoI-head extensions can receive the listed detection data without changing existing model behavior, with coverage for the affected models.

Written by the indexing model from the issue text.

Assessment

Tech stack
python
Domain
computer-vision
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
25/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.