Extract polygon of given coordinates from image using OpenCV

I have a set of points, for example:

     <data:polygon>
                            <data:point x="542" y="107"/>
                            <data:point x="562" y="102"/>
                            <data:point x="582" y="110"/>
                            <data:point x="598" y="142"/>
                            <data:point x="600" y="192"/>
                            <data:point x="601" y="225"/>
                            <data:point x="592" y="261"/>
                            <data:point x="572" y="263"/>
                            <data:point x="551" y="245"/>
                            <data:point x="526" y="220"/>
                            <data:point x="520" y="188"/>
                            <data:point x="518" y="152"/>
                            <data:point x="525" y="127"/>
                            <data:point x="542" y="107"/
 </data:polygon>

      

I want to draw a polygon defined by these points in the image and then extract it. How can I do this using OpenCV with python?

+3


source to share


1 answer


Use cv2.fillConvexPoly

so you can specify a 2D array of points and define a mask that fills the shape defined by these glasses should be white in the mask. Some fair warning should be made where the points defined in your polygon are convex (hence the name fillConvexPoly

).

We can then convert that to a Boolean mask and use it to index into your image to extract the pixels we want. The code below creates an array named mask

and it will contain the boolean mask of the pixels you want to keep from the image. In addition, the array out

will contain the required extracted submag, which was defined by the polygon. Note that the image is initialized completely dark and that the only pixels that need to be copied are the pixels defined by the polygon.

Assuming the actual image is named img

, and assuming that your points are x

and y

are the horizontal and vertical coordinates of the image, you can do something like this:

import numpy as np
import cv2

pts = np.array([[542, 107], [562, 102], [582, 110], [598, 142], [600, 192], [601, 225], [592, 261], [572, 263], [551, 245], [526, 220], [520, 188], [518, 152], [525, 127], [524, 107]], dtype=np.int32)

mask = np.zeros((img.shape[0], img.shape[1]))

cv2.fillConvexPoly(mask, pts, 1)
mask = mask.astype(np.bool)

out = np.zeros_like(img)
out[mask] = img[mask]

      

out

should be black except for the area to be copied. If you want to display this image, you can do something like:

cv2.imshow('Extracted Image', out)
cv2.waitKey(0)
cv2.destroyAllWindows()

      

This will display the extracted image from the polygon points and wait for the key you pressed. When you are finished viewing the image, you can press any key while the display window has focus.

If you want to save the image to a file, follow these steps:

cv2.imwrite('output.png', out)

      

This will save the image in a file named output.png

. I am specifying the PNG format because it is lossless.


As a simple test, let's define a white image 300 x 700

that is far beyond the largest coordinates in what you define. Select the area defined by this polygon and show what the output looks like.

img = 255*np.ones((300, 700, 3), dtype=np.uint8)

      

Using the above test image, we get the following image:

enter image description here

Edit

If you want to translate the extracted image so that it is in the middle and then put a square around the bounding box, a trick I can suggest is to use cv2.remap

to translate the image. When you're done, use a cv2.rectangle

square to draw.

The way it works cv2.remap

is for each pixel in the output you need to specify the spatial coordinate at which you want to access the pixel in the original image. Since you end up moving the output to the center of the image, you need to add an offset at each location x

and y

on the target image to get the original pixel.

To determine the correct offsets to move the image, simply calculate the center of gravity of the polygon, translate the polygon so that the centroid is at the origin, and then re-rotate it so that it is in the center of the image.

Using the variables listed above, you can find the centroid:



(meanx, meany) = pts.mean(axis=0)

      

Once you find the centroid, you take all the points and subtract that centroid, then add the corresponding coordinates to retransmit to the center of the image. The center of the image can be found by:

(cenx, ceny) = (img.shape[1]/2, img.shape[0]/2)

      

It is also important to convert the coordinates to an integer, as the pixel coordinates are:

(meanx, meany, cenx, ceny) = np.floor([meanx, meany, cenx, ceny]).astype(np.int32)

      

Now, to understand the offset, do it like we said before:

(offsetx, offsety) = (-meanx + cenx, -meany + ceny)

      

Now translate the image. You need to define a mapping for each pixel in the output image, where for each point (x,y)

in the target image you need to specify where to sample from the source. The computed offset translates each pixel from the source to its destination. Because we do the opposite , where for each destination pixel we find which source pixel to sample, we have to subtract the offset, not add. So first define a grid of (x,y)

points and then subtract the offset. Once you're done, translate the image:

(mx, my) = np.meshgrid(np.arange(img.shape[1]), np.arange(img.shape[0]))
ox = (mx - offsetx).astype(np.float32)
oy = (my - offsety).astype(np.float32)
out_translate = cv2.remap(out, ox, oy, cv2.INTER_LINEAR)

      

If we rendered out_translate

with the above example, this is what we get:

enter image description here


Cool! Now it's time to draw a rectangle over this image. All you have to do is figure out the top left and bottom right corners of the rectangle. This can be done by taking the top-left and bottom-right corners of the polygon and adding an offset to move those points to the center of the image:

topleft = pts.min(axis=0) + [offsetx, offsety]
bottomright = pts.max(axis=0) + [offsetx, offsety]
cv2.rectangle(out_translate, tuple(topleft), tuple(bottomright), color=(255,0,0))

      

If we show this image, we get:

enter image description here


The above code draws a rectangle around the centered image in blue. So the complete code, from the very beginning (extracting the pixel area) to the end (translating and drawing the rectangle):

# Import relevant modules
import numpy as np
import cv2

# Define points
pts = np.array([[542, 107], [562, 102], [582, 110], [598, 142], [600, 192], [601, 225], [592, 261], [572, 263], [551, 245], [526, 220], [520, 188], [518, 152], [525, 127], [524, 107]], dtype=np.int32)

### Define image here
img = 255*np.ones((300, 700, 3), dtype=np.uint8)

# Initialize mask
mask = np.zeros((img.shape[0], img.shape[1]))

# Create mask that defines the polygon of points
cv2.fillConvexPoly(mask, pts, 1)
mask = mask.astype(np.bool)

# Create output image (untranslated)
out = np.zeros_like(img)
out[mask] = img[mask]

# Find centroid of polygon
(meanx, meany) = pts.mean(axis=0)

# Find centre of image
(cenx, ceny) = (img.shape[1]/2, img.shape[0]/2)

# Make integer coordinates for each of the above
(meanx, meany, cenx, ceny) = np.floor([meanx, meany, cenx, ceny]).astype(np.int32)

# Calculate final offset to translate source pixels to centre of image
(offsetx, offsety) = (-meanx + cenx, -meany + ceny)

# Define remapping coordinates
(mx, my) = np.meshgrid(np.arange(img.shape[1]), np.arange(img.shape[0]))
ox = (mx - offsetx).astype(np.float32)
oy = (my - offsety).astype(np.float32)

# Translate the image to centre
out_translate = cv2.remap(out, ox, oy, cv2.INTER_LINEAR)

# Determine top left and bottom right of translated image
topleft = pts.min(axis=0) + [offsetx, offsety]
bottomright = pts.max(axis=0) + [offsetx, offsety]

# Draw rectangle
cv2.rectangle(out_translate, tuple(topleft), tuple(bottomright), color=(255,0,0))

# Show image, wait for user input, then save the image
cv2.imshow('Output Image', out_translate)
cv2.waitKey(0)
cv2.destroyAllWindows()
cv2.imwrite('output.png', out_translate)

      

+9


source







All Articles