| import torch |
| import torch.nn as nn |
|
|
| class RoIPool(nn.Module): |
| def __init__(self, output_size, pooling_mode="loop"): |
| super().__init__() |
| self.output_size = output_size |
| self.pooling_mode = pooling_mode |
| self.adaptive_pool = nn.AdaptiveMaxPool2d(output_size) |
|
|
| def forward(self, feature_maps, batch_proposals, batch_img_height, batch_img_width): |
| channels = feature_maps.shape[1] |
| feat_height, feat_width = feature_maps.shape[2], feature_maps.shape[3] |
| out_h, out_w = self.output_size |
|
|
| stride_x = batch_img_width // feat_width |
| stride_y = batch_img_height // feat_height |
|
|
| pooled_batch = [] |
| for feature_map, proposals in zip(feature_maps, batch_proposals): |
| if proposals.shape[0] == 0: |
| |
| pooled_batch.append(feature_map.new_empty((0, channels, out_h, out_w))) |
| continue |
|
|
| projected = self._project_to_feature_map(proposals, feat_height, feat_width, stride_x, stride_y) |
|
|
| pooled_proposals = [] |
| for proposal_coords in projected: |
| roi_feature_map = self._select_roi_feature_map(feature_map, proposal_coords) |
| |
| if self.pooling_mode == "adaptive": |
| pooled_proposals.append(self._max_pool_roi_adaptive(roi_feature_map)) |
| else: |
| pooled_proposals.append(self._max_pool_roi(roi_feature_map)) |
|
|
| pooled_batch.append(torch.stack(pooled_proposals, dim=0)) |
|
|
| return pooled_batch |
|
|
| def _project_to_feature_map(self, proposals, feat_height, feat_width, stride_x, stride_y): |
| x1 = proposals[:, 0] |
| y1 = proposals[:, 1] |
| x2 = proposals[:, 2] |
| y2 = proposals[:, 3] |
|
|
| fx1 = torch.round(x1 / stride_x) |
| fy1 = torch.round(y1 / stride_y) |
| fx2 = torch.round(x2 / stride_x) |
| fy2 = torch.round(y2 / stride_y) |
|
|
| |
| fx1 = fx1.long() |
| fy1 = fy1.long() |
| fx2 = fx2.long() |
| fy2 = fy2.long() |
|
|
| fx1 = torch.clamp(fx1, 0, feat_width - 1) |
| fx2 = torch.clamp(fx2, 0, feat_width - 1) |
| fy1 = torch.clamp(fy1, 0, feat_height - 1) |
| fy2 = torch.clamp(fy2, 0, feat_height - 1) |
|
|
| return torch.stack([fx1, fy1, fx2, fy2], dim=-1) |
|
|
| def _select_roi_feature_map(self, feature_map, proposal_coords): |
| fx1, fy1, fx2, fy2 = proposal_coords |
|
|
| return feature_map[:, fy1:fy2 + 1, fx1:fx2 + 1] |
|
|
| def _max_pool_roi(self, roi_feature_map): |
| channels, roi_h, roi_w = roi_feature_map.shape |
| out_h, out_w = self.output_size |
| device = roi_feature_map.device |
|
|
| |
| |
| |
| h_idx = torch.arange(out_h, device=device) |
| h_starts = torch.clamp(torch.floor(h_idx * roi_h / out_h).long(), 0, roi_h) |
| h_ends = torch.clamp(torch.ceil((h_idx + 1) * roi_h / out_h).long(), 0, roi_h) |
|
|
| w_idx = torch.arange(out_w, device=device) |
| w_starts = torch.clamp(torch.floor(w_idx * roi_w / out_w).long(), 0, roi_w) |
| w_ends = torch.clamp(torch.ceil((w_idx + 1) * roi_w / out_w).long(), 0, roi_w) |
|
|
| pooled = roi_feature_map.new_empty((channels, out_h, out_w)) |
| for ph in range(out_h): |
| for pw in range(out_w): |
| bin_region = roi_feature_map[:, h_starts[ph]:h_ends[ph], w_starts[pw]:w_ends[pw]] |
| pooled[:, ph, pw] = bin_region.amax(dim=(1, 2)) |
|
|
| return pooled |
|
|
| def _max_pool_roi_adaptive(self, roi_feature_map): |
| |
| pooled = self.adaptive_pool(roi_feature_map.unsqueeze(0)) |
| return pooled.squeeze(0) |
|
|