<p>ROIPooling layer is typically used for object detection networks such as <a href="https://arxiv.org/abs/1311.2524" rel="nofollow noreferrer">R-CNN</a> and its variants (<a href="https://arxiv.org/abs/1504.08083" rel="nofollow noreferrer">Fast R-CNN</a> and <a href="https://arxiv.org/abs/1506.01497" rel="nofollow noreferrer">Faster R-CNN</a>). The essential part of all these architectures is a component (neural or classical CV) that generates region proposals. These region proposals are basically ROIs that need to be fed into the ROIPooling layer. The output of ROIPooling layer is going to be a batch of tensors, where each tensor represents one cropped area of an image. Each of these tensors are processed independently for classification. For example, in R-CNN, these tensors are crops of the image in RGB, which are then run through a classification network. In Fast R-CNN and Faster R-CNN, tensors are features out of a convolutional network, for example ResNet34.</p>

<p>In your example, whether through a classic computer vision algorithm (as in R-CNN and Fast R-CNN) or using a Region Proposal Network (as in Faster R-CNN), you need to generate some ROIs that are <em>candidates</em> for containing object of interest. Once you have these ROIs for each image in one mini-batch, you then need to combine them into one NDArray of <code>[[batch_index, x1, y1, x2, y2]]</code>. What this dimensioning means is that you can basically have as many ROIs as you want, and for each ROI, you must specify which image in the batch to crop (hence the <code>batch_index</code>) and what coordinates to crop it at (hence the <code>(x1, y1)</code> for top-left-corner and <code>(x2,y2)</code> for bottom-right-corner coordinates).</p>

<p>So based on the above, if you're implementing something similar to R-CNN, you would be passing your images directly into the RoiPooling layer:</p>

<pre><code>class ClassifyObjects(gluon.HybridBlock):
    def __init__(self, num_classes, pooled_size):
        super(ClassifyObjects, self).__init__()
        self.classifier = gluon.model_zoo.vision.resnet34_v2(classes=num_classes)
        self.pooled_size = pooled_size

    def hybrid_forward(self, F, imgs, rois):
        return self.classifier(
            F.ROIPooling(
                imgs, rois, pooled_size=self.pooled_size, spatial_scale=1.0))


# num_classes are 10 categories plus 1 class for "no-object-in-this-box" category
net = ClassifyObjects(num_classes=11, pooled_size=(64, 64))
# Initialize parameters and overload pre-trained weights
net.collect_params().initialize()
pretrained_net = gluon.model_zoo.vision.resnet34_v2(pretrained=True)
net.classifier.features = pretrained_net.features
</code></pre>

<p>Now if we send dummy data through the network, you can see that if roi array contains 4 rois, the output is going to contain 4 classification results:</p>

<pre><code># Dummy forward pass through the network
imgs = x = nd.random.uniform(shape=(2, 3, 128, 128))  # shape is (batch_size, channels, height, width)
rois = nd.array([[0, 10, 10, 100, 100], [0, 20, 20, 120, 120],
                 [1, 15, 15, 110, 110], [1, 25, 25, 128, 128]])
out = net(imgs, rois)
print(out.shape)
</code></pre>

<p>Outputs:</p>

<pre><code>(4, 11)
</code></pre>

<p>If you want to, however, use ROIPooling with similar to Fast R-CNN or Faster R-CNN model, you need access to the features of the network before they are average pooled. These features are then ROIPooled before being passed up to classification. Here an example where the features are from the pre-trained network, the ROIPooling's <code>pooled_size</code> is 4x4, and a simple GlobalAveragePooling followed by a Dense layer is used for classification after ROIPooling. Note that because the image is max-pooled by a factor of 32 through the ResNet network, <code>spatial_scale</code> is set to <code>1.0/32</code> to let the ROIPooling layer automatically compensate the rois for that.</p>

<pre><code>def GetResnetFeatures(resnet):
    resnet.features._children.pop()  # Pop Flatten layer
    resnet.features._children.pop()  # Pop GlobalAveragePooling layer
    return resnet.features


class ClassifyObjects(gluon.HybridBlock):
    def __init__(self, num_classes, pooled_size):
        super(ClassifyObjects, self).__init__()
        # Add a placeholder for features block
        self.features = gluon.nn.HybridSequential()
        # Add a classifier block
        self.classifier = gluon.nn.HybridSequential()
        self.classifier.add(gluon.nn.GlobalAvgPool2D())
        self.classifier.add(gluon.nn.Flatten())
        self.classifier.add(gluon.nn.Dense(num_classes))
        self.pooled_size = pooled_size

    def hybrid_forward(self, F, imgs, rois):
        features = self.features(imgs)
        return self.classifier(
            F.ROIPooling(
                features, rois, pooled_size=self.pooled_size, spatial_scale=1.0/32))


# num_classes are 10 categories plus 1 class for "no-object-in-this-box" category
net = ClassifyObjects(num_classes=11, pooled_size=(4, 4))
# Initialize parameters and overload pre-trained weights
net.collect_params().initialize()
net.features = GetResnetFeatures(gluon.model_zoo.vision.resnet34_v2(pretrained=True))
</code></pre>

<p>Now if we send dummy data through the network, you can see that if roi array contains 4 rois, the output is going to contain 4 classification results:</p>

<pre><code># Dummy forward pass through the network
# shape of each image is (batch_size, channels, height, width)
imgs = x = nd.random.uniform(shape=(2, 3, 128, 128))
# rois is the output of region proposal module of your architecture
# Each ROI entry contains [batch_index, x1, y1, x2, y2]
rois = nd.array([[0, 10, 10, 100, 100], [0, 20, 20, 120, 120],
                 [1, 15, 15, 110, 110], [1, 25, 25, 128, 128]])
out = net(imgs, rois)
print(out.shape)
</code></pre>

<p>Outputs:</p>

<pre><code>(4, 11)
</code></pre>
Using ROIPooling layer with a pretrained ResNet34 model in MxNet-Gluon
<p>Assume  I have a Resnet34 pretained model in MXNet and I want to add to it the premade ROIPooling Layer included in the API:</p>

<p><a href="https://mxnet.incubator.apache.org/api/python/ndarray/ndarray.html#mxnet.ndarray.ROIPooling" rel="nofollow noreferrer">https://mxnet.incubator.apache.org/api/python/ndarray/ndarray.html#mxnet.ndarray.ROIPooling</a></p>

<p>If the code for initializing Resnet is the following, how can I add ROIPooling at the last layer of the Resnet features before the classifier?</p>

<p>Actually, how can I utilize the ROIPooling function in my model in general?</p>

<p>How can I incorporate multiple different ROIs in the ROIpooling layer? How should they be stored? <strong>How should the data iterator be changed in order to give me the Batch index required by the ROIPooling function ?</strong></p>

<p>Let us assume that I use this along with the VOC 2012 Dataset for the task of action recognition</p>

<pre><code>batch_size = 40
num_classes = 11
init_lr = 0.001
step_epochs = [2]

train_iter, val_iter, num_samples = get_iterators(batch_size,num_classes)
resnet34 = vision.resnet34_v2(pretrained=True, ctx=ctx)

net = vision.resnet34_v2(classes=num_classes)

class ROIPOOLING(gluon.HybridBlock):
    def __init__(self):
        super(ROIPOOLING, self).__init__()

    def hybrid_forward(self, F, x):
        #print(x)
        a = mx.nd.array([[0, 0, 0, 7, 7]]).tile((40,1))
        return F.ROIPooling(x, a, (2,2), 1.0)

net_cl = nn.HybridSequential(prefix='resnetv20')
with net_cl.name_scope():
    for l in xrange(4):
        net_cl.add(resnet34.classifier._children[l])
    net_cl.add(nn.Dense(num_classes,  in_units=resnet34.classifier._children[-1]._in_units))

net.classifier = net_cl
net.classifier[-1].collect_params().initialize(mx.init.Xavier(rnd_type='gaussian', factor_type="in", magnitude=2), ctx=ctx)

net.features = resnet34.features
net.features._children.append(ROIPOOLING())

net.collect_params().reset_ctx(ctx)
</code></pre>