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
Result
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
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.
Starting from your 2nd provided image, here's my approach to solving this problem:
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.
Now to isolate the soil and the pot, we apply a scaling factor to obtain this
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
Soil ROI
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)
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
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)
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:
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()
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With