├── AdvancedLaneFinding.ipynb ├── LICENSE ├── README.md ├── camera_cal ├── calibration1.jpg ├── calibration10.jpg ├── calibration11.jpg ├── calibration12.jpg ├── calibration13.jpg ├── calibration14.jpg ├── calibration15.jpg ├── calibration16.jpg ├── calibration17.jpg ├── calibration18.jpg ├── calibration19.jpg ├── calibration2.jpg ├── calibration20.jpg ├── calibration3.jpg ├── calibration4.jpg ├── calibration5.jpg ├── calibration6.jpg ├── calibration7.jpg ├── calibration8.jpg └── calibration9.jpg ├── camera_cal_undistorted ├── calibration10.jpg ├── calibration11.jpg ├── calibration12.jpg ├── calibration13.jpg ├── calibration14.jpg ├── calibration15.jpg ├── calibration16.jpg ├── calibration2.jpg ├── calibration3.jpg ├── calibration6.jpg ├── calibration7.jpg └── calibration8.jpg ├── challenge_video.mp4 ├── example_writeup.pdf ├── examples ├── .ipynb_checkpoints │ └── example-checkpoint.ipynb ├── binary_combo_example.jpg ├── color_fit_lines.jpg ├── example.ipynb ├── example.py ├── example_output.jpg ├── undistort_output.png └── warped_straight_lines.jpg ├── harder_challenge_video.mp4 ├── output_images ├── advanced_lane_finding.gif ├── binary_thr.JPG ├── ending_points.JPG ├── fit_poly.JPG ├── lines_test.jpg ├── prev_poly.JPG ├── save_output_here.txt ├── undistorted_chessboard.JPG ├── undistorted_road.JPG ├── warp_perspective.JPG ├── warped1_test.jpg └── warped2_test.jpg ├── project_video.mp4 ├── project_video_output.mp4 ├── set_git.sh ├── test_images ├── straight_lines1.jpg ├── straight_lines2.jpg ├── test1.jpg ├── test2.jpg ├── test3.jpg ├── test4.jpg ├── test5.jpg └── test6.jpg └── writeup_template.md /AdvancedLaneFinding.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 1, 6 | "metadata": {}, 7 | "outputs": [], 8 | "source": [ 9 | "#import libraries\n", 10 | "import matplotlib.pyplot as plt\n", 11 | "import matplotlib.image as mpimg\n", 12 | "import numpy as np\n", 13 | "import cv2\n", 14 | "import glob\n", 15 | "import os\n", 16 | "from moviepy.editor import VideoFileClip\n", 17 | "%matplotlib inline" 18 | ] 19 | }, 20 | { 21 | "cell_type": "code", 22 | "execution_count": 2, 23 | "metadata": {}, 24 | "outputs": [], 25 | "source": [ 26 | "### STEP 1: Camera Calibration ###\n", 27 | "\n", 28 | "def distortion_factors():\n", 29 | " # Prepare object points\n", 30 | " # From the provided calibration images, 9*6 corners are identified \n", 31 | " nx = 9\n", 32 | " ny = 6\n", 33 | " objpoints = []\n", 34 | " imgpoints = []\n", 35 | " # Object points are real world points, here a 3D coordinates matrix is generated\n", 36 | " # z coordinates are 0 and x, y are equidistant as it is known that the chessboard is made of identical squares\n", 37 | " objp = np.zeros((6*9,3), np.float32)\n", 38 | " objp[:,:2] = np.mgrid[0:9,0:6].T.reshape(-1,2)\n", 39 | " \n", 40 | " # Make a list of calibration images\n", 41 | " os.listdir(\"camera_cal/\")\n", 42 | " cal_img_list = os.listdir(\"camera_cal/\") \n", 43 | " \n", 44 | " # Imagepoints are the coresspondant object points with their coordinates in the distorted image\n", 45 | " # They are found in the image using the Open CV 'findChessboardCorners' function\n", 46 | " for image_name in cal_img_list:\n", 47 | " import_from = 'camera_cal/' + image_name\n", 48 | " img = cv2.imread(import_from)\n", 49 | " # Convert to grayscale\n", 50 | " gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)\n", 51 | " # Find the chessboard corners\n", 52 | " ret, corners = cv2.findChessboardCorners(gray, (nx, ny), None)\n", 53 | " # If found, draw corners\n", 54 | " if ret == True:\n", 55 | " # Draw and display the corners\n", 56 | " #cv2.drawChessboardCorners(img, (nx, ny), corners, ret)\n", 57 | " imgpoints.append(corners)\n", 58 | " objpoints.append(objp)\n", 59 | " ret, mtx, dist, rvecs, tvecs = cv2.calibrateCamera(objpoints, imgpoints, gray.shape[::-1], None, None)\n", 60 | " #undist = cv2.undistort(img, mtx, dist, None, mtx)\n", 61 | " #export_to = 'camera_cal_undistorted/' + image_name\n", 62 | " #save the image in the destination folder\n", 63 | " #plt.imsave(export_to, undist)\n", 64 | " \n", 65 | " return mtx, dist \n" 66 | ] 67 | }, 68 | { 69 | "cell_type": "code", 70 | "execution_count": 3, 71 | "metadata": {}, 72 | "outputs": [], 73 | "source": [ 74 | "### STEP 2: Perspective Transform from Car Camera to Bird's Eye View ###\n", 75 | "mtx, dist = distortion_factors()\n", 76 | "\n", 77 | "def warp(img):\n", 78 | " undist = cv2.undistort(img, mtx, dist, None, mtx)\n", 79 | " img_size = (img.shape[1], img.shape[0])\n", 80 | " offset = 300\n", 81 | " \n", 82 | " # Source points taken from images with straight lane lines, these are to become parallel after the warp transform\n", 83 | " src = np.float32([\n", 84 | " (190, 720), # bottom-left corner\n", 85 | " (596, 447), # top-left corner\n", 86 | " (685, 447), # top-right corner\n", 87 | " (1125, 720) # bottom-right corner\n", 88 | " ])\n", 89 | " # Destination points are to be parallel, taken into account the image size\n", 90 | " dst = np.float32([\n", 91 | " [offset, img_size[1]], # bottom-left corner\n", 92 | " [offset, 0], # top-left corner\n", 93 | " [img_size[0]-offset, 0], # top-right corner\n", 94 | " [img_size[0]-offset, img_size[1]] # bottom-right corner\n", 95 | " ])\n", 96 | " # Calculate the transformation matrix and it's inverse transformation\n", 97 | " M = cv2.getPerspectiveTransform(src, dst)\n", 98 | " M_inv = cv2.getPerspectiveTransform(dst, src)\n", 99 | " warped = cv2.warpPerspective(undist, M, img_size)\n", 100 | " \n", 101 | " return warped, M_inv" 102 | ] 103 | }, 104 | { 105 | "cell_type": "code", 106 | "execution_count": 4, 107 | "metadata": {}, 108 | "outputs": [], 109 | "source": [ 110 | "### STEP 3: Process Binary Thresholded Images ###\n", 111 | "\n", 112 | "#img = cv2.imread('test_images/straight_lines1.jpg')\n", 113 | "#img = cv2.imread('test_images/straight_lines2.jpg')\n", 114 | "#img = cv2.imread('test_images/test1.jpg')\n", 115 | "#img = cv2.imread('test_images/test2.jpg')\n", 116 | "#img = cv2.imread('test_images/test3.jpg')\n", 117 | "img = cv2.imread('test_images/test4.jpg')\n", 118 | "#img = cv2.imread('test_images/test5.jpg')\n", 119 | "#img = cv2.imread('test_images/test6.jpg')\n", 120 | "\n", 121 | "def binary_thresholded(img):\n", 122 | " # Transform image to gray scale\n", 123 | " gray_img =cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)\n", 124 | " # Apply sobel (derivative) in x direction, this is usefull to detect lines that tend to be vertical\n", 125 | " sobelx = cv2.Sobel(gray_img, cv2.CV_64F, 1, 0)\n", 126 | " abs_sobelx = np.absolute(sobelx)\n", 127 | " # Scale result to 0-255\n", 128 | " scaled_sobel = np.uint8(255*abs_sobelx/np.max(abs_sobelx))\n", 129 | " sx_binary = np.zeros_like(scaled_sobel)\n", 130 | " # Keep only derivative values that are in the margin of interest\n", 131 | " sx_binary[(scaled_sobel >= 30) & (scaled_sobel <= 255)] = 1\n", 132 | "\n", 133 | " # Detect pixels that are white in the grayscale image\n", 134 | " white_binary = np.zeros_like(gray_img)\n", 135 | " white_binary[(gray_img > 200) & (gray_img <= 255)] = 1\n", 136 | "\n", 137 | " # Convert image to HLS\n", 138 | " hls = cv2.cvtColor(img, cv2.COLOR_BGR2HLS)\n", 139 | " H = hls[:,:,0]\n", 140 | " S = hls[:,:,2]\n", 141 | " sat_binary = np.zeros_like(S)\n", 142 | " # Detect pixels that have a high saturation value\n", 143 | " sat_binary[(S > 90) & (S <= 255)] = 1\n", 144 | "\n", 145 | " hue_binary = np.zeros_like(H)\n", 146 | " # Detect pixels that are yellow using the hue component\n", 147 | " hue_binary[(H > 10) & (H <= 25)] = 1\n", 148 | "\n", 149 | " # Combine all pixels detected above\n", 150 | " binary_1 = cv2.bitwise_or(sx_binary, white_binary)\n", 151 | " binary_2 = cv2.bitwise_or(hue_binary, sat_binary)\n", 152 | " binary = cv2.bitwise_or(binary_1, binary_2)\n", 153 | " #plt.imshow(binary, cmap='gray')\n", 154 | "\n", 155 | " #img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)\n", 156 | " # Draw figure for binary images\n", 157 | " #f, axarr = plt.subplots(1,6)\n", 158 | " #f.set_size_inches(25, 8)\n", 159 | " #axarr[0].imshow(img)\n", 160 | " #axarr[1].imshow(sx_binary, cmap='gray')\n", 161 | " #axarr[2].imshow(white_binary, cmap='gray')\n", 162 | " #axarr[3].imshow(sat_binary, cmap='gray')\n", 163 | " #axarr[4].imshow(hue_binary, cmap='gray')\n", 164 | " #axarr[5].imshow(binary, cmap='gray')\n", 165 | " #axarr[0].set_title(\"Undistorted Image\")\n", 166 | " #axarr[1].set_title(\"x Sobel Derivative\")\n", 167 | " #axarr[2].set_title(\"White Threshold\")\n", 168 | " #axarr[3].set_title(\"Saturation Threshold\")\n", 169 | " #axarr[4].set_title(\"Hue Threshold\")\n", 170 | " #axarr[5].set_title(\"Combined\")\n", 171 | " #axarr[0].axis('off')\n", 172 | " #axarr[1].axis('off')\n", 173 | " #axarr[2].axis('off')\n", 174 | " #axarr[3].axis('off')\n", 175 | " #axarr[4].axis('off')\n", 176 | " #axarr[5].axis('off')\n", 177 | " \n", 178 | " return binary\n", 179 | "\n", 180 | "#out_img = binary_thresholded(img)" 181 | ] 182 | }, 183 | { 184 | "cell_type": "code", 185 | "execution_count": 5, 186 | "metadata": {}, 187 | "outputs": [ 188 | { 189 | "data": { 190 | "text/plain": [ 191 | "" 192 | ] 193 | }, 194 | "execution_count": 5, 195 | "metadata": {}, 196 | "output_type": "execute_result" 197 | }, 198 | { 199 | "data": { 200 | "image/png": "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\n", 201 | "text/plain": [ 202 | "" 203 | ] 204 | }, 205 | "metadata": { 206 | "needs_background": "light" 207 | }, 208 | "output_type": "display_data" 209 | } 210 | ], 211 | "source": [ 212 | "#img = cv2.imread('test_images/straight_lines1.jpg')\n", 213 | "#img = cv2.imread('test_images/straight_lines2.jpg')\n", 214 | "#img = cv2.imread('test_images/test1.jpg')\n", 215 | "#img = cv2.imread('test_images/test2.jpg')\n", 216 | "#img = cv2.imread('test_images/test3.jpg')\n", 217 | "#img = cv2.imread('test_images/test4.jpg')\n", 218 | "#img = cv2.imread('test_images/test5.jpg')\n", 219 | "img = cv2.imread('test_images/test6.jpg')\n", 220 | "\n", 221 | "binary_thresh = binary_thresholded(img)\n", 222 | "out_img = np.dstack((binary_thresh, binary_thresh, binary_thresh))*255\n", 223 | "binary_warped, M_inv = warp(binary_thresh)\n", 224 | "plt.imshow(out_img, cmap='gray')" 225 | ] 226 | }, 227 | { 228 | "cell_type": "code", 229 | "execution_count": 6, 230 | "metadata": {}, 231 | "outputs": [], 232 | "source": [ 233 | "### STEP 4: Detection of Lane Lines Using Histogram ###\n", 234 | "\n", 235 | "def find_lane_pixels_using_histogram(binary_warped):\n", 236 | " # Take a histogram of the bottom half of the image\n", 237 | " histogram = np.sum(binary_warped[binary_warped.shape[0]//2:,:], axis=0)\n", 238 | " \n", 239 | " # Find the peak of the left and right halves of the histogram\n", 240 | " # These will be the starting point for the left and right lines\n", 241 | " midpoint = np.int(histogram.shape[0]//2)\n", 242 | " leftx_base = np.argmax(histogram[:midpoint])\n", 243 | " rightx_base = np.argmax(histogram[midpoint:]) + midpoint\n", 244 | "\n", 245 | " # Choose the number of sliding windows\n", 246 | " nwindows = 9\n", 247 | " # Set the width of the windows +/- margin\n", 248 | " margin = 100\n", 249 | " # Set minimum number of pixels found to recenter window\n", 250 | " minpix = 50\n", 251 | "\n", 252 | " # Set height of windows - based on nwindows above and image shape\n", 253 | " window_height = np.int(binary_warped.shape[0]//nwindows)\n", 254 | " # Identify the x and y positions of all nonzero pixels in the image\n", 255 | " nonzero = binary_warped.nonzero()\n", 256 | " nonzeroy = np.array(nonzero[0])\n", 257 | " nonzerox = np.array(nonzero[1])\n", 258 | " # Current positions to be updated later for each window in nwindows\n", 259 | " leftx_current = leftx_base\n", 260 | " rightx_current = rightx_base\n", 261 | "\n", 262 | " # Create empty lists to receive left and right lane pixel indices\n", 263 | " left_lane_inds = []\n", 264 | " right_lane_inds = []\n", 265 | "\n", 266 | " # Step through the windows one by one\n", 267 | " for window in range(nwindows):\n", 268 | " # Identify window boundaries in x and y (and right and left)\n", 269 | " win_y_low = binary_warped.shape[0] - (window+1)*window_height\n", 270 | " win_y_high = binary_warped.shape[0] - window*window_height\n", 271 | " win_xleft_low = leftx_current - margin\n", 272 | " win_xleft_high = leftx_current + margin\n", 273 | " win_xright_low = rightx_current - margin\n", 274 | " win_xright_high = rightx_current + margin\n", 275 | " \n", 276 | " # Identify the nonzero pixels in x and y within the window #\n", 277 | " good_left_inds = ((nonzeroy >= win_y_low) & (nonzeroy < win_y_high) & \n", 278 | " (nonzerox >= win_xleft_low) & (nonzerox < win_xleft_high)).nonzero()[0]\n", 279 | " good_right_inds = ((nonzeroy >= win_y_low) & (nonzeroy < win_y_high) & \n", 280 | " (nonzerox >= win_xright_low) & (nonzerox < win_xright_high)).nonzero()[0]\n", 281 | " \n", 282 | " # Append these indices to the lists\n", 283 | " left_lane_inds.append(good_left_inds)\n", 284 | " right_lane_inds.append(good_right_inds)\n", 285 | " \n", 286 | " # If you found > minpix pixels, recenter next window on their mean position\n", 287 | " if len(good_left_inds) > minpix:\n", 288 | " leftx_current = np.int(np.mean(nonzerox[good_left_inds]))\n", 289 | " if len(good_right_inds) > minpix: \n", 290 | " rightx_current = np.int(np.mean(nonzerox[good_right_inds]))\n", 291 | "\n", 292 | " # Concatenate the arrays of indices (previously was a list of lists of pixels)\n", 293 | " try:\n", 294 | " left_lane_inds = np.concatenate(left_lane_inds)\n", 295 | " right_lane_inds = np.concatenate(right_lane_inds)\n", 296 | " except ValueError:\n", 297 | " # Avoids an error if the above is not implemented fully\n", 298 | " pass\n", 299 | "\n", 300 | " # Extract left and right line pixel positions\n", 301 | " leftx = nonzerox[left_lane_inds]\n", 302 | " lefty = nonzeroy[left_lane_inds] \n", 303 | " rightx = nonzerox[right_lane_inds]\n", 304 | " righty = nonzeroy[right_lane_inds]\n", 305 | "\n", 306 | " return leftx, lefty, rightx, righty\n", 307 | "\n", 308 | "\n", 309 | "def fit_poly(binary_warped,leftx, lefty, rightx, righty):\n", 310 | " ### Fit a second order polynomial to each with np.polyfit() ###\n", 311 | " left_fit = np.polyfit(lefty, leftx, 2)\n", 312 | " right_fit = np.polyfit(righty, rightx, 2) \n", 313 | " \n", 314 | " # Generate x and y values for plotting\n", 315 | " ploty = np.linspace(0, binary_warped.shape[0]-1, binary_warped.shape[0] )\n", 316 | " try:\n", 317 | " left_fitx = left_fit[0]*ploty**2 + left_fit[1]*ploty + left_fit[2]\n", 318 | " right_fitx = right_fit[0]*ploty**2 + right_fit[1]*ploty + right_fit[2]\n", 319 | " except TypeError:\n", 320 | " # Avoids an error if `left` and `right_fit` are still none or incorrect\n", 321 | " print('The function failed to fit a line!')\n", 322 | " left_fitx = 1*ploty**2 + 1*ploty\n", 323 | " right_fitx = 1*ploty**2 + 1*ploty\n", 324 | " \n", 325 | " return left_fit, right_fit, left_fitx, right_fitx, ploty\n", 326 | "\n", 327 | "def draw_poly_lines(binary_warped, left_fitx, right_fitx, ploty): \n", 328 | " # Create an image to draw on and an image to show the selection window\n", 329 | " out_img = np.dstack((binary_warped, binary_warped, binary_warped))*255\n", 330 | " window_img = np.zeros_like(out_img)\n", 331 | " \n", 332 | " margin = 100\n", 333 | " # Generate a polygon to illustrate the search window area\n", 334 | " # And recast the x and y points into usable format for cv2.fillPoly()\n", 335 | " left_line_window1 = np.array([np.transpose(np.vstack([left_fitx-margin, ploty]))])\n", 336 | " left_line_window2 = np.array([np.flipud(np.transpose(np.vstack([left_fitx+margin, \n", 337 | " ploty])))])\n", 338 | " left_line_pts = np.hstack((left_line_window1, left_line_window2))\n", 339 | " right_line_window1 = np.array([np.transpose(np.vstack([right_fitx-margin, ploty]))])\n", 340 | " right_line_window2 = np.array([np.flipud(np.transpose(np.vstack([right_fitx+margin, \n", 341 | " ploty])))])\n", 342 | " right_line_pts = np.hstack((right_line_window1, right_line_window2))\n", 343 | "\n", 344 | " # Draw the lane onto the warped blank image\n", 345 | " cv2.fillPoly(window_img, np.int_([left_line_pts]), (100, 100, 0))\n", 346 | " cv2.fillPoly(window_img, np.int_([right_line_pts]), (100, 100, 0))\n", 347 | " result = cv2.addWeighted(out_img, 1, window_img, 0.3, 0)\n", 348 | " \n", 349 | " # Plot the polynomial lines onto the image\n", 350 | " plt.plot(left_fitx, ploty, color='green')\n", 351 | " plt.plot(right_fitx, ploty, color='blue')\n", 352 | " ## End visualization steps ##\n", 353 | " return result\n", 354 | " \n", 355 | "#leftx, lefty, rightx, righty = find_lane_pixels_using_histogram(binary_warped)\n", 356 | "#left_fit, right_fit, left_fitx, right_fitx, ploty = fit_poly(binary_warped,leftx, lefty, rightx, righty)\n", 357 | "#print(left_fit)\n", 358 | "#out_img = draw_poly_lines(binary_warped, left_fitx, right_fitx, ploty)\n", 359 | "#plt.imshow(out_img)\n", 360 | "\n", 361 | "#prev_left_fit, prev_right_fit = left_fit, right_fit\n", 362 | "#print('prev left fit: ', prev_left_fit)\n", 363 | "#print('prev right fit: ', prev_right_fit)\n" 364 | ] 365 | }, 366 | { 367 | "cell_type": "code", 368 | "execution_count": 7, 369 | "metadata": {}, 370 | "outputs": [], 371 | "source": [ 372 | "### STEP 5: Detection of Lane Lines Based on Previous Step ###\n", 373 | "\n", 374 | "#img = cv2.imread('test_images/test5.jpg')\n", 375 | "#binary_thresh = binary_thresholded(img)\n", 376 | "#binary_warped, M_inv = warp(binary_thresh)\n", 377 | "\n", 378 | "# Polynomial fit values from the previous frame\n", 379 | "# Make sure to grab the actual values from the previous step in your project!\n", 380 | "#left_fit = np.array([ 2.13935315e-04, -3.77507980e-01, 4.76902175e+02])\n", 381 | "#right_fit = np.array([4.17622148e-04, -4.93848953e-01, 1.11806170e+03])\n", 382 | "\n", 383 | "def find_lane_pixels_using_prev_poly(binary_warped):\n", 384 | " global prev_left_fit\n", 385 | " global prev_right_fit\n", 386 | " # width of the margin around the previous polynomial to search\n", 387 | " margin = 100\n", 388 | " # Grab activated pixels\n", 389 | " nonzero = binary_warped.nonzero()\n", 390 | " nonzeroy = np.array(nonzero[0])\n", 391 | " nonzerox = np.array(nonzero[1]) \n", 392 | " ### Set the area of search based on activated x-values ###\n", 393 | " ### within the +/- margin of our polynomial function ###\n", 394 | " left_lane_inds = ((nonzerox > (prev_left_fit[0]*(nonzeroy**2) + prev_left_fit[1]*nonzeroy + \n", 395 | " prev_left_fit[2] - margin)) & (nonzerox < (prev_left_fit[0]*(nonzeroy**2) + \n", 396 | " prev_left_fit[1]*nonzeroy + prev_left_fit[2] + margin))).nonzero()[0]\n", 397 | " right_lane_inds = ((nonzerox > (prev_right_fit[0]*(nonzeroy**2) + prev_right_fit[1]*nonzeroy + \n", 398 | " prev_right_fit[2] - margin)) & (nonzerox < (prev_right_fit[0]*(nonzeroy**2) + \n", 399 | " prev_right_fit[1]*nonzeroy + prev_right_fit[2] + margin))).nonzero()[0]\n", 400 | " # Again, extract left and right line pixel positions\n", 401 | " leftx = nonzerox[left_lane_inds]\n", 402 | " lefty = nonzeroy[left_lane_inds] \n", 403 | " rightx = nonzerox[right_lane_inds]\n", 404 | " righty = nonzeroy[right_lane_inds]\n", 405 | "\n", 406 | " return leftx, lefty, rightx, righty\n", 407 | "\n", 408 | "\n", 409 | "#leftx, lefty, rightx, righty = find_lane_pixels_using_prev_poly(binary_warped)\n", 410 | "#left_fit, right_fit, left_fitx, right_fitx, ploty = fit_poly(binary_warped,leftx, lefty, rightx, righty)\n", 411 | "#out_img = draw_poly_lines(binary_warped, left_fitx, right_fitx, ploty)\n", 412 | "#plt.imshow(out_img)" 413 | ] 414 | }, 415 | { 416 | "cell_type": "code", 417 | "execution_count": 8, 418 | "metadata": {}, 419 | "outputs": [], 420 | "source": [ 421 | "### STEP 6: Calculate Vehicle Position and Curve Radius ###\n", 422 | "\n", 423 | "def measure_curvature_meters(binary_warped, left_fitx, right_fitx, ploty):\n", 424 | " # Define conversions in x and y from pixels space to meters\n", 425 | " ym_per_pix = 30/720 # meters per pixel in y dimension\n", 426 | " xm_per_pix = 3.7/700 # meters per pixel in x dimension\n", 427 | " \n", 428 | " left_fit_cr = np.polyfit(ploty*ym_per_pix, left_fitx*xm_per_pix, 2)\n", 429 | " right_fit_cr = np.polyfit(ploty*ym_per_pix, right_fitx*xm_per_pix, 2)\n", 430 | " # Define y-value where we want radius of curvature\n", 431 | " # We'll choose the maximum y-value, corresponding to the bottom of the image\n", 432 | " y_eval = np.max(ploty)\n", 433 | " \n", 434 | " # Calculation of R_curve (radius of curvature)\n", 435 | " left_curverad = ((1 + (2*left_fit_cr[0]*y_eval*ym_per_pix + left_fit_cr[1])**2)**1.5) / np.absolute(2*left_fit_cr[0])\n", 436 | " right_curverad = ((1 + (2*right_fit_cr[0]*y_eval*ym_per_pix + right_fit_cr[1])**2)**1.5) / np.absolute(2*right_fit_cr[0])\n", 437 | " \n", 438 | " return left_curverad, right_curverad\n", 439 | "\n", 440 | "def measure_position_meters(binary_warped, left_fit, right_fit):\n", 441 | " # Define conversion in x from pixels space to meters\n", 442 | " xm_per_pix = 3.7/700 # meters per pixel in x dimension\n", 443 | " # Choose the y value corresponding to the bottom of the image\n", 444 | " y_max = binary_warped.shape[0]\n", 445 | " # Calculate left and right line positions at the bottom of the image\n", 446 | " left_x_pos = left_fit[0]*y_max**2 + left_fit[1]*y_max + left_fit[2]\n", 447 | " right_x_pos = right_fit[0]*y_max**2 + right_fit[1]*y_max + right_fit[2] \n", 448 | " # Calculate the x position of the center of the lane \n", 449 | " center_lanes_x_pos = (left_x_pos + right_x_pos)//2\n", 450 | " # Calculate the deviation between the center of the lane and the center of the picture\n", 451 | " # The car is assumed to be placed in the center of the picture\n", 452 | " # If the deviation is negative, the car is on the felt hand side of the center of the lane\n", 453 | " veh_pos = ((binary_warped.shape[1]//2) - center_lanes_x_pos) * xm_per_pix \n", 454 | " return veh_pos\n", 455 | "\n", 456 | "#left_curverad, right_curverad = measure_curvature_meters(binary_warped, left_fitx, right_fitx, ploty)\n", 457 | "#print('left curve radius in meters = ', left_curverad)\n", 458 | "#print('right curve radius in meters = ', right_curverad)\n", 459 | "#veh_pos = measure_position_meters(binary_warped, left_fit, right_fit)\n", 460 | "#print('vehicle position relative to center = ', veh_pos)\n" 461 | ] 462 | }, 463 | { 464 | "cell_type": "code", 465 | "execution_count": 9, 466 | "metadata": {}, 467 | "outputs": [], 468 | "source": [ 469 | "### STEP 7: Project Lane Delimitations Back on Image Plane and Add Text for Lane Info ###\n", 470 | "\n", 471 | "def project_lane_info(img, binary_warped, ploty, left_fitx, right_fitx, M_inv, left_curverad, right_curverad, veh_pos):\n", 472 | " # Create an image to draw the lines on\n", 473 | " warp_zero = np.zeros_like(binary_warped).astype(np.uint8)\n", 474 | " color_warp = np.dstack((warp_zero, warp_zero, warp_zero))\n", 475 | " \n", 476 | " # Recast the x and y points into usable format for cv2.fillPoly()\n", 477 | " pts_left = np.array([np.transpose(np.vstack([left_fitx, ploty]))])\n", 478 | " pts_right = np.array([np.flipud(np.transpose(np.vstack([right_fitx, ploty])))])\n", 479 | " pts = np.hstack((pts_left, pts_right))\n", 480 | " \n", 481 | " # Draw the lane onto the warped blank image\n", 482 | " cv2.fillPoly(color_warp, np.int_([pts]), (0,255, 0))\n", 483 | " \n", 484 | " # Warp the blank back to original image space using inverse perspective matrix (Minv)\n", 485 | " newwarp = cv2.warpPerspective(color_warp, M_inv, (img.shape[1], img.shape[0]))\n", 486 | " \n", 487 | " # Combine the result with the original image\n", 488 | " out_img = cv2.addWeighted(img, 1, newwarp, 0.3, 0)\n", 489 | " \n", 490 | " cv2.putText(out_img,'Curve Radius [m]: '+str((left_curverad+right_curverad)/2)[:7],(40,70), cv2.FONT_HERSHEY_COMPLEX_SMALL, 1.6, (255,255,255),2,cv2.LINE_AA)\n", 491 | " cv2.putText(out_img,'Center Offset [m]: '+str(veh_pos)[:7],(40,150), cv2.FONT_HERSHEY_COMPLEX_SMALL, 1.6,(255,255,255),2,cv2.LINE_AA)\n", 492 | " \n", 493 | " return out_img\n", 494 | "\n", 495 | "#new_img = project_lane_info(cv2.cvtColor(img, cv2.COLOR_BGR2RGB), binary_warped, ploty, left_fitx, right_fitx, M_inv, left_curverad, right_curverad, veh_pos)\n", 496 | "\n", 497 | "# Plot the result\n", 498 | "#f, (ax1, ax2) = plt.subplots(1, 2, figsize=(24, 9))\n", 499 | "#f.tight_layout()\n", 500 | "#ax1.imshow(cv2.cvtColor(img, cv2.COLOR_BGR2RGB))\n", 501 | "#ax1.set_title('Original Image', fontsize=20)\n", 502 | "#ax2.imshow(new_img, cmap='gray')\n", 503 | "#ax2.set_title('Image With Lane Marked', fontsize=20)" 504 | ] 505 | }, 506 | { 507 | "cell_type": "code", 508 | "execution_count": 10, 509 | "metadata": {}, 510 | "outputs": [], 511 | "source": [ 512 | "global left_fit_hist \n", 513 | "left_fit_hist = np.array([])\n", 514 | "#print(len(left_fit_hist))\n", 515 | "\n", 516 | "global right_fit_hist \n", 517 | "right_fit_hist = np.array([])\n", 518 | "\n", 519 | "#left_fit = [1.3, 4.5, 6.7]\n", 520 | "#left_fit_hist = np.array(left_fit)\n", 521 | "#print(len(left_fit_hist))\n", 522 | "#left_fit = [2.3, 5.5, 9.7]\n", 523 | "#new_fit = np.array(left_fit)\n", 524 | "#left_fit_hist = np.vstack([left_fit_hist, new_fit])\n", 525 | "#left_fit = [8.3, 3.5, 21.7]\n", 526 | "#new_fit = np.array(left_fit)\n", 527 | "#left_fit_hist = np.vstack([left_fit_hist, new_fit])\n", 528 | "#print(left_fit_hist)\n", 529 | "#print(len(left_fit_hist))\n", 530 | "#prev_left_fit = [np.mean(left_fit_hist[:,0]), np.mean(left_fit_hist[:,1]), np.mean(left_fit_hist[:,2])]\n", 531 | "#left_fit_hist = np.delete(left_fit_hist, 0,0)\n", 532 | "\n", 533 | "#print(left_fit_hist)\n", 534 | "#print(prev_left_fit)" 535 | ] 536 | }, 537 | { 538 | "cell_type": "code", 539 | "execution_count": 11, 540 | "metadata": {}, 541 | "outputs": [ 542 | { 543 | "name": "stdout", 544 | "output_type": "stream", 545 | "text": [ 546 | "[MoviePy] >>>> Building video project_video_output.mp4\n", 547 | "[MoviePy] Writing video project_video_output.mp4\n" 548 | ] 549 | }, 550 | { 551 | "name": "stderr", 552 | "output_type": "stream", 553 | "text": [ 554 | "100%|█████████▉| 1260/1261 [04:04<00:00, 5.11it/s]\n" 555 | ] 556 | }, 557 | { 558 | "name": "stdout", 559 | "output_type": "stream", 560 | "text": [ 561 | "[MoviePy] Done.\n", 562 | "[MoviePy] >>>> Video ready: project_video_output.mp4 \n", 563 | "\n", 564 | "CPU times: user 1min 47s, sys: 15.9 s, total: 2min 2s\n", 565 | "Wall time: 4min 7s\n" 566 | ] 567 | } 568 | ], 569 | "source": [ 570 | "### STEP 8: Lane Finding Pipeline on Video ###\n", 571 | "\n", 572 | "def lane_finding_pipeline(img):\n", 573 | " global left_fit_hist \n", 574 | " global right_fit_hist\n", 575 | " global prev_left_fit\n", 576 | " global prev_right_fit\n", 577 | " binary_thresh = binary_thresholded(img)\n", 578 | " binary_warped, M_inv = warp(binary_thresh)\n", 579 | " #out_img = np.dstack((binary_thresh, binary_thresh, binary_thresh))*255\n", 580 | " if (len(left_fit_hist) == 0):\n", 581 | " leftx, lefty, rightx, righty = find_lane_pixels_using_histogram(binary_warped)\n", 582 | " left_fit, right_fit, left_fitx, right_fitx, ploty = fit_poly(binary_warped,leftx, lefty, rightx, righty)\n", 583 | " # Store fit in history\n", 584 | " left_fit_hist = np.array(left_fit)\n", 585 | " new_left_fit = np.array(left_fit)\n", 586 | " left_fit_hist = np.vstack([left_fit_hist, new_left_fit])\n", 587 | " right_fit_hist = np.array(right_fit)\n", 588 | " new_right_fit = np.array(right_fit)\n", 589 | " right_fit_hist = np.vstack([right_fit_hist, new_right_fit])\n", 590 | " else:\n", 591 | " prev_left_fit = [np.mean(left_fit_hist[:,0]), np.mean(left_fit_hist[:,1]), np.mean(left_fit_hist[:,2])]\n", 592 | " prev_right_fit = [np.mean(right_fit_hist[:,0]), np.mean(right_fit_hist[:,1]), np.mean(right_fit_hist[:,2])]\n", 593 | " leftx, lefty, rightx, righty = find_lane_pixels_using_prev_poly(binary_warped)\n", 594 | " if (len(lefty) == 0 or len(righty) == 0):\n", 595 | " leftx, lefty, rightx, righty = find_lane_pixels_using_histogram(binary_warped)\n", 596 | " left_fit, right_fit, left_fitx, right_fitx, ploty = fit_poly(binary_warped,leftx, lefty, rightx, righty)\n", 597 | " \n", 598 | " # Add new values to history\n", 599 | " new_left_fit = np.array(left_fit)\n", 600 | " left_fit_hist = np.vstack([left_fit_hist, new_left_fit])\n", 601 | " new_right_fit = np.array(right_fit)\n", 602 | " right_fit_hist = np.vstack([right_fit_hist, new_right_fit])\n", 603 | " \n", 604 | " # Remove old values from history\n", 605 | " if (len(left_fit_hist) > 10):\n", 606 | " left_fit_hist = np.delete(left_fit_hist, 0,0)\n", 607 | " right_fit_hist = np.delete(right_fit_hist, 0,0)\n", 608 | " \n", 609 | " left_curverad, right_curverad = measure_curvature_meters(binary_warped, left_fitx, right_fitx, ploty)\n", 610 | " #measure_curvature_meters(binary_warped, left_fitx, right_fitx, ploty)\n", 611 | " veh_pos = measure_position_meters(binary_warped, left_fit, right_fit) \n", 612 | " out_img = project_lane_info(img, binary_warped, ploty, left_fitx, right_fitx, M_inv, left_curverad, right_curverad, veh_pos)\n", 613 | " return out_img\n", 614 | "\n", 615 | "video_output = 'project_video_output.mp4'\n", 616 | "clip1 = VideoFileClip(\"project_video.mp4\")\n", 617 | "output_clip = clip1.fl_image(lane_finding_pipeline)\n", 618 | "%time output_clip.write_videofile(video_output, audio=False)\n" 619 | ] 620 | } 621 | ], 622 | "metadata": { 623 | "kernelspec": { 624 | "display_name": "Python 3", 625 | "language": "python", 626 | "name": "python3" 627 | }, 628 | "language_info": { 629 | "codemirror_mode": { 630 | "name": "ipython", 631 | "version": 3 632 | }, 633 | "file_extension": ".py", 634 | "mimetype": "text/x-python", 635 | "name": "python", 636 | "nbconvert_exporter": "python", 637 | "pygments_lexer": "ipython3", 638 | "version": "3.6.3" 639 | } 640 | }, 641 | "nbformat": 4, 642 | "nbformat_minor": 2 643 | } 644 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016-2018 Udacity, Inc. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Advanced Lane Detection 2 | 3 | Computer Vision algorithm to compute road curvature and lane vehicle offset using OpenCV Image Processing, Camera Calibration, Perspective Transform, Color Masks, Sobels and Polynomial Fit. 4 | 5 | ![GIF](output_images/advanced_lane_finding.gif) 6 | 7 | 8 | The *Advanced Lane Finding* project is a step further from [Lane Lines Detection](https://github.com/OanaGaskey/Lane-Lines-Detection) in identifying the geometry of the road ahead. 9 | 10 | Using a video recording of highway driving, this project's goal is to compute the radius of the curvature of the road. Curved roads are a more challenging task than straight ones. To correctly compute the curvature, the lane lines need to be identified but on top of that, the images needs to be undistorted. Image transformation is necessary for camera calibration and for perspective transform to obtain a bird's eye view of the road. 11 | 12 | This project is implemented in Python and uses OpenCV image processing library. The source code can be found in the `AdvancedLaneFinding.ipynb` Jupyter Notebook file above. 13 | The starter code for this project is provided by Udacity and can be found [here](https://github.com/udacity/CarND-Advanced-Lane-Lines). 14 | 15 | 16 | 17 | ## Camera Calibration 18 | 19 | [Optic distortion](https://en.wikipedia.org/wiki/Distortion_(optics)) is a physical phenomenon that occurs in image recording, in which straight lines are projected as slightly curved ones when perceived through camera lenses. The highway driving video is recorded using the front facing camera on the car and the images are distorted. The distortion coefficients are specific to each camera and can be calculated using known geometrical forms. 20 | 21 | Chessboard images captured with the embedded camera are provided in `camera_cal` folder. The advantage of these images is that they have high contrast and known geometry. The images provided present 9 * 6 corners to work with. 22 | 23 | ``` 24 | # Object points are real world points, here a 3D coordinates matrix is generated 25 | # z coordinates are 0 and x, y are equidistant as it is known that the chessboard is made of identical squares 26 | objp = np.zeros((6*9,3), np.float32) 27 | objp[:,:2] = np.mgrid[0:9,0:6].T.reshape(-1,2) 28 | ``` 29 | 30 | Object points are set based on the common understanding that in a chess board pattern, all squares are equal. This implies that object points will have x and y coordinates generated from grid indexes, and z is always 0. The image points represent the corresponding object points found in the image using OpenCV’s function `findChessboardCorners`. 31 | 32 | ``` 33 | # Convert to grayscale 34 | gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) 35 | 36 | # Find the chessboard corners 37 | nx = 9 38 | ny = 6 39 | ret, corners = cv2.findChessboardCorners(gray, (nx, ny), None) 40 | ``` 41 | 42 | After scanning through all the images, the image point list has enough data to compare against the object points in order to compute camera matrix and distortion coefficients. This leads to an accurate camera matrix and distortion coefficients identification using ‘calibrateCamera’ function. 43 | 44 | ``` 45 | ret, mtx, dist, rvecs, tvecs = cv2.calibrateCamera(objpoints, imgpoints, gray.shape[::-1], None, None) 46 | undist = cv2.undistort(img, mtx, dist, None, mtx) 47 | ``` 48 | 49 | OpenCV `undistort` function is used to transform the images using the camera matrix and distortion coefficients. 50 | 51 | ![undistorted_chessboard](output_images/undistorted_chessboard.JPG) 52 | 53 | The result of the camera calibration technique is visible when comparing these pictures. While on the chessboard picture the distortion is more obvious, on the road picture it's more subtle. Nevertheless, an undistorted picture would lead to an incorrect road curvature calculation. 54 | 55 | ![undistorted_road](output_images/undistorted_road.JPG) 56 | 57 | 58 | 59 | ## Perspective Transform from Camera Angle to Bird's Eye View 60 | 61 | To calucluate curvature, the ideal perspective is a bird's eye view. This means that the road is perceived from above, instead of at an angle through the vehicle's windshield. 62 | 63 | This perspective transform is computed using a straight lane scenario and prior common knowledge that the lane lines are in fact parallel. Source and destination points are identified directly from the image for the perspective transform. 64 | 65 | ![ending_points](output_images/ending_points.JPG) 66 | 67 | ``` 68 | #Source points taken from images with straight lane lines, these are to become parallel after the warp transform 69 | src = np.float32([ 70 | (190, 720), # bottom-left corner 71 | (596, 447), # top-left corner 72 | (685, 447), # top-right corner 73 | (1125, 720) # bottom-right corner 74 | ]) 75 | 76 | # Destination points are to be parallel, taking into account the image size 77 | dst = np.float32([ 78 | [offset, img_size[1]], # bottom-left corner 79 | [offset, 0], # top-left corner 80 | [img_size[0]-offset, 0], # top-right corner 81 | [img_size[0]-offset, img_size[1]] # bottom-right corner 82 | ]) 83 | 84 | ``` 85 | 86 | OpenCV provides perspective transform functions to calculate the transformation matrix for the images given the source and destination points. Using `warpPerspective` function, the bird's eye view perspective transform is performed. 87 | 88 | ``` 89 | # Calculate the transformation matrix and it's inverse transformation 90 | M = cv2.getPerspectiveTransform(src, dst) 91 | M_inv = cv2.getPerspectiveTransform(dst, src) 92 | warped = cv2.warpPerspective(undist, M, img_size) 93 | ``` 94 | 95 | ![warp_perspective](output_images/warp_perspective.JPG) 96 | 97 | 98 | 99 | ## Process Binary Thresholded Images 100 | 101 | The objective is to process the image in such a way that the lane line pixels are preserved and easily differentiated from the road. Four transformations are applied and then combined. 102 | 103 | The first transformation takes the `x sobel` on the gray-scaled image. This represents the derivative in the x direction and helps detect lines that tend to be vertical. Only the values above a minimum threshold are kept. 104 | 105 | ``` 106 | # Transform image to gray scale 107 | gray_img =cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) 108 | 109 | # Apply sobel (derivative) in x direction, this is usefull to detect lines that tend to be vertical 110 | sobelx = cv2.Sobel(gray_img, cv2.CV_64F, 1, 0) 111 | abs_sobelx = np.absolute(sobelx) 112 | 113 | # Scale result to 0-255 114 | scaled_sobel = np.uint8(255*abs_sobelx/np.max(abs_sobelx)) 115 | sx_binary = np.zeros_like(scaled_sobel) 116 | 117 | # Keep only derivative values that are in the margin of interest 118 | sx_binary[(scaled_sobel >= 30) & (scaled_sobel <= 255)] = 1 119 | ``` 120 | 121 | The second transformation selects the white pixels in the gray scaled image. White is defined by values between 200 and 255 which were picked using trial and error on the given pictures. 122 | 123 | ``` 124 | # Detect pixels that are white in the grayscale image 125 | white_binary = np.zeros_like(gray_img) 126 | white_binary[(gray_img > 200) & (gray_img <= 255)] = 1 127 | ``` 128 | 129 | The third transformation is on the saturation component using the HLS colorspace. This is particularly important to detect yellow lines on light concrete road. 130 | 131 | ``` 132 | # Convert image to HLS 133 | hls = cv2.cvtColor(img, cv2.COLOR_BGR2HLS) 134 | H = hls[:,:,0] 135 | S = hls[:,:,2] 136 | sat_binary = np.zeros_like(S) 137 | 138 | # Detect pixels that have a high saturation value 139 | sat_binary[(S > 90) & (S <= 255)] = 1 140 | ``` 141 | 142 | The fourth transformation is on the hue component with values from 10 to 25, which were identified as corresponding to yellow. 143 | 144 | ``` 145 | hue_binary = np.zeros_like(H) 146 | 147 | # Detect pixels that are yellow using the hue component 148 | hue_binary[(H > 10) & (H <= 25)] = 1 149 | ``` 150 | 151 | 152 | ![binary_thr](output_images/binary_thr.JPG) 153 | 154 | 155 | 156 | ## Lane Line Detection Using Histogram 157 | 158 | The lane line detection is performed on binary thresholded images that have already been undistorted and warped. Initially a histogram is computed on the image. This means that the pixel values are summed on each column to detect the most probable x position of left and right lane lines. 159 | 160 | ``` 161 | # Take a histogram of the bottom half of the image 162 | histogram = np.sum(binary_warped[binary_warped.shape[0]//2:,:], axis=0) 163 | 164 | # Find the peak of the left and right halves of the histogram 165 | # These will be the starting point for the left and right lines 166 | midpoint = np.int(histogram.shape[0]//2) 167 | leftx_base = np.argmax(histogram[:midpoint]) 168 | rightx_base = np.argmax(histogram[midpoint:]) + midpoint 169 | ``` 170 | 171 | Starting with these base positions on the bottom of the image, the sliding window method is applied going upwards searching for line pixels. Lane pixels are considered when the x and y coordinates are within the area defined by the window. When enough pixels are detected to be confident they are part of a line, their average position is computed and kept as starting point for the next upward window. 172 | 173 | ``` 174 | # Choose the number of sliding windows 175 | nwindows = 9 176 | 177 | # Set the width of the windows +/- margin 178 | margin = 100 179 | 180 | # Set minimum number of pixels found to recenter window 181 | minpix = 50 182 | 183 | # Identify window boundaries in x and y (and right and left) 184 | win_y_low = binary_warped.shape[0] - (window+1)*window_height 185 | win_y_high = binary_warped.shape[0] - window*window_height 186 | win_xleft_low = leftx_current - margin 187 | win_xleft_high = leftx_current + margin 188 | win_xright_low = rightx_current - margin 189 | win_xright_high = rightx_current + margin 190 | 191 | # Identify the nonzero pixels in x and y within the window # 192 | good_left_inds = ((nonzeroy >= win_y_low) & (nonzeroy < win_y_high) & 193 | (nonzerox >= win_xleft_low) & (nonzerox < win_xleft_high)).nonzero()[0] 194 | good_right_inds = ((nonzeroy >= win_y_low) & (nonzeroy < win_y_high) & 195 | (nonzerox >= win_xright_low) & (nonzerox < win_xright_high)).nonzero()[0] 196 | 197 | # Append these indices to the lists 198 | left_lane_inds.append(good_left_inds) 199 | right_lane_inds.append(good_right_inds) 200 | ``` 201 | 202 | All these pixels are put together in a list of their x and y coordinates. This is done symmetrically on both lane lines. `leftx`, `lefty`, `rightx`, `righty` pixel positions are returned from the function and afterwards, a second-degree polynomial is fitted on each left and right side to find the best line fit of the selected pixels. 203 | 204 | ``` 205 | # Fit a second order polynomial to each with np.polyfit() ### 206 | left_fit = np.polyfit(lefty, leftx, 2) 207 | right_fit = np.polyfit(righty, rightx, 2) 208 | ``` 209 | 210 | Here, the identified left and right line pixels are marked in red and blue respectively. The second degree polynomial is traced on the resulting image. 211 | 212 | ![fit_poly](output_images/fit_poly.JPG) 213 | 214 | 215 | 216 | ## Detection of Lane Lines Based on Previous Cycle 217 | 218 | To speed up the lane line search from one video frame to the next, information from the previous cycle is used. It is more likely that the next image will have lane lines in proximity to the previous lane lines. This is where the polynomial fit for the left line and right line of the previous image are used to define the searching area. 219 | 220 | The sliding window method is still used, but instead of starting with the histogram’s peak points, the search is conducted along the previous lines with a given margin for the window’s width. 221 | 222 | ``` 223 | # Set the area of search based on activated x-values within the +/- margin of our polynomial function 224 | left_lane_inds = ((nonzerox > (prev_left_fit[0]*(nonzeroy**2) + prev_left_fit[1]*nonzeroy + 225 | prev_left_fit[2] - margin)) & (nonzerox < (prev_left_fit[0]*(nonzeroy**2) + 226 | prev_left_fit[1]*nonzeroy + prev_left_fit[2] + margin))).nonzero()[0] 227 | right_lane_inds = ((nonzerox > (prev_right_fit[0]*(nonzeroy**2) + prev_right_fit[1]*nonzeroy + 228 | prev_right_fit[2] - margin)) & (nonzerox < (prev_right_fit[0]*(nonzeroy**2) + 229 | prev_right_fit[1]*nonzeroy + prev_right_fit[2] + margin))).nonzero()[0] 230 | 231 | # Again, extract left and right line pixel positions 232 | leftx = nonzerox[left_lane_inds] 233 | lefty = nonzeroy[left_lane_inds] 234 | rightx = nonzerox[right_lane_inds] 235 | righty = nonzeroy[right_lane_inds] 236 | ``` 237 | 238 | The search returns `leftx`, `lefty`, `rightx`, `righty` pixel coordinates that are fitted with a second degree polynomial function for each left and right side. 239 | 240 | 241 | ![prev_poly](output_images/prev_poly.JPG) 242 | 243 | 244 | 245 | ## Calculate Vehicle Position and Curve Radius 246 | 247 | To calculate the radius and the vehicle's position on the road in meters, scaling factors are needed to convert from pixels. The corresponding scaling values are 30 meters to 720 pixels in the y direction and 3.7 meters to 700 pixels in the x dimension. 248 | 249 | A polynomial fit is used to make the conversion. Using the x coordinates of the aligned pixels from the fitted line of each right and left lane line, the conversion factors are applied and polynomial fit is performed on each. 250 | 251 | ``` 252 | # Define conversions in x and y from pixels space to meters 253 | ym_per_pix = 30/720 # meters per pixel in y dimension 254 | xm_per_pix = 3.7/700 # meters per pixel in x dimension 255 | 256 | left_fit_cr = np.polyfit(ploty*ym_per_pix, left_fitx*xm_per_pix, 2) 257 | right_fit_cr = np.polyfit(ploty*ym_per_pix, right_fitx*xm_per_pix, 2) 258 | 259 | # Define y-value where we want radius of curvature 260 | # We'll choose the maximum y-value, corresponding to the bottom of the image 261 | y_eval = np.max(ploty) 262 | 263 | # Calculation of R_curve (radius of curvature) 264 | left_curverad = ((1 + (2*left_fit_cr[0]*y_eval*ym_per_pix + left_fit_cr[1])**2)**1.5) / np.absolute(2*left_fit_cr[0]) 265 | right_curverad = ((1 + (2*right_fit_cr[0]*y_eval*ym_per_pix + right_fit_cr[1])**2)**1.5) / np.absolute(2*right_fit_cr[0]) 266 | ``` 267 | 268 | The radius of the curvature is calculated using the y point at the bottom of the image. To calculate the vehicle’s position, the polynomial fit in pixels is used to determine the x position of the left and right lane corresponding to the y at the bottom of the image. 269 | 270 | ``` 271 | # Define conversion in x from pixels space to meters 272 | xm_per_pix = 3.7/700 # meters per pixel in x dimension 273 | 274 | # Choose the y value corresponding to the bottom of the image 275 | y_max = binary_warped.shape[0] 276 | 277 | # Calculate left and right line positions at the bottom of the image 278 | left_x_pos = left_fit[0]*y_max**2 + left_fit[1]*y_max + left_fit[2] 279 | right_x_pos = right_fit[0]*y_max**2 + right_fit[1]*y_max + right_fit[2] 280 | 281 | # Calculate the x position of the center of the lane 282 | center_lanes_x_pos = (left_x_pos + right_x_pos)//2 283 | 284 | # Calculate the deviation between the center of the lane and the center of the picture 285 | # The car is assumed to be placed in the center of the picture 286 | # If the deviation is negative, the car is on the felt hand side of the center of the lane 287 | veh_pos = ((binary_warped.shape[1]//2) - center_lanes_x_pos) * xm_per_pix 288 | ``` 289 | 290 | The average of these two values gives the position of the center of the lane in the image. If the lane’s center is shifted to the right by `nbp` amount of pixels that means that the car is shifted to the left by `nbp * xm_per_pix meters`. This is based on the assumption that the camera is mounted on the central axis of the vehicle. 291 | 292 | 293 | 294 | ## Video Output 295 | 296 | Check out the restulting video! You can download the video here: [project_video_output.mp4](project_video_output.mp4) 297 | -------------------------------------------------------------------------------- /camera_cal/calibration1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicedaddy/Advanced-Lane-Detection/b5dd6373e1883e3bccfee4bd28dfbec8ae05a8ef/camera_cal/calibration1.jpg -------------------------------------------------------------------------------- /camera_cal/calibration10.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicedaddy/Advanced-Lane-Detection/b5dd6373e1883e3bccfee4bd28dfbec8ae05a8ef/camera_cal/calibration10.jpg -------------------------------------------------------------------------------- /camera_cal/calibration11.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicedaddy/Advanced-Lane-Detection/b5dd6373e1883e3bccfee4bd28dfbec8ae05a8ef/camera_cal/calibration11.jpg -------------------------------------------------------------------------------- /camera_cal/calibration12.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicedaddy/Advanced-Lane-Detection/b5dd6373e1883e3bccfee4bd28dfbec8ae05a8ef/camera_cal/calibration12.jpg -------------------------------------------------------------------------------- /camera_cal/calibration13.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicedaddy/Advanced-Lane-Detection/b5dd6373e1883e3bccfee4bd28dfbec8ae05a8ef/camera_cal/calibration13.jpg -------------------------------------------------------------------------------- /camera_cal/calibration14.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicedaddy/Advanced-Lane-Detection/b5dd6373e1883e3bccfee4bd28dfbec8ae05a8ef/camera_cal/calibration14.jpg -------------------------------------------------------------------------------- /camera_cal/calibration15.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicedaddy/Advanced-Lane-Detection/b5dd6373e1883e3bccfee4bd28dfbec8ae05a8ef/camera_cal/calibration15.jpg -------------------------------------------------------------------------------- /camera_cal/calibration16.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicedaddy/Advanced-Lane-Detection/b5dd6373e1883e3bccfee4bd28dfbec8ae05a8ef/camera_cal/calibration16.jpg -------------------------------------------------------------------------------- /camera_cal/calibration17.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicedaddy/Advanced-Lane-Detection/b5dd6373e1883e3bccfee4bd28dfbec8ae05a8ef/camera_cal/calibration17.jpg -------------------------------------------------------------------------------- /camera_cal/calibration18.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicedaddy/Advanced-Lane-Detection/b5dd6373e1883e3bccfee4bd28dfbec8ae05a8ef/camera_cal/calibration18.jpg -------------------------------------------------------------------------------- /camera_cal/calibration19.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicedaddy/Advanced-Lane-Detection/b5dd6373e1883e3bccfee4bd28dfbec8ae05a8ef/camera_cal/calibration19.jpg -------------------------------------------------------------------------------- /camera_cal/calibration2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicedaddy/Advanced-Lane-Detection/b5dd6373e1883e3bccfee4bd28dfbec8ae05a8ef/camera_cal/calibration2.jpg -------------------------------------------------------------------------------- /camera_cal/calibration20.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicedaddy/Advanced-Lane-Detection/b5dd6373e1883e3bccfee4bd28dfbec8ae05a8ef/camera_cal/calibration20.jpg -------------------------------------------------------------------------------- /camera_cal/calibration3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicedaddy/Advanced-Lane-Detection/b5dd6373e1883e3bccfee4bd28dfbec8ae05a8ef/camera_cal/calibration3.jpg -------------------------------------------------------------------------------- /camera_cal/calibration4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicedaddy/Advanced-Lane-Detection/b5dd6373e1883e3bccfee4bd28dfbec8ae05a8ef/camera_cal/calibration4.jpg -------------------------------------------------------------------------------- /camera_cal/calibration5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicedaddy/Advanced-Lane-Detection/b5dd6373e1883e3bccfee4bd28dfbec8ae05a8ef/camera_cal/calibration5.jpg -------------------------------------------------------------------------------- /camera_cal/calibration6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicedaddy/Advanced-Lane-Detection/b5dd6373e1883e3bccfee4bd28dfbec8ae05a8ef/camera_cal/calibration6.jpg -------------------------------------------------------------------------------- /camera_cal/calibration7.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicedaddy/Advanced-Lane-Detection/b5dd6373e1883e3bccfee4bd28dfbec8ae05a8ef/camera_cal/calibration7.jpg -------------------------------------------------------------------------------- /camera_cal/calibration8.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicedaddy/Advanced-Lane-Detection/b5dd6373e1883e3bccfee4bd28dfbec8ae05a8ef/camera_cal/calibration8.jpg -------------------------------------------------------------------------------- /camera_cal/calibration9.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicedaddy/Advanced-Lane-Detection/b5dd6373e1883e3bccfee4bd28dfbec8ae05a8ef/camera_cal/calibration9.jpg -------------------------------------------------------------------------------- /camera_cal_undistorted/calibration10.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicedaddy/Advanced-Lane-Detection/b5dd6373e1883e3bccfee4bd28dfbec8ae05a8ef/camera_cal_undistorted/calibration10.jpg -------------------------------------------------------------------------------- /camera_cal_undistorted/calibration11.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicedaddy/Advanced-Lane-Detection/b5dd6373e1883e3bccfee4bd28dfbec8ae05a8ef/camera_cal_undistorted/calibration11.jpg -------------------------------------------------------------------------------- /camera_cal_undistorted/calibration12.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicedaddy/Advanced-Lane-Detection/b5dd6373e1883e3bccfee4bd28dfbec8ae05a8ef/camera_cal_undistorted/calibration12.jpg -------------------------------------------------------------------------------- /camera_cal_undistorted/calibration13.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicedaddy/Advanced-Lane-Detection/b5dd6373e1883e3bccfee4bd28dfbec8ae05a8ef/camera_cal_undistorted/calibration13.jpg -------------------------------------------------------------------------------- /camera_cal_undistorted/calibration14.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicedaddy/Advanced-Lane-Detection/b5dd6373e1883e3bccfee4bd28dfbec8ae05a8ef/camera_cal_undistorted/calibration14.jpg -------------------------------------------------------------------------------- /camera_cal_undistorted/calibration15.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicedaddy/Advanced-Lane-Detection/b5dd6373e1883e3bccfee4bd28dfbec8ae05a8ef/camera_cal_undistorted/calibration15.jpg -------------------------------------------------------------------------------- /camera_cal_undistorted/calibration16.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicedaddy/Advanced-Lane-Detection/b5dd6373e1883e3bccfee4bd28dfbec8ae05a8ef/camera_cal_undistorted/calibration16.jpg -------------------------------------------------------------------------------- /camera_cal_undistorted/calibration2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicedaddy/Advanced-Lane-Detection/b5dd6373e1883e3bccfee4bd28dfbec8ae05a8ef/camera_cal_undistorted/calibration2.jpg -------------------------------------------------------------------------------- /camera_cal_undistorted/calibration3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicedaddy/Advanced-Lane-Detection/b5dd6373e1883e3bccfee4bd28dfbec8ae05a8ef/camera_cal_undistorted/calibration3.jpg -------------------------------------------------------------------------------- /camera_cal_undistorted/calibration6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicedaddy/Advanced-Lane-Detection/b5dd6373e1883e3bccfee4bd28dfbec8ae05a8ef/camera_cal_undistorted/calibration6.jpg -------------------------------------------------------------------------------- /camera_cal_undistorted/calibration7.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicedaddy/Advanced-Lane-Detection/b5dd6373e1883e3bccfee4bd28dfbec8ae05a8ef/camera_cal_undistorted/calibration7.jpg -------------------------------------------------------------------------------- /camera_cal_undistorted/calibration8.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicedaddy/Advanced-Lane-Detection/b5dd6373e1883e3bccfee4bd28dfbec8ae05a8ef/camera_cal_undistorted/calibration8.jpg -------------------------------------------------------------------------------- /challenge_video.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicedaddy/Advanced-Lane-Detection/b5dd6373e1883e3bccfee4bd28dfbec8ae05a8ef/challenge_video.mp4 -------------------------------------------------------------------------------- /example_writeup.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicedaddy/Advanced-Lane-Detection/b5dd6373e1883e3bccfee4bd28dfbec8ae05a8ef/example_writeup.pdf -------------------------------------------------------------------------------- /examples/.ipynb_checkpoints/example-checkpoint.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [], 3 | "metadata": {}, 4 | "nbformat": 4, 5 | "nbformat_minor": 1 6 | } 7 | -------------------------------------------------------------------------------- /examples/binary_combo_example.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicedaddy/Advanced-Lane-Detection/b5dd6373e1883e3bccfee4bd28dfbec8ae05a8ef/examples/binary_combo_example.jpg -------------------------------------------------------------------------------- /examples/color_fit_lines.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicedaddy/Advanced-Lane-Detection/b5dd6373e1883e3bccfee4bd28dfbec8ae05a8ef/examples/color_fit_lines.jpg -------------------------------------------------------------------------------- /examples/example.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "## Advanced Lane Finding Project\n", 8 | "\n", 9 | "The goals / steps of this project are the following:\n", 10 | "\n", 11 | "* Compute the camera calibration matrix and distortion coefficients given a set of chessboard images.\n", 12 | "* Apply a distortion correction to raw images.\n", 13 | "* Use color transforms, gradients, etc., to create a thresholded binary image.\n", 14 | "* Apply a perspective transform to rectify binary image (\"birds-eye view\").\n", 15 | "* Detect lane pixels and fit to find the lane boundary.\n", 16 | "* Determine the curvature of the lane and vehicle position with respect to center.\n", 17 | "* Warp the detected lane boundaries back onto the original image.\n", 18 | "* Output visual display of the lane boundaries and numerical estimation of lane curvature and vehicle position.\n", 19 | "\n", 20 | "---\n", 21 | "## First, I'll compute the camera calibration using chessboard images" 22 | ] 23 | }, 24 | { 25 | "cell_type": "code", 26 | "execution_count": 3, 27 | "metadata": { 28 | "collapsed": true 29 | }, 30 | "outputs": [], 31 | "source": [ 32 | "import numpy as np\n", 33 | "import cv2\n", 34 | "import glob\n", 35 | "import matplotlib.pyplot as plt\n", 36 | "%matplotlib qt\n", 37 | "\n", 38 | "# prepare object points, like (0,0,0), (1,0,0), (2,0,0) ....,(6,5,0)\n", 39 | "objp = np.zeros((6*9,3), np.float32)\n", 40 | "objp[:,:2] = np.mgrid[0:9,0:6].T.reshape(-1,2)\n", 41 | "\n", 42 | "# Arrays to store object points and image points from all the images.\n", 43 | "objpoints = [] # 3d points in real world space\n", 44 | "imgpoints = [] # 2d points in image plane.\n", 45 | "\n", 46 | "# Make a list of calibration images\n", 47 | "images = glob.glob('../camera_cal/calibration*.jpg')\n", 48 | "\n", 49 | "# Step through the list and search for chessboard corners\n", 50 | "for fname in images:\n", 51 | " img = cv2.imread(fname)\n", 52 | " gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)\n", 53 | "\n", 54 | " # Find the chessboard corners\n", 55 | " ret, corners = cv2.findChessboardCorners(gray, (9,6),None)\n", 56 | "\n", 57 | " # If found, add object points, image points\n", 58 | " if ret == True:\n", 59 | " objpoints.append(objp)\n", 60 | " imgpoints.append(corners)\n", 61 | "\n", 62 | " # Draw and display the corners\n", 63 | " img = cv2.drawChessboardCorners(img, (9,6), corners, ret)\n", 64 | " cv2.imshow('img',img)\n", 65 | " cv2.waitKey(500)\n", 66 | "\n", 67 | "cv2.destroyAllWindows()" 68 | ] 69 | }, 70 | { 71 | "cell_type": "markdown", 72 | "metadata": {}, 73 | "source": [ 74 | "## And so on and so forth..." 75 | ] 76 | }, 77 | { 78 | "cell_type": "code", 79 | "execution_count": null, 80 | "metadata": { 81 | "collapsed": true 82 | }, 83 | "outputs": [], 84 | "source": [] 85 | } 86 | ], 87 | "metadata": { 88 | "anaconda-cloud": {}, 89 | "kernelspec": { 90 | "display_name": "Python [conda root]", 91 | "language": "python", 92 | "name": "conda-root-py" 93 | }, 94 | "language_info": { 95 | "codemirror_mode": { 96 | "name": "ipython", 97 | "version": 3 98 | }, 99 | "file_extension": ".py", 100 | "mimetype": "text/x-python", 101 | "name": "python", 102 | "nbconvert_exporter": "python", 103 | "pygments_lexer": "ipython3", 104 | "version": "3.5.2" 105 | } 106 | }, 107 | "nbformat": 4, 108 | "nbformat_minor": 1 109 | } 110 | -------------------------------------------------------------------------------- /examples/example.py: -------------------------------------------------------------------------------- 1 | def warper(img, src, dst): 2 | 3 | # Compute and apply perpective transform 4 | img_size = (img.shape[1], img.shape[0]) 5 | M = cv2.getPerspectiveTransform(src, dst) 6 | warped = cv2.warpPerspective(img, M, img_size, flags=cv2.INTER_NEAREST) # keep same size as input image 7 | 8 | return warped 9 | -------------------------------------------------------------------------------- /examples/example_output.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicedaddy/Advanced-Lane-Detection/b5dd6373e1883e3bccfee4bd28dfbec8ae05a8ef/examples/example_output.jpg -------------------------------------------------------------------------------- /examples/undistort_output.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicedaddy/Advanced-Lane-Detection/b5dd6373e1883e3bccfee4bd28dfbec8ae05a8ef/examples/undistort_output.png -------------------------------------------------------------------------------- /examples/warped_straight_lines.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicedaddy/Advanced-Lane-Detection/b5dd6373e1883e3bccfee4bd28dfbec8ae05a8ef/examples/warped_straight_lines.jpg -------------------------------------------------------------------------------- /harder_challenge_video.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicedaddy/Advanced-Lane-Detection/b5dd6373e1883e3bccfee4bd28dfbec8ae05a8ef/harder_challenge_video.mp4 -------------------------------------------------------------------------------- /output_images/advanced_lane_finding.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicedaddy/Advanced-Lane-Detection/b5dd6373e1883e3bccfee4bd28dfbec8ae05a8ef/output_images/advanced_lane_finding.gif -------------------------------------------------------------------------------- /output_images/binary_thr.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicedaddy/Advanced-Lane-Detection/b5dd6373e1883e3bccfee4bd28dfbec8ae05a8ef/output_images/binary_thr.JPG -------------------------------------------------------------------------------- /output_images/ending_points.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicedaddy/Advanced-Lane-Detection/b5dd6373e1883e3bccfee4bd28dfbec8ae05a8ef/output_images/ending_points.JPG -------------------------------------------------------------------------------- /output_images/fit_poly.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicedaddy/Advanced-Lane-Detection/b5dd6373e1883e3bccfee4bd28dfbec8ae05a8ef/output_images/fit_poly.JPG -------------------------------------------------------------------------------- /output_images/lines_test.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicedaddy/Advanced-Lane-Detection/b5dd6373e1883e3bccfee4bd28dfbec8ae05a8ef/output_images/lines_test.jpg -------------------------------------------------------------------------------- /output_images/prev_poly.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicedaddy/Advanced-Lane-Detection/b5dd6373e1883e3bccfee4bd28dfbec8ae05a8ef/output_images/prev_poly.JPG -------------------------------------------------------------------------------- /output_images/save_output_here.txt: -------------------------------------------------------------------------------- 1 | Please save your output images to this folder and include a description in your README of what each image shows. -------------------------------------------------------------------------------- /output_images/undistorted_chessboard.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicedaddy/Advanced-Lane-Detection/b5dd6373e1883e3bccfee4bd28dfbec8ae05a8ef/output_images/undistorted_chessboard.JPG -------------------------------------------------------------------------------- /output_images/undistorted_road.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicedaddy/Advanced-Lane-Detection/b5dd6373e1883e3bccfee4bd28dfbec8ae05a8ef/output_images/undistorted_road.JPG -------------------------------------------------------------------------------- /output_images/warp_perspective.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicedaddy/Advanced-Lane-Detection/b5dd6373e1883e3bccfee4bd28dfbec8ae05a8ef/output_images/warp_perspective.JPG -------------------------------------------------------------------------------- /output_images/warped1_test.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicedaddy/Advanced-Lane-Detection/b5dd6373e1883e3bccfee4bd28dfbec8ae05a8ef/output_images/warped1_test.jpg -------------------------------------------------------------------------------- /output_images/warped2_test.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicedaddy/Advanced-Lane-Detection/b5dd6373e1883e3bccfee4bd28dfbec8ae05a8ef/output_images/warped2_test.jpg -------------------------------------------------------------------------------- /project_video.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicedaddy/Advanced-Lane-Detection/b5dd6373e1883e3bccfee4bd28dfbec8ae05a8ef/project_video.mp4 -------------------------------------------------------------------------------- /project_video_output.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicedaddy/Advanced-Lane-Detection/b5dd6373e1883e3bccfee4bd28dfbec8ae05a8ef/project_video_output.mp4 -------------------------------------------------------------------------------- /set_git.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Make sure you have the latest version of the repo 4 | echo 5 | git pull 6 | echo 7 | 8 | # Ask the user for login details 9 | read -p 'Git repository url: ' upstreamVar 10 | read -p 'Git Username: ' userVar 11 | read -p 'Git email: ' emailVar 12 | 13 | echo 14 | echo Thank you $userVar!, we now have your credentials 15 | echo for upstream $upstreamVar. You must supply your password for each push. 16 | echo 17 | 18 | echo setting up git 19 | 20 | git config --global user.name $userVar 21 | git config --global user.email $emailVar 22 | git remote set-url origin $upstreamVar 23 | echo 24 | 25 | echo Please verify remote: 26 | git remote -v 27 | echo 28 | 29 | echo Please verify your credentials: 30 | echo username: `git config user.name` 31 | echo email: `git config user.email` -------------------------------------------------------------------------------- /test_images/straight_lines1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicedaddy/Advanced-Lane-Detection/b5dd6373e1883e3bccfee4bd28dfbec8ae05a8ef/test_images/straight_lines1.jpg -------------------------------------------------------------------------------- /test_images/straight_lines2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicedaddy/Advanced-Lane-Detection/b5dd6373e1883e3bccfee4bd28dfbec8ae05a8ef/test_images/straight_lines2.jpg -------------------------------------------------------------------------------- /test_images/test1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicedaddy/Advanced-Lane-Detection/b5dd6373e1883e3bccfee4bd28dfbec8ae05a8ef/test_images/test1.jpg -------------------------------------------------------------------------------- /test_images/test2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicedaddy/Advanced-Lane-Detection/b5dd6373e1883e3bccfee4bd28dfbec8ae05a8ef/test_images/test2.jpg -------------------------------------------------------------------------------- /test_images/test3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicedaddy/Advanced-Lane-Detection/b5dd6373e1883e3bccfee4bd28dfbec8ae05a8ef/test_images/test3.jpg -------------------------------------------------------------------------------- /test_images/test4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicedaddy/Advanced-Lane-Detection/b5dd6373e1883e3bccfee4bd28dfbec8ae05a8ef/test_images/test4.jpg -------------------------------------------------------------------------------- /test_images/test5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicedaddy/Advanced-Lane-Detection/b5dd6373e1883e3bccfee4bd28dfbec8ae05a8ef/test_images/test5.jpg -------------------------------------------------------------------------------- /test_images/test6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicedaddy/Advanced-Lane-Detection/b5dd6373e1883e3bccfee4bd28dfbec8ae05a8ef/test_images/test6.jpg -------------------------------------------------------------------------------- /writeup_template.md: -------------------------------------------------------------------------------- 1 | ## Writeup Template 2 | 3 | ### You can use this file as a template for your writeup if you want to submit it as a markdown file, but feel free to use some other method and submit a pdf if you prefer. 4 | 5 | --- 6 | 7 | **Advanced Lane Finding Project** 8 | 9 | The goals / steps of this project are the following: 10 | 11 | * Compute the camera calibration matrix and distortion coefficients given a set of chessboard images. 12 | * Apply a distortion correction to raw images. 13 | * Use color transforms, gradients, etc., to create a thresholded binary image. 14 | * Apply a perspective transform to rectify binary image ("birds-eye view"). 15 | * Detect lane pixels and fit to find the lane boundary. 16 | * Determine the curvature of the lane and vehicle position with respect to center. 17 | * Warp the detected lane boundaries back onto the original image. 18 | * Output visual display of the lane boundaries and numerical estimation of lane curvature and vehicle position. 19 | 20 | [//]: # (Image References) 21 | 22 | [image1]: ./examples/undistort_output.png "Undistorted" 23 | [image2]: ./test_images/test1.jpg "Road Transformed" 24 | [image3]: ./examples/binary_combo_example.jpg "Binary Example" 25 | [image4]: ./examples/warped_straight_lines.jpg "Warp Example" 26 | [image5]: ./examples/color_fit_lines.jpg "Fit Visual" 27 | [image6]: ./examples/example_output.jpg "Output" 28 | [video1]: ./project_video.mp4 "Video" 29 | 30 | ## [Rubric](https://review.udacity.com/#!/rubrics/571/view) Points 31 | 32 | ### Here I will consider the rubric points individually and describe how I addressed each point in my implementation. 33 | 34 | --- 35 | 36 | ### Writeup / README 37 | 38 | #### 1. Provide a Writeup / README that includes all the rubric points and how you addressed each one. You can submit your writeup as markdown or pdf. [Here](https://github.com/udacity/CarND-Advanced-Lane-Lines/blob/master/writeup_template.md) is a template writeup for this project you can use as a guide and a starting point. 39 | 40 | You're reading it! 41 | 42 | ### Camera Calibration 43 | 44 | #### 1. Briefly state how you computed the camera matrix and distortion coefficients. Provide an example of a distortion corrected calibration image. 45 | 46 | The code for this step is contained in the first code cell of the IPython notebook located in "./examples/example.ipynb" (or in lines # through # of the file called `some_file.py`). 47 | 48 | I start by preparing "object points", which will be the (x, y, z) coordinates of the chessboard corners in the world. Here I am assuming the chessboard is fixed on the (x, y) plane at z=0, such that the object points are the same for each calibration image. Thus, `objp` is just a replicated array of coordinates, and `objpoints` will be appended with a copy of it every time I successfully detect all chessboard corners in a test image. `imgpoints` will be appended with the (x, y) pixel position of each of the corners in the image plane with each successful chessboard detection. 49 | 50 | I then used the output `objpoints` and `imgpoints` to compute the camera calibration and distortion coefficients using the `cv2.calibrateCamera()` function. I applied this distortion correction to the test image using the `cv2.undistort()` function and obtained this result: 51 | 52 | ![alt text][image1] 53 | 54 | ### Pipeline (single images) 55 | 56 | #### 1. Provide an example of a distortion-corrected image. 57 | 58 | To demonstrate this step, I will describe how I apply the distortion correction to one of the test images like this one: 59 | ![alt text][image2] 60 | 61 | #### 2. Describe how (and identify where in your code) you used color transforms, gradients or other methods to create a thresholded binary image. Provide an example of a binary image result. 62 | 63 | I used a combination of color and gradient thresholds to generate a binary image (thresholding steps at lines # through # in `another_file.py`). Here's an example of my output for this step. (note: this is not actually from one of the test images) 64 | 65 | ![alt text][image3] 66 | 67 | #### 3. Describe how (and identify where in your code) you performed a perspective transform and provide an example of a transformed image. 68 | 69 | The code for my perspective transform includes a function called `warper()`, which appears in lines 1 through 8 in the file `example.py` (output_images/examples/example.py) (or, for example, in the 3rd code cell of the IPython notebook). The `warper()` function takes as inputs an image (`img`), as well as source (`src`) and destination (`dst`) points. I chose the hardcode the source and destination points in the following manner: 70 | 71 | ```python 72 | src = np.float32( 73 | [[(img_size[0] / 2) - 55, img_size[1] / 2 + 100], 74 | [((img_size[0] / 6) - 10), img_size[1]], 75 | [(img_size[0] * 5 / 6) + 60, img_size[1]], 76 | [(img_size[0] / 2 + 55), img_size[1] / 2 + 100]]) 77 | dst = np.float32( 78 | [[(img_size[0] / 4), 0], 79 | [(img_size[0] / 4), img_size[1]], 80 | [(img_size[0] * 3 / 4), img_size[1]], 81 | [(img_size[0] * 3 / 4), 0]]) 82 | ``` 83 | 84 | This resulted in the following source and destination points: 85 | 86 | | Source | Destination | 87 | |:-------------:|:-------------:| 88 | | 585, 460 | 320, 0 | 89 | | 203, 720 | 320, 720 | 90 | | 1127, 720 | 960, 720 | 91 | | 695, 460 | 960, 0 | 92 | 93 | I verified that my perspective transform was working as expected by drawing the `src` and `dst` points onto a test image and its warped counterpart to verify that the lines appear parallel in the warped image. 94 | 95 | ![alt text][image4] 96 | 97 | #### 4. Describe how (and identify where in your code) you identified lane-line pixels and fit their positions with a polynomial? 98 | 99 | Then I did some other stuff and fit my lane lines with a 2nd order polynomial kinda like this: 100 | 101 | ![alt text][image5] 102 | 103 | #### 5. Describe how (and identify where in your code) you calculated the radius of curvature of the lane and the position of the vehicle with respect to center. 104 | 105 | I did this in lines # through # in my code in `my_other_file.py` 106 | 107 | #### 6. Provide an example image of your result plotted back down onto the road such that the lane area is identified clearly. 108 | 109 | I implemented this step in lines # through # in my code in `yet_another_file.py` in the function `map_lane()`. Here is an example of my result on a test image: 110 | 111 | ![alt text][image6] 112 | 113 | --- 114 | 115 | ### Pipeline (video) 116 | 117 | #### 1. Provide a link to your final video output. Your pipeline should perform reasonably well on the entire project video (wobbly lines are ok but no catastrophic failures that would cause the car to drive off the road!). 118 | 119 | Here's a [link to my video result](./project_video.mp4) 120 | 121 | --- 122 | 123 | ### Discussion 124 | 125 | #### 1. Briefly discuss any problems / issues you faced in your implementation of this project. Where will your pipeline likely fail? What could you do to make it more robust? 126 | 127 | Here I'll talk about the approach I took, what techniques I used, what worked and why, where the pipeline might fail and how I might improve it if I were going to pursue this project further. 128 | --------------------------------------------------------------------------------