I am currently trying to understand the method of generating anchor boxes for object detection. I am looking at a code where the author has done this task in a very flexible way. But I am having problems understanding some part of it.
As every part of code is highly dependent each other, misunderstanding a small part leads to confusion and then I have to start from beginning. Please help...
This is the function used by the author in his code:
def generate_anchor_boxes_for_layer(self, feature_map_size, aspect_ratios, this_scale, next_scale, this_steps=None, this_offsets=None, diagnostics=False): ''' Computes an array of the spatial positions and sizes of the anchor boxes for one predictor layer of size `feature_map_size == [feature_map_height, feature_map_width]`. Arguments: feature_map_size (tuple): A list or tuple `[feature_map_height, feature_map_width]` with the spatial dimensions of the feature map for which to generate the anchor boxes. aspect_ratios (list): A list of floats, the aspect ratios for which anchor boxes are to be generated. All list elements must be unique. this_scale (float): A float in [0, 1], the scaling factor for the size of the generate anchor boxes as a fraction of the shorter side of the input image. next_scale (float): A float in [0, 1], the next larger scaling factor. Only relevant if `self.two_boxes_for_ar1 == True`. diagnostics (bool, optional): If true, the following additional outputs will be returned: 1) A list of the center point `x` and `y` coordinates for each spatial location. 2) A list containing `(width, height)` for each box aspect ratio. 3) A tuple containing `(step_height, step_width)` 4) A tuple containing `(offset_height, offset_width)` This information can be useful to understand in just a few numbers what the generated grid of anchor boxes actually looks like, i.e. how large the different boxes are and how dense their spatial distribution is, in order to determine whether the box grid covers the input images appropriately and whether the box sizes are appropriate to fit the sizes of the objects to be detected. Returns: A 4D Numpy tensor of shape `(feature_map_height, feature_map_width, n_boxes_per_cell, 4)` where the last dimension contains `(xmin, xmax, ymin, ymax)` for each anchor box in each cell of the feature map. ''' # Compute box width and height for each aspect ratio. # The shorter side of the image will be used to compute `w` and `h` using `scale` and `aspect_ratios`. size = min(self.img_height, self.img_width) # Compute the box widths and and heights for all aspect ratios wh_list =  for ar in aspect_ratios: if (ar == 1): # Compute the regular anchor box for aspect ratio 1. box_height = box_width = this_scale * size wh_list.append((box_width, box_height)) if self.two_boxes_for_ar1: # Compute one slightly larger version using the geometric mean of this scale value and the next. box_height = box_width = np.sqrt(this_scale * next_scale) * size wh_list.append((box_width, box_height)) else: box_width = this_scale * size * np.sqrt(ar) box_height = this_scale * size / np.sqrt(ar) wh_list.append((box_width, box_height)) wh_list = np.array(wh_list) n_boxes = len(wh_list) # Compute the grid of box center points. They are identical for all aspect ratios. # Compute the step sizes, i.e. how far apart the anchor box center points will be vertically and horizontally. if (this_steps is None): step_height = self.img_height / feature_map_size step_width = self.img_width / feature_map_size else: if isinstance(this_steps, (list, tuple)) and (len(this_steps) == 2): step_height = this_steps step_width = this_steps elif isinstance(this_steps, (int, float)): step_height = this_steps step_width = this_steps # Compute the offsets, i.e. at what pixel values the first anchor box center point will be from the top and from the left of the image. if (this_offsets is None): offset_height = 0.5 offset_width = 0.5 else: if isinstance(this_offsets, (list, tuple)) and (len(this_offsets) == 2): offset_height = this_offsets offset_width = this_offsets elif isinstance(this_offsets, (int, float)): offset_height = this_offsets offset_width = this_offsets # Now that we have the offsets and step sizes, compute the grid of anchor box center points. cy = np.linspace(offset_height * step_height, (offset_height + feature_map_size - 1) * step_height, feature_map_size) cx = np.linspace(offset_width * step_width, (offset_width + feature_map_size - 1) * step_width, feature_map_size) cx_grid, cy_grid = np.meshgrid(cx, cy) cx_grid = np.expand_dims(cx_grid, -1) # This is necessary for np.tile() to do what we want further down cy_grid = np.expand_dims(cy_grid, -1) # This is necessary for np.tile() to do what we want further down # Create a 4D tensor template of shape `(feature_map_height, feature_map_width, n_boxes, 4)` # where the last dimension will contain `(cx, cy, w, h)` boxes_tensor = np.zeros((feature_map_size, feature_map_size, n_boxes, 4)) boxes_tensor[:, :, :, 0] = np.tile(cx_grid, (1, 1, n_boxes)) # Set cx boxes_tensor[:, :, :, 1] = np.tile(cy_grid, (1, 1, n_boxes)) # Set cy boxes_tensor[:, :, :, 2] = wh_list[:, 0] # Set w boxes_tensor[:, :, :, 3] = wh_list[:, 1] # Set h # Convert `(cx, cy, w, h)` to `(xmin, ymin, xmax, ymax)` boxes_tensor = convert_coordinates(boxes_tensor, start_index=0, conversion='centroids2corners') # If `clip_boxes` is enabled, clip the coordinates to lie within the image boundaries if self.clip_boxes: x_coords = boxes_tensor[:,:,:,[0, 2]] x_coords[x_coords >= self.img_width] = self.img_width - 1 x_coords[x_coords < 0] = 0 boxes_tensor[:,:,:,[0, 2]] = x_coords y_coords = boxes_tensor[:,:,:,[1, 3]] y_coords[y_coords >= self.img_height] = self.img_height - 1 y_coords[y_coords < 0] = 0 boxes_tensor[:,:,:,[1, 3]] = y_coords # `normalize_coords` is enabled, normalize the coordinates to be within [0,1] if self.normalize_coords: boxes_tensor[:, :, :, [0, 2]] /= self.img_width boxes_tensor[:, :, :, [1, 3]] /= self.img_height # TODO: Implement box limiting directly for `(cx, cy, w, h)` so that we don't have to unnecessarily convert back and forth. if self.coords == 'centroids': # Convert `(xmin, ymin, xmax, ymax)` back to `(cx, cy, w, h)`. boxes_tensor = convert_coordinates(boxes_tensor, start_index=0, conversion='corners2centroids', border_pixels='half') elif self.coords == 'minmax': # Convert `(xmin, ymin, xmax, ymax)` to `(xmin, xmax, ymin, ymax). boxes_tensor = convert_coordinates(boxes_tensor, start_index=0, conversion='corners2minmax', border_pixels='half') if diagnostics: return boxes_tensor, (cy, cx), wh_list, (step_height, step_width), (offset_height, offset_width) else: return boxes_tensor
Here, I can't understand the terms this_steps and this_offsets. I tried understanding them in the way it was described in comments but then I could not be able to understand the rest of the code from how the center x and y are calculated and how the boxes are generated from it.
Please help.. Thank you..
Edit: I also didn't get why the author normalized the anchor box size using image width and image height. I mean we perform normalization in such cases in order to make our anchor boxes independent of different size of images. Then, why did he use image width and height to normalize the anchor box size?