예제 #1
0
 def test_counterclockwise_with_check(self):
     assert not is_clockwise([(0, 0), (50, 250), (100, 200), (100, 0)],
                             check_convexity=True)
     assert not is_clockwise([(0, 0), (50, 500), (150, 300), (150, 200),
                              (100, 0)],
                             check_convexity=True)
     assert not is_clockwise([(0, 0), (50, 110), (100, 0)],
                             check_convexity=True)
예제 #2
0
def crop_cv2(image, polygon, fillcolor=None, cut_out=False, clip=False):
    """
    Crop image according to given (convex) polygon.

    .. note::

       All edges of the polygon are included in the cropped image. This is not the same behaviour as
       :py:meth:`PIL.Image.Image.crop` when using a straight rectangular crop box because the ``right`` and ``bottom``
       coordinates are not included in the cropped image.

    :param numpy.ndarray image: the (opencv) image to crop
    :param list[tuple[int,int]] polygon: the list of points coordinates of the polygon to crop
    :param fillcolor: the color to use for filling area outside of polygon
    :param bool cut_out: if ``False`` (the default), the fillcolor is only used for filling areas outside of original
      image when doing crop. Il ``True``, then all areas outside of polygon are filled with fillcolor.
    :param bool clip: if ``False`` (the default), the cropped image will include the whole polygon, even if some of its
      coordinates are outside of the original image, which can lead to a large image with areas filled with fillcolor on
      edges of cropped image. If ``True``, the polygon is first clipped to the original image box, thus leading to the
      minimal cropped image containing all the *visible* parts of polygon in original image.
    :return: the cropped image
    :rtype: numpy.ndarray
    """
    im_height, im_width = image.shape[:2]
    if cut_out:
        image = image.copy()
        cut_out_cv2(image, polygon, fillcolor=fillcolor)

    flip = not is_clockwise(polygon, check_convexity=False)
    resize_params, rotate_params, crop_params = _get_transforms_params(polygon, im_width, im_height, clip=clip)
    center, angle = rotate_params
    if fillcolor is None:
        fillcolor = 0
    left_add, top_add, new_width, new_height = resize_params
    if left_add > 0 or top_add > 0 or new_width > im_width or new_height > im_height:
        new_image = np.empty((new_height, new_width, *image.shape[2:]), dtype=image.dtype)
        new_image[:, :] = fillcolor
        new_image[top_add:top_add+im_height, left_add:left_add+im_width] = image
        image = new_image

    if angle != 0:
        matrix = cv2.getRotationMatrix2D(center, angle, 1)
        image = cv2.warpAffine(
            image, matrix, (new_width, new_height), flags=cv2.INTER_CUBIC,
            borderMode=cv2.BORDER_CONSTANT, borderValue=fillcolor
        )
    left, top, right, bottom = crop_params
    # Add 1 to include right and bottom edges in the cropped image
    image = image[top:bottom + 1, left:right + 1]
    if flip:
        image = cv2.flip(cv2.rotate(image, cv2.ROTATE_90_COUNTERCLOCKWISE), 0)
    return image
예제 #3
0
def crop_pil(image, polygon, fillcolor=None, cut_out=False, clip=False):
    """
    Crop image according to given (convex) polygon.

    .. note::

       All edges of the polygon are included in the cropped image. This is not the same behaviour as
       :py:meth:`PIL.Image.Image.crop` when using a straight rectangular crop box because the ``right`` and ``bottom``
       coordinates are not included in the cropped image.

    :param PIL.Image.Image image: the (pillow) image to crop
    :param list[tuple[int,int]] polygon: the list of points coordinates of the polygon to crop
    :param fillcolor: the color to use for filling area outside of polygon
    :param bool cut_out: if ``False`` (the default), the fillcolor is only used for filling areas outside of original
      image when doing crop. Il ``True``, then all areas outside of polygon are filled with fillcolor.
    :param bool clip: if ``False`` (the default), the cropped image will include the whole polygon, even if some of its
      coordinates are outside of the original image, which can lead to a large image with areas filled with fillcolor on
      edges of cropped image. If ``True``, the polygon is first clipped to the original image box, thus leading to the
      minimal cropped image containing all the *visible* parts of polygon in original image.
    :return: the cropped image
    :rtype: PIL.Image.Image
    """
    im_width, im_height = image.size
    if cut_out:
        image = image.copy()
        cut_out_pil(image, polygon, fillcolor=fillcolor)

    flip = not is_clockwise(polygon, check_convexity=False)
    # using expand=True in PIL.Image.Image.rotate assumes that center is the center of image, so it does not give
    # correct result when center is near an image side. This is the reason why we manually apply a first transform to
    # resize image before rotation and crop.
    resize_params, rotate_params, crop_params = _get_transforms_params(polygon, im_width, im_height, clip=clip)
    _, angle = rotate_params
    left_add, top_add, new_width, new_height = resize_params
    if left_add > 0 or top_add > 0 or new_width > im_width or new_height > im_height:
        image = image.transform(
            size=(new_width, new_height), method=Image.AFFINE, data=(1, 0, -left_add, 0, 1, -top_add),
            fillcolor=fillcolor
        )
    left, top, right, bottom = crop_params
    if angle != 0:
        # recompute center because the center computed by minAreaRect seems not to be the exact center (as if right and
        # bottom edges of the rectangle are not included in the crop box). PIL.Image.rotate requires the exact center
        center = (left + (right + 1 - left) / 2, top + (bottom + 1 - top) / 2)
        image = image.rotate(angle, resample=Image.BICUBIC, expand=False, center=center, fillcolor=fillcolor)
    # Add 1 to include right and bottom edges in the cropped image
    image = image.crop((left, top, right + 1, bottom + 1))
    if flip:
        image = image.transpose(Image.ROTATE_270).transpose(Image.FLIP_LEFT_RIGHT)
    return image
예제 #4
0
 def test_flat_angle_with_sufficient_number_of_vertices(self):
     assert is_clockwise([(0, 0), (100, 0), (200, 0), (100, 50)],
                         check_convexity=True)
     assert not is_clockwise([(0, 0), (100, 50), (200, 0), (100, 0)],
                             check_convexity=True)
예제 #5
0
 def test_null_angle(self):
     with pytest.raises(ValueError):
         is_clockwise([(0, 0), (100, 0), (50, 0)])
     with pytest.raises(ValueError):
         is_clockwise([(0, 0), (0, 100), (0, 50)])
예제 #6
0
 def test_clockwise(self):
     assert is_clockwise([(0, 0), (100, 0), (100, 200), (50, 250)])
     assert is_clockwise([(0, 0), (100, 0), (150, 200), (150, 300),
                          (50, 500)])
     assert is_clockwise([(0, 0), (100, 0), (50, 110)])
예제 #7
0
 def test_not_convex_without_check(self):
     assert is_clockwise([(100, 100), (150, 100), (150, 150), (200, 150),
                          (200, 200), (100, 200)])
예제 #8
0
 def test_not_convex_with_check(self):
     with pytest.raises(ValueError):
         is_clockwise([(100, 100), (150, 100), (150, 150), (200, 150)],
                      check_convexity=True)
예제 #9
0
 def test_insufficient_number_of_distinct_vertices(self):
     with pytest.raises(ValueError):
         is_clockwise([(0, 0), (100, 100), (100, 100), (0, 0)])