How to connect the ends of edges in order to close the holes between them?
Asked Answered
M

2

19

My task is to detect the cracks on the soil surface and calculate crack's total area. I used Canny edge detection for this purpose.

Input image

Input Image

Result

Here

My next step is to convert canny edges to contours since I want to filtrate the cracks using cv2.mean and calculate their area using cv2.contourArea functions. On this step, I faced the problem. When I used:

canny_cracks = cv2.Canny(gray, 100, 200)
contours, _ = cv2.findContours(canny_cracks, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)

It does not convert properly because of the holes the ends of edges. See problem here

here

My question is how can I connect the ends of edges in order to close the hole between them?

Note: I used contours detection without applying Canny edges. The problem is that contours detection gives a lot of noise and doesn't detect all cracks well. Or maybe I do not know how to find contours as canny edges do.

Macarthur answered 25/6, 2019 at 12:47 Comment(3)
You can use Morphological Transformations to connect the linesIvanivana
Since the cracks are darker than the soil, you can also look into Thresholding or color based separation, though it may not generalize well to other images or other types of soil.Ivanivana
Thank you for your response. I used color based separation, it gives a lot of noise that is not easy to deal with. Regarding morphological Transformation, do you mean dilate?Macarthur
I
3

You can use Morphological Close. This closes gaps between white pixels. If you input your Canny image in the script below, you can try for yourself.

Result:

enter image description here

Code:

    import cv2
    import numpy as np  

    # function that handles trackbar changes
    def doClose(val):
            # create a kernel based on trackbar input
            kernel = np.ones((val,val))
            # do a morphologic close
            res = cv2.morphologyEx(img,cv2.MORPH_CLOSE, kernel)
            # display result
            cv2.imshow("Result", res)

    #load image as grayscale
    img = cv2.imread("KbMHp.png",0)

    # create window and add trackbar
    cv2.namedWindow('Result')
    cv2.createTrackbar('KernelSize','Result',0,15,doClose)

    # display image
    cv2.imshow("Result", img)
    cv2.waitKey(0)
    cv2.destroyAllWindows()  
Ivanivana answered 25/6, 2019 at 22:17 Comment(0)
C
10

enter image description here

Starting from your 2nd provided image, here's my approach to solving this problem:

  • Gaussian blur image and convert to grayscale
  • Isolate soil from pot
    • Create circle mask of just the soil
    • Extract soil ROI
  • Perform morphological transformations to close holes
  • Find contours and filter by contour area
  • Sum area to obtain result

We begin by Gaussian blurring and converting the image to grayscale.

image = cv2.imread('5.png')
original = image.copy()

blur = cv2.GaussianBlur(image, (3,3), 0)
gray = cv2.cvtColor(blur, cv2.COLOR_BGR2GRAY)
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (5,5))

The goal is to isolate the soil edges from the pot edges. To do this, we find the outer circle of the pot using cv2.HoughCircles(), scale down the circle to grab the soil region, and create a mask using the shape of the original image.

circle_mask = np.zeros(original.shape, dtype=np.uint8) 
circles = cv2.HoughCircles(gray, cv2.HOUGH_GRADIENT, 1.5, 200) 

# Convert the (x, y) coordinates and radius of the circles to integers
circles = np.round(circles[0, :]).astype("int")
circle_ratio = 0.85

# Loop over the (x, y) coordinates and radius of the circles
for (x, y, r) in circles:
    # Draw the circle, create mask, and obtain soil ROI
    cv2.circle(image, (x, y), int(r * circle_ratio), (0, 255, 0), 2)
    cv2.circle(circle_mask, (x, y), int(r * circle_ratio), (255, 255, 255), -1)
    soil_ROI = cv2.bitwise_and(original, circle_mask)

We loop over coordinates to find the radius of the circle. From here we draw the largest outer circle.

enter image description here

Now to isolate the soil and the pot, we apply a scaling factor to obtain this

enter image description here

Next, we fill in the circle to obtain a mask and then apply that on the original image to obtain the soil ROI.

Soil mask

enter image description here

Soil ROI

enter image description here

Your question was

How can I connect the ends of edges in order to close the hole between them?

To do this, you can perform a morphological transformation using cv2.morphologyEx() to close holes which results in this

gray_soil_ROI = cv2.cvtColor(soil_ROI, cv2.COLOR_BGR2GRAY)
close = cv2.morphologyEx(gray_soil_ROI, cv2.MORPH_CLOSE, kernel)

enter image description here

Now we find contours using cv2.findContours() and filter using cv2.contourArea() with a minimum threshold area to remove small noise such as the rocks. You can adjust the minimum area to control filter strength.

cnts = cv2.findContours(close, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]

crack_area = 0
minumum_area = 25
for c in cnts:
    area = cv2.contourArea(c)
    if area > minumum_area:
        cv2.drawContours(original,[c], 0, (36,255,12), 2)
        crack_area += area

enter image description here

Finally, we sum the area which gives us the crack's total area

3483.5

import cv2
import numpy as np

image = cv2.imread('5.png')
original = image.copy()

blur = cv2.GaussianBlur(image, (3,3), 0)
gray = cv2.cvtColor(blur, cv2.COLOR_BGR2GRAY)
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (5,5))

circle_mask = np.zeros(original.shape, dtype=np.uint8) 
circles = cv2.HoughCircles(gray, cv2.HOUGH_GRADIENT, 1.5, 200) 

# Convert the (x, y) coordinates and radius of the circles to integers
circles = np.round(circles[0, :]).astype("int")
circle_ratio = 0.85

# Loop over the (x, y) coordinates and radius of the circles
for (x, y, r) in circles:
    # Draw the circle, create mask, and obtain soil ROI
    cv2.circle(image, (x, y), int(r * circle_ratio), (0, 255, 0), 2)
    cv2.circle(circle_mask, (x, y), int(r * circle_ratio), (255, 255, 255), -1)
    soil_ROI = cv2.bitwise_and(original, circle_mask)

gray_soil_ROI = cv2.cvtColor(soil_ROI, cv2.COLOR_BGR2GRAY)
close = cv2.morphologyEx(gray_soil_ROI, cv2.MORPH_CLOSE, kernel)

cnts = cv2.findContours(close, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]

crack_area = 0
minumum_area = 25
for c in cnts:
    area = cv2.contourArea(c)
    if area > minumum_area:
        cv2.drawContours(original,[c], 0, (36,255,12), 2)
        crack_area += area

print(crack_area)
cv2.imshow('close', close)
cv2.imshow('circle_mask', circle_mask)
cv2.imshow('soil_ROI', soil_ROI)
cv2.imshow('original', original)
cv2.waitKey(0)
Criticize answered 25/6, 2019 at 23:53 Comment(0)
I
3

You can use Morphological Close. This closes gaps between white pixels. If you input your Canny image in the script below, you can try for yourself.

Result:

enter image description here

Code:

    import cv2
    import numpy as np  

    # function that handles trackbar changes
    def doClose(val):
            # create a kernel based on trackbar input
            kernel = np.ones((val,val))
            # do a morphologic close
            res = cv2.morphologyEx(img,cv2.MORPH_CLOSE, kernel)
            # display result
            cv2.imshow("Result", res)

    #load image as grayscale
    img = cv2.imread("KbMHp.png",0)

    # create window and add trackbar
    cv2.namedWindow('Result')
    cv2.createTrackbar('KernelSize','Result',0,15,doClose)

    # display image
    cv2.imshow("Result", img)
    cv2.waitKey(0)
    cv2.destroyAllWindows()  
Ivanivana answered 25/6, 2019 at 22:17 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.