Пример #1
0
def test_boundbox_rescale(boxes):

    scale = (3, 4)
    scales = (4, 3) * 4

    boundbox_array = BoundBoxArray.from_boxes(boxes)

    scaled_boxes = [tuple(x / s for x, s in zip(box, scales)) for box in boxes]
    scaled_boundbox_array = BoundBoxArray.from_boxes(scaled_boxes)

    assert scaled_boundbox_array.equals(boundbox_array.rescale(scale))
Пример #2
0
def test_boundbox_creation(boundboxes, centerboxes, boxes, classnames):

    from_boundboxes = BoundBoxArray.from_boundboxes(boundboxes, classnames)

    assert from_boundboxes.shape == (2, 8)
    assert (from_boundboxes.index == classnames).all()

    from_centerboxes = BoundBoxArray.from_centerboxes(centerboxes, classnames)

    assert from_boundboxes.equals(from_centerboxes)

    from_boxes = BoundBoxArray.from_boxes(boxes, classnames)

    assert from_boundboxes.equals(from_boxes)
    assert from_centerboxes.equals(from_boxes)
Пример #3
0
    def plot_matching_bboxes(self,
                             save_path,
                             default_boxes,
                             threshold,
                             class_mapping,
                             colormap,
                             background_class,
                             filename=None):
        """Plot and save image with matching bounding boxes"""

        filename = filename or self.filename
        if not filename:
            raise ValueError("`filename` should be specified either on image"
                             " creation or when calling this function.")

        normalized = self if self._bboxes_normalized else self.normalize_bboxes(
        )

        labels, offsets = normalized.labels_and_offsets(
            default_boxes, threshold, class_mapping)

        reverse_mapping = reverse_dict(class_mapping)
        reverse_mapping[background_class] = "background"
        classnames = [reverse_mapping[label] for label in labels]

        # recover default boxes
        height, width = self.size
        scale = (1 / height, 1 / width)
        recovered = default_boxes.rescale(scale)

        matched = BoundBoxArray.from_boxes(recovered.as_matrix(), classnames)
        matched = matched[matched.classnames != "background"]

        plot_with_bboxes(normalized.image, matched, colormap, save_path,
                         filename)
Пример #4
0
def test_non_maximum_supression():

    default_boxes = get_default_boxes([(9, 9, 32), (3, 3, 32)],
                                      [(1, 1 / 3, 3), (1, 1 / 3, 3)])
    n_boxes = len(default_boxes)

    print(default_boxes)

    class_mapping = dict(cat=1, dog=2, cow=3, table=4)

    confidences = np.array([(1, 0, 0, 0)] * n_boxes, dtype=np.float32)
    # default box #6 should be associated with class `dog`
    confidences[5] = (0.0, 0.05, 0.9, 0.05)
    # default box #100 should be associated with class `cow`
    confidences[100] = (0.0, 0.05, 0.0, 0.95)
    # default box #256 should be associated with class `cat`
    # and box #253 and #259 should NOT since they
    # have lower confidence and these boxes have big overlap
    confidences[253] = (0.0, 0.75, 0, 0.25)
    confidences[256] = (0.0, 0.9, 0.1, 0.0)
    confidences[259] = (0.0, 0.8, 0.1, 0.1)
    # default box #200 should be associated with class `cat`
    confidences[200] = (0.0, 0.70, 0, 0.30)

    offsets = np.zeros((n_boxes, 4), dtype=np.float32)
    offsets[100] = (0.5, 0, np.log(0.5), np.log(0.1))

    image = np.zeros((300, 300, 3))
    nms_threshold = 0.5
    filename = "foo"
    max_boxes = 10

    annotated_image = non_maximum_supression(confidences,
                                             offsets,
                                             default_boxes,
                                             class_mapping,
                                             image,
                                             nms_threshold,
                                             filename,
                                             max_boxes,
                                             clip=False)

    bboxes = annotated_image.bboxes

    assert isinstance(annotated_image, AnnotatedImage)

    print(bboxes.boundboxes)
    assert len(bboxes) == 4
    assert set(bboxes.classnames) == {"cat", "dog", "cow"}
    assert np.allclose(bboxes.loc["dog"], default_boxes.iloc[5])
    assert np.allclose(bboxes.loc["cat"], default_boxes.iloc[[256, 200]])

    cow_box = default_boxes.centerboxes.iloc[100]
    cow_box.x_center += 0.5 * cow_box.width
    cow_box.width *= 0.5
    cow_box.height *= 0.1
    cow_box = BoundBoxArray.from_centerboxes([cow_box.as_matrix()])
    assert np.allclose(bboxes.loc["cow"], cow_box)

    assert annotated_image.filename == filename
Пример #5
0
def test_offsets():

    original = BoundBoxArray.from_boundboxes([(0, 0, 0.5, 0.5),
                                              (0.3, 0.4, 0.6, 0.9)])
    default_boxes = BoundBoxArray.from_boundboxes([(0, 0, 0.5, 0.4),
                                                   (0.2, 0.4, 0.6, 0.9)])

    # get offsets
    offsets = calculate_offsets(default_boxes, original)
    offsets = BoundBoxArray.from_centerboxes(offsets)

    # apply them to get original bboxes
    recovered = apply_offsets(default_boxes, offsets)
    recovered = BoundBoxArray.from_centerboxes(recovered)

    assert np.allclose(original, recovered)
Пример #6
0
def test_iou(non_overlapping_boundboxes, full_and_quarter_boundboxes):
    """Test IOU calculation"""

    first, second = non_overlapping_boundboxes
    first = BoundBoxArray.from_boundboxes(first)
    second = BoundBoxArray.from_boundboxes(second)

    assert np.allclose(first.iou(second), np.zeros((2, 2)))
    assert np.allclose(second.iou(first), np.zeros((2, 2)))
    assert np.allclose(first.iou(first), np.eye(2))
    assert np.allclose(second.iou(second), np.eye(2))

    full, quarter = full_and_quarter_boundboxes
    full = BoundBoxArray.from_boundboxes(full)
    quarter = BoundBoxArray.from_boundboxes(quarter)

    assert np.allclose(full.iou(quarter), np.array(0.25))
    assert np.allclose(quarter.iou(full), np.array(0.25))
Пример #7
0
def test_boundbox_clipping(boxes):

    boundbox_array = BoundBoxArray.from_boxes(boxes)

    clipped = boundbox_array.clip((40, 50), (40, 50))
    assert (clipped.x_min >= 40).all()
    assert (clipped.y_min >= 40).all()
    assert (clipped.x_max <= 50).all()
    assert (clipped.y_max <= 50).all()
Пример #8
0
def test_random_hflip():

    bboxes = BoundBoxArray.from_boundboxes([(100, 100, 200, 200)],
                                           classnames=["cat"])
    image = AnnotatedImage(np.ones((300, 300, 3)), bboxes)
    image = image.normalize_bboxes()

    flipped_image = image.random_hflip(probability=1.0)
    # check that centered bbox wasn't flipped
    assert np.allclose(flipped_image.bboxes.boundboxes.as_matrix(),
                       image.bboxes.boundboxes.as_matrix())
    # but image was
    assert np.array_equal(np.fliplr(image.image), flipped_image.image)

    bboxes = BoundBoxArray.from_boundboxes([(0, 0, 150, 300)],
                                           classnames=["cat"])
    image = AnnotatedImage(np.ones((300, 300, 3)), bboxes)
    image = image.normalize_bboxes()

    flipped_image = image.random_hflip(probability=1.0)
    assert np.allclose(flipped_image.bboxes.boundboxes.as_matrix(),
                       np.array([0.5, 0, 1, 1]))
    assert np.array_equal(np.fliplr(image.image), flipped_image.image)
Пример #9
0
def parse_annotation(annotation):
    """Parses PascalVOC-like .xml annotation. BoundBoxArray is returned"""

    root = xml_parser.parse(annotation).getroot()

    boxes = list()
    classnames = list()

    for obj in root.findall('object'):
        # xmin, ymin, xmax, ymax
        boxes.append([int(coord.text) for coord in obj.find('bndbox')])
        classnames.append(obj.find('name').text)

    return BoundBoxArray.from_boundboxes(boxes, classnames)
Пример #10
0
def crop(image, selection):
    """Crops selection (which is a 4-length tuple
    with normalized (to (0, 1) interval)
    (x_min, y_min, x_max, y_max) coordinates) from image"""

    x_min, y_min, x_max, y_max = selection
    height, width = image.size

    x_min_int = int(x_min * width)
    y_min_int = int(y_min * height)
    x_max_int = int(x_max * width)
    y_max_int = int(y_max * height)

    cropped_image = image.image[y_min_int:y_max_int, x_min_int:x_max_int]
    cropped_image = imresize(cropped_image, image.size)

    scale_x = (x_max - x_min)
    scale_y = (y_max - y_min)
    normalized_bboxes = image.normalize_bboxes().bboxes
    # discard all bboxes that lie outside the crop
    valid_bboxes = (
        (normalized_bboxes.centerboxes.x_center > x_min) &
        (normalized_bboxes.centerboxes.x_center < x_max) &
        (normalized_bboxes.centerboxes.y_center < y_max) &
        (normalized_bboxes.centerboxes.y_center > y_min)
    )

    # return original image if there are no bboxes in the selection
    if not valid_bboxes.any():
        return image

    normalized_bboxes = normalized_bboxes[valid_bboxes]
    cropped_bboxes = normalized_bboxes.clip(
        vertical_clip_value=(y_min, y_max),
        horizontal_clip_value=(x_min, x_max)
    )
    shift = [x_min, y_min, x_min, y_min]
    shifted_bboxes = cropped_bboxes.boundboxes.as_matrix() - shift
    shifted_bboxes = BoundBoxArray.from_boundboxes(
        shifted_bboxes,
        classnames=cropped_bboxes.classnames
    )
    resized_bboxes = shifted_bboxes.rescale((scale_y, scale_x)).clip()
    
    cropped = image.__class__(cropped_image,
                              resized_bboxes,
                              filename=image.filename,
                              bboxes_normalized=True)
    
    return cropped
Пример #11
0
def test_labels_and_offsets():

    default_boxes = BoundBoxArray.from_boundboxes([(0, 0, 0.5, 0.5),
                                                   (0, 0, 1.0, 1.0),
                                                   (0.45, 0.45, 0.9, 0.9)])

    bboxes = BoundBoxArray.from_boundboxes([(0, 0, 150, 150), (0, 0, 120, 120),
                                            (150, 150, 300, 300)],
                                           classnames=["cat", "pig", "dog"])
    class_mapping = dict(cat=1, pig=2, dog=3)
    threshold = 0.5

    image = AnnotatedImage(np.ones((300, 300, 3)), bboxes)
    image = image.normalize_bboxes()

    labels, offsets = image.labels_and_offsets(default_boxes, threshold,
                                               class_mapping)

    # cat matched to first, dog matched to third
    # pig wasn't matched since cat has higher IOU
    assert (labels == [1, 0, 3]).all()
    # cat matched perfectly, second default box
    # wasn't matched
    assert (offsets[[0, 1]] == [0, 0, 0, 0]).all()
Пример #12
0
def test_random_crop():

    bboxes = BoundBoxArray.from_boundboxes([(0, 0, 150, 150), (0, 0, 120, 120),
                                            (150, 150, 300, 300)],
                                           classnames=["cat", "pig", "dog"])
    image = AnnotatedImage(np.ones((300, 300, 3)), bboxes)
    image = image.normalize_bboxes()

    # perform 20 random crops
    for _ in range(20):
        cropped_image = image.random_crop(probability=0.9)
        assert cropped_image.size == (300, 300)
        assert (cropped_image.bboxes.x_min >= 0).all()
        assert (cropped_image.bboxes.y_min >= 0).all()
        assert (cropped_image.bboxes.x_max <= 1).all()
        assert (cropped_image.bboxes.y_max <= 1).all()
Пример #13
0
def hflip(image):
    """Flips an instance of AnnotatedImage"""

    flipped_image = np.fliplr(image.image)
    normalized_image = image.normalize_bboxes()

    bboxes = normalized_image.bboxes.boundboxes
    flipped_bboxes = (1 - bboxes.x_max, 1 - bboxes.y_max,
                      1 - bboxes.x_min, 1 - bboxes.y_min)
    flipped_bboxes = np.array(flipped_bboxes, dtype=np.float32).T
    flipped_bboxes = BoundBoxArray.from_boundboxes(
        flipped_bboxes,
        classnames=bboxes.classnames
    )
    flipped = image.__class__(flipped_image,
                              flipped_bboxes,
                              filename=image.filename,
                              bboxes_normalized=True)
    return flipped
Пример #14
0
def get_default_boxes(out_shapes, box_ratios):
    """Returns BoundBoxArray of default boxes
    
    Args:
        out_shapes: a list of tuples with output shapes
        box_ration: a list of box aspect ratios
    
    Returns:
    """

    default_boxes = []
    n_outs = len(out_shapes)
    scales = (box_scale(n_out + 1, n_outs) for n_out in range(n_outs))

    layer_params = zip(out_shapes, scales, box_ratios)
    for out_shape, scale, layer_box_ratios in layer_params:
        height, width = height_and_width(out_shape)
        layer_boxes = [[[
            get_default_box(i, j, scale, box_ratio, width, height)
            for box_ratio in layer_box_ratios
        ] for i in range(height)] for j in range(width)]
        default_boxes.append(layer_boxes)

    return BoundBoxArray.from_centerboxes(flatten_list(default_boxes)).clip()