user1241241
user1241241

Reputation: 674

Optimize the cropping function

I'm using the following code to crop an image and retrieve a non-rectangular patch.

def crop_image(img,roi):
    height = img.shape[0]
    width = img.shape[1]

    mask = np.zeros((height, width), dtype=np.uint8)
    points = np.array([roi])
    cv2.fillPoly(mask, points, (255))

    res = cv2.bitwise_and(img, img, mask=mask)

    rect = cv2.boundingRect(points)  # returns (x,y,w,h) of the rect
    cropped = res[rect[1]: rect[1] + rect[3], rect[0]: rect[0] + rect[2]]

    return cropped, res

The roi is [(1053, 969), (1149, 1071), (883, 1075), (813, 983)]. The above code works however How do I optimize the speed of the code? It is too slow. Is there any other better way of cropping non-rectangular patches?

Upvotes: 1

Views: 520

Answers (2)

fmw42
fmw42

Reputation: 53081

Here is one way using Python/OpenCV and Numpy.

Input:

enter image description here

import cv2
import numpy as np

# read image
img = cv2.imread("efile.jpg")

points = np.array( [[ [693,67], [23,85], [62,924], [698,918] ]] )

# get bounding rectangle of points
x,y,w,h = cv2.boundingRect(points)
print(x,y,w,h)

# draw white filled polygon from points on black background as mask
mask = np.zeros_like(img)
cv2.fillPoly(mask, points, (255,255,255))

# fill background of image with black according to mask
masked = img.copy()
masked[mask==0] = 0

# crop to bounding rectangle
cropped = masked[y:y+h, x:x+w]

# write results
cv2.imwrite("efile_mask.jpg", mask)
cv2.imwrite("efile_masked.jpg", masked)
cv2.imwrite("efile_cropped.jpg", cropped)

# display it
cv2.imshow("efile_mask", mask)
cv2.imshow("efile_masked", masked)
cv2.imshow("efile_cropped", cropped)
cv2.waitKey(0)


Mask from provided points:

enter image description here

Image with background made black:

enter image description here

Cropped result:

enter image description here

Upvotes: 1

Baraa
Baraa

Reputation: 1526

I see two parts that could be optimized.

  1. Cropping the image to the bounding rectangle bounds could be applied as the first step. Benefit? you dramatically reduce the size of the images you are working with. You only have to translate the points of the roi by the x,y of the rect and you are good to go.
  2. At the bitwise_and operation, you are "anding" the image with itself and checking at each pixel whether it is allowed by the mask to output it. I guess this is where most time is spent. Instead, you can directly "and" with the mask and save your precious time (no extra mask checking step). Again, a minor tweak to be able to do so, the mask image should have exactly the same shape as the input image (including channels).

Edit: Modify code to support any number of channels in the input image

The code below does these two things:

def crop_image(img, roi):
height = img.shape[0]
width = img.shape[1]
channels = img.shape[2] if len(img.shape) > 2 else 1

points = np.array([roi])

rect = cv2.boundingRect(points)

mask_shape = (rect[3], rect[2]) if channels == 1 else (rect[3], rect[2], img.shape[2])

#Notice how the mask image size is now the size of the bounding rect
mask = np.zeros(mask_shape, dtype=np.uint8)

#tranlsate the points so that their origin is the bounding rect top left point
for p in points[0]:
    p[0] -= rect[0]
    p[1] -= rect[1]


mask_filling = tuple(255 for _ in range(channels))
cv2.fillPoly(mask, points, mask_filling)

cropped = img[rect[1]: rect[1] + rect[3], rect[0]: rect[0] + rect[2]]

res = cv2.bitwise_and(cropped, mask)

return cropped, res

Upvotes: 1

Related Questions