'Determine if a point is inside or outside of a shape with opencv
I have images with white background and simple shapes in them (each image has one shape). I want to determine if a certain point (x,y) is inside the shape or not. How can I do that with opencv?
Solution 1:[1]
Use pointPolygonTest
function. Here's tutorial.
Solution 2:[2]
To determine if a point is inside, outside, or on the edge of a shape you can check if the point is within a contour using cv2.pointPolygonTest()
. The function returns +1
, -1
, or 0
to indicate if a point is inside, outside, or on the contour, respectively. Assuming we already have the contour of the shape, we can simply pass the contour and the (x,y)
point to the function.
result = cv2.pointPolygonTest(contour, (x,y), False)
In the function, the third argument is measureDist
. If it is True
, it finds the shortest distance between a point in the image and a contour. If False
, it finds whether the point is inside, outside, or on the contour. Since we don't want to find the distance, we set the measureDist
argument to False
Here's an example that finds the square contour then checks if the points are within the contour
Test image
Image after finding contour and checking points
Results
point1: -1.0
point2: 1.0
point3: 0.0
Therefore point1 is outside, point2 is inside, and point3 is on the contour
import cv2
image = cv2.imread('1.png')
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
canny = cv2.Canny(gray, 120, 255, 1)
cnts = cv2.findContours(canny, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
point1 = (25, 50)
point2 = (200, 250)
point3 = (200, 350)
# Perform check if point is inside contour/shape
for c in cnts:
cv2.drawContours(image, [c], -1, (36, 255, 12), 2)
result1 = cv2.pointPolygonTest(c, point1, False)
result2 = cv2.pointPolygonTest(c, point2, False)
result3 = cv2.pointPolygonTest(c, point3, False)
# Draw points
cv2.circle(image, point1, 8, (100, 100, 255), -1)
cv2.putText(image, 'point1', (point1[0] -10, point1[1] -20), cv2.FONT_HERSHEY_SIMPLEX, 1.0, (0, 0, 0), lineType=cv2.LINE_AA)
cv2.circle(image, point2, 8, (200, 100, 55), -1)
cv2.putText(image, 'point2', (point2[0] -10, point2[1] -20), cv2.FONT_HERSHEY_SIMPLEX, 1.0, (0, 0, 0), lineType=cv2.LINE_AA)
cv2.circle(image, point3, 8, (150, 50, 155), -1)
cv2.putText(image, 'point3', (point3[0] -10, point3[1] -20), cv2.FONT_HERSHEY_SIMPLEX, 1.0, (0, 0, 0), lineType=cv2.LINE_AA)
print('point1:', result1)
print('point2:', result2)
print('point3:', result3)
cv2.imshow('image', image)
cv2.waitKey()
Solution 3:[3]
In my case I needed to select all points inside the contour. I have a scatter plot for x
and y
, which are lists of 2D Cartesian coordinates, of the same length
plt.scatter(x, y)
and a contour plot
cs = axes.contour(x[left:right], y[bottom:top], quantity(x,y), level = [value])
where quantity
is some other value, for example density, overlapped on the scatter plot.
It is important, not only to select the pixels inside the contour, but also to retrieve the coordinates of those points used in the scatter plot. If the contour is well defined and you really have a single closed line than you can access it in Matplotlib
with
cs.allsegs[0][0]
and this looks like:
[[array([[1933.769124 , 690.53960716],
[1933.904277 , 690.52641719],
[1934.03943 , 690.5241056 ],
...,
[1933.633971 , 690.56338213],
[1933.76492346, 690.540361 ],
[1933.769124 , 690.53960716]])]]
Now loop along all tuples in the contour and increment a counter
as soon as:
- a vertical tuple coordinate from the contour is greater than the one of the point being checked;
- a vertical tuple coordinate from the contour is smaller than the one of the point being checked;
- an horizontal tuple coordinate from the contour is greater than the one of the point being checked;
- an horizontal tuple coordinate from the contour is smaller than the one of the point being checked;
If the counter reaches 4, the point is inside, provided that the contour is closed. The definition makes use of a tolerance value tol
which should be smaller comparable with the distance between the points which make the contour:
def OnePointInsideContour(contourArray, PointTuple, tol):
L = len(contourArray);
y0 = PointTuple[0]; # horizontal
x0 = PointTuple[1]; # vertical
ret = [];
inside = False;
counter = 0
for k in range(L):
ycont = list(contourArray[k])[0];
xcont = list(contourArray[k])[1];
if ycont > y0 - tol and ycont < y0 + tol and xcont > x0:
p = (ycont, xcont);
counter += 1;
ret.append(p);
break
for k in range(L):
ycont = list(contourArray[k])[0];
xcont = list(contourArray[k])[1];
if ycont > y0 - tol and ycont < y0 + tol and xcont < x0:
p = (ycont, xcont);
counter += 1;
ret.append(p);
break
for k in range(L):
ycont = list(contourArray[k])[0];
xcont = list(contourArray[k])[1];
if xcont > x0 - tol and xcont < x0 + tol and ycont < y0:
p = (ycont, xcont);
counter += 1;
ret.append(p);
break
for k in range(L):
ycont = list(contourArray[k])[0];
xcont = list(contourArray[k])[1];
if xcont > x0 - tol and xcont < x0 + tol and ycont > y0:
p = (ycont, xcont);
counter += 1;
ret.append(p);
break
if counter == 4:
inside = True
return inside, ret
Example: point not inside (only 2 hits on the contour)
Example: point inside (4 hits on the contour)
Solution 4:[4]
if you want to access all the points inside the convex hull, you can do masking
I solve this by first painting my convex hull white colour with cv2.fillPoly() on a black frame
- First create black frame that follows your frame's shape
black_frame = np.zeros_like(your_frame).astype(np.uint8)
- Paint the convex hull with white
cv2.fillPoly(black_frame , [hull], (255, 255, 255))
- Create a mask by using numpy boolean indexing, it will produce a mask with True/False values inside, it will be True is the pixel value is white
mask = black_frame == 255
- You can access your pixel values by getting the product between your frame and mask, if False, value will
targetROI = your_frame * mask
- Access your pixels by using the mask.
black_frame = np.zeros_like(your_frame).astype(np.uint8)
cv2.fillPoly(black_frame , [hull], (255, 255, 255))
mask = black_frame == 255
targetROI = your_frame * mask
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | Pylyp Dukhov |
Solution 2 | |
Solution 3 | |
Solution 4 | yptheangel |