├── .gitignore ├── .ipynb_checkpoints └── P1-checkpoint.ipynb ├── P1_example.mp4 ├── challenge.mp4 ├── detection.py ├── example_images ├── laneLines_thirdPass.jpg └── line-segments-example.jpg ├── generate_output.sh ├── notebook.ipynb ├── raw-lines-example.mp4 ├── readme.md ├── solidWhiteRight.mp4 ├── solidYellowLeft.mp4 └── test_images ├── detected ├── solidWhiteCurve_detected.jpg ├── solidWhiteRight_detected.jpg ├── solidYellowCurve2_detected.jpg ├── solidYellowCurve_detected.jpg ├── solidYellowLeft_detected.jpg └── whiteCarLaneSwitch_detected.jpg ├── marked ├── solidWhiteCurve_detected.jpg ├── solidWhiteRight_detected.jpg ├── solidYellowCurve2_detected.jpg ├── solidYellowCurve_detected.jpg ├── solidYellowLeft_detected.jpg └── whiteCarLaneSwitch_detected.jpg ├── solidWhiteCurve.jpg ├── solidWhiteRight.jpg ├── solidWhiteRight_output.png ├── solidYellowCurve.jpg ├── solidYellowCurve2.jpg ├── solidYellowLeft.jpg └── whiteCarLaneSwitch.jpg /.gitignore: -------------------------------------------------------------------------------- 1 | .ipynb_checkpoints 2 | output_videos 3 | .DS_Store 4 | *output.jpg 5 | *output.png 6 | 7 | white.mp4 8 | yellow.mp4 9 | extra.mp4 10 | 11 | -------------------------------------------------------------------------------- /P1_example.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidawad/Lane-Detection/bc0b3c4513535c242f0b2ffb839ef303405f4c0a/P1_example.mp4 -------------------------------------------------------------------------------- /challenge.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidawad/Lane-Detection/bc0b3c4513535c242f0b2ffb839ef303405f4c0a/challenge.mp4 -------------------------------------------------------------------------------- /detection.py: -------------------------------------------------------------------------------- 1 | #!/usr/local/bin/python3 2 | # -*- coding: utf-8 -*- 3 | from moviepy.editor import VideoFileClip 4 | import matplotlib.pyplot as plt 5 | import matplotlib.image as mpimg 6 | import numpy as np 7 | import argparse 8 | import math 9 | import cv2 10 | 11 | ## 12 | # @Author David Awad 13 | # Detection.py, traces and identifies lane 14 | # markings in an image or .mp4 video 15 | # usage: detection.py [-h] [-f FILE] [-v VIDEO] 16 | 17 | 18 | def region_of_interest(img, vertices): 19 | #defining a blank mask to start with 20 | mask = np.zeros_like(img) 21 | 22 | #defining a 3 channel or 1 channel color to fill the mask with depending on the input image 23 | if len(img.shape) > 2: 24 | channel_count = img.shape[2] # i.e. 3 or 4 depending on your image 25 | ignore_mask_color = (255,) * channel_count 26 | else: 27 | ignore_mask_color = 255 28 | 29 | #filling pixels inside the polygon defined by "vertices" with the fill color 30 | cv2.fillPoly(mask, vertices, ignore_mask_color) 31 | #returning the image only where mask pixels are nonzero 32 | masked_image = cv2.bitwise_and(img, mask) 33 | return masked_image 34 | 35 | 36 | def draw_lines(img, lines, color=[255, 0, 0], thickness=8): 37 | # reshape lines to a 2d matrix 38 | lines = lines.reshape(lines.shape[0], lines.shape[2]) 39 | # create array of slopes 40 | slopes = (lines[:,3] - lines[:,1]) /(lines[:,2] - lines[:,0]) 41 | # remove junk from lists 42 | lines = lines[~np.isnan(lines) & ~np.isinf(lines)] 43 | slopes = slopes[~np.isnan(slopes) & ~np.isinf(slopes)] 44 | # convert lines into list of points 45 | lines.shape = (lines.shape[0]//2,2) 46 | 47 | # Right lane 48 | # move all points with negative slopes into right "lane" 49 | right_slopes = slopes[slopes < 0] 50 | right_lines = np.array(list(filter(lambda x: x[0] > (img.shape[1]/2), lines))) 51 | max_right_x, max_right_y = right_lines.max(axis=0) 52 | min_right_x, min_right_y = right_lines.min(axis=0) 53 | 54 | # Left lane 55 | # all positive slopes go into left "lane" 56 | left_slopes = slopes[slopes > 0] 57 | left_lines = np.array(list(filter(lambda x: x[0] < (img.shape[1]/2), lines))) 58 | max_left_x, max_left_y = left_lines.max(axis=0) 59 | min_left_x, min_left_y = left_lines.min(axis=0) 60 | 61 | # Curve fitting approach 62 | # calculate polynomial fit for the points in right lane 63 | right_curve = np.poly1d(np.polyfit(right_lines[:,1], right_lines[:,0], 2)) 64 | left_curve = np.poly1d(np.polyfit(left_lines[:,1], left_lines[:,0], 2)) 65 | 66 | # shared ceiling on the horizon for both lines 67 | min_y = min(min_left_y, min_right_y) 68 | 69 | # use new curve function f(y) to calculate x values 70 | max_right_x = int(right_curve(img.shape[0])) 71 | min_right_x = int(right_curve(min_right_y)) 72 | 73 | min_left_x = int(left_curve(img.shape[0])) 74 | 75 | r1 = (min_right_x, min_y) 76 | r2 = (max_right_x, img.shape[0]) 77 | print('Right points r1 and r2,', r1, r2) 78 | cv2.line(img, r1, r2, color, thickness) 79 | 80 | l1 = (max_left_x, min_y) 81 | l2 = (min_left_x, img.shape[0]) 82 | print('Left points l1 and l2,', l1, l2) 83 | cv2.line(img, l1, l2, color, thickness) 84 | 85 | 86 | def hough_lines(img, rho, theta, threshold, min_line_len, max_line_gap): 87 | """ 88 | `img` should be the output of a Canny transform. 89 | Returns an image with hough lines drawn. 90 | """ 91 | lines = cv2.HoughLinesP(img, rho, theta, threshold, np.array([]), minLineLength=min_line_len, maxLineGap=max_line_gap) 92 | line_img = np.zeros((img.shape[0], img.shape[1], 3), dtype=np.uint8) 93 | draw_lines(line_img, lines) 94 | return line_img 95 | 96 | # Takes in a single frame or an image and returns a marked image 97 | def mark_lanes(image): 98 | if image is None: raise ValueError("no image given to mark_lanes") 99 | # grayscale the image to make finding gradients clearer 100 | gray = cv2.cvtColor(image,cv2.COLOR_RGB2GRAY) 101 | 102 | # Define a kernel size and apply Gaussian smoothing 103 | kernel_size = 5 104 | blur_gray = cv2.GaussianBlur(gray,(kernel_size, kernel_size), 0) 105 | 106 | # Define our parameters for Canny and apply 107 | low_threshold = 50 108 | high_threshold = 150 109 | edges_img = cv2.Canny(np.uint8(blur_gray), low_threshold, high_threshold) 110 | 111 | 112 | imshape = image.shape 113 | vertices = np.array([[(0, imshape[0]), 114 | (450, 320), 115 | (490, 320), 116 | (imshape[1], imshape[0]) ]], 117 | dtype=np.int32) 118 | 119 | masked_edges = region_of_interest(edges_img, vertices ) 120 | 121 | 122 | # Define the Hough transform parameters 123 | rho = 2 # distance resolution in pixels of the Hough grid 124 | theta = np.pi/180 # angular resolution in radians of the Hough grid 125 | threshold = 15 # minimum number of votes (intersections in Hough grid cell) 126 | min_line_length = 20 # minimum number of pixels making up a line 127 | max_line_gap = 20 # maximum gap in pixels between connectable line segments 128 | 129 | line_image = hough_lines(masked_edges, rho, theta, threshold, min_line_length, max_line_gap) 130 | 131 | # Draw the lines on the edge image 132 | # initial_img * α + img * β + λ 133 | lines_edges = cv2.addWeighted(image, 0.8, line_image, 1, 0) 134 | return lines_edges 135 | 136 | 137 | def read_image_for_marking(img_filepath): 138 | # read in the image 139 | image = mpimg.imread(img_filepath) 140 | print('Reading image :', img_filepath, '\nDimensions:', image.shape) 141 | 142 | marked_lanes = mark_lanes(image) 143 | 144 | # show the image to plotter and then save it to a file 145 | plt.imshow(marked_lanes) 146 | plt.savefig(img_filepath[:-4] + '_output.png') 147 | 148 | 149 | if __name__ == "__main__": 150 | # set up parser 151 | parser = argparse.ArgumentParser() 152 | parser.add_argument("-f", "--file", help="filepath for image to mark", default='test_images/solidWhiteRight.jpg') 153 | parser.add_argument("-v", "--video", help="filepath for video to mark") 154 | args = parser.parse_args() 155 | 156 | if args.video: 157 | clip = VideoFileClip(args.video) 158 | clip = clip.fl_image(mark_lanes) 159 | clip.write_videofile('output_' + args.video, audio=False) 160 | 161 | else: 162 | # if nothing passed running algorithm on image 163 | read_image_for_marking(args.file) 164 | -------------------------------------------------------------------------------- /example_images/laneLines_thirdPass.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidawad/Lane-Detection/bc0b3c4513535c242f0b2ffb839ef303405f4c0a/example_images/laneLines_thirdPass.jpg -------------------------------------------------------------------------------- /example_images/line-segments-example.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidawad/Lane-Detection/bc0b3c4513535c242f0b2ffb839ef303405f4c0a/example_images/line-segments-example.jpg -------------------------------------------------------------------------------- /generate_output.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | rm test_images/*_output.png 4 | 5 | for i in test_images/*; do python detection.py -f $i; done 6 | -------------------------------------------------------------------------------- /raw-lines-example.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidawad/Lane-Detection/bc0b3c4513535c242f0b2ffb839ef303405f4c0a/raw-lines-example.mp4 -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Lane Detection 2 | 3 | This project uses Canny Edge Detection, Hough Transforms, and linear regression to identify and mark lane lines on a road. 4 | 5 | This repo was written with the hope that it would be easy to understand for someone not farmiliar with the project. 6 | 7 | ![](test_images/solidWhiteRight_output.png) 8 | 9 | 10 | ## How it works 11 | We can describe this process in a straightforward way. 12 | 13 | - Detect the lane lines by looking at color contrast gradients in the image 14 | - fit a curve to the points that make the line 15 | - draw the red lines on top of the lane lines 16 | 17 | This is an algorithm that uses Canny Edge detection hough transformations and polynomial regression to determine the the edges of lines in order to perform lane detection 18 | 19 | There are a couple of algorithms to decide how to draw the lines, regression being the last thing I came up with. 20 | 21 | 22 | ### Approach 1 - Simple Extremum 23 | 24 | The first approach was to simply collect all the points in the lane, find the topmost left and rightmost points and graph lines between them. This worked somewhat well but it lacked the ability to "fill in" where there was an absence of lane markings. 25 | 26 | ```python 27 | # Line Extremum Approach 28 | right_lines = np.array(list(filter(lambda x: x[0] > (img.shape[1]/2), lines))) 29 | max_right_x, max_right_y = right_lines.max(axis=0) 30 | min_right_x, min_right_y = right_lines.min(axis=0) 31 | ``` 32 | 33 | 34 | 35 | ### Approach 2 - Average Slope 36 | 37 | Another, more robust way that should work theorhetically would be to predict the x coordinates based on the average slope and solving for it with the average slope and average y and y intercept. 38 | 39 | ```python 40 | # Average slope Approach 41 | avg_right_slope = right_slopes.mean() 42 | 43 | # average point 44 | avg_right_point = np.mean(right_lines, axis=0) 45 | 46 | # y intercept of right line 47 | # b = y - mx 48 | right_y_intercept = avg_right_point[1] - avg_right_slope * avg_right_point[0] 49 | 50 | # Calculate x values based on average slope and y intercept 51 | max_right_x = int((min_right_y - right_y_intercept)/avg_right_slope) 52 | min_right_x = int((max_right_y - right_y_intercept)/avg_right_slope) 53 | 54 | r1 = (min_right_x, min_right_y) 55 | r2 = (max_right_x, img.shape[0]) # ground to bottom of image 56 | ``` 57 | 58 | This however would get thrown off by outliers returned by the Hough transform. 59 | 60 | 61 | ### Approach 3 - Curve fitting 62 | 63 | This felt like the most robust way to connect the dots by using numpy to generate a polynomial curve that modeled each lane, and the one that I'm using in my submission. I'm definitely the most happy with the results for this one becuase it allows us to generate sensible x values to complete the lanes when there are dashed lines in the road. 64 | 65 | ```python 66 | # Curve fitting approach 67 | # calculate polynomial fit for the points in right lane 68 | right_curve = np.poly1d(np.polyfit(right_lines[:,1], right_lines[:,0], 2)) 69 | left_curve = np.poly1d(np.polyfit(left_lines[:,1], left_lines[:,0], 2)) 70 | 71 | # use new curve function f(y) to calculate x values 72 | max_right_x = int(right_curve(img.shape[0])) 73 | min_left_x = int(left_curve(img.shape[0])) 74 | ``` 75 | 76 | And with that our output lines are pretty good! 77 | 78 | 79 | ### Requirements 80 | - numpy 81 | - matplotlib 82 | - opencv 83 | - python3 84 | 85 | 86 | Thank you to the Udacity mentors for the learning resources 87 | -------------------------------------------------------------------------------- /solidWhiteRight.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidawad/Lane-Detection/bc0b3c4513535c242f0b2ffb839ef303405f4c0a/solidWhiteRight.mp4 -------------------------------------------------------------------------------- /solidYellowLeft.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidawad/Lane-Detection/bc0b3c4513535c242f0b2ffb839ef303405f4c0a/solidYellowLeft.mp4 -------------------------------------------------------------------------------- /test_images/detected/solidWhiteCurve_detected.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidawad/Lane-Detection/bc0b3c4513535c242f0b2ffb839ef303405f4c0a/test_images/detected/solidWhiteCurve_detected.jpg -------------------------------------------------------------------------------- /test_images/detected/solidWhiteRight_detected.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidawad/Lane-Detection/bc0b3c4513535c242f0b2ffb839ef303405f4c0a/test_images/detected/solidWhiteRight_detected.jpg -------------------------------------------------------------------------------- /test_images/detected/solidYellowCurve2_detected.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidawad/Lane-Detection/bc0b3c4513535c242f0b2ffb839ef303405f4c0a/test_images/detected/solidYellowCurve2_detected.jpg -------------------------------------------------------------------------------- /test_images/detected/solidYellowCurve_detected.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidawad/Lane-Detection/bc0b3c4513535c242f0b2ffb839ef303405f4c0a/test_images/detected/solidYellowCurve_detected.jpg -------------------------------------------------------------------------------- /test_images/detected/solidYellowLeft_detected.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidawad/Lane-Detection/bc0b3c4513535c242f0b2ffb839ef303405f4c0a/test_images/detected/solidYellowLeft_detected.jpg -------------------------------------------------------------------------------- /test_images/detected/whiteCarLaneSwitch_detected.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidawad/Lane-Detection/bc0b3c4513535c242f0b2ffb839ef303405f4c0a/test_images/detected/whiteCarLaneSwitch_detected.jpg -------------------------------------------------------------------------------- /test_images/marked/solidWhiteCurve_detected.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidawad/Lane-Detection/bc0b3c4513535c242f0b2ffb839ef303405f4c0a/test_images/marked/solidWhiteCurve_detected.jpg -------------------------------------------------------------------------------- /test_images/marked/solidWhiteRight_detected.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidawad/Lane-Detection/bc0b3c4513535c242f0b2ffb839ef303405f4c0a/test_images/marked/solidWhiteRight_detected.jpg -------------------------------------------------------------------------------- /test_images/marked/solidYellowCurve2_detected.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidawad/Lane-Detection/bc0b3c4513535c242f0b2ffb839ef303405f4c0a/test_images/marked/solidYellowCurve2_detected.jpg -------------------------------------------------------------------------------- /test_images/marked/solidYellowCurve_detected.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidawad/Lane-Detection/bc0b3c4513535c242f0b2ffb839ef303405f4c0a/test_images/marked/solidYellowCurve_detected.jpg -------------------------------------------------------------------------------- /test_images/marked/solidYellowLeft_detected.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidawad/Lane-Detection/bc0b3c4513535c242f0b2ffb839ef303405f4c0a/test_images/marked/solidYellowLeft_detected.jpg -------------------------------------------------------------------------------- /test_images/marked/whiteCarLaneSwitch_detected.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidawad/Lane-Detection/bc0b3c4513535c242f0b2ffb839ef303405f4c0a/test_images/marked/whiteCarLaneSwitch_detected.jpg -------------------------------------------------------------------------------- /test_images/solidWhiteCurve.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidawad/Lane-Detection/bc0b3c4513535c242f0b2ffb839ef303405f4c0a/test_images/solidWhiteCurve.jpg -------------------------------------------------------------------------------- /test_images/solidWhiteRight.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidawad/Lane-Detection/bc0b3c4513535c242f0b2ffb839ef303405f4c0a/test_images/solidWhiteRight.jpg -------------------------------------------------------------------------------- /test_images/solidWhiteRight_output.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidawad/Lane-Detection/bc0b3c4513535c242f0b2ffb839ef303405f4c0a/test_images/solidWhiteRight_output.png -------------------------------------------------------------------------------- /test_images/solidYellowCurve.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidawad/Lane-Detection/bc0b3c4513535c242f0b2ffb839ef303405f4c0a/test_images/solidYellowCurve.jpg -------------------------------------------------------------------------------- /test_images/solidYellowCurve2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidawad/Lane-Detection/bc0b3c4513535c242f0b2ffb839ef303405f4c0a/test_images/solidYellowCurve2.jpg -------------------------------------------------------------------------------- /test_images/solidYellowLeft.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidawad/Lane-Detection/bc0b3c4513535c242f0b2ffb839ef303405f4c0a/test_images/solidYellowLeft.jpg -------------------------------------------------------------------------------- /test_images/whiteCarLaneSwitch.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidawad/Lane-Detection/bc0b3c4513535c242f0b2ffb839ef303405f4c0a/test_images/whiteCarLaneSwitch.jpg --------------------------------------------------------------------------------