Determine if a point is inside or outside of a shape with opencv

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()

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.

enter image description here

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)

enter image description here

Example: point inside (4 hits on the contour)

enter image description here


Use pointPolygonTest function. Here's tutorial.

Tags:

Opencv