In this project, lane line of the road identified using Python and OpenCV. A pipeline is created to test the result initially on images and later applied the results to video stream (series of images).
Raw input image of a road.
Output Image with identified lane lines.
The pipeline for image consist of 11 steps-
Read the image of the road whose lane line is needed to be detected
#reading an image
image = mpimg.imread('test_images/whiteCarLaneSwitch.jpg')
#printing out its type, dimensions and plotting it
print('This image is:', type(image), 'with dimensions:', image.shape)
plt.imshow(image) # if you wanted to show a single color channel image called 'gray', for example, call as plt.imshow(gray, cmap='gray')grayscale() function is used to convert the test_image to the grayscaled image. This function uses the cv2.cvtColor() opencv function which convert images from one color space to another.
#show the grayscaled image
gray = grayscale(image)
plt.imshow(gray, cmap='gray')hsvscale() function is defined to convert the test_image to the HSV (Hue, Saturation, and Value) color space. This function uses the cv2.cvtColor() opencv function which convert images from one color space to another.
#show the grayscaled image
hsv_img = hsvscale(image)
plt.imshow(hsv_img)Upper and lower range for the lane color (here, yellow and white) is defiined. With use of opencv function cv2.inRange(), yellow and white part within defined range extracted from the hsv image. cv2.bitwise_or function is used to combine both extracted images like logical 'OR' operation.
#range of color in HSV image
lower_range_y = np.array([20,100,100])
upper_range_y = np.array([30,255,255])
lower_range_w = np.array([0,0,235])
upper_range_w = np.array([255,255,255])
# extract yellow and white part from the image
yellow_part = cv2.inRange(hsv_img, lower_range_y, upper_range_y)
white_part = cv2. inRange(hsv_img, lower_range_w, upper_range_w)
# masking both extracted images
whole_mask = cv2.bitwise_or(yellow_part, white_part)
plt.imshow(whole_mask)Dividing the pixel values of a grayscale image (gray) by 2 and then converting the resulting array to the data type uint8. This operation will reduce the overall brightness of the image by half.
# brightness reduction of gray image
lower_brightness_gray = (gray / 2).astype('uint8')
plt.imshow(lower_brightness_gray)The bitwise_or() operation combines the two images so that the resulting image has the lane line highlighted on the darker image background. By applying the 'whole_mask' on top of the 'lower_brightness_gray' image, the lane line is emphasized and made more visible, while the rest of the image remains darkened.
#boosted image
boost_lane = cv2.bitwise_or(lower_brightness_gray, whole_mask)
plt.imshow(boost_lane)gaussian_blur() function is used to blur the input image. This function uses the cv2.GaussianBlur() opencv function which reduce image noise and smooth out details in an image.
#gaussian blur
blur_img = gaussian_blur(boost_lane, 5)
plt.imshow(blur_img)canny() function is used to convert input image to edges image. Here it uses the cv2.canny() opencv function which returns a binary image with white pixels tracing out the detected edges and black everywhere else.
# canny edge detection apply
edges = canny(blur_img, 60, 150)
plt.imshow(edges)The region_of_interest() function is used to mask a area according to your desired shape in a image. Here we have used a quardilateral shaped masking region with following vertices.
# masked edges
vertices = np.array([[(0, 540), (450,315), (500,315), (960,540)]], dtype=np.int32)
masked_edges = region_of_interest(edges, vertices)
plt.imshow(masked_edges)rho = 3 # distance resolution in pixels of the Hough grid
theta = np.pi/180 # angular resolution in radians of the Hough grid
threshold = 40 # minimum number of votes (intersections in Hough grid cell)
min_line_len = 70 #minimum number of pixels making up a line
max_line_gap = 250 # maximum gap in pixels between connectable line segments
# Run Hough on edge detected image
color_edges = hough_lines(masked_edges, rho, theta, threshold, min_line_len, max_line_gap)
plt.imshow(color_edges)In order to draw a single line for the left and right lane the draw_line() function has been modified (line extrapolaion) to -
def draw_lines(img, lines, color=[255, 0, 0], thickness=2):
slope_lines = []
intercept_lines = []
slope_left = []
slope_right = []
intercept_left = []
intercept_right = []
img_x_size = img.shape[1]
img_y_size = img.shape[0]
slope_left_sign = []
slope_right_sign = []
# slopes and intercept of all lines
for line in lines:
for x1, y1, x2, y2 in line:
slope_ind = (y2-y1)/(x2-x1)
intercept_ind = y1 - x1 * ((y2-y1)/(x2-x1))
slope_lines.append(slope_ind)
intercept_lines.append(intercept_ind)
for val in slope_lines:
if val < 0:
slope_left_sign.append(val)
else:
slope_right_sign.append(val)
mean_slope_left = np.mean(slope_left_sign)
std_slope_left = np.std(slope_left_sign)
mean_slope_right = np.mean(slope_right_sign)
std_slope_right = np.std(slope_right_sign)
for s,i in zip(slope_lines, intercept_lines):
if abs(s-mean_slope_left) < std_slope_left:
slope_left.append(s)
intercept_left.append(i)
elif abs(s-mean_slope_right) < std_slope_right:
slope_right.append(s)
intercept_right.append(i)
if len(slope_left) > 0:
left_line_para = [sum(slope_left)/len(slope_left), sum(intercept_left)/len(intercept_left)]
left_y1 = int(img_y_size)
left_x1 = int((left_y1 - left_line_para[1])/ left_line_para[0])
left_y2 = int(img_y_size * 0.580)
left_x2 = int((left_y2 - left_line_para[1])/ left_line_para[0])
cv2.line(img, (left_x1, left_y1), (left_x2, left_y2), color, 10)
if len(slope_right) > 0:
right_line_para = [sum(slope_right)/len(slope_right), sum(intercept_right)/len(intercept_right)]
right_y1 = int(img_y_size)
right_x1 = int((right_y1 - right_line_para[1])/ right_line_para[0])
right_y2 = int(img_y_size * 0.580)
right_x2 = int((right_y2 - right_line_para[1])/ right_line_para[0])
cv2.line(img, (right_x1, right_y1), (right_x2, right_y2), color, 10)
After modifying draw_line(), the output of hough_lines() as
Weighted_img() uses the cv2.addWeighted() opencv function which returns the added weighted sum of the array of two images i.e line image and raw image.
# drawing line on edges image
line_edge = weighted_img(color_edges, image)
plt.imshow(line_edge)
mpimg.imsave('test_images_output/whiteCarLaneSwitch.jpg', line_edge)The pipeline for video contain one step-
ef process_image(image):
#gray scale conversion
gray = grayscale(image)
#hsv image
hsv_img = hsvscale(image)
#range of color in HSV image
lower_range_y = np.array([20,100,100])
upper_range_y = np.array([30,255,255])
lower_range_w = np.array([0,0,235])
upper_range_w = np.array([255,255,255])
# extract yellow and white part from the image
yellow_part = cv2.inRange(hsv_img, lower_range_y, upper_range_y)
white_part = cv2. inRange(hsv_img, lower_range_w, upper_range_w)
whole_mask = cv2.bitwise_or(yellow_part, white_part)
# brightness reduction of gray image
lower_brightness_gray = (gray / 2).astype('uint8')
#boosted image
boost_lane = cv2.bitwise_or(lower_brightness_gray, whole_mask)
#gaussian blur
blur_img = gaussian_blur(boost_lane, 5)
# canny edge detection apply
edges = canny(blur_img, 60, 150)
# masked edges
vertices = np.array([[(50, 540), (460,310), (500,310), (960,540)]], dtype=np.int32)
masked_edges = region_of_interest(edges, vertices)
# hough lines
color_edges = hough_lines(masked_edges, 3, np.pi/180, 40, 70,250)
# hough_lines(img, rho, theta, threshold, min_line_len, max_line_gap)
# drawing line on edges image
result = weighted_img(color_edges, image)
return result










