I am trying to find the points representing the left and right edge of a road mask(binary image). I have tried using contour detection but it is giving contour of whole image(including upper and lower edge, have attached image of it).
What I want is 2 array, representing the points of left and right road edge. What i am getting is a long list(Contour) of points including the lower part of the road as well. I have tried filtering unnecessary points by removing those points from the array which are close to the boundary and now i am left with an array containing the points from only left and right road edge but now it is difficult to tell which point belongs to which road edge(left or right).
Apart from this, i have used sobel edge detection but it dosent tells the points and only produces an image. I even thought of applying contour detection on the output of sobel edge detector but it is leading to some incompatibility issue.
Code:
import cv2
import numpy as np
image = cv2.imread('/home/user/Desktop/seg_output/1.jpg')
gray_image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
_, binary_image = cv2.threshold(gray_image, 127, 255, cv2.THRESH_BINARY)
contours, _ = cv2.findContours(binary_image, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
# Sort contours based on their area
contours = sorted(contours, key=cv2.contourArea, reverse=True)
print("Contours: ", np.squeeze(contours[0]))
print("Contours shape: ", len(contours))
contour_image = cv2.drawContours(image.copy(), contours, 0, (0, 255, 0), 2)
print("Output Shape: ", contour_image.shape)
cv2.imshow('Contours', contour_image)
cv2.waitKey(0)
cv2.destroyAllWindows()
Input Image: Initial Input Image
You can get the coordinates of the contour and filter out what you want, here is the code:
im = cv2.imread('road.jpg')
imGray = cv2.cvtColor(im, cv2.COLOR_BGR2GRAY)
_, thresh = cv2.threshold(imGray, 127, 255, cv2.THRESH_BINARY) # thresholding
contours, _ = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE) # none approximation is better than simple in this case!
largestContour = max(contours, key=cv2.contourArea) # get largest contour
height, width = im.shape[:2] # get shape
X,Y = np.array(contours).T # unpack contour to X and Y coordinates
indicesX = np.where((largestContour[:, :, 0] > 1) & (largestContour[:, :, 0] < width-1)) # get if X higher than 1 and lower than width -1
indicesY = np.where((largestContour[:, :, 1] > 1) & (largestContour[:, :, 1] < height-1))# get if Y higher than 1 and lower than height -1
combinedIndices = np.intersect1d(indicesX[0], indicesY[0]) # intersect indices
filteredContour = largestContour[combinedIndices] # get filtered contour
imContouredWithoutFilter = cv2.drawContours(im.copy(), largestContour, -1, (255, 0, 0), 3) # draw for plot
imContouredWithFilter = cv2.drawContours(im.copy(), filteredContour, -1, (0, 255, 0), 3) # draw for plot
# plotting
fig, ax = plt.subplots(nrows = 1, ncols = 2, sharex = True, sharey = True)
ax[0].imshow(imContouredWithoutFilter)
ax[0].axis("off")
ax[1].imshow(imContouredWithFilter)
ax[1].axis("off")
plt.show()
The most relevant lines in this case is where the np.where
and the np.intersect1d
are used.
Here are the results (to the left unfiltered, to the right filtered):
V2.0: better identification of left and right
To do this just invert the image, here is the code:
im = cv2.imread('road.jpg')
imGray = cv2.cvtColor(im, cv2.COLOR_BGR2GRAY)
_, thresh = cv2.threshold(imGray, 127, 255, cv2.THRESH_BINARY_INV) # thresholding
contours, _ = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE) # none approximation is better than simple in this case!
imContoured = cv2.drawContours(im.copy(), contours, 0, (0, 255, 0), 3) # draw first contour for plot
imContoured = cv2.drawContours(imContoured, contours, 1, (255, 0, 0), 3) # draw second contour for plot
# plotting
fig, ax = plt.subplots(nrows = 1, ncols = 3, sharex = True, sharey = True)
ax[0].imshow(im)
ax[0].axis("off")
ax[0].set_title("Original")
ax[1].imshow(thresh)
ax[1].axis("off")
ax[1].set_title("Intermediate")
ax[2].imshow(imContoured)
ax[2].axis("off")
ax[2].set_title("results")
plt.show()
I included the results of cv2.THRESH_BINARY_INV
to show you the logic behind the approach. In this case, the positive blobs are separated by the road, and hence the contour is gonna be on the left blob and the top blob.
Here are the results, with the first contour plotted in green and second in red. To get left contour, just use left = contours[1]
, and right is right = contours[0]
.