0

I'm trying to detect when a zone has some "holes" and where they are located from a source image. Source images can be random and have more than 1 hole.

enter image description here

I would like to find the red circle zones with my code but I don't where to start from.

I don't see any function to help me with that. drawContours won't be helpful there I think (already has been usefull to get those "zones")

Does someone see a way to do that in Python ? (perhaps not using openCV, i'm open to any free library)

Thanks !

Damien F
  • 415
  • 1
  • 5
  • 8
  • (1) locate "endpoints". (2) for an endpoint, calculate nearest other end point, and consider if that's near enough or not. -- (1) is the trickier part. one approach is skeletonization and then traversing the skeleton and establishing the graph it represents. – Christoph Rackwitz Dec 13 '21 at 23:37

2 Answers2

2

One possible method involves extracting the biggest contour (this is the largest "path" in your image), the filtering criteria could also involve directly getting the outermost contour. Then, check for the discontinuous points, in other words, the endpoints of the path. You are essentially asking for a lot of code. Luckily, I've developed a similar algorithm. There are two steps:

  1. Find the biggest contour/blob/path
  2. Find the path’s endpoints.

Let's see the code:

# Imports
import cv2
import numpy as np

fileName = "shape01.png"
path = "D://opencvImages//"

# Reading an image in default mode:
inputImage = cv2.imread(path + fileName)
# Prepare a deep copy of the input for results:
inputImageCopy = inputImage.copy()

# Grayscale conversion:
grayscaleImage = cv2.cvtColor(inputImage, cv2.COLOR_BGR2GRAY)

# Threshold via Otsu:
_, binaryImage = cv2.threshold(grayscaleImage, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)

# Find the biggest blob:
binaryImage = findBiggestBlob(binaryImage)

The first steps involves computing a binary image of the input. Then, extract the biggest contour from there. I have implemented a function called findBiggestBlob, which just computes the biggest blob on the image using its area. It returns an image of the biggest blob isolated; this comes from a C++ implementation I wrote of the same idea.

This is the binary image:

This is the biggest contour:

Now, what follows is getting the skeleton of the binary image. The skeleton is a version of the binary image where lines have been normalized to have a width of 1 pixel. This is useful because we can then convolve the image with a 3 x 3 kernel and look for specific pixel patterns - those that identify an end-point. Let's compute the skeleton using OpenCV's extended image processing module:

# Compute the skeleton:
skeleton = cv2.ximgproc.thinning(binaryImage, None, 1)

This is the skeleton:

The contour has now 1 px of width, so we can detect the endpoints. The main idea of this approach is that the convolution yields a very specific value where patterns of black and white pixels are found in the input image. The value we are looking for is 110, but we need to perform some operations before the actual convolution. Refer to the original post for details. These are the operations:

# Threshold the image so that white pixels get a value of 0 and
# black pixels a value of 10:
_, binaryImage = cv2.threshold(skeleton, 128, 10, cv2.THRESH_BINARY)

# Set the end-points kernel:
h = np.array([[1, 1, 1],
              [1, 10, 1],
              [1, 1, 1]])

# Convolve the image with the kernel:
imgFiltered = cv2.filter2D(binaryImage, -1, h)

# Extract only the end-points pixels, those with
# an intensity value of 110:
endPointsMask = np.where(imgFiltered == 110, 255, 0)

# The above operation converted the image to 32-bit float,
# convert back to 8-bit uint
endPointsMask = endPointsMask.astype(np.uint8)

If we imshow the endPointsMask, we would get something like this:

In the above image, you can faintly see the location of the identified end-points. Let's get the coordinates of these white pixels:

# Get the coordinates of the end-points:
(Y, X) = np.where(endPointsMask == 255)

Finally, loop through these points and draw some circles located at the endpoints:

# Draw the end-points:
for i in range(len(X)):
    # Get coordinates:
    x = X[i]
    y = Y[i]
    # Set circle color:
    color = (0, 0, 255)
    # Draw Circle
    cv2.circle(inputImageCopy, (x, y), 3, color, -1)

    cv2.imshow("Points", inputImageCopy)
    cv2.waitKey(0)

For shape one, this is the final result:

This is the findBiggestBlob function:

def findBiggestBlob(inputImage):
    # Store a copy of the input image:
    biggestBlob = inputImage.copy()
    # Set initial values for the
    # largest contour:
    largestArea = 0
    largestContourIndex = 0

    # Find the contours on the binary image:
    contours, hierarchy = cv2.findContours(inputImage, cv2.RETR_CCOMP, cv2.CHAIN_APPROX_SIMPLE)

    # Get the largest contour in the contours list:
    for i, cc in enumerate(contours):
        # Find the area of the contour:
        area = cv2.contourArea(cc)
        # Store the index of the largest contour:
        if area > largestArea:
            largestArea = area
            largestContourIndex = i

    # Once we get the biggest blob, paint it black:
    tempMat = inputImage.copy()
    cv2.drawContours(tempMat, contours, largestContourIndex, (0, 0, 0), -1, 8, hierarchy)
    # Erase smaller blobs:
    biggestBlob = biggestBlob - tempMat

    return biggestBlob
stateMachine
  • 5,227
  • 4
  • 13
  • 29
0

Thanks, for your first answer so great ! So, i managed to do it on simple forms like posted above. Now I'm starting the second step : Real Life.

What I want to do is from that : enter image description here

Isolate the minimap and find the output. Some filtering with :

def highlightWalls(image):
img_hsv = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)

hsv_color3 = np.asarray([18, 98, 80])   
hsv_color4 = np.asarray([28, 125, 137])   
hsv_color5 = np.asarray([9, 0, 57])   
hsv_color6 = np.asarray([71, 29, 134])   
kernel = np.ones((3, 3))

mask1 = cv2.inRange(img_hsv, hsv_color3,hsv_color4)
mask2 = cv2.inRange(img_hsv, hsv_color5,hsv_color6)
mask = cv2.bitwise_xor(mask1, mask2)
blur = cv2.bilateralFilter(image,9,75,75)
image1= cv2.bitwise_and(blur,blur,mask=mask)
cv2.imshow("iamge1", image1)

finalMinimap = cv2.Canny(image1, 150, 200, None, 3)
img_dilate = cv2.dilate(finalMinimap, kernel, iterations=3)
cv2.imshow("walls", img_dilate)

return img_dilate

gives : enter image description here

And Skeleton gives now :

enter image description here

Great job by now ! But I'm missing a last step : Remove all those little "branches" of the skeleton. If not : enter image description here

Any idea from a boss there ? P.S. : If you have some ideas to better filter my first image let me know of course !

Damien F
  • 415
  • 1
  • 5
  • 8