Search code examples
pythonopencvcomputer-visioncontouredge-detection

Unable to get left and right road edge


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

Output Image: Image representing the whole contout including lower boundary


Solution

  • 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):

    results

    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].

    Results