3

I have used opencv to create some contours, and I need to identify a specific point on a contour, which is usually the innermost point of a 'V' shape. In the attached image, the point I want to identify is shown by the green arrows.

Example

On the left is an easy case, where identification can be done (for example) by computing a convex hull of the contour, and then finding the point furthest from the hull.

However, on the right of the attached image is a much more difficult case, where instead of 1 contour, I get several, and the nice 'V' shape is not present, making it impossible to identify the innermost point of the 'V'. As shown by the red dotted line, one solution might be to extrapolate the higher contour until it intersects with the lower one. Does anyone know how I might go about this? Or have a better solution?

For the record I have tried:

  • dilation/erosion (works when multiple contours are close together, otherwise not)

  • hough transform p (tends to mislocate the target point)

Any pointers would be hugely appreciated.

Jeru Luke
  • 20,118
  • 13
  • 80
  • 87
ennjaycee
  • 31
  • 4

1 Answers1

1

This solution will work for the two images that you provided. This should also be a good solution for all other images that have a similar coloration and a 'v' shape (or at least a partial 'v' shape) that points to the right.

Let's take a look at the easier image first. I started by segmenting the image using color spaces.

# Convert frame to hsv color space
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
# Define range of pink color in HSV
(b,r,g,b1,r1,g1) = 0,0,0,110,255,255
lower = np.array([b,r,g])
upper = np.array([b1,r1,g1])
# Threshold the HSV image to get only pink colors
mask = cv2.inRange(hsv, lower, upper)

color spaces

Next, I found the mid_point where there was an equal amount of white above and below that row.

# Calculate the mid point
mid_point = 1
top, bottom = 0, 1
while top < bottom:
    top = sum(sum(mask[:mid_point, :]))
    bottom = sum(sum(mask[mid_point:, :]))
    mid_point += 1

Then, I floodfilled the image starting at the midpoint: bg = np.zeros((h+2, w+2), np.uint8)

kernel = np.ones((k_size, k_size),np.uint8)  
cv2.floodFill(mask, bg, (0, mid_point), 123)

floodfilled

Now that I have the floodfilled image, I know the point that I am looking for is the gray pixel that is the closest to the right side of the image.

# Find the gray pixel that is furthest to the right
idx = 0
while True:
    column = mask_temp[:,idx:idx+1]
    element_id, gray_px, found = 0, [], False
    for element in column:
        if element == 123:
            v_point = idx, element_id
            found = True
        element_id += 1
    # If no gray pixel is found, break out of the loop
    if not found: break
    idx += 1

The result:

result1

Now for the harder image. In the image on the right, the 'v' does not fully connect:

does not connect

To close the 'v', I iteratively dilated the mask checked if it connected:

# Flood fill and dilate loop
k_size, iters = 1, 1
while True:
    bg = np.zeros((h+2, w+2), np.uint8)
    mask_temp = mask.copy()    
    kernel = np.ones((k_size, k_size),np.uint8)    
    mask_temp = cv2.dilate(mask_temp,kernel,iterations = iters)
    cv2.floodFill(mask_temp, bg, (0, mid_point), 123)
    cv2.imshow('mask', mask_temp)
    cv2.waitKey()
    k_size += 1
    iters += 1
    # Break out of the loop of the right side of the image is black
    if mask_temp[h-1,w-1]==0 and mask_temp[1, w-1]==0: break

dilation

This is the resulting output:

output2

Stephen Meschke
  • 2,820
  • 1
  • 13
  • 25
  • Thank you very much! For the more difficult image, the effect of dilation is to push the target point slightly farther to the left than it should actually be, but I think I can easily compensate for this as a function of the number of dilation iterations. Thanks again for your hard work and excellent solution. – ennjaycee May 21 '19 at 10:02