-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathAdvanced_LaneLines.py
534 lines (388 loc) · 16.4 KB
/
Advanced_LaneLines.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
import matplotlib.pyplot as plt
import matplotlib.image as im
import numpy as np
import cv2
import glob
import pickle
from numpy.linalg import inv
from moviepy.editor import VideoFileClip
import grad_color_combo as thresh #Binary Thresholding file
#To update the parameters of each frame
class Line():
def __init__(self):
# was the line detected in the last iteration?
self.detected = False
# coefficients values of the last N fits of the line
self.recent_fits = []
#polynomial coefficients averaged over the last n iterations
self.best_fit = None
#polynomial coefficients for the most recent fit
self.current_fit = [np.array([False])]
#radius of curvature of the line in some units
self.radius_of_curvature = None
#Undistort the image
def undistortImage (img, cmt,dist):
#Undistort the image
undist = cv2.undistort(img,cmt,dist)
return undist
#Perform Perspective Transform
def BirdsEye_view (img):
h = img.shape[0]
w = img.shape[1]
#Posotion as a ratio of the width and heigh of image
x1_pos_ratio = 0.15
x2_pos_ratio = 0.45
y_pos_ratio = 0.64
src = np.array ([
[int(x1_pos_ratio*w),h-1],
[int(x2_pos_ratio*w),int(y_pos_ratio*h)],
[int((1-x2_pos_ratio)*w),int(y_pos_ratio*h)],
[int((1-x1_pos_ratio)*w),h-1]
],np.float32)
#destination points selection
x_ratio = 0.25
dst = np.array ([
[x_ratio*w,h-1],
[x_ratio*w,0],
[((1-x_ratio)*w),0],
[((1-x_ratio)*w),h-1]
],np.float32)
#Plot selected points (if needed)
# plt.imshow(img,cmap='gray')
# plt.plot(250,670,'.')
# plt.plot(615,435,'.')
# plt.plot(661 , 435,'.')
# plt.plot(1041 , 670,'.')
# plt.title('Selected Points')
# plt.show()
#Perspective transform
M = cv2.getPerspectiveTransform(src,dst)
warped = cv2.warpPerspective(img,M,(w,h))
return M,warped
#Obtain the color and gradient thresholded binary image
def thresh_combo (img):
#Combined Gradient
grad = thresh.grad_combined(img)
#HLS threshold
hls_select = thresh.hlsThresh(img, (130,255))
#Combined Color and Gradient
combined = np.zeros_like(grad)
combined [(grad == 1) | (hls_select ==1)] = 1
return combined
#Find the lanes pixels using sliding window
def find_lane_pixels (binaryImg):
#Find the nearst part where lanes are almost vertical
bottom_half = binaryImg[int(binaryImg.shape[0]/2):,:]
#Compute histogram for the y axis (height)
histogram = np.sum(bottom_half,axis = 0)
# Visualize histogram (if needed)
# plt.plot(histo)
# plt.show()
#Create image to visualize the binary_output
color_img = np.dstack((binaryImg,binaryImg,binaryImg))
#Split the histogram
midpoint = np.int(histogram.shape[0]//2)
#left peak index
leftx_base = np.argmax(histogram[:midpoint])
#right peak index
rightx_base = np.argmax(histogram[midpoint:]) + midpoint
#Window Parameters
N =9 #Number of windows per lane in a frame
margin = 70 #Half window width value
pix_mini = 50 #threshold for window recentering
#Compute window height
window_height = np.int(binaryImg.shape[0]//N)
#relevent non zero pixels indices in the frame
pix_ind = binaryImg.nonzero()
pix_x = np.array(pix_ind[1])
pix_y = np.array(pix_ind[0])
#Window position update for each step
current_leftx = leftx_base
current_rightx = rightx_base
#Lists to hold each lane's pixels indices
left_lane_ind = []
right_lane_ind = []
# Iteration over the N number of windows
for window in range(1,N+1): #from 1 to number of windows
#Construct window dimensions
#Y-axis (common for both lanes)
y_min = binaryImg.shape[0] - window*window_height
y_max = y_min + window_height
#X-axis
leftx_min = current_leftx - margin
leftx_max = current_leftx + margin
rightx_min = current_rightx - margin
rightx_max = current_rightx + margin
#Draw the window
#Left window
cv2.rectangle(color_img,(leftx_min,y_min),(leftx_max,y_max),(0,255,0), 2)
#Right window
cv2.rectangle(color_img,(rightx_min,y_min),(rightx_max,y_max),(0,255,0), 2)
#Extract non zero pixels indecies in-side each window
left_ind = ((pix_y >= y_min) & (pix_y < y_max) &
(pix_x >= leftx_min) & (pix_x < leftx_max)).nonzero()[0]
right_ind = ((pix_y >= y_min) & (pix_y < y_max) &
(pix_x >= rightx_min) & (pix_x < rightx_max)).nonzero()[0]
#Append the new values in left lane , right lane Lists
left_lane_ind.append (left_ind)
right_lane_ind.append(right_ind)
#Check for window recentering
if len(left_ind) > pix_mini:
#Recenter the left window at the mean position of the pixels
current_leftx = np.int(np.mean(pix_x [left_ind]))
if len(right_ind) > pix_mini:
#Recenter the right window at the mean position of the pixels
current_rightx = np.int(np.mean(pix_x [right_ind]))
# Concatenate the arrays of indices
left_lane_ind = np.concatenate(left_lane_ind)
right_lane_ind = np.concatenate(right_lane_ind)
# Left and Right lane pixels positions
leftx = pix_x [left_lane_ind]
lefty = pix_y [left_lane_ind]
rightx = pix_x [right_lane_ind]
righty = pix_y [right_lane_ind]
return leftx, lefty, rightx, righty, color_img
#find lane line by searching previous fitted lines
def search_around_poly(binaryImg,prev_left_fit,prev_right_fit):
#Margin to search around fitted poly
margin = 100
#relevent non zero pixels indices in the frame
pix_ind = binaryImg.nonzero()
pix_x = np.array(pix_ind[1])
pix_y = np.array(pix_ind[0])
#Extract non zero pixels indecies around each fitted poly
left_ind = ((pix_x > (prev_left_fit[0]*pix_y**2) + (prev_left_fit[1]*pix_y) + prev_left_fit[2] - margin)&\
(pix_x < (prev_left_fit[0]*pix_y**2) + (prev_left_fit[1]*pix_y) + prev_left_fit[2] + margin))
right_ind = ((pix_x > (prev_right_fit[0]*pix_y**2) + (prev_right_fit[1]*pix_y) + prev_right_fit[2] - margin)&\
(pix_x < (prev_right_fit[0]*pix_y**2) + (prev_right_fit[1]*pix_y) + prev_right_fit[2] + margin))
# Left and Right lane pixels positions
new_leftx = pix_x [left_ind]
new_lefty = pix_y [left_ind]
new_rightx = pix_x [right_ind]
new_righty = pix_y [right_ind]
return new_leftx, new_lefty, new_rightx, new_righty
#Fit the lanes in a polynomial (default is to fit using sliding window)
def lane_fitting (binaryImg,prev_available ='False',leftline = None ,rightline = None):
#Check for previous poly fits
if (prev_available): #Previous lane fit found
#Find new lane pixels position
leftx, lefty, rightx, righty = search_around_poly(binaryImg, leftline, rightline)
#Fit 2nd degree polynomial
left_fit = np.polyfit(lefty,leftx,2)
right_fit = np.polyfit(righty,rightx,2)
#Plot the fitted polynomials
#Y-axis
y = np.linspace(0, binaryImg.shape[0]-1, binaryImg.shape[0])
# X = A y**2 + By + C
#X-axis for left and right lanes
left_polyx = left_fit[0]*(y**2) + left_fit[1]*y + left_fit[2]
right_polyx = right_fit[0]*(y**2) + right_fit[1]*y + right_fit[2]
return y, left_polyx ,right_polyx , None
else : #First frame (use sliding windows)
# Find lane pixels position
leftx, lefty, rightx, righty,img = find_lane_pixels(binaryImg)
#Fit 2nd degree polynomial
left_fit = np.polyfit(lefty,leftx,2)
right_fit = np.polyfit(righty,rightx,2)
#Plot the fitted polynomials
#Y-axis
y = np.linspace(0, binaryImg.shape[0]-1, binaryImg.shape[0])
# X = A y**2 + By + C
#X-axis for left and right lanes
left_polyx = left_fit[0]*(y**2) + left_fit[1]*y + left_fit[2]
right_polyx = right_fit[0]*(y**2) + right_fit[1]*y + right_fit[2]
#Visualization
#Visualize the pixels inside the window
img [lefty , leftx] = [255,0,0]
img [righty,rightx] = [0,0,255]
return y, left_polyx ,right_polyx , img
#Compute curvature radius and offset from center
def compute_curvatureAndOffset(width,y,left_fit,right_fit):
#from pixel to meters conversion
ym_per_pix = 30/720 # meters per pixel in y dimension
xm_per_pix = 3.7/608 # meters per pixel in x dimension
#################
###1. Offset ####
#################
#Get x position for left and right lanes at the max bottom of the image
xleft = left_fit[len(left_fit)-1]
xright = right_fit[len(right_fit)-1]
#Lane Center
lane_center = ((xright-xleft)/2) + xleft
img_center = width/2
#offset (in pixels)
offset = img_center - lane_center
#offset (in meters)
offset_m = offset*xm_per_pix
#####################
#### 2. Curvature ###
#####################
#Fit the polynomial in meters
left_fit_m = np.polyfit(y*ym_per_pix , left_fit*xm_per_pix , 2)
right_fit_m = np.polyfit(y*ym_per_pix , right_fit*xm_per_pix , 2)
#Calculate for the largest y value (bottom half)
y_eval = np.max(y)
#Compute the radius of the curve at the max y point using curvature radius eqn.
#Calculation of the left lane
left_curvR = (np.sqrt((1+((2*left_fit_m[0]*y_eval*ym_per_pix)+left_fit_m[1])**2)**3))/(np.absolute(2*left_fit_m[0]))
#Calculation of the right lane
right_curvR = (np.sqrt((1+((2*right_fit_m[0]*y_eval*ym_per_pix)+right_fit_m[1])**2)**3))/(np.absolute(2*right_fit_m[0]))
return left_curvR, right_curvR,offset_m
#Draw the results on the undistort image
def drawResults (undistImg, warpedImg, mtx,poly_y , left_fitx, right_fitx):
# Color image to draw the lines on
gray_warp = np.zeros_like(warpedImg).astype(np.uint8)
color_warp = np.dstack((gray_warp, gray_warp, gray_warp))
#init points for creating poly
pts_left = np.array([np.transpose(np.vstack([left_fitx, poly_y]))])
pts_right = np.array([np.flipud(np.transpose(np.vstack([right_fitx, poly_y])))])
poly_pts = np.hstack((pts_left, pts_right))
#Draw the lane in warped image (in blue)
cv2.fillPoly(color_warp, np.int_([poly_pts]), (0,0, 255))
#Warp the image back to the original perspective (using the transform matrix inverse(mtx))
unwarp = cv2.warpPerspective(color_warp, inv(mtx), (undistImg.shape[1], undistImg.shape[0]))
#Result
result = cv2.addWeighted(undistImg, 1, unwarp, 0.3, 0)
return result
#Image Lane detection pipline
def Lane_detection(img):
# #Visualize Original Image if needed
# plt.figure(1)
# plt.imshow(img)
# plt.title('Original Image')
####################
### 1. undistort ###
####################
undist_img = undistortImage(img, cmt, dist)
# #Visualize and save image if needed
# plt.figure(2)
# plt.imshow(undist_img)
# plt.title('Undistorted Image')
#
# #Save Image
# plt.imsave('output_images/undistorted.jpg', undist_img)
###############################################
### 2. Gradient and Color Thresholding ########
###############################################
binary_img = thresh_combo(undist_img)
# #Visualize and save image if needed
# plt.figure(3)
# plt.imshow(binary_img,cmap='gray')
# plt.title('Gradient and Color Thresholded Binary Image')
#
# #Save Image
# plt.imsave('output_images/combo_thresh.jpg', binary_img,cmap='gray')
######################################
#### 3. Bird's-Eye View Transform ####
######################################
mtx,warped_img = BirdsEye_view (binary_img)
# #Visualize and save image if needed
# plt.figure(4)
# plt.imshow(warped_img,cmap='gray')
# plt.title('Warped Birds-Eye View Image')
#
# #Save Image
# plt.imsave('output_images/BirdEye_view.jpg', warped_img,cmap='gray')
################################
###### 4. Lane Fitting #########
################################
#If previous fit is found
if (leftline_obj.detected) and (rightline_obj.detected):
poly_y , leftline_obj.current_fit , rightline_obj.current_fit , fit_img = lane_fitting(warped_img ,True ,leftline_obj.current_fit ,rightline_obj.current_fit)
else:
poly_y , leftline_obj.current_fit , rightline_obj.current_fit , fit_img = lane_fitting(warped_img ,False)
#Activate the fit detected variable
if len(leftline_obj.current_fit) > 0 & len(poly_y) >0:
leftline_obj.detected = True
if len(rightline_obj.current_fit) > 0 & len(poly_y) > 0:
rightline_obj.detected = True
else :
leftline_obj.detected = False
rightline_obj.detected = False
#Append the current coeeficients fits values
leftline_obj.recent_fits .append(leftline_obj.current_fit)
rightline_obj.recent_fits .append(rightline_obj.current_fit)
#################################
#### 4.1 Smoothing the curve ####
#################################
#Number of frames to be averaged
Number_of_frames = 5
#delete the oldest value in the fits list if the length > Number of frames
if len(leftline_obj.recent_fits) > Number_of_frames:
leftline_obj.recent_fits.pop(0)
if len(rightline_obj.recent_fits) > Number_of_frames:
rightline_obj.recent_fits.pop(0)
#Average the last N coefficients
leftline_obj.best_fit = np.average((leftline_obj.recent_fits),axis = 0)
rightline_obj.best_fit = np.average((rightline_obj.recent_fits),axis = 0)
# #Visualize and save image if needed
# plt.figure(5)
# plt.imshow(fit_img,cmap='gray')
# plt.title('Fitted Lane Lines')
# plt.plot(leftline_obj.best_fit, poly_y, color='yellow')
# plt.plot(rightline_obj.best_fit,poly_y, color='yellow')
#
# #Save Image
# plt.savefig('output_images/Fitted_LaneLines.jpg')
#######################################################
###### 6. Compute Curvature and Cetner Offset #########
#######################################################
#Store the new curvature radius values and offset
leftline_obj.radius_of_curvature , rightline_obj.radius_of_curvature , offset = compute_curvatureAndOffset(undist_img.shape[1],poly_y,leftline_obj.best_fit,rightline_obj.best_fit)
#Compute the mean of left and right radius
mean_curvR = (leftline_obj.radius_of_curvature + rightline_obj.radius_of_curvature)/2
############################################################
###### 7. Draw lines on origianl undistorted Image #########
############################################################
final_img = drawResults (undist_img, warped_img, mtx, poly_y, leftline_obj.best_fit, rightline_obj.best_fit)
#Print the output values on the image
text1 = "Radius of Curvature = "+str(int(mean_curvR))+"m"
if offset < 0:
text2 = "Vehicle is "+str(round(-offset, 2))+"m Left of Center"
else:
text2 = "Vehicle is "+str(round(offset, 2))+"m Right of Center"
pos_1 = (100,50)
pos_2 = (100,120)
font = cv2.FONT_HERSHEY_SIMPLEX
#Print curvature value
cv2.putText(final_img, text1, pos_1,font,2,(255,255,255),2,cv2.LINE_AA)
#print offset value
cv2.putText(final_img, text2, pos_2,font,2,(255,255,255),2,cv2.LINE_AA)
# #Visualize and save image if needed
# plt.figure(6)
# plt.imshow(final_img)
# plt.title('Lane Detection')
#
# #Save Image
# plt.imsave('output_images/final_res.jpg', final_img)
# plt.show()
return final_img
#################
### Load Data ###
#################
#Load the camera calibration variables
dist_pickle = pickle.load( open( "camera_cal/calib_mtx.p", "rb" ) )
cmt = dist_pickle["cmt"]
dist = dist_pickle["dist"]
########################################
### Init left and right lines object####
########################################
leftline_obj = Line()
rightline_obj = Line()
############################
### Image lane detection ###
############################
# #Load test image
# testimg = im.imread('test_images/test4.jpg')
#
# #Image Lane detection
# final_img = Lane_detection(testimg)
############################
### Video lane detection ###
############################
#Load video
in_clip = VideoFileClip('test_vids/project_video.mp4')
#Process video
out_clip = in_clip.fl_image(Lane_detection)
#Save the result video
out_clip.write_videofile( 'output_vids/project_video.mp4', audio=False)