Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 654582a

Browse files
committedApr 29, 2018
fix: Treat bounding box border pixels correctly
It is strongly recommended to pull this fix if you're on one of the recent commits. A recent commit made a subtle adjustment to how border pixels of bounding boxes are treated by default. This change had an unexpected negative impact on the training performance. This has now been fixed and the training performance is back to normal.
1 parent 2ad4eb9 commit 654582a

7 files changed

+131
-82
lines changed
 

‎bounding_box_utils/bounding_box_utils.py

+42-20
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
from __future__ import division
2323
import numpy as np
2424

25-
def convert_coordinates(tensor, start_index, conversion):
25+
def convert_coordinates(tensor, start_index, conversion, border_pixels='half'):
2626
'''
2727
Convert coordinates for axis-aligned 2D boxes between two coordinate formats.
2828
@@ -39,19 +39,31 @@ def convert_coordinates(tensor, start_index, conversion):
3939
conversion (str, optional): The conversion direction. Can be 'minmax2centroids',
4040
'centroids2minmax', 'corners2centroids', 'centroids2corners', 'minmax2corners',
4141
or 'corners2minmax'.
42+
border_pixels (str, optional): How to treat the border pixels of the bounding boxes.
43+
Can be 'include', 'exclude', or 'half'. If 'include', the border pixels belong
44+
to the boxes. If 'exclude', the border pixels do not belong to the boxes.
45+
If 'half', then one of each of the two horizontal and vertical borders belong
46+
to the boxex, but not the other.
4247
4348
Returns:
4449
A Numpy nD array, a copy of the input tensor with the converted coordinates
4550
in place of the original coordinates and the unaltered elements of the original
4651
tensor elsewhere.
4752
'''
53+
if border_pixels == 'half':
54+
d = 0
55+
elif border_pixels == 'include':
56+
d = 1
57+
elif border_pixels == 'exclude':
58+
d = -1
59+
4860
ind = start_index
4961
tensor1 = np.copy(tensor).astype(np.float)
5062
if conversion == 'minmax2centroids':
5163
tensor1[..., ind] = (tensor[..., ind] + tensor[..., ind+1]) / 2.0 # Set cx
5264
tensor1[..., ind+1] = (tensor[..., ind+2] + tensor[..., ind+3]) / 2.0 # Set cy
53-
tensor1[..., ind+2] = tensor[..., ind+1] - tensor[..., ind] # Set w
54-
tensor1[..., ind+3] = tensor[..., ind+3] - tensor[..., ind+2] # Set h
65+
tensor1[..., ind+2] = tensor[..., ind+1] - tensor[..., ind] + d # Set w
66+
tensor1[..., ind+3] = tensor[..., ind+3] - tensor[..., ind+2] + d # Set h
5567
elif conversion == 'centroids2minmax':
5668
tensor1[..., ind] = tensor[..., ind] - tensor[..., ind+2] / 2.0 # Set xmin
5769
tensor1[..., ind+1] = tensor[..., ind] + tensor[..., ind+2] / 2.0 # Set xmax
@@ -60,8 +72,8 @@ def convert_coordinates(tensor, start_index, conversion):
6072
elif conversion == 'corners2centroids':
6173
tensor1[..., ind] = (tensor[..., ind] + tensor[..., ind+2]) / 2.0 # Set cx
6274
tensor1[..., ind+1] = (tensor[..., ind+1] + tensor[..., ind+3]) / 2.0 # Set cy
63-
tensor1[..., ind+2] = tensor[..., ind+2] - tensor[..., ind] # Set w
64-
tensor1[..., ind+3] = tensor[..., ind+3] - tensor[..., ind+1] # Set h
75+
tensor1[..., ind+2] = tensor[..., ind+2] - tensor[..., ind] + d # Set w
76+
tensor1[..., ind+3] = tensor[..., ind+3] - tensor[..., ind+1] + d # Set h
6577
elif conversion == 'centroids2corners':
6678
tensor1[..., ind] = tensor[..., ind] - tensor[..., ind+2] / 2.0 # Set xmin
6779
tensor1[..., ind+1] = tensor[..., ind+1] - tensor[..., ind+3] / 2.0 # Set ymin
@@ -105,7 +117,7 @@ def convert_coordinates2(tensor, start_index, conversion):
105117

106118
return tensor1
107119

108-
def intersection_area(boxes1, boxes2, coords='centroids', mode='outer_product', include_border_pixels=True):
120+
def intersection_area(boxes1, boxes2, coords='centroids', mode='outer_product', border_pixels='half'):
109121
'''
110122
Computes the intersection areas of two sets of axis-aligned 2D rectangular boxes.
111123
@@ -132,9 +144,11 @@ def intersection_area(boxes1, boxes2, coords='centroids', mode='outer_product',
132144
`n` boxes in `boxes2`. In 'element-wise' mode, returns a 1D array and the shapes of `boxes1` and `boxes2`
133145
must be boadcast-compatible. If both `boxes1` and `boxes2` have `m` boxes, then this returns an array of
134146
length `m` where the i-th position contains the intersection area of `boxes1[i]` with `boxes2[i]`.
135-
include_border_pixels (bool, optional): Whether the border pixels of the bounding boxes belong to them or not.
136-
For example, if a bounding box has an `xmax` pixel value of 367, this determines whether the pixels with
137-
x-value 367 belong to the bounding box or not.
147+
border_pixels (str, optional): How to treat the border pixels of the bounding boxes.
148+
Can be 'include', 'exclude', or 'half'. If 'include', the border pixels belong
149+
to the boxes. If 'exclude', the border pixels do not belong to the boxes.
150+
If 'half', then one of each of the two horizontal and vertical borders belong
151+
to the boxex, but not the other.
138152
139153
Returns:
140154
A 1D or 2D Numpy array (refer to the `mode` argument for details) of dtype float containing values with
@@ -174,9 +188,11 @@ def intersection_area(boxes1, boxes2, coords='centroids', mode='outer_product',
174188
ymin = 2
175189
ymax = 3
176190

177-
if include_border_pixels: # Whether to include or exclude the border pixels of the boxes.
191+
if border_pixels == 'half':
192+
d = 0
193+
elif border_pixels == 'include':
178194
d = 1 # If border pixels are supposed to belong to the bounding boxes, we have to add one pixel to any difference `xmax - xmin` or `ymax - ymin`.
179-
else:
195+
elif border_pixels == 'exclude':
180196
d = -1 # If border pixels are not supposed to belong to the bounding boxes, we have to subtract one pixel from any difference `xmax - xmin` or `ymax - ymin`.
181197

182198
# Compute the intersection areas.
@@ -208,7 +224,7 @@ def intersection_area(boxes1, boxes2, coords='centroids', mode='outer_product',
208224

209225
return side_lengths[:,0] * side_lengths[:,1]
210226

211-
def intersection_area_(boxes1, boxes2, coords='corners', mode='outer_product', include_border_pixels=True):
227+
def intersection_area_(boxes1, boxes2, coords='corners', mode='outer_product', border_pixels='half'):
212228
'''
213229
The same as 'intersection_area()' but for internal use, i.e. without all the safety checks.
214230
'''
@@ -228,9 +244,11 @@ def intersection_area_(boxes1, boxes2, coords='corners', mode='outer_product', i
228244
ymin = 2
229245
ymax = 3
230246

231-
if include_border_pixels: # Whether to include or exclude the border pixels of the boxes.
247+
if border_pixels == 'half':
248+
d = 0
249+
elif border_pixels == 'include':
232250
d = 1 # If border pixels are supposed to belong to the bounding boxes, we have to add one pixel to any difference `xmax - xmin` or `ymax - ymin`.
233-
else:
251+
elif border_pixels == 'exclude':
234252
d = -1 # If border pixels are not supposed to belong to the bounding boxes, we have to subtract one pixel from any difference `xmax - xmin` or `ymax - ymin`.
235253

236254
# Compute the intersection areas.
@@ -263,7 +281,7 @@ def intersection_area_(boxes1, boxes2, coords='corners', mode='outer_product', i
263281
return side_lengths[:,0] * side_lengths[:,1]
264282

265283

266-
def iou(boxes1, boxes2, coords='centroids', mode='outer_product', include_border_pixels=True):
284+
def iou(boxes1, boxes2, coords='centroids', mode='outer_product', border_pixels='half'):
267285
'''
268286
Computes the intersection-over-union similarity (also known as Jaccard similarity)
269287
of two sets of axis-aligned 2D rectangular boxes.
@@ -291,9 +309,11 @@ def iou(boxes1, boxes2, coords='centroids', mode='outer_product', include_border
291309
`n` boxes in `boxes2`. In 'element-wise' mode, returns a 1D array and the shapes of `boxes1` and `boxes2`
292310
must be boadcast-compatible. If both `boxes1` and `boxes2` have `m` boxes, then this returns an array of
293311
length `m` where the i-th position contains the IoU overlap of `boxes1[i]` with `boxes2[i]`.
294-
include_border_pixels (bool, optional): Whether the border pixels of the bounding boxes belong to them or not.
295-
For example, if a bounding box has an `xmax` pixel value of 367, this determines whether the pixels with
296-
x-value 367 belong to the bounding box or not.
312+
border_pixels (str, optional): How to treat the border pixels of the bounding boxes.
313+
Can be 'include', 'exclude', or 'half'. If 'include', the border pixels belong
314+
to the boxes. If 'exclude', the border pixels do not belong to the boxes.
315+
If 'half', then one of each of the two horizontal and vertical borders belong
316+
to the boxex, but not the other.
297317
298318
Returns:
299319
A 1D or 2D Numpy array (refer to the `mode` argument for details) of dtype float containing values in [0,1],
@@ -342,9 +362,11 @@ def iou(boxes1, boxes2, coords='centroids', mode='outer_product', include_border
342362
ymin = 2
343363
ymax = 3
344364

345-
if include_border_pixels: # Whether to include or exclude the border pixels of the boxes.
365+
if border_pixels == 'half':
366+
d = 0
367+
elif border_pixels == 'include':
346368
d = 1 # If border pixels are supposed to belong to the bounding boxes, we have to add one pixel to any difference `xmax - xmin` or `ymax - ymin`.
347-
else:
369+
elif border_pixels == 'exclude':
348370
d = -1 # If border pixels are not supposed to belong to the bounding boxes, we have to subtract one pixel from any difference `xmax - xmin` or `ymax - ymin`.
349371

350372
if mode == 'outer_product':

‎data_generator/data_augmentation_chain_original_ssd.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,8 @@ def __init__(self, labels_format={'class_id': 0, 'xmin': 1, 'ymin': 2, 'xmax': 3
8080
# meets the requirements defined by `bound_generator`.
8181
self.image_validator = ImageValidator(overlap_criterion='iou',
8282
n_boxes_min=1,
83-
labels_format=self.labels_format)
83+
labels_format=self.labels_format,
84+
border_pixels='half')
8485

8586
# Performs crops according to the parameters set in the objects above.
8687
# Runs until either a valid patch is found or the original input image

‎data_generator/object_detection_2d_image_boxes_validation_utils.py

+22-10
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ def __init__(self,
9090
overlap_bounds=(0.3, 1.0),
9191
min_area=16,
9292
labels_format={'class_id': 0, 'xmin': 1, 'ymin': 2, 'xmax': 3, 'ymax': 4},
93-
include_border_pixels=True):
93+
border_pixels='half'):
9494
'''
9595
Arguments:
9696
check_overlap (bool, optional): Whether or not to enforce the overlap requirements defined by
@@ -124,9 +124,11 @@ def __init__(self,
124124
labels_format (dict, optional): A dictionary that defines which index in the last axis of the labels
125125
of an image contains which bounding box coordinate. The dictionary maps at least the keywords
126126
'xmin', 'ymin', 'xmax', and 'ymax' to their respective indices within last axis of the labels array.
127-
include_border_pixels (bool, optional): Whether the border pixels of the bounding boxes belong to them or not.
128-
For example, if a bounding box has an `xmax` pixel value of 367, this determines whether the pixels with
129-
x-value 367 belong to the bounding box or not.
127+
border_pixels (str, optional): How to treat the border pixels of the bounding boxes.
128+
Can be 'include', 'exclude', or 'half'. If 'include', the border pixels belong
129+
to the boxes. If 'exclude', the border pixels do not belong to the boxes.
130+
If 'half', then one of each of the two horizontal and vertical borders belong
131+
to the boxex, but not the other.
130132
'''
131133
if not isinstance(overlap_bounds, (list, tuple, BoundGenerator)):
132134
raise ValueError("`overlap_bounds` must be either a 2-tuple of scalars or a `BoundGenerator` object.")
@@ -141,7 +143,7 @@ def __init__(self,
141143
self.check_min_area = check_min_area
142144
self.check_degenerate = check_degenerate
143145
self.labels_format = labels_format
144-
self.include_border_pixels = include_border_pixels
146+
self.border_pixels = border_pixels
145147

146148
def __call__(self,
147149
labels,
@@ -196,13 +198,15 @@ def __call__(self,
196198
# Compute the patch coordinates.
197199
image_coords = np.array([0, 0, image_width, image_height])
198200
# Compute the IoU between the patch and all of the ground truth boxes.
199-
image_boxes_iou = iou(image_coords, labels[:, [xmin, ymin, xmax, ymax]], coords='corners', mode='element-wise', include_border_pixels=self.include_border_pixels)
201+
image_boxes_iou = iou(image_coords, labels[:, [xmin, ymin, xmax, ymax]], coords='corners', mode='element-wise', border_pixels=self.border_pixels)
200202
requirements_met *= (image_boxes_iou > lower) * (image_boxes_iou <= upper)
201203

202204
elif self.overlap_criterion == 'area':
203-
if self.include_border_pixels: # Whether to include or exclude the border pixels of the boxes.
205+
if self.border_pixels == 'half':
206+
d = 0
207+
elif self.border_pixels == 'include':
204208
d = 1 # If border pixels are supposed to belong to the bounding boxes, we have to add one pixel to any difference `xmax - xmin` or `ymax - ymin`.
205-
else:
209+
elif self.border_pixels == 'exclude':
206210
d = -1 # If border pixels are not supposed to belong to the bounding boxes, we have to subtract one pixel from any difference `xmax - xmin` or `ymax - ymin`.
207211
# Compute the areas of the boxes.
208212
box_areas = (labels[:,xmax] - labels[:,xmin] + d) * (labels[:,ymax] - labels[:,ymin] + d)
@@ -238,7 +242,8 @@ def __init__(self,
238242
overlap_criterion='center_point',
239243
bounds=(0.3, 1.0),
240244
n_boxes_min=1,
241-
labels_format={'class_id': 0, 'xmin': 1, 'ymin': 2, 'xmax': 3, 'ymax': 4}):
245+
labels_format={'class_id': 0, 'xmin': 1, 'ymin': 2, 'xmax': 3, 'ymax': 4},
246+
border_pixels='half'):
242247
'''
243248
Arguments:
244249
overlap_criterion (str, optional): Can be either of 'center_point', 'iou', or 'area'. Determines
@@ -258,19 +263,26 @@ def __init__(self,
258263
labels_format (dict, optional): A dictionary that defines which index in the last axis of the labels
259264
of an image contains which bounding box coordinate. The dictionary maps at least the keywords
260265
'xmin', 'ymin', 'xmax', and 'ymax' to their respective indices within last axis of the labels array.
266+
border_pixels (str, optional): How to treat the border pixels of the bounding boxes.
267+
Can be 'include', 'exclude', or 'half'. If 'include', the border pixels belong
268+
to the boxes. If 'exclude', the border pixels do not belong to the boxes.
269+
If 'half', then one of each of the two horizontal and vertical borders belong
270+
to the boxex, but not the other.
261271
'''
262272
if not ((isinstance(n_boxes_min, int) and n_boxes_min > 0) or n_boxes_min == 'all'):
263273
raise ValueError("`n_boxes_min` must be a positive integer or 'all'.")
264274
self.overlap_criterion = overlap_criterion
265275
self.bounds = bounds
266276
self.n_boxes_min = n_boxes_min
267277
self.labels_format = labels_format
278+
self.border_pixels = border_pixels
268279
self.box_filter = BoxFilter(check_overlap=True,
269280
check_min_area=False,
270281
check_degenerate=False,
271282
overlap_criterion=self.overlap_criterion,
272283
overlap_bounds=self.bounds,
273-
labels_format=self.labels_format)
284+
labels_format=self.labels_format,
285+
border_pixels=self.border_pixels)
274286

275287
def __call__(self,
276288
labels,
There was a problem loading the remainder of the diff.

0 commit comments

Comments
 (0)
Please sign in to comment.