Introduction

The goals / steps of this project are the following:

  • Compute the camera calibration matrix and distortion coefficients given a set of chessboard images.
  • Apply a distortion correction to raw images.
  • Use color transforms, gradients, etc., to create a thresholded binary image.
  • Apply a perspective transform to rectify binary image ("birds-eye view").
  • Detect lane pixels and fit to find the lane boundary.
  • Determine the curvature of the lane and vehicle position with respect to center.
  • Warp the detected lane boundaries back onto the original image.
  • Output visual display of the lane boundaries and numerical estimation of lane curvature and vehicle position.

Here is the my [project code](https://github.com/jaeoh2/self-driving-car-nd/CarND-Advanced-Lane-Lines-P4/Advanced-Lane-Lines.ipynb


In [1]:
import numpy as np
import cv2
import os,sys
import glob
import matplotlib.pyplot as plt
import matplotlib.image as mpimg

%matplotlib inline

In [2]:
camera_cal = './camera_cal/'
output_imgs = './output_images/'
test_imgs = './test_images/'

In [3]:
testimg = cv2.imread(camera_cal + 'calibration5.jpg')
nx = 9
ny = 6
imgSize = (testimg.shape[1], testimg.shape[0])
plt.imshow(testimg)
print("image size :{}".format(imgSize))


image size :(1280, 720)

Camera Calibration

OpenCV functions were used to calculate the camera calibration matrix and distortion coefficients.

  • Find the chessboard corners on images provided from project using findChessboardCorners function.
  • Based on corners objects, we get camera calibration matrix using calibrateCamera function.
  • Transform to the undistorted images using undistort function with camera calibration matrix.

In [4]:
# prepare object points, like (0,0,0), (1,0,0), (2,0,0) ....,(6,5,0)
objp = np.zeros((nx*ny,3), np.float32)
objp[:,:2] = np.mgrid[0:nx, 0:ny].T.reshape(-1,2)

# Arrays to store object points and image points from all the images.
objpoints = [] # 3d points in real world space
imgpoints = [] # 2d points in image plane.

# Make a list of calibration images

#Make a list of calibration images
images = glob.glob(camera_cal + 'calibration*.jpg')

In [5]:
for idx, fname in enumerate(images):
    img = cv2.imread(fname)
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    
    #Find the chessboard corners
    ret, corners = cv2.findChessboardCorners(gray, (nx,ny), None)
    
    # If found, add obj points, img points
    if ret == True:
        objpoints.append(objp)
        imgpoints.append(corners)
        
        cv2.drawChessboardCorners(img, (nx,ny), corners, ret)
        plt.imshow(img)



In [6]:
testImg = mpimg.imread(images[5])
imgSize = (img.shape[1], img.shape[0])

ret, mtx, dist, rvecs, tvecs = cv2.calibrateCamera(objpoints, imgpoints, imgSize, None, None)
testDst = cv2.undistort(testImg, mtx, dist, None, mtx)

plt.figure(dpi=300)
plt.subplot(1,2,1), plt.imshow(testImg), plt.title('original')
plt.subplot(1,2,2), plt.imshow(testDst), plt.title('undistorted')


Out[6]:
(<matplotlib.axes._subplots.AxesSubplot at 0x7f8aceab3588>,
 <matplotlib.image.AxesImage at 0x7f8acea46ac8>,
 <matplotlib.text.Text at 0x7f8acea276a0>)

In [7]:
#undistortion image
img = mpimg.imread(test_imgs + "test1.jpg")
#img = mpimg.imread(test_imgs + "straight_lines2.jpg")

imgSize = (img.shape[1], img.shape[0])

ret, mtx, dist, rvecs, tvecs = cv2.calibrateCamera(objpoints, imgpoints, imgSize, None, None)
dstImage = cv2.undistort(img, mtx, dist, None, mtx)

plt.figure(dpi=300)
plt.subplot(1,2,1), plt.imshow(img), plt.title('original')
plt.subplot(1,2,2), plt.imshow(dstImage), plt.title('undistorted')


Out[7]:
(<matplotlib.axes._subplots.AxesSubplot at 0x7f8ac802fcc0>,
 <matplotlib.image.AxesImage at 0x7f8ab87b5390>,
 <matplotlib.text.Text at 0x7f8ab8794160>)

Color space and Threshold

I combined several threshold methods(i.e., HLS color transform, absoulute/magnitude gradients and direction gradients) to create a binary images containing the lane pixels. There is no ground truth here, I tested threshold method with manual parameter tunning. When I calculate the gradient, I directly used converted color space image besides using color thresholded binary image. I tested Gray, HLS and HSV color space, and using HLS color space's S-channel was the best for me.

Here are the some results below:


In [8]:
# Color space transform
def select_yellow(img, lower, upper):
    hsv = cv2.cvtColor(img, cv2.COLOR_RGB2HSV)
    #lower = np.array([20,60,60])
    #upper = np.array([38,174,250])
    mask = cv2.inRange(hsv, lower, upper)
    
    return mask

def select_white(img, lower, upper):
    #lower = np.array([202,202,202])
    #upper = np.array([255,255,255])
    mask = cv2.inRange(img, lower, upper)
    
    return mask

def sobel_x_thresh(image, sobel_kernel=3, thresh=(20,100)):
    hls = cv2.cvtColor(image, cv2.COLOR_RGB2HLS)
    
    sobelx1 = cv2.Sobel(hls[:,:,1], cv2.CV_64F, 1,0, ksize=sobel_kernel)
    sobelx2 = cv2.Sobel(hls[:,:,2], cv2.CV_64F, 1,0, ksize=sobel_kernel)
    
    #Scale to 8-bit (0 - 255) and convert to type = np.uint8
    scaled_sobelx1 = np.uint8(255*sobelx1/ np.max(sobelx1))
    scaled_sobelx2 = np.uint8(255*sobelx2/ np.max(sobelx2))

    #Create a binary mask where mag thresholds are met
    binary_outputx1 = np.zeros_like(scaled_sobelx1)
    binary_outputx1[(scaled_sobelx1 >= thresh[0]) & (scaled_sobelx1 <= thresh[1])] = 1

    binary_outputx2 = np.zeros_like(scaled_sobelx2)
    binary_outputx2[(scaled_sobelx2 >= thresh[0]) & (scaled_sobelx2 <= thresh[1])] = 1

    binary_output = np.zeros_like(scaled_sobelx1)
    binary_output[(binary_outputx1 ==1) | (binary_outputx2 ==1)]=1
    # 6) Return this mask as your binary_output image
    return binary_output

def abs_sobel_thresh(image, orient='x', sobel_kernel=3, thresh=(0, 255)):
    # Calculate directional gradient
    gray = cv2.cvtColor(image, cv2.COLOR_RGB2GRAY)
    hls = cv2.cvtColor(image, cv2.COLOR_RGB2HLS)
    hsv = cv2.cvtColor(image, cv2.COLOR_RGB2HSV)
    
    #cvtImg = gray
    cvtImg = hls[:,:,2] #s-channel
    #cvtImg = hsv[:,:,1] #s-channel
    
    if orient=='x':
        sobel = cv2.Sobel(cvtImg, cv2.CV_64F, 1, 0, ksize=sobel_kernel)
    else:
        sobel = cv2.Sobel(cvtImg, cv2.CV_64F, 0, 1, ksize=sobel_kernel)
        
    abs_sobel = np.absolute(sobel)
    scaled_sobel = np.uint8(255*abs_sobel / np.max(abs_sobel))
    grad_binary = np.zeros_like(scaled_sobel)
    grad_binary[(scaled_sobel >= thresh[0]) & (scaled_sobel <= thresh[1])] = 1
    # Apply threshold
    return grad_binary

def mag_thresh(image, sobel_kernel=3, mag_thresh=(0, 255)):
    # Calculate gradient magnitude
    gray = cv2.cvtColor(image, cv2.COLOR_RGB2GRAY)
    hls = cv2.cvtColor(image, cv2.COLOR_RGB2HLS)
    hsv = cv2.cvtColor(image, cv2.COLOR_RGB2HSV)
    
    #cvtImg = gray
    cvtImg = hls[:,:,2] #s-channel
    #cvtImg = hsv[:,:,1] #s-channel
    
    sobelx = cv2.Sobel(cvtImg, cv2.CV_64F, 1, 0, ksize=sobel_kernel)
    sobely = cv2.Sobel(cvtImg, cv2.CV_64F, 0, 1, ksize=sobel_kernel)
    gradmag = np.sqrt(sobelx**2 + sobely**2)
    scale_factor = np.max(gradmag)/255 
    gradmag = (gradmag/scale_factor).astype(np.uint8) 
    # Apply threshold
    mag_binary = np.zeros_like(gradmag)
    mag_binary[(gradmag >= mag_thresh[0]) & (gradmag <= mag_thresh[1])] = 1
    return mag_binary

def dir_threshold(image, sobel_kernel=3, thresh=(0, np.pi/2)):
    # Calculate gradient direction
    gray = cv2.cvtColor(image, cv2.COLOR_RGB2GRAY)
    hls = cv2.cvtColor(image, cv2.COLOR_RGB2HLS)
    hsv = cv2.cvtColor(image, cv2.COLOR_RGB2HSV)
    
    #cvtImg = gray
    cvtImg = hls[:,:,2] #s-channel
    #cvtImg = hsv[:,:,1] #s-channel
    
    sobelx = cv2.Sobel(cvtImg, cv2.CV_64F, 1, 0, ksize=sobel_kernel)
    sobely = cv2.Sobel(cvtImg, cv2.CV_64F, 0, 1, ksize=sobel_kernel)
    absgraddir = np.arctan2(np.absolute(sobely), np.absolute(sobelx))
    # Apply threshold
    dir_binary = np.zeros_like(absgraddir)
    dir_binary[(absgraddir >= thresh[0]) & (absgraddir <= thresh[1])] = 1

    return dir_binary

In [9]:
ksize = 15
thresh = (20,200)
dirThresh = (np.pi/4, np.pi/3)

In [10]:
gradx = abs_sobel_thresh(dstImage, orient='x', sobel_kernel=9, thresh=(80,220))
plt.imshow(gradx,cmap='gray'), plt.title('Gradient X'), plt.axis('off')


Out[10]:
(<matplotlib.image.AxesImage at 0x7f8ab8720c50>,
 <matplotlib.text.Text at 0x7f8ab8760a20>,
 (-0.5, 1279.5, 719.5, -0.5))

In [11]:
grady = abs_sobel_thresh(dstImage, orient='y', sobel_kernel=ksize, thresh=(80,220))
plt.imshow(grady,cmap='gray'), plt.title('Gradient Y'), plt.axis('off')


Out[11]:
(<matplotlib.image.AxesImage at 0x7f8acebfde80>,
 <matplotlib.text.Text at 0x7f8acebb8c18>,
 (-0.5, 1279.5, 719.5, -0.5))

In [12]:
sobelx_binary = sobel_x_thresh(dstImage, sobel_kernel=9, thresh=(80,220))
plt.imshow(sobelx_binary,cmap='gray'), plt.title('sobelx_binary'), plt.axis('off')


Out[12]:
(<matplotlib.image.AxesImage at 0x7f8ab86c91d0>,
 <matplotlib.text.Text at 0x7f8acec704a8>,
 (-0.5, 1279.5, 719.5, -0.5))

In [13]:
mag_binary = mag_thresh(dstImage, sobel_kernel=ksize, mag_thresh=(20,200))
plt.imshow(mag_binary,cmap='gray'), plt.title('Gradient magnitude'), plt.axis('off')


Out[13]:
(<matplotlib.image.AxesImage at 0x7f8ab86cce80>,
 <matplotlib.text.Text at 0x7f8aceb56b70>,
 (-0.5, 1279.5, 719.5, -0.5))

In [14]:
dir_binary = dir_threshold(dstImage, sobel_kernel=ksize, thresh=(0.7,1.3))
plt.imshow(dir_binary,cmap='gray'), plt.title('Gradient direction'), plt.axis('off')


Out[14]:
(<matplotlib.image.AxesImage at 0x7f8ab86b4940>,
 <matplotlib.text.Text at 0x7f8ab8693630>,
 (-0.5, 1279.5, 719.5, -0.5))

In [15]:
yellow_low = np.array([0,100,100])
yellow_high = np.array([50,255,255])
yellow_binary = select_yellow(dstImage, yellow_low, yellow_high)
plt.imshow(yellow_binary, cmap='gray'), plt.title('Yellow threshold'), plt.axis('off')


Out[15]:
(<matplotlib.image.AxesImage at 0x7f8ab861f588>,
 <matplotlib.text.Text at 0x7f8ab85fe278>,
 (-0.5, 1279.5, 719.5, -0.5))

In [16]:
white_low = np.array([190,190,190]) # 202,202,202
white_high = np.array([255,255,255]) # 255,255,255

white_binary = select_white(dstImage, white_low, white_high)
plt.imshow(white_binary, cmap='gray'), plt.title('White threshold'), plt.axis('off')


Out[16]:
(<matplotlib.image.AxesImage at 0x7f8ab8585278>,
 <matplotlib.text.Text at 0x7f8ab85e0f28>,
 (-0.5, 1279.5, 719.5, -0.5))

In [17]:
combined = np.zeros_like(dir_binary)
combined2 = np.zeros_like(dir_binary)
#combined[((gradx == 1) & (grady == 1)) | ((mag_binary == 1) & (dir_binary == 1))] = 1
combined[((sobelx_binary == 1) | (yellow_binary == 1) | (white_binary == 1))] = 1
combined2[(sobelx_binary == 1) | (yellow_binary == 1)] = 1
plt.figure(dpi=200)
plt.subplot(3,1,1),plt.imshow(dstImage),plt.axis('off')
plt.subplot(3,1,2),plt.imshow(combined, cmap='gray'),plt.title("")
plt.subplot(3,1,3),plt.imshow(combined2, cmap='gray'),plt.title("")


Out[17]:
(<matplotlib.axes._subplots.AxesSubplot at 0x7f8ab8572358>,
 <matplotlib.image.AxesImage at 0x7f8ab8482438>,
 <matplotlib.text.Text at 0x7f8ab84e1128>)

Perspective Transform

I used OpenCV function to correctly rectify each image to a bird-eye view.

  • Define the source 4 points of rectangle shape onto the road image. I used the straight-lane image to set perspective source points correctly.
  • Find the perspective transform matrix using cv2.getPerspectiveTransform function.
  • Based on perspective transform matrix, we get warped images using cv2.warpPerspective function.

In [18]:
#set source corners
srcPoint = np.float32([
    [585,460], #585,460
    [200,720], #200,720
    [1108,720],#1108,720
    [710,460]  #710,460
])
#set destinatino corners
dstPoint = np.float32([
    [320,0],
    [320,720],
    [960,720],
    [960,0]    
])

In [19]:
#get PerspectiveTransform
M = cv2.getPerspectiveTransform(srcPoint, dstPoint)
Minv = cv2.getPerspectiveTransform(dstPoint, srcPoint)

warpedImage = cv2.warpPerspective(combined, M, imgSize)

plt.figure(dpi=300)
plt.subplot(3,1,1),plt.imshow(dstImage),
plt.title('Undistorted Image',fontsize=5),plt.axis('off')
x,y = srcPoint.T
plt.scatter(x,y, s=1, color='r')

plt.subplot(3,1,2),plt.imshow(combined, cmap='gray'),
plt.title('Binary Image',fontsize=5),plt.axis('off')
plt.scatter(x,y, s=1, color='r')

plt.subplot(3,1,3),plt.imshow(warpedImage, cmap='gray'),
plt.title('Binary perspective',fontsize=5),plt.axis('off')
x,y = dstPoint.T
plt.scatter(x,y, s=1, color='r')


Out[19]:
<matplotlib.collections.PathCollection at 0x7f8ab8399b00>

Lane detection and Curve Fitting

Here the idea is to take the measurements of where the lane lines are and estimate how much the road is curving and where the vehicle is located with respect to the center of the lane.

  • First I take a histogram along all the columns in the lower half of the images.
  • With this histogram I used the sliding window approach to find the places around the line centers in each discrete windows.
  • 9 windows used. Margin was 100 pixels.
  • Based on each windows center points, It can fit a polynomial to those pixel points.
  • Finally, we can get a smooth poly-fitted curve lines.

In [20]:
hist = np.sum(warpedImage[warpedImage.shape[0]//2:,:], axis=0) 
plt.subplot(2,1,1),plt.imshow(warpedImage, aspect='auto', cmap='gray'),plt.title("Binary perspective"),plt.axis('off')
plt.subplot(2,1,2),plt.plot(hist),plt.title("histogram of lane"),plt.axis('off')


Out[20]:
(<matplotlib.axes._subplots.AxesSubplot at 0x7f8ab87c0ac8>,
 [<matplotlib.lines.Line2D at 0x7f8ab83304a8>],
 <matplotlib.text.Text at 0x7f8ab8310160>,
 (-63.950000000000003, 1342.95, -15.259960937500001, 320.45917968750001))

In [21]:
def sliding_window_lane_finding(img, nwindows=9, margin = 100, minpix = 50, init_frame=False):
    histogram = np.sum(img[img.shape[0]//2:, :], axis=0)
    out_img = (np.dstack((img, img, img))*255).astype(np.uint8)


    midpoint = np.int(histogram.shape[0]//2)
    leftx_base = np.argmax(histogram[:midpoint])
    rightx_base = np.argmax(histogram[midpoint:]) + midpoint

    nwindows = nwindows
    window_height = np.int(img.shape[0]/nwindows)
    nonzero = img.nonzero()
    nonzeroy = np.array(nonzero[0])
    nonzerox = np.array(nonzero[1])
    # Current positions to be updated for each window
    leftx_current = leftx_base
    rightx_current = rightx_base
    # Set the width of the windows +/- margin
    margin = margin
    # Set minimum number of pixels found to recenter window
    minpix = minpix
    # Create empty lists to receive left and right lane pixel indices
    left_lane_inds = []
    right_lane_inds = []
    
    # Step through the windows one by one
    for window in range(nwindows):
        # Identify window boundaries in x and y (and right and left)
        win_y_low = img.shape[0] - (window+1)*window_height
        win_y_high = img.shape[0] - window*window_height
        win_xleft_low = leftx_current - margin
        win_xleft_high = leftx_current + margin
        win_xright_low = rightx_current - margin
        win_xright_high = rightx_current + margin
        # Draw the windows on the visualization image
        cv2.rectangle(out_img,(win_xleft_low,win_y_low),(win_xleft_high,win_y_high),
        (0,255,0), 2) 
        cv2.rectangle(out_img,(win_xright_low,win_y_low),(win_xright_high,win_y_high),
        (0,255,0), 2) 
        # Identify the nonzero pixels in x and y within the window
        good_left_inds = ((nonzeroy >= win_y_low) & (nonzeroy < win_y_high) & 
        (nonzerox >= win_xleft_low) &  (nonzerox < win_xleft_high)).nonzero()[0]
        good_right_inds = ((nonzeroy >= win_y_low) & (nonzeroy < win_y_high) & 
        (nonzerox >= win_xright_low) &  (nonzerox < win_xright_high)).nonzero()[0]
        # Append these indices to the lists
        left_lane_inds.append(good_left_inds)
        right_lane_inds.append(good_right_inds)
        # If you found > minpix pixels, recenter next window on their mean position
        if len(good_left_inds) > minpix:
            leftx_current = np.int(np.mean(nonzerox[good_left_inds]))
        if len(good_right_inds) > minpix:        
            rightx_current = np.int(np.mean(nonzerox[good_right_inds]))
    # Concatenate the arrays of indices
    left_lane_inds = np.concatenate(left_lane_inds)
    right_lane_inds = np.concatenate(right_lane_inds)

    # Extract left and right line pixel positions
    leftx = nonzerox[left_lane_inds]
    lefty = nonzeroy[left_lane_inds] 
    rightx = nonzerox[right_lane_inds]
    righty = nonzeroy[right_lane_inds] 

    # Fit a second order polynomial to each
    left_fit = np.polyfit(lefty, leftx, 2)
    right_fit = np.polyfit(righty, rightx, 2)
    
    out_img[nonzeroy[left_lane_inds], nonzerox[left_lane_inds]] = [255, 0, 0]
    out_img[nonzeroy[right_lane_inds], nonzerox[right_lane_inds]] = [0, 0, 255]

    
    return out_img, left_fit, right_fit

In [22]:
out_img, left_fit, right_fit = sliding_window_lane_finding(
    warpedImage,
    nwindows=9,
    margin = 100,
    minpix = 50,
    init_frame=False)

In [23]:
# Generate x and y values for plotting
ploty = np.linspace(0, warpedImage.shape[0]-1, warpedImage.shape[0] )
left_fitx = left_fit[0]*ploty**2 + left_fit[1]*ploty + left_fit[2]
right_fitx = right_fit[0]*ploty**2 + right_fit[1]*ploty + right_fit[2]

plt.figure(dpi=100)
plt.imshow(out_img)
plt.plot(left_fitx, ploty, color='yellow')
plt.plot(right_fitx, ploty, color='yellow')
plt.xlim(0, 1280)
plt.ylim(720, 0)


Out[23]:
(720, 0)

Calculate Radius of curvature and Position of Vehicle

The radius of curvature at any point x of the function x = f(x) is given as follow: Using this equation for radius of curvature, we can get poly fitted curve's radius. But calculated curve radius based on pixel values, so the radius is not the same as real world values in meters.

To convert radius to physical value from pixel value, we assume that the lane is about 30 meters long and 3.7 meters wide. Finally, we can get physical value of curve radius using those conversion factors.


In [24]:
# Define y-value where we want radius of curvature
# I'll choose the maximum y-value, corresponding to the bottom of the image
y_eval = np.max(ploty)
left_curverad = ((1 + (2*left_fit[0]*y_eval + left_fit[1])**2)**1.5) / np.absolute(2*left_fit[0])
right_curverad = ((1 + (2*right_fit[0]*y_eval + right_fit[1])**2)**1.5) / np.absolute(2*right_fit[0])

In [25]:
left_curverad, right_curverad


Out[25]:
(2720.9516127337042, 3717.5370810760423)

In [26]:
# Convert Radius to physical value from pixel value
# 30 m long and 3.7 m wide
ym_per_pix = 30./720.
xm_per_pix = 3.7/700.

left_fit_cr = np.polyfit(ploty*ym_per_pix, left_fitx*xm_per_pix, 2)
right_fit_cr = np.polyfit(ploty*ym_per_pix, right_fitx*xm_per_pix, 2)

In [27]:
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])
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])

center_point_from_leftfit = left_fit[0]*720**2 + left_fit[1]*720 + left_fit[2]
center_point_from_rightfit = right_fit[0]*720**2 + right_fit[1]*720 + right_fit[2]
center_offset = (1280/2. - (center_point_from_leftfit + center_point_from_rightfit)/2.)*xm_per_pix
  • First we assume that camera is mounted on center of the vehicle.
  • To estimate the center offset from the each lane, we can calculate the y-intercept value from the fitting coefficient.(y = ax^2 + bx + c)
  • Comparing the estimated center position and middle pixel of the image in y-axis, we can find the center offset values.

In [28]:
print("Left CurvRadius: {:.3f}m\nRight CurvRadius: {:.3f}m\nCenter offset: {:.3f}m".format(left_curverad, right_curverad, center_offset))


Left CurvRadius: 891.417m
Right CurvRadius: 1220.376m
Center offset: -0.203m

Warp Back image and overlay

  • The images from the rectified region has been warped back to the original image and plotted to identify the lane boundaries.
  • Bird views, threshold views and curve radius values overlayed onto original image.

In [29]:
# Create an image to draw the lines on
warp_zero = np.zeros_like(warpedImage).astype(np.uint8)
color_warp = np.dstack((warp_zero, warp_zero, warp_zero))

# Recast the x and y points into usable format for cv2.fillPoly()
pts_left = np.array([np.transpose(np.vstack([left_fitx, ploty]))])
pts_right = np.array([np.flipud(np.transpose(np.vstack([right_fitx, ploty])))])
pts = np.hstack((pts_left, pts_right))

# Draw the lane onto the warped blank image
cv2.fillPoly(color_warp, np.int_([pts]), (0,255, 0))

# Warp the blank back to original image space using inverse perspective matrix (Minv)
newwarp = cv2.warpPerspective(color_warp, Minv, (color_warp.shape[1], color_warp.shape[0])) 
# Combine the result with the original image
result = cv2.addWeighted(dstImage, 1, newwarp, 0.3, 0)

plt.figure(dpi=100),plt.imshow(result),plt.title("Unwarped Image")


Out[29]:
(<matplotlib.figure.Figure at 0x7f8ab82e41d0>,
 <matplotlib.image.AxesImage at 0x7f8ab8232240>,
 <matplotlib.text.Text at 0x7f8ab820c908>)

In [30]:
combinedColor = cv2.cvtColor((combined*255).astype(np.uint8), cv2.COLOR_GRAY2BGR)
warpedImageColor = cv2.cvtColor((warpedImage*255).astype(np.uint8), cv2.COLOR_GRAY2BGR)
combinedColor.shape, warpedImageColor.shape


Out[30]:
((720, 1280, 3), (720, 1280, 3))

In [31]:
#Combine images
#refer : https://github.com/asgunzi/ArnaldoGunzi_CarNDAdvancedLaneFinding/advancedLine.ipynb
img_out = np.zeros((576,1280,3), dtype=np.uint8)
img_out=np.zeros((576,1280,3), dtype=np.uint8)

#a) Unwarped Lane
img_out[0:576,0:1024,:] =cv2.resize(result,(1024,576))
#b) Threshold
img_out[0:288,1024:1280,:] =cv2.resize(combinedColor,(256,288))
#c)Birds eye view
img_out[310:576,1024:1280,:] =cv2.resize(warpedImageColor,(256,266))

#Write curvature and center in image
TextL = "Left curv: " + str(int(left_curverad)) + " m"
TextR = "Right curv: " + str(int(right_curverad))+ " m"
TextC = "Center offset: " + str(round(center_offset,2)) + "m"
fontScale=1
thickness=2
fontFace = cv2.FONT_HERSHEY_SIMPLEX

cv2.putText(img_out, TextL, (130,40), fontFace, fontScale,(255,255,255), thickness,  lineType = cv2.LINE_AA)
cv2.putText(img_out, TextR, (130,70), fontFace, fontScale,(255,255,255), thickness,  lineType = cv2.LINE_AA)
cv2.putText(img_out, TextC, (130,100), fontFace, fontScale,(255,255,255), thickness,  lineType = cv2.LINE_AA)

cv2.putText(img_out, "Thresh. view", (1070,30), fontFace, .8,(200,200,0), thickness,  lineType = cv2.LINE_AA)
cv2.putText(img_out, "Birds-eye", (1080,305), fontFace, .8,(200,200,0), thickness,  lineType = cv2.LINE_AA)

plt.figure(dpi=200),plt.imshow(img_out)


Out[31]:
(<matplotlib.figure.Figure at 0x7f8ab8232b70>,
 <matplotlib.image.AxesImage at 0x7f8ab81a0438>)

In [32]:
def processImage(input_image):
    #Undistortion images
    undistImage = cv2.undistort(input_image, mtx, dist, None, mtx)
    #Threshold binary images
    #gradx = abs_sobel_thresh(undistImage, orient='x', sobel_kernel=15, thresh=(20,200))
    #grady = abs_sobel_thresh(undistImage, orient='y', sobel_kernel=15, thresh=(20,200))
    #mag_binary = mag_thresh(undistImage, sobel_kernel=15, mag_thresh=(20,200))
    #dir_binary = dir_threshold(undistImage, sobel_kernel=15, thresh=(0.7,1.3))
    yellow_low = np.array([0,100,100])
    yellow_high = np.array([50,255,255])
    white_low = np.array([202,202,202])
    white_high = np.array([255,255,255])
    yellow_binary = select_yellow(undistImage, yellow_low, yellow_high)
    white_binary = select_white(undistImage, white_low, white_high)
    sobelx_binary = sobel_x_thresh(undistImage, sobel_kernel=15, thresh=(80,220))
    
    combined = np.zeros_like(dir_binary)
    #combined[((gradx == 1) & (grady == 1)) | ((mag_binary == 1) & (dir_binary == 1))] = 1
    #combined[((mag_binary == 1) & (dir_binary == 1)) | (yellow_binary == 1) | (white_binary == 1)] = 1
    #combined[(yellow_binary == 1) | (white_binary == 1)] = 1
    #combined[((mag_binary == 1) | (yellow_binary == 1) | (white_binary == 1))] = 1
    combined[((sobelx_binary == 1) | (yellow_binary == 1) | (white_binary == 1))] = 1

    #Bird-view images and Curve radius
    warpedImage = cv2.warpPerspective(combined, M, imgSize)
    
    #Lane finding
    out_img, left_fit, right_fit = sliding_window_lane_finding(
        warpedImage,
        nwindows = 9,
        margin = 100,
        minpix = 50,
        init_frame=False)
    
    #Curve fitting
    ploty = np.linspace(0, warpedImage.shape[0]-1, warpedImage.shape[0] )
    left_fitx = left_fit[0]*ploty**2 + left_fit[1]*ploty + left_fit[2]
    right_fitx = right_fit[0]*ploty**2 + right_fit[1]*ploty + right_fit[2]
    
    #Curve Radius calculation
    y_eval = np.max(ploty)
    left_curverad = ((1 + (2*left_fit[0]*y_eval + left_fit[1])**2)**1.5) / np.absolute(2*left_fit[0])
    right_curverad = ((1 + (2*right_fit[0]*y_eval + right_fit[1])**2)**1.5) / np.absolute(2*right_fit[0])
    
    ym_per_pix = 30./720.
    xm_per_pix = 3.7/700.
    left_fit_cr = np.polyfit(ploty*ym_per_pix, left_fitx*xm_per_pix, 2)
    right_fit_cr = np.polyfit(ploty*ym_per_pix, right_fitx*xm_per_pix, 2)
    
    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])
    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])

    center_point_from_leftfit = left_fit[0]*720**2 + left_fit[1]*720 + left_fit[2]
    center_point_from_rightfit = right_fit[0]*720**2 + right_fit[1]*720 + right_fit[2]
    center_offset = (1280/2.- (center_point_from_leftfit + center_point_from_rightfit)/2.)*xm_per_pix
    
    #Warp back image
    # Create an image to draw the lines on
    warp_zero = np.zeros_like(warpedImage).astype(np.uint8)
    color_warp = np.dstack((warp_zero, warp_zero, warp_zero))

    #Recast the x and y points into usable format for cv2.fillPoly()
    pts_left = np.array([np.transpose(np.vstack([left_fitx, ploty]))])
    pts_right = np.array([np.flipud(np.transpose(np.vstack([right_fitx, ploty])))])
    pts = np.hstack((pts_left, pts_right))

    #Draw the lane onto the warped blank image
    cv2.fillPoly(color_warp, np.int_([pts]), (0,255, 0))

    #Warp the blank back to original image space using inverse perspective matrix (Minv)
    newwarp = cv2.warpPerspective(color_warp, Minv, (color_warp.shape[1], color_warp.shape[0])) 
    # Combine the result with the original image
    result = cv2.addWeighted(undistImage, 1, newwarp, 0.3, 0)

    #Make final result
    combinedColor = cv2.cvtColor((combined*255).astype(np.uint8), cv2.COLOR_GRAY2BGR)
    warpedImageColor = cv2.cvtColor((warpedImage*255).astype(np.uint8), cv2.COLOR_GRAY2BGR)
    #Combine images
    #refer : https://github.com/asgunzi/ArnaldoGunzi_CarNDAdvancedLaneFinding/advancedLine.ipynb
    img_out = np.zeros((576,1280,3), dtype=np.uint8)
    img_out=np.zeros((576,1280,3), dtype=np.uint8)

    #a) Unwarped Lane
    img_out[0:576,0:1024,:] =cv2.resize(result,(1024,576))
    #b) Threshold
    img_out[0:288,1024:1280,:] =cv2.resize(combinedColor,(256,288))
    #c)Birds eye view
    img_out[310:576,1024:1280,:] =cv2.resize(warpedImageColor,(256,266))

    #Write curvature and center in image
    TextL = "Left curv: " + str(int(left_curverad)) + " m"
    TextR = "Right curv: " + str(int(right_curverad))+ " m"
    TextC = "Center offset: " + str(round(center_offset,2)) + "m"
    fontScale=1
    thickness=2
    fontFace = cv2.FONT_HERSHEY_SIMPLEX

    cv2.putText(img_out, TextL, (130,40), fontFace, fontScale,(255,255,255), thickness,  lineType = cv2.LINE_AA)
    cv2.putText(img_out, TextR, (130,70), fontFace, fontScale,(255,255,255), thickness,  lineType = cv2.LINE_AA)
    cv2.putText(img_out, TextC, (130,100), fontFace, fontScale,(255,255,255), thickness,  lineType = cv2.LINE_AA)

    cv2.putText(img_out, "Thresh. view", (1070,30), fontFace, .8,(200,200,0), thickness,  lineType = cv2.LINE_AA)
    cv2.putText(img_out, "Birds-eye", (1080,305), fontFace, .8,(200,200,0), thickness,  lineType = cv2.LINE_AA)

    return img_out

Result (image, video)

  • The image processing pipeline was applied to find the lane lines from images and video.
  • The output image here created as a new image where the combination of lanes regions, curve radius and center offset.

In [33]:
images = glob.glob(test_imgs + 'test*.jpg')

In [34]:
plt.figure(dpi=200)
for i,imgName in enumerate(images):
    img = mpimg.imread(imgName)
    testImg = processImage(img)
    plt.subplot(3,2,i+1),plt.imshow(testImg),plt.axis('off')



In [35]:
from moviepy.editor import VideoFileClip

In [37]:
output1 = 'out_project_video.mp4'
clip1 = VideoFileClip("./project_video.mp4")
out_clip1 = clip1.fl_image(processImage)
%time out_clip1.write_videofile(output1, audio=False)


[MoviePy] >>>> Building video out_project_video.mp4
[MoviePy] Writing video out_project_video.mp4
100%|█████████▉| 1260/1261 [03:07<00:00,  6.92it/s]
[MoviePy] Done.
[MoviePy] >>>> Video ready: out_project_video.mp4 

CPU times: user 3min 42s, sys: 3.98 s, total: 3min 46s
Wall time: 3min 8s
[MoviePy] >>>> Building video out_challenge_video.mp4
[MoviePy] Writing video out_challenge_video.mp4
100%|██████████| 485/485 [01:07<00:00,  7.60it/s]
[MoviePy] Done.
[MoviePy] >>>> Video ready: out_challenge_video.mp4 

CPU times: user 1min 21s, sys: 1.84 s, total: 1min 23s
Wall time: 1min 7s
[MoviePy] >>>> Building video out_harder_challenge_video.mp4
[MoviePy] Writing video out_harder_challenge_video.mp4
 84%|████████▍ | 1007/1200 [02:30<00:28,  6.74it/s]
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<timed eval> in <module>()

<decorator-gen-175> in write_videofile(self, filename, fps, codec, bitrate, audio, audio_fps, preset, audio_nbytes, audio_codec, audio_bitrate, audio_bufsize, temp_audiofile, rewrite_audio, remove_temp, write_logfile, verbose, threads, ffmpeg_params, progress_bar)

/usr/local/lib/python3.5/dist-packages/moviepy/decorators.py in requires_duration(f, clip, *a, **k)
     52         raise ValueError("Attribute 'duration' not set")
     53     else:
---> 54         return f(clip, *a, **k)
     55 
     56 

<decorator-gen-174> in write_videofile(self, filename, fps, codec, bitrate, audio, audio_fps, preset, audio_nbytes, audio_codec, audio_bitrate, audio_bufsize, temp_audiofile, rewrite_audio, remove_temp, write_logfile, verbose, threads, ffmpeg_params, progress_bar)

/usr/local/lib/python3.5/dist-packages/moviepy/decorators.py in use_clip_fps_by_default(f, clip, *a, **k)
    135              for (k,v) in k.items()}
    136 
--> 137     return f(clip, *new_a, **new_kw)

<decorator-gen-173> in write_videofile(self, filename, fps, codec, bitrate, audio, audio_fps, preset, audio_nbytes, audio_codec, audio_bitrate, audio_bufsize, temp_audiofile, rewrite_audio, remove_temp, write_logfile, verbose, threads, ffmpeg_params, progress_bar)

/usr/local/lib/python3.5/dist-packages/moviepy/decorators.py in convert_masks_to_RGB(f, clip, *a, **k)
     20     if clip.ismask:
     21         clip = clip.to_RGB()
---> 22     return f(clip, *a, **k)
     23 
     24 @decorator.decorator

/usr/local/lib/python3.5/dist-packages/moviepy/video/VideoClip.py in write_videofile(self, filename, fps, codec, bitrate, audio, audio_fps, preset, audio_nbytes, audio_codec, audio_bitrate, audio_bufsize, temp_audiofile, rewrite_audio, remove_temp, write_logfile, verbose, threads, ffmpeg_params, progress_bar)
    347                            verbose=verbose, threads=threads,
    348                            ffmpeg_params=ffmpeg_params,
--> 349                            progress_bar=progress_bar)
    350 
    351         if remove_temp and make_audio:

/usr/local/lib/python3.5/dist-packages/moviepy/video/io/ffmpeg_writer.py in ffmpeg_write_video(clip, filename, fps, codec, bitrate, preset, withmask, write_logfile, audiofile, verbose, threads, ffmpeg_params, progress_bar)
    207 
    208     for t,frame in clip.iter_frames(progress_bar=progress_bar, with_times=True,
--> 209                                     fps=fps, dtype="uint8"):
    210         if withmask:
    211             mask = (255*clip.mask.get_frame(t))

/usr/local/lib/python3.5/dist-packages/tqdm/_tqdm.py in __iter__(self)
    831 """, fp_write=getattr(self.fp, 'write', sys.stderr.write))
    832 
--> 833             for obj in iterable:
    834                 yield obj
    835                 # Update and print the progressbar.

/usr/local/lib/python3.5/dist-packages/moviepy/Clip.py in generator()
    473         def generator():
    474             for t in np.arange(0, self.duration, 1.0/fps):
--> 475                 frame = self.get_frame(t)
    476                 if (dtype is not None) and (frame.dtype != dtype):
    477                     frame = frame.astype(dtype)

<decorator-gen-138> in get_frame(self, t)

/usr/local/lib/python3.5/dist-packages/moviepy/decorators.py in wrapper(f, *a, **kw)
     87         new_kw = {k: fun(v) if k in varnames else v
     88                  for (k,v) in kw.items()}
---> 89         return f(*new_a, **new_kw)
     90     return decorator.decorator(wrapper)
     91 

/usr/local/lib/python3.5/dist-packages/moviepy/Clip.py in get_frame(self, t)
     93                 return frame
     94         else:
---> 95             return self.make_frame(t)
     96 
     97     def fl(self, fun, apply_to=[], keep_duration=True):

/usr/local/lib/python3.5/dist-packages/moviepy/Clip.py in <lambda>(t)
    134 
    135         #mf = copy(self.make_frame)
--> 136         newclip = self.set_make_frame(lambda t: fun(self.get_frame, t))
    137 
    138         if not keep_duration:

/usr/local/lib/python3.5/dist-packages/moviepy/video/VideoClip.py in <lambda>(gf, t)
    531         `get_frame(t)` by another frame,  `image_func(get_frame(t))`
    532         """
--> 533         return self.fl(lambda gf, t: image_func(gf(t)), apply_to)
    534 
    535     # --------------------------------------------------------------

<ipython-input-32-d865ebc1dd20> in processImage(input_image)
     31         margin = 100,
     32         minpix = 50,
---> 33         init_frame=False)
     34 
     35     #Curve fitting

<ipython-input-21-5e5ba14297d2> in sliding_window_lane_finding(img, nwindows, margin, minpix, init_frame)
     62 
     63     # Fit a second order polynomial to each
---> 64     left_fit = np.polyfit(lefty, leftx, 2)
     65     right_fit = np.polyfit(righty, rightx, 2)
     66 

~/.local/lib/python3.5/site-packages/numpy/lib/polynomial.py in polyfit(x, y, deg, rcond, full, w, cov)
    553         raise TypeError("expected 1D vector for x")
    554     if x.size == 0:
--> 555         raise TypeError("expected non-empty vector for x")
    556     if y.ndim < 1 or y.ndim > 2:
    557         raise TypeError("expected 1D or 2D array for y")

TypeError: expected non-empty vector for x

In [ ]:
output2 = 'out_challenge_video.mp4'
clip2 = VideoFileClip("./challenge_video.mp4")
out_clip2 = clip2.fl_image(processImage)
%time out_clip2.write_videofile(output2, audio=False)

In [39]:
output3 = 'out_harder_challenge_video.mp4'
clip3 = VideoFileClip("./harder_challenge_video.mp4")
out_clip3 = clip3.subclip(0,30).fl_image(processImage)
%time out_clip3.write_videofile(output3, audio=False)


[MoviePy] >>>> Building video out_harder_challenge_video.mp4
[MoviePy] Writing video out_harder_challenge_video.mp4
  0%|          | 0/751 [00:00<?, ?it/s]
  0%|          | 1/751 [00:00<01:53,  6.63it/s]
  0%|          | 2/751 [00:00<01:54,  6.57it/s]
  0%|          | 3/751 [00:00<01:57,  6.39it/s]
  1%|          | 4/751 [00:00<01:57,  6.35it/s]
  1%|          | 5/751 [00:00<01:58,  6.32it/s]
  1%|          | 6/751 [00:00<01:57,  6.31it/s]
  1%|          | 7/751 [00:01<01:58,  6.26it/s]
  1%|          | 8/751 [00:01<01:59,  6.24it/s]
  1%|          | 9/751 [00:01<01:56,  6.36it/s]
  1%|▏         | 10/751 [00:01<01:54,  6.45it/s]
  1%|▏         | 11/751 [00:01<01:52,  6.59it/s]
  2%|▏         | 12/751 [00:01<01:51,  6.63it/s]
  2%|▏         | 13/751 [00:02<01:55,  6.37it/s]
  2%|▏         | 14/751 [00:02<01:53,  6.47it/s]
  2%|▏         | 15/751 [00:02<01:52,  6.56it/s]
  2%|▏         | 16/751 [00:02<01:51,  6.61it/s]
  2%|▏         | 17/751 [00:02<01:49,  6.73it/s]
  2%|▏         | 18/751 [00:02<01:47,  6.82it/s]
  3%|▎         | 19/751 [00:02<01:46,  6.87it/s]
  3%|▎         | 20/751 [00:03<01:45,  6.92it/s]
  3%|▎         | 21/751 [00:03<01:45,  6.95it/s]
  3%|▎         | 22/751 [00:03<01:45,  6.93it/s]
  3%|▎         | 23/751 [00:03<01:45,  6.87it/s]
  3%|▎         | 24/751 [00:03<01:45,  6.90it/s]
  3%|▎         | 25/751 [00:03<01:44,  6.96it/s]
  3%|▎         | 26/751 [00:03<01:44,  6.92it/s]
  4%|▎         | 27/751 [00:04<01:44,  6.94it/s]
  4%|▎         | 28/751 [00:04<01:43,  6.95it/s]
  4%|▍         | 29/751 [00:04<01:44,  6.88it/s]
  4%|▍         | 30/751 [00:04<01:45,  6.85it/s]
  4%|▍         | 31/751 [00:04<01:46,  6.74it/s]
  4%|▍         | 32/751 [00:04<01:48,  6.66it/s]
  4%|▍         | 33/751 [00:04<01:48,  6.60it/s]
  5%|▍         | 34/751 [00:05<01:50,  6.50it/s]
  5%|▍         | 35/751 [00:05<01:48,  6.58it/s]
  5%|▍         | 36/751 [00:05<01:46,  6.70it/s]
  5%|▍         | 37/751 [00:05<01:45,  6.78it/s]
  5%|▌         | 38/751 [00:05<01:45,  6.75it/s]
  5%|▌         | 39/751 [00:05<01:44,  6.79it/s]
  5%|▌         | 40/751 [00:06<01:44,  6.81it/s]
  5%|▌         | 41/751 [00:06<01:44,  6.82it/s]
  6%|▌         | 42/751 [00:06<01:45,  6.74it/s]
  6%|▌         | 43/751 [00:06<01:46,  6.66it/s]
  6%|▌         | 44/751 [00:06<01:45,  6.71it/s]
  6%|▌         | 45/751 [00:06<01:44,  6.74it/s]
  6%|▌         | 46/751 [00:06<01:45,  6.71it/s]
  6%|▋         | 47/751 [00:07<01:46,  6.60it/s]
  6%|▋         | 48/751 [00:07<01:46,  6.62it/s]
  7%|▋         | 49/751 [00:07<01:49,  6.43it/s]
  7%|▋         | 50/751 [00:07<01:48,  6.43it/s]
  7%|▋         | 51/751 [00:07<01:49,  6.37it/s]
  7%|▋         | 52/751 [00:07<01:49,  6.37it/s]
  7%|▋         | 53/751 [00:07<01:47,  6.47it/s]
  7%|▋         | 54/751 [00:08<01:45,  6.59it/s]
  7%|▋         | 55/751 [00:08<01:47,  6.50it/s]
  7%|▋         | 56/751 [00:08<01:46,  6.54it/s]
  8%|▊         | 57/751 [00:08<01:46,  6.53it/s]
  8%|▊         | 58/751 [00:08<01:48,  6.38it/s]
  8%|▊         | 59/751 [00:08<01:46,  6.50it/s]
  8%|▊         | 60/751 [00:09<01:46,  6.48it/s]
  8%|▊         | 61/751 [00:09<01:47,  6.43it/s]
  8%|▊         | 62/751 [00:09<01:45,  6.50it/s]
  8%|▊         | 63/751 [00:09<01:44,  6.57it/s]
  9%|▊         | 64/751 [00:09<01:41,  6.74it/s]
  9%|▊         | 65/751 [00:09<01:41,  6.77it/s]
  9%|▉         | 66/751 [00:09<01:41,  6.72it/s]
  9%|▉         | 67/751 [00:10<01:40,  6.79it/s]
  9%|▉         | 68/751 [00:10<01:41,  6.71it/s]
  9%|▉         | 69/751 [00:10<01:41,  6.69it/s]
  9%|▉         | 70/751 [00:10<01:40,  6.75it/s]
  9%|▉         | 71/751 [00:10<01:39,  6.85it/s]
 10%|▉         | 72/751 [00:10<01:38,  6.87it/s]
 10%|▉         | 73/751 [00:10<01:37,  6.92it/s]
 10%|▉         | 74/751 [00:11<01:37,  6.94it/s]
 10%|▉         | 75/751 [00:11<01:38,  6.88it/s]
 10%|█         | 76/751 [00:11<01:38,  6.88it/s]
 10%|█         | 77/751 [00:11<01:37,  6.95it/s]
 10%|█         | 78/751 [00:11<01:36,  7.00it/s]
 11%|█         | 79/751 [00:11<01:35,  7.02it/s]
 11%|█         | 80/751 [00:11<01:34,  7.09it/s]
 11%|█         | 81/751 [00:12<01:36,  6.93it/s]
 11%|█         | 82/751 [00:12<01:38,  6.82it/s]
 11%|█         | 83/751 [00:12<01:37,  6.84it/s]
 11%|█         | 84/751 [00:12<01:37,  6.85it/s]
 11%|█▏        | 85/751 [00:12<01:37,  6.86it/s]
 11%|█▏        | 86/751 [00:12<01:37,  6.82it/s]
 12%|█▏        | 87/751 [00:13<01:36,  6.85it/s]
 12%|█▏        | 88/751 [00:13<01:35,  6.97it/s]
 12%|█▏        | 89/751 [00:13<01:34,  6.99it/s]
 12%|█▏        | 90/751 [00:13<01:34,  7.00it/s]
 12%|█▏        | 91/751 [00:13<01:33,  7.03it/s]
 12%|█▏        | 92/751 [00:13<01:37,  6.76it/s]
 12%|█▏        | 93/751 [00:13<01:38,  6.70it/s]
 13%|█▎        | 94/751 [00:14<01:37,  6.73it/s]
 13%|█▎        | 95/751 [00:14<01:39,  6.57it/s]
 13%|█▎        | 96/751 [00:14<01:38,  6.68it/s]
 13%|█▎        | 97/751 [00:14<01:37,  6.72it/s]
 13%|█▎        | 98/751 [00:14<01:36,  6.78it/s]
 13%|█▎        | 99/751 [00:14<01:35,  6.83it/s]
 13%|█▎        | 100/751 [00:14<01:35,  6.83it/s]
 13%|█▎        | 101/751 [00:15<01:37,  6.65it/s]
 14%|█▎        | 102/751 [00:15<01:36,  6.72it/s]
 14%|█▎        | 103/751 [00:15<01:36,  6.68it/s]
 14%|█▍        | 104/751 [00:15<01:36,  6.70it/s]
 14%|█▍        | 105/751 [00:15<01:36,  6.71it/s]
 14%|█▍        | 106/751 [00:15<01:38,  6.56it/s]
 14%|█▍        | 107/751 [00:15<01:37,  6.62it/s]
 14%|█▍        | 108/751 [00:16<01:40,  6.43it/s]
 15%|█▍        | 109/751 [00:16<01:40,  6.39it/s]
 15%|█▍        | 110/751 [00:16<01:40,  6.40it/s]
 15%|█▍        | 111/751 [00:16<01:38,  6.51it/s]
 15%|█▍        | 112/751 [00:16<01:38,  6.48it/s]
 15%|█▌        | 113/751 [00:16<01:38,  6.45it/s]
 15%|█▌        | 114/751 [00:17<01:41,  6.30it/s]
 15%|█▌        | 115/751 [00:17<01:42,  6.21it/s]
 15%|█▌        | 116/751 [00:17<01:40,  6.34it/s]
 16%|█▌        | 117/751 [00:17<01:36,  6.57it/s]
 16%|█▌        | 118/751 [00:17<01:34,  6.69it/s]
 16%|█▌        | 119/751 [00:17<01:32,  6.83it/s]
 16%|█▌        | 120/751 [00:18<01:46,  5.94it/s]
 16%|█▌        | 121/751 [00:18<01:44,  6.05it/s]
 16%|█▌        | 122/751 [00:18<01:42,  6.16it/s]
 16%|█▋        | 123/751 [00:18<01:41,  6.21it/s]
 17%|█▋        | 124/751 [00:18<01:40,  6.24it/s]
 17%|█▋        | 125/751 [00:18<01:37,  6.40it/s]
 17%|█▋        | 126/751 [00:18<01:38,  6.36it/s]
 17%|█▋        | 127/751 [00:19<01:35,  6.52it/s]
 17%|█▋        | 128/751 [00:19<01:34,  6.58it/s]
 17%|█▋        | 129/751 [00:19<01:35,  6.54it/s]
 17%|█▋        | 130/751 [00:19<01:34,  6.60it/s]
 17%|█▋        | 131/751 [00:19<01:32,  6.73it/s]
 18%|█▊        | 132/751 [00:19<01:30,  6.86it/s]
 18%|█▊        | 133/751 [00:20<01:29,  6.94it/s]
 18%|█▊        | 134/751 [00:20<01:29,  6.88it/s]
 18%|█▊        | 135/751 [00:20<01:29,  6.90it/s]
 18%|█▊        | 136/751 [00:20<01:30,  6.82it/s]
 18%|█▊        | 137/751 [00:20<01:28,  6.93it/s]
 18%|█▊        | 138/751 [00:20<01:29,  6.82it/s]
 19%|█▊        | 139/751 [00:20<01:28,  6.90it/s]
 19%|█▊        | 140/751 [00:21<01:28,  6.89it/s]
 19%|█▉        | 141/751 [00:21<01:28,  6.92it/s]
 19%|█▉        | 142/751 [00:21<01:27,  6.97it/s]
 19%|█▉        | 143/751 [00:21<01:25,  7.10it/s]
 19%|█▉        | 144/751 [00:21<01:25,  7.14it/s]
 19%|█▉        | 145/751 [00:21<01:24,  7.21it/s]
 19%|█▉        | 146/751 [00:21<01:23,  7.26it/s]
 20%|█▉        | 147/751 [00:21<01:21,  7.37it/s]
 20%|█▉        | 148/751 [00:22<01:20,  7.46it/s]
 20%|█▉        | 149/751 [00:22<01:20,  7.44it/s]
 20%|█▉        | 150/751 [00:22<01:22,  7.30it/s]
 20%|██        | 151/751 [00:22<01:22,  7.29it/s]
 20%|██        | 152/751 [00:22<01:23,  7.14it/s]
 20%|██        | 153/751 [00:22<01:23,  7.12it/s]
 21%|██        | 154/751 [00:22<01:23,  7.12it/s]
 21%|██        | 155/751 [00:23<01:21,  7.30it/s]
 21%|██        | 156/751 [00:23<01:21,  7.33it/s]
 21%|██        | 157/751 [00:23<01:20,  7.35it/s]
 21%|██        | 158/751 [00:23<01:19,  7.42it/s]
 21%|██        | 159/751 [00:23<01:18,  7.50it/s]
 21%|██▏       | 160/751 [00:23<01:19,  7.39it/s]
 21%|██▏       | 161/751 [00:23<01:23,  7.04it/s]
 22%|██▏       | 162/751 [00:24<01:22,  7.15it/s]
 22%|██▏       | 163/751 [00:24<01:23,  7.04it/s]
 22%|██▏       | 164/751 [00:24<01:29,  6.58it/s]
 22%|██▏       | 165/751 [00:24<01:27,  6.69it/s]
 22%|██▏       | 166/751 [00:24<01:25,  6.87it/s]
 22%|██▏       | 167/751 [00:24<01:23,  6.95it/s]
 22%|██▏       | 168/751 [00:24<01:23,  7.01it/s]
 23%|██▎       | 169/751 [00:25<01:23,  6.96it/s]
 23%|██▎       | 170/751 [00:25<01:25,  6.80it/s]
 23%|██▎       | 171/751 [00:25<01:25,  6.79it/s]
 23%|██▎       | 172/751 [00:25<01:23,  6.91it/s]
 23%|██▎       | 173/751 [00:25<01:21,  7.09it/s]
 23%|██▎       | 174/751 [00:25<01:22,  7.00it/s]
 23%|██▎       | 175/751 [00:25<01:22,  7.00it/s]
 23%|██▎       | 176/751 [00:26<01:21,  7.03it/s]
 24%|██▎       | 177/751 [00:26<01:21,  7.06it/s]
 24%|██▎       | 178/751 [00:26<01:24,  6.80it/s]
 24%|██▍       | 179/751 [00:26<01:23,  6.87it/s]
 24%|██▍       | 180/751 [00:26<01:24,  6.79it/s]
 24%|██▍       | 181/751 [00:26<01:24,  6.74it/s]
 24%|██▍       | 182/751 [00:26<01:23,  6.80it/s]
 24%|██▍       | 183/751 [00:27<01:21,  6.93it/s]
 25%|██▍       | 184/751 [00:27<01:23,  6.81it/s]
 25%|██▍       | 185/751 [00:27<01:20,  7.00it/s]
 25%|██▍       | 186/751 [00:27<01:20,  7.06it/s]
 25%|██▍       | 187/751 [00:27<01:19,  7.14it/s]
 25%|██▌       | 188/751 [00:27<01:18,  7.19it/s]
 25%|██▌       | 189/751 [00:27<01:20,  6.95it/s]
 25%|██▌       | 190/751 [00:28<01:19,  7.04it/s]
 25%|██▌       | 191/751 [00:28<01:17,  7.19it/s]
 26%|██▌       | 192/751 [00:28<01:19,  7.04it/s]
 26%|██▌       | 193/751 [00:28<01:19,  7.04it/s]
 26%|██▌       | 194/751 [00:28<01:18,  7.13it/s]
 26%|██▌       | 195/751 [00:28<01:16,  7.28it/s]
 26%|██▌       | 196/751 [00:28<01:15,  7.37it/s]
 26%|██▌       | 197/751 [00:29<01:15,  7.34it/s]
 26%|██▋       | 198/751 [00:29<01:15,  7.29it/s]
 26%|██▋       | 199/751 [00:29<01:17,  7.10it/s]
 27%|██▋       | 200/751 [00:29<01:17,  7.07it/s]
 27%|██▋       | 201/751 [00:29<01:18,  7.04it/s]
 27%|██▋       | 202/751 [00:29<01:18,  6.97it/s]
 27%|██▋       | 203/751 [00:29<01:18,  7.00it/s]
 27%|██▋       | 204/751 [00:30<01:20,  6.78it/s]
 27%|██▋       | 205/751 [00:30<01:20,  6.77it/s]
 27%|██▋       | 206/751 [00:30<01:20,  6.77it/s]
 28%|██▊       | 207/751 [00:30<01:21,  6.71it/s]
 28%|██▊       | 208/751 [00:30<01:21,  6.64it/s]
 28%|██▊       | 209/751 [00:30<01:24,  6.38it/s]
 28%|██▊       | 210/751 [00:31<01:26,  6.29it/s]
 28%|██▊       | 211/751 [00:31<01:24,  6.40it/s]
 28%|██▊       | 212/751 [00:31<01:24,  6.40it/s]
 28%|██▊       | 213/751 [00:31<01:24,  6.35it/s]
 28%|██▊       | 214/751 [00:31<01:23,  6.44it/s]
 29%|██▊       | 215/751 [00:31<01:22,  6.53it/s]
 29%|██▉       | 216/751 [00:31<01:21,  6.60it/s]
 29%|██▉       | 217/751 [00:32<01:21,  6.58it/s]
 29%|██▉       | 218/751 [00:32<01:24,  6.31it/s]
 29%|██▉       | 219/751 [00:32<01:27,  6.10it/s]
 29%|██▉       | 220/751 [00:32<01:25,  6.23it/s]
 29%|██▉       | 221/751 [00:32<01:23,  6.33it/s]
 30%|██▉       | 222/751 [00:32<01:25,  6.20it/s]
 30%|██▉       | 223/751 [00:33<01:26,  6.10it/s]
 30%|██▉       | 224/751 [00:33<01:24,  6.22it/s]
 30%|██▉       | 225/751 [00:33<01:25,  6.16it/s]
 30%|███       | 226/751 [00:33<01:26,  6.07it/s]
 30%|███       | 227/751 [00:33<01:24,  6.23it/s]
 30%|███       | 228/751 [00:33<01:22,  6.34it/s]
 30%|███       | 229/751 [00:34<01:21,  6.41it/s]
 31%|███       | 230/751 [00:34<01:21,  6.39it/s]
 31%|███       | 231/751 [00:34<01:20,  6.43it/s]
 31%|███       | 232/751 [00:34<01:20,  6.46it/s]
 31%|███       | 233/751 [00:34<01:20,  6.42it/s]
 31%|███       | 234/751 [00:34<01:20,  6.41it/s]
 31%|███▏      | 235/751 [00:34<01:21,  6.29it/s]
 31%|███▏      | 236/751 [00:35<01:22,  6.27it/s]
 32%|███▏      | 237/751 [00:35<01:20,  6.36it/s]
 32%|███▏      | 238/751 [00:35<01:22,  6.21it/s]
 32%|███▏      | 239/751 [00:35<01:22,  6.22it/s]
 32%|███▏      | 240/751 [00:35<01:21,  6.25it/s]
 32%|███▏      | 241/751 [00:35<01:19,  6.40it/s]
 32%|███▏      | 242/751 [00:36<01:19,  6.44it/s]
 32%|███▏      | 243/751 [00:36<01:18,  6.49it/s]
 32%|███▏      | 244/751 [00:36<01:18,  6.43it/s]
 33%|███▎      | 245/751 [00:36<01:17,  6.54it/s]
 33%|███▎      | 246/751 [00:36<01:15,  6.65it/s]
 33%|███▎      | 247/751 [00:36<01:16,  6.62it/s]
 33%|███▎      | 248/751 [00:36<01:15,  6.68it/s]
 33%|███▎      | 249/751 [00:37<01:15,  6.69it/s]
 33%|███▎      | 250/751 [00:37<01:16,  6.52it/s]
 33%|███▎      | 251/751 [00:37<01:15,  6.60it/s]
 34%|███▎      | 252/751 [00:37<01:14,  6.72it/s]
 34%|███▎      | 253/751 [00:37<01:13,  6.75it/s]
 34%|███▍      | 254/751 [00:37<01:13,  6.76it/s]
 34%|███▍      | 255/751 [00:38<01:14,  6.70it/s]
 34%|███▍      | 256/751 [00:38<01:17,  6.41it/s]
 34%|███▍      | 257/751 [00:38<01:15,  6.54it/s]
 34%|███▍      | 258/751 [00:38<01:15,  6.57it/s]
 34%|███▍      | 259/751 [00:38<01:14,  6.62it/s]
 35%|███▍      | 260/751 [00:38<01:14,  6.63it/s]
 35%|███▍      | 261/751 [00:38<01:18,  6.25it/s]
 35%|███▍      | 262/751 [00:39<01:18,  6.27it/s]
 35%|███▌      | 263/751 [00:39<01:19,  6.14it/s]
 35%|███▌      | 264/751 [00:39<01:21,  5.98it/s]
 35%|███▌      | 265/751 [00:39<01:19,  6.10it/s]
 35%|███▌      | 266/751 [00:39<01:18,  6.18it/s]
 36%|███▌      | 267/751 [00:39<01:17,  6.22it/s]
 36%|███▌      | 268/751 [00:40<01:17,  6.22it/s]
 36%|███▌      | 269/751 [00:40<01:17,  6.25it/s]
 36%|███▌      | 270/751 [00:40<01:14,  6.44it/s]
 36%|███▌      | 271/751 [00:40<01:13,  6.51it/s]
 36%|███▌      | 272/751 [00:40<01:13,  6.51it/s]
 36%|███▋      | 273/751 [00:40<01:14,  6.45it/s]
 36%|███▋      | 274/751 [00:41<01:12,  6.57it/s]
 37%|███▋      | 275/751 [00:41<01:11,  6.68it/s]
 37%|███▋      | 276/751 [00:41<01:11,  6.65it/s]
 37%|███▋      | 277/751 [00:41<01:12,  6.56it/s]
 37%|███▋      | 278/751 [00:41<01:13,  6.46it/s]
 37%|███▋      | 279/751 [00:41<01:12,  6.54it/s]
 37%|███▋      | 280/751 [00:41<01:11,  6.60it/s]
 37%|███▋      | 281/751 [00:42<01:09,  6.76it/s]
 38%|███▊      | 282/751 [00:42<01:11,  6.56it/s]
 38%|███▊      | 283/751 [00:42<01:11,  6.56it/s]
 38%|███▊      | 284/751 [00:42<01:11,  6.55it/s]
 38%|███▊      | 285/751 [00:42<01:11,  6.51it/s]
 38%|███▊      | 286/751 [00:42<01:11,  6.54it/s]
 38%|███▊      | 287/751 [00:42<01:10,  6.55it/s]
 38%|███▊      | 288/751 [00:43<01:10,  6.61it/s]
 38%|███▊      | 289/751 [00:43<01:12,  6.41it/s]
 39%|███▊      | 290/751 [00:43<01:11,  6.42it/s]
 39%|███▊      | 291/751 [00:43<01:10,  6.50it/s]
 39%|███▉      | 292/751 [00:43<01:12,  6.37it/s]
 39%|███▉      | 293/751 [00:43<01:12,  6.33it/s]
 39%|███▉      | 294/751 [00:44<01:12,  6.27it/s]
 39%|███▉      | 295/751 [00:44<01:12,  6.30it/s]
 39%|███▉      | 296/751 [00:44<01:10,  6.42it/s]
 40%|███▉      | 297/751 [00:44<01:09,  6.50it/s]
 40%|███▉      | 298/751 [00:44<01:08,  6.57it/s]
 40%|███▉      | 299/751 [00:44<01:09,  6.47it/s]
 40%|███▉      | 300/751 [00:45<01:10,  6.37it/s]
 40%|████      | 301/751 [00:45<01:09,  6.49it/s]
 40%|████      | 302/751 [00:45<01:10,  6.36it/s]
 40%|████      | 303/751 [00:45<01:09,  6.45it/s]
 40%|████      | 304/751 [00:45<01:09,  6.47it/s]
 41%|████      | 305/751 [00:45<01:07,  6.65it/s]
 41%|████      | 306/751 [00:45<01:05,  6.77it/s]
 41%|████      | 307/751 [00:46<01:04,  6.92it/s]
 41%|████      | 308/751 [00:46<01:04,  6.84it/s]
 41%|████      | 309/751 [00:46<01:04,  6.88it/s]
 41%|████▏     | 310/751 [00:46<01:04,  6.87it/s]
 41%|████▏     | 311/751 [00:46<01:03,  6.96it/s]
 42%|████▏     | 312/751 [00:46<01:05,  6.68it/s]
 42%|████▏     | 313/751 [00:46<01:06,  6.58it/s]
 42%|████▏     | 314/751 [00:47<01:06,  6.54it/s]
 42%|████▏     | 315/751 [00:47<01:05,  6.70it/s]
 42%|████▏     | 316/751 [00:47<01:05,  6.69it/s]
 42%|████▏     | 317/751 [00:47<01:04,  6.73it/s]
 42%|████▏     | 318/751 [00:47<01:03,  6.81it/s]
 42%|████▏     | 319/751 [00:47<01:03,  6.81it/s]
 43%|████▎     | 320/751 [00:47<01:01,  6.96it/s]
 43%|████▎     | 321/751 [00:48<01:01,  7.04it/s]
 43%|████▎     | 322/751 [00:48<01:01,  6.96it/s]
 43%|████▎     | 323/751 [00:48<01:02,  6.85it/s]
 43%|████▎     | 324/751 [00:48<01:02,  6.79it/s]
 43%|████▎     | 325/751 [00:48<01:02,  6.81it/s]
 43%|████▎     | 326/751 [00:48<01:02,  6.77it/s]
 44%|████▎     | 327/751 [00:49<01:04,  6.62it/s]
 44%|████▎     | 328/751 [00:49<01:03,  6.68it/s]
 44%|████▍     | 329/751 [00:49<01:05,  6.49it/s]
 44%|████▍     | 330/751 [00:49<01:04,  6.55it/s]
 44%|████▍     | 331/751 [00:49<01:04,  6.52it/s]
 44%|████▍     | 332/751 [00:49<01:02,  6.70it/s]
 44%|████▍     | 333/751 [00:49<01:01,  6.81it/s]
 44%|████▍     | 334/751 [00:50<01:01,  6.80it/s]
 45%|████▍     | 335/751 [00:50<01:00,  6.87it/s]
 45%|████▍     | 336/751 [00:50<01:02,  6.69it/s]
 45%|████▍     | 337/751 [00:50<01:00,  6.79it/s]
 45%|████▌     | 338/751 [00:50<01:00,  6.86it/s]
 45%|████▌     | 339/751 [00:50<00:59,  6.96it/s]
 45%|████▌     | 340/751 [00:50<00:58,  7.06it/s]
 45%|████▌     | 341/751 [00:51<00:57,  7.10it/s]
 46%|████▌     | 342/751 [00:51<00:59,  6.90it/s]
 46%|████▌     | 343/751 [00:51<00:59,  6.89it/s]
 46%|████▌     | 344/751 [00:51<00:59,  6.88it/s]
 46%|████▌     | 345/751 [00:51<00:59,  6.82it/s]
 46%|████▌     | 346/751 [00:51<00:59,  6.81it/s]
 46%|████▌     | 347/751 [00:51<00:57,  6.97it/s]
 46%|████▋     | 348/751 [00:52<00:57,  7.06it/s]
 46%|████▋     | 349/751 [00:52<00:57,  7.00it/s]
 47%|████▋     | 350/751 [00:52<00:58,  6.84it/s]
 47%|████▋     | 351/751 [00:52<00:59,  6.72it/s]
 47%|████▋     | 352/751 [00:52<00:58,  6.81it/s]
 47%|████▋     | 353/751 [00:52<00:58,  6.80it/s]
 47%|████▋     | 354/751 [00:52<00:58,  6.79it/s]
 47%|████▋     | 355/751 [00:53<00:58,  6.71it/s]
 47%|████▋     | 356/751 [00:53<00:59,  6.64it/s]
 48%|████▊     | 357/751 [00:53<00:59,  6.67it/s]
 48%|████▊     | 358/751 [00:53<00:57,  6.80it/s]
 48%|████▊     | 359/751 [00:53<00:58,  6.68it/s]
 48%|████▊     | 360/751 [00:53<00:57,  6.77it/s]
 48%|████▊     | 361/751 [00:54<00:58,  6.68it/s]
 48%|████▊     | 362/751 [00:54<00:58,  6.68it/s]
 48%|████▊     | 363/751 [00:54<00:57,  6.75it/s]
 48%|████▊     | 364/751 [00:54<00:56,  6.81it/s]
 49%|████▊     | 365/751 [00:54<00:56,  6.78it/s]
 49%|████▊     | 366/751 [00:54<00:57,  6.64it/s]
 49%|████▉     | 367/751 [00:54<00:59,  6.49it/s]
 49%|████▉     | 368/751 [00:55<00:59,  6.49it/s]
 49%|████▉     | 369/751 [00:55<00:59,  6.38it/s]
 49%|████▉     | 370/751 [00:55<01:01,  6.19it/s]
 49%|████▉     | 371/751 [00:55<01:01,  6.16it/s]
 50%|████▉     | 372/751 [00:55<00:59,  6.36it/s]
 50%|████▉     | 373/751 [00:55<00:58,  6.48it/s]
 50%|████▉     | 374/751 [00:56<00:57,  6.55it/s]
 50%|████▉     | 375/751 [00:56<00:57,  6.53it/s]
 50%|█████     | 376/751 [00:56<00:58,  6.39it/s]
 50%|█████     | 377/751 [00:56<00:59,  6.32it/s]
 50%|█████     | 378/751 [00:56<00:57,  6.47it/s]
 50%|█████     | 379/751 [00:56<00:56,  6.54it/s]
 51%|█████     | 380/751 [00:56<00:56,  6.58it/s]
 51%|█████     | 381/751 [00:57<00:56,  6.50it/s]
 51%|█████     | 382/751 [00:57<00:55,  6.60it/s]
 51%|█████     | 383/751 [00:57<00:56,  6.46it/s]
 51%|█████     | 384/751 [00:57<00:55,  6.56it/s]
 51%|█████▏    | 385/751 [00:57<00:57,  6.38it/s]
 51%|█████▏    | 386/751 [00:57<00:56,  6.46it/s]
 52%|█████▏    | 387/751 [00:58<00:56,  6.47it/s]
 52%|█████▏    | 388/751 [00:58<00:55,  6.52it/s]
 52%|█████▏    | 389/751 [00:58<00:55,  6.54it/s]
 52%|█████▏    | 390/751 [00:58<00:55,  6.51it/s]
 52%|█████▏    | 391/751 [00:58<00:54,  6.59it/s]
 52%|█████▏    | 392/751 [00:58<00:54,  6.59it/s]
 52%|█████▏    | 393/751 [00:58<00:56,  6.39it/s]
 52%|█████▏    | 394/751 [00:59<00:56,  6.32it/s]
 53%|█████▎    | 395/751 [00:59<00:55,  6.38it/s]
 53%|█████▎    | 396/751 [00:59<00:55,  6.40it/s]
 53%|█████▎    | 397/751 [00:59<00:55,  6.35it/s]
 53%|█████▎    | 398/751 [00:59<00:54,  6.44it/s]
 53%|█████▎    | 399/751 [00:59<00:53,  6.60it/s]
 53%|█████▎    | 400/751 [01:00<00:52,  6.68it/s]
 53%|█████▎    | 401/751 [01:00<00:51,  6.75it/s]
 54%|█████▎    | 402/751 [01:00<00:51,  6.84it/s]
 54%|█████▎    | 403/751 [01:00<00:52,  6.62it/s]
 54%|█████▍    | 404/751 [01:00<00:53,  6.49it/s]
 54%|█████▍    | 405/751 [01:00<00:52,  6.59it/s]
 54%|█████▍    | 406/751 [01:00<00:52,  6.58it/s]
 54%|█████▍    | 407/751 [01:01<00:53,  6.42it/s]
 54%|█████▍    | 408/751 [01:01<00:53,  6.37it/s]
 54%|█████▍    | 409/751 [01:01<00:53,  6.43it/s]
 55%|█████▍    | 410/751 [01:01<00:51,  6.63it/s]
 55%|█████▍    | 411/751 [01:01<00:50,  6.74it/s]
 55%|█████▍    | 412/751 [01:01<00:50,  6.67it/s]
 55%|█████▍    | 413/751 [01:01<00:50,  6.75it/s]
 55%|█████▌    | 414/751 [01:02<00:51,  6.51it/s]
 55%|█████▌    | 415/751 [01:02<00:52,  6.37it/s]
 55%|█████▌    | 416/751 [01:02<00:50,  6.58it/s]
 56%|█████▌    | 417/751 [01:02<00:49,  6.70it/s]
 56%|█████▌    | 418/751 [01:02<00:50,  6.58it/s]
 56%|█████▌    | 419/751 [01:02<00:49,  6.67it/s]
 56%|█████▌    | 420/751 [01:03<00:49,  6.73it/s]
 56%|█████▌    | 421/751 [01:03<00:48,  6.75it/s]
 56%|█████▌    | 422/751 [01:03<00:49,  6.65it/s]
 56%|█████▋    | 423/751 [01:03<00:50,  6.55it/s]
 56%|█████▋    | 424/751 [01:03<00:50,  6.47it/s]
 57%|█████▋    | 425/751 [01:03<00:49,  6.61it/s]
 57%|█████▋    | 426/751 [01:03<00:49,  6.51it/s]
 57%|█████▋    | 427/751 [01:04<00:49,  6.58it/s]
 57%|█████▋    | 428/751 [01:04<00:49,  6.47it/s]
 57%|█████▋    | 429/751 [01:04<00:49,  6.46it/s]
 57%|█████▋    | 430/751 [01:04<00:48,  6.57it/s]
 57%|█████▋    | 431/751 [01:04<00:50,  6.38it/s]
 58%|█████▊    | 432/751 [01:04<00:48,  6.53it/s]
 58%|█████▊    | 433/751 [01:05<00:47,  6.64it/s]
 58%|█████▊    | 434/751 [01:05<00:47,  6.70it/s]
 58%|█████▊    | 435/751 [01:05<00:47,  6.64it/s]
 58%|█████▊    | 436/751 [01:05<00:46,  6.73it/s]
 58%|█████▊    | 437/751 [01:05<00:46,  6.74it/s]
 58%|█████▊    | 438/751 [01:05<00:45,  6.85it/s]
 58%|█████▊    | 439/751 [01:05<00:45,  6.79it/s]
 59%|█████▊    | 440/751 [01:06<00:46,  6.63it/s]
 59%|█████▊    | 441/751 [01:06<00:47,  6.50it/s]
 59%|█████▉    | 442/751 [01:06<00:48,  6.44it/s]
 59%|█████▉    | 443/751 [01:06<00:47,  6.43it/s]
 59%|█████▉    | 444/751 [01:06<00:47,  6.40it/s]
 59%|█████▉    | 445/751 [01:06<00:47,  6.45it/s]
 59%|█████▉    | 446/751 [01:07<00:46,  6.55it/s]
 60%|█████▉    | 447/751 [01:07<00:45,  6.65it/s]
 60%|█████▉    | 448/751 [01:07<00:44,  6.74it/s]
 60%|█████▉    | 449/751 [01:07<00:44,  6.77it/s]
 60%|█████▉    | 450/751 [01:07<00:44,  6.72it/s]
 60%|██████    | 451/751 [01:07<00:45,  6.55it/s]
 60%|██████    | 452/751 [01:07<00:45,  6.64it/s]
 60%|██████    | 453/751 [01:08<00:44,  6.77it/s]
 60%|██████    | 454/751 [01:08<00:43,  6.76it/s]
 61%|██████    | 455/751 [01:08<00:45,  6.51it/s]
 61%|██████    | 456/751 [01:08<00:46,  6.40it/s]
 61%|██████    | 457/751 [01:08<00:46,  6.35it/s]
 61%|██████    | 458/751 [01:08<00:46,  6.26it/s]
 61%|██████    | 459/751 [01:09<00:45,  6.45it/s]
 61%|██████▏   | 460/751 [01:09<00:43,  6.65it/s]
 61%|██████▏   | 461/751 [01:09<00:43,  6.72it/s]
 62%|██████▏   | 462/751 [01:09<00:43,  6.69it/s]
 62%|██████▏   | 463/751 [01:09<00:44,  6.52it/s]
 62%|██████▏   | 464/751 [01:09<00:44,  6.46it/s]
 62%|██████▏   | 465/751 [01:09<00:43,  6.62it/s]
 62%|██████▏   | 466/751 [01:10<00:42,  6.67it/s]
 62%|██████▏   | 467/751 [01:10<00:42,  6.70it/s]
 62%|██████▏   | 468/751 [01:10<00:43,  6.56it/s]
 62%|██████▏   | 469/751 [01:10<00:41,  6.75it/s]
 63%|██████▎   | 470/751 [01:10<00:41,  6.81it/s]
 63%|██████▎   | 471/751 [01:10<00:40,  6.88it/s]
 63%|██████▎   | 472/751 [01:10<00:39,  7.00it/s]
 63%|██████▎   | 473/751 [01:11<00:38,  7.13it/s]
 63%|██████▎   | 474/751 [01:11<00:38,  7.12it/s]
 63%|██████▎   | 475/751 [01:11<00:40,  6.84it/s]
 63%|██████▎   | 476/751 [01:11<00:40,  6.83it/s]
 64%|██████▎   | 477/751 [01:11<00:40,  6.81it/s]
 64%|██████▎   | 478/751 [01:11<00:39,  6.89it/s]
 64%|██████▍   | 479/751 [01:11<00:38,  7.03it/s]
 64%|██████▍   | 480/751 [01:12<00:37,  7.14it/s]
 64%|██████▍   | 481/751 [01:12<00:39,  6.80it/s]
 64%|██████▍   | 482/751 [01:12<00:40,  6.70it/s]
 64%|██████▍   | 483/751 [01:12<00:40,  6.70it/s]
 64%|██████▍   | 484/751 [01:12<00:42,  6.35it/s]
 65%|██████▍   | 485/751 [01:12<00:41,  6.43it/s]
 65%|██████▍   | 486/751 [01:12<00:40,  6.58it/s]
 65%|██████▍   | 487/751 [01:13<00:39,  6.69it/s]
 65%|██████▍   | 488/751 [01:13<00:38,  6.77it/s]
 65%|██████▌   | 489/751 [01:13<00:38,  6.84it/s]
 65%|██████▌   | 490/751 [01:13<00:38,  6.85it/s]
 65%|██████▌   | 491/751 [01:13<00:38,  6.71it/s]
 66%|██████▌   | 492/751 [01:13<00:38,  6.76it/s]
 66%|██████▌   | 493/751 [01:14<00:37,  6.79it/s]
 66%|██████▌   | 494/751 [01:14<00:37,  6.83it/s]
 66%|██████▌   | 495/751 [01:14<00:38,  6.64it/s]
 66%|██████▌   | 496/751 [01:14<00:38,  6.66it/s]
 66%|██████▌   | 497/751 [01:14<00:38,  6.51it/s]
 66%|██████▋   | 498/751 [01:14<00:38,  6.54it/s]
 66%|██████▋   | 499/751 [01:14<00:37,  6.63it/s]
 67%|██████▋   | 500/751 [01:15<00:38,  6.55it/s]
 67%|██████▋   | 501/751 [01:15<00:38,  6.48it/s]
 67%|██████▋   | 502/751 [01:15<00:39,  6.30it/s]
 67%|██████▋   | 503/751 [01:15<00:39,  6.30it/s]
 67%|██████▋   | 504/751 [01:15<00:39,  6.21it/s]
 67%|██████▋   | 505/751 [01:15<00:39,  6.25it/s]
 67%|██████▋   | 506/751 [01:16<00:39,  6.28it/s]
 68%|██████▊   | 507/751 [01:16<00:38,  6.27it/s]
 68%|██████▊   | 508/751 [01:16<00:38,  6.23it/s]
 68%|██████▊   | 509/751 [01:16<00:38,  6.23it/s]
 68%|██████▊   | 510/751 [01:16<00:37,  6.42it/s]
 68%|██████▊   | 511/751 [01:16<00:37,  6.38it/s]
 68%|██████▊   | 512/751 [01:16<00:36,  6.60it/s]
 68%|██████▊   | 513/751 [01:17<00:35,  6.74it/s]
 68%|██████▊   | 514/751 [01:17<00:34,  6.83it/s]
 69%|██████▊   | 515/751 [01:17<00:34,  6.88it/s]
 69%|██████▊   | 516/751 [01:17<00:34,  6.84it/s]
 69%|██████▉   | 517/751 [01:17<00:34,  6.69it/s]
 69%|██████▉   | 518/751 [01:17<00:35,  6.63it/s]
 69%|██████▉   | 519/751 [01:18<00:35,  6.58it/s]
 69%|██████▉   | 520/751 [01:18<00:35,  6.45it/s]
 69%|██████▉   | 521/751 [01:18<00:37,  6.15it/s]
 70%|██████▉   | 522/751 [01:18<00:36,  6.32it/s]
 70%|██████▉   | 523/751 [01:18<00:35,  6.47it/s]
 70%|██████▉   | 524/751 [01:18<00:34,  6.58it/s]
 70%|██████▉   | 525/751 [01:18<00:33,  6.70it/s]
 70%|███████   | 526/751 [01:19<00:33,  6.74it/s]
 70%|███████   | 527/751 [01:19<00:33,  6.74it/s]
 70%|███████   | 528/751 [01:19<00:33,  6.71it/s]
 70%|███████   | 529/751 [01:19<00:32,  6.73it/s]
 71%|███████   | 530/751 [01:19<00:32,  6.89it/s]
 71%|███████   | 531/751 [01:19<00:32,  6.85it/s]
 71%|███████   | 532/751 [01:19<00:33,  6.58it/s]
 71%|███████   | 533/751 [01:20<00:32,  6.73it/s]
 71%|███████   | 534/751 [01:20<00:31,  6.81it/s]
 71%|███████   | 535/751 [01:20<00:31,  6.76it/s]
 71%|███████▏  | 536/751 [01:20<00:32,  6.67it/s]
 72%|███████▏  | 537/751 [01:20<00:31,  6.71it/s]
 72%|███████▏  | 538/751 [01:20<00:31,  6.87it/s]
 72%|███████▏  | 539/751 [01:21<00:31,  6.70it/s]
 72%|███████▏  | 540/751 [01:21<00:31,  6.61it/s]
 72%|███████▏  | 541/751 [01:21<00:32,  6.47it/s]
 72%|███████▏  | 542/751 [01:21<00:31,  6.61it/s]
 72%|███████▏  | 543/751 [01:21<00:31,  6.64it/s]
 72%|███████▏  | 544/751 [01:21<00:30,  6.79it/s]
 73%|███████▎  | 545/751 [01:21<00:30,  6.79it/s]
 73%|███████▎  | 546/751 [01:22<00:29,  6.88it/s]
 73%|███████▎  | 547/751 [01:22<00:30,  6.74it/s]
 73%|███████▎  | 548/751 [01:22<00:29,  6.80it/s]
 73%|███████▎  | 549/751 [01:22<00:29,  6.77it/s]
 73%|███████▎  | 550/751 [01:22<00:29,  6.90it/s]
 73%|███████▎  | 551/751 [01:22<00:28,  6.93it/s]
 74%|███████▎  | 552/751 [01:22<00:28,  6.89it/s]
 74%|███████▎  | 553/751 [01:23<00:29,  6.75it/s]
 74%|███████▍  | 554/751 [01:23<00:29,  6.78it/s]
 74%|███████▍  | 555/751 [01:23<00:28,  6.92it/s]
 74%|███████▍  | 556/751 [01:23<00:28,  6.90it/s]
 74%|███████▍  | 557/751 [01:23<00:28,  6.87it/s]
 74%|███████▍  | 558/751 [01:23<00:28,  6.68it/s]
 74%|███████▍  | 559/751 [01:23<00:28,  6.83it/s]
 75%|███████▍  | 560/751 [01:24<00:27,  7.00it/s]
 75%|███████▍  | 561/751 [01:24<00:26,  7.05it/s]
 75%|███████▍  | 562/751 [01:24<00:27,  6.92it/s]
 75%|███████▍  | 563/751 [01:24<00:26,  7.02it/s]
 75%|███████▌  | 564/751 [01:24<00:26,  7.12it/s]
 75%|███████▌  | 565/751 [01:24<00:26,  6.99it/s]
 75%|███████▌  | 566/751 [01:24<00:26,  6.89it/s]
 75%|███████▌  | 567/751 [01:25<00:25,  7.08it/s]
 76%|███████▌  | 568/751 [01:25<00:26,  7.03it/s]
 76%|███████▌  | 569/751 [01:25<00:25,  7.04it/s]
 76%|███████▌  | 570/751 [01:25<00:26,  6.89it/s]
 76%|███████▌  | 571/751 [01:25<00:26,  6.86it/s]
 76%|███████▌  | 572/751 [01:25<00:26,  6.88it/s]
 76%|███████▋  | 573/751 [01:25<00:26,  6.67it/s]
 76%|███████▋  | 574/751 [01:26<00:25,  6.86it/s]
 77%|███████▋  | 575/751 [01:26<00:25,  6.93it/s]
 77%|███████▋  | 576/751 [01:26<00:25,  6.99it/s]
 77%|███████▋  | 577/751 [01:26<00:24,  6.99it/s]
 77%|███████▋  | 578/751 [01:26<00:24,  7.06it/s]
 77%|███████▋  | 579/751 [01:26<00:24,  7.06it/s]
 77%|███████▋  | 580/751 [01:26<00:23,  7.21it/s]
 77%|███████▋  | 581/751 [01:27<00:23,  7.23it/s]
 77%|███████▋  | 582/751 [01:27<00:23,  7.19it/s]
 78%|███████▊  | 583/751 [01:27<00:23,  7.21it/s]
 78%|███████▊  | 584/751 [01:27<00:23,  7.12it/s]
 78%|███████▊  | 585/751 [01:27<00:23,  7.17it/s]
 78%|███████▊  | 586/751 [01:27<00:23,  6.95it/s]
 78%|███████▊  | 587/751 [01:27<00:24,  6.82it/s]
 78%|███████▊  | 588/751 [01:28<00:23,  6.89it/s]
 78%|███████▊  | 589/751 [01:28<00:23,  7.03it/s]
 79%|███████▊  | 590/751 [01:28<00:22,  7.14it/s]
 79%|███████▊  | 591/751 [01:28<00:22,  7.12it/s]
 79%|███████▉  | 592/751 [01:28<00:22,  6.94it/s]
 79%|███████▉  | 593/751 [01:28<00:22,  7.03it/s]
 79%|███████▉  | 594/751 [01:28<00:22,  7.03it/s]
 79%|███████▉  | 595/751 [01:29<00:22,  6.97it/s]
 79%|███████▉  | 596/751 [01:29<00:22,  6.87it/s]
 79%|███████▉  | 597/751 [01:29<00:22,  6.91it/s]
 80%|███████▉  | 598/751 [01:29<00:22,  6.87it/s]
 80%|███████▉  | 599/751 [01:29<00:22,  6.86it/s]
 80%|███████▉  | 600/751 [01:29<00:21,  6.88it/s]
 80%|████████  | 601/751 [01:29<00:21,  6.92it/s]
 80%|████████  | 602/751 [01:30<00:21,  6.99it/s]
 80%|████████  | 603/751 [01:30<00:21,  7.04it/s]
 80%|████████  | 604/751 [01:30<00:20,  7.07it/s]
 81%|████████  | 605/751 [01:30<00:21,  6.84it/s]
 81%|████████  | 606/751 [01:30<00:21,  6.89it/s]
 81%|████████  | 607/751 [01:30<00:20,  6.95it/s]
 81%|████████  | 608/751 [01:30<00:20,  6.97it/s]
 81%|████████  | 609/751 [01:31<00:20,  6.97it/s]
 81%|████████  | 610/751 [01:31<00:20,  6.97it/s]
 81%|████████▏ | 611/751 [01:31<00:20,  6.88it/s]
 81%|████████▏ | 612/751 [01:31<00:20,  6.86it/s]
 82%|████████▏ | 613/751 [01:31<00:20,  6.83it/s]
 82%|████████▏ | 614/751 [01:31<00:20,  6.72it/s]
 82%|████████▏ | 615/751 [01:32<00:20,  6.70it/s]
 82%|████████▏ | 616/751 [01:32<00:19,  6.82it/s]
 82%|████████▏ | 617/751 [01:32<00:19,  6.73it/s]
 82%|████████▏ | 618/751 [01:32<00:19,  6.78it/s]
 82%|████████▏ | 619/751 [01:32<00:19,  6.76it/s]
 83%|████████▎ | 620/751 [01:32<00:19,  6.61it/s]
 83%|████████▎ | 621/751 [01:32<00:19,  6.55it/s]
 83%|████████▎ | 622/751 [01:33<00:19,  6.46it/s]
 83%|████████▎ | 623/751 [01:33<00:19,  6.47it/s]
 83%|████████▎ | 624/751 [01:33<00:19,  6.58it/s]
 83%|████████▎ | 625/751 [01:33<00:18,  6.63it/s]
 83%|████████▎ | 626/751 [01:33<00:18,  6.58it/s]
 83%|████████▎ | 627/751 [01:33<00:18,  6.60it/s]
 84%|████████▎ | 628/751 [01:33<00:18,  6.55it/s]
 84%|████████▍ | 629/751 [01:34<00:18,  6.59it/s]
 84%|████████▍ | 630/751 [01:34<00:18,  6.68it/s]
 84%|████████▍ | 631/751 [01:34<00:18,  6.61it/s]
 84%|████████▍ | 632/751 [01:34<00:18,  6.54it/s]
 84%|████████▍ | 633/751 [01:34<00:18,  6.45it/s]
 84%|████████▍ | 634/751 [01:34<00:18,  6.43it/s]
 85%|████████▍ | 635/751 [01:35<00:17,  6.52it/s]
 85%|████████▍ | 636/751 [01:35<00:17,  6.48it/s]
 85%|████████▍ | 637/751 [01:35<00:17,  6.38it/s]
 85%|████████▍ | 638/751 [01:35<00:17,  6.58it/s]
 85%|████████▌ | 639/751 [01:35<00:16,  6.66it/s]
 85%|████████▌ | 640/751 [01:35<00:17,  6.52it/s]
 85%|████████▌ | 641/751 [01:35<00:17,  6.42it/s]
 85%|████████▌ | 642/751 [01:36<00:16,  6.54it/s]
 86%|████████▌ | 643/751 [01:36<00:16,  6.48it/s]
 86%|████████▌ | 644/751 [01:36<00:16,  6.60it/s]
 86%|████████▌ | 645/751 [01:36<00:15,  6.68it/s]
 86%|████████▌ | 646/751 [01:36<00:15,  6.60it/s]
 86%|████████▌ | 647/751 [01:36<00:15,  6.73it/s]
 86%|████████▋ | 648/751 [01:37<00:15,  6.69it/s]
 86%|████████▋ | 649/751 [01:37<00:15,  6.71it/s]
 87%|████████▋ | 650/751 [01:37<00:15,  6.65it/s]
 87%|████████▋ | 651/751 [01:37<00:15,  6.64it/s]
 87%|████████▋ | 652/751 [01:37<00:15,  6.55it/s]
 87%|████████▋ | 653/751 [01:37<00:14,  6.56it/s]
 87%|████████▋ | 654/751 [01:37<00:14,  6.57it/s]
 87%|████████▋ | 655/751 [01:38<00:14,  6.55it/s]
 87%|████████▋ | 656/751 [01:38<00:14,  6.54it/s]
 87%|████████▋ | 657/751 [01:38<00:14,  6.45it/s]
 88%|████████▊ | 658/751 [01:38<00:14,  6.38it/s]
 88%|████████▊ | 659/751 [01:38<00:14,  6.45it/s]
 88%|████████▊ | 660/751 [01:38<00:14,  6.33it/s]
 88%|████████▊ | 661/751 [01:39<00:13,  6.44it/s]
 88%|████████▊ | 662/751 [01:39<00:13,  6.57it/s]
 88%|████████▊ | 663/751 [01:39<00:13,  6.51it/s]
 88%|████████▊ | 664/751 [01:39<00:13,  6.51it/s]
 89%|████████▊ | 665/751 [01:39<00:13,  6.46it/s]
 89%|████████▊ | 666/751 [01:39<00:13,  6.44it/s]
 89%|████████▉ | 667/751 [01:39<00:13,  6.33it/s]
 89%|████████▉ | 668/751 [01:40<00:12,  6.43it/s]
 89%|████████▉ | 669/751 [01:40<00:12,  6.49it/s]
 89%|████████▉ | 670/751 [01:40<00:12,  6.53it/s]
 89%|████████▉ | 671/751 [01:40<00:12,  6.56it/s]
 89%|████████▉ | 672/751 [01:40<00:11,  6.68it/s]
 90%|████████▉ | 673/751 [01:40<00:11,  6.78it/s]
 90%|████████▉ | 674/751 [01:40<00:11,  6.82it/s]
 90%|████████▉ | 675/751 [01:41<00:11,  6.85it/s]
 90%|█████████ | 676/751 [01:41<00:10,  6.97it/s]
 90%|█████████ | 677/751 [01:41<00:11,  6.71it/s]
 90%|█████████ | 678/751 [01:41<00:10,  6.76it/s]
 90%|█████████ | 679/751 [01:41<00:10,  6.81it/s]
 91%|█████████ | 680/751 [01:41<00:10,  6.92it/s]
 91%|█████████ | 681/751 [01:42<00:10,  6.98it/s]
 91%|█████████ | 682/751 [01:42<00:09,  7.13it/s]
 91%|█████████ | 683/751 [01:42<00:09,  6.86it/s]
 91%|█████████ | 684/751 [01:42<00:09,  6.82it/s]
 91%|█████████ | 685/751 [01:42<00:09,  6.88it/s]
 91%|█████████▏| 686/751 [01:42<00:09,  6.65it/s]
 91%|█████████▏| 687/751 [01:42<00:09,  6.53it/s]
 92%|█████████▏| 688/751 [01:43<00:09,  6.65it/s]
 92%|█████████▏| 689/751 [01:43<00:09,  6.81it/s]
 92%|█████████▏| 690/751 [01:43<00:08,  6.84it/s]
 92%|█████████▏| 691/751 [01:43<00:08,  6.81it/s]
 92%|█████████▏| 692/751 [01:43<00:08,  6.95it/s]
 92%|█████████▏| 693/751 [01:43<00:08,  6.96it/s]
 92%|█████████▏| 694/751 [01:43<00:08,  7.11it/s]
 93%|█████████▎| 695/751 [01:44<00:07,  7.18it/s]
 93%|█████████▎| 696/751 [01:44<00:07,  7.11it/s]
 93%|█████████▎| 697/751 [01:44<00:07,  7.10it/s]
 93%|█████████▎| 698/751 [01:44<00:07,  7.04it/s]
 93%|█████████▎| 699/751 [01:44<00:07,  6.92it/s]
 93%|█████████▎| 700/751 [01:44<00:07,  6.96it/s]
 93%|█████████▎| 701/751 [01:44<00:07,  6.98it/s]
 93%|█████████▎| 702/751 [01:45<00:07,  6.99it/s]
 94%|█████████▎| 703/751 [01:45<00:06,  7.09it/s]
 94%|█████████▎| 704/751 [01:45<00:06,  7.18it/s]
 94%|█████████▍| 705/751 [01:45<00:06,  7.16it/s]
 94%|█████████▍| 706/751 [01:45<00:06,  7.09it/s]
 94%|█████████▍| 707/751 [01:45<00:06,  7.06it/s]
 94%|█████████▍| 708/751 [01:45<00:06,  7.01it/s]
 94%|█████████▍| 709/751 [01:46<00:05,  7.03it/s]
 95%|█████████▍| 710/751 [01:46<00:06,  6.83it/s]
 95%|█████████▍| 711/751 [01:46<00:05,  6.83it/s]
 95%|█████████▍| 712/751 [01:46<00:05,  6.79it/s]
 95%|█████████▍| 713/751 [01:46<00:05,  6.81it/s]
 95%|█████████▌| 714/751 [01:46<00:05,  6.84it/s]
 95%|█████████▌| 715/751 [01:46<00:05,  6.77it/s]
 95%|█████████▌| 716/751 [01:47<00:05,  6.73it/s]
 95%|█████████▌| 717/751 [01:47<00:04,  6.84it/s]
 96%|█████████▌| 718/751 [01:47<00:04,  6.84it/s]
 96%|█████████▌| 719/751 [01:47<00:04,  6.81it/s]
 96%|█████████▌| 720/751 [01:47<00:04,  6.61it/s]
 96%|█████████▌| 721/751 [01:47<00:04,  6.71it/s]
 96%|█████████▌| 722/751 [01:47<00:04,  6.63it/s]
 96%|█████████▋| 723/751 [01:48<00:04,  6.56it/s]
 96%|█████████▋| 724/751 [01:48<00:04,  6.59it/s]
 97%|█████████▋| 725/751 [01:48<00:03,  6.65it/s]
 97%|█████████▋| 726/751 [01:48<00:03,  6.37it/s]
 97%|█████████▋| 727/751 [01:48<00:03,  6.41it/s]
 97%|█████████▋| 728/751 [01:48<00:03,  6.44it/s]
 97%|█████████▋| 729/751 [01:49<00:03,  6.54it/s]
 97%|█████████▋| 730/751 [01:49<00:03,  6.35it/s]
 97%|█████████▋| 731/751 [01:49<00:03,  6.40it/s]
 97%|█████████▋| 732/751 [01:49<00:02,  6.55it/s]
 98%|█████████▊| 733/751 [01:49<00:02,  6.50it/s]
 98%|█████████▊| 734/751 [01:49<00:02,  6.64it/s]
 98%|█████████▊| 735/751 [01:49<00:02,  6.70it/s]
 98%|█████████▊| 736/751 [01:50<00:02,  6.53it/s]
 98%|█████████▊| 737/751 [01:50<00:02,  6.58it/s]
 98%|█████████▊| 738/751 [01:50<00:01,  6.66it/s]
 98%|█████████▊| 739/751 [01:50<00:01,  6.72it/s]
 99%|█████████▊| 740/751 [01:50<00:01,  6.69it/s]
 99%|█████████▊| 741/751 [01:50<00:01,  6.59it/s]
 99%|█████████▉| 742/751 [01:51<00:01,  6.52it/s]
 99%|█████████▉| 743/751 [01:51<00:01,  6.59it/s]
 99%|█████████▉| 744/751 [01:51<00:01,  6.62it/s]
 99%|█████████▉| 745/751 [01:51<00:00,  6.46it/s]
 99%|█████████▉| 746/751 [01:51<00:00,  6.54it/s]
 99%|█████████▉| 747/751 [01:51<00:00,  6.50it/s]
100%|█████████▉| 748/751 [01:51<00:00,  6.40it/s]
100%|█████████▉| 749/751 [01:52<00:00,  6.58it/s]
100%|█████████▉| 750/751 [01:52<00:00,  6.75it/s]
[MoviePy] Done.
[MoviePy] >>>> Video ready: out_harder_challenge_video.mp4 

CPU times: user 2min 13s, sys: 2.69 s, total: 2min 16s
Wall time: 1min 53s

Conclusion

Using advanced computer vision algorithm (Undistortion, Color space transform, Gradient threshold, Sliding windows and Perspective projection), we can easily get the lane detection program. But it's not enough to adapt in real situations. In different roads, weathers and brightness, there are chance to fail detecting lane lines. To come up with this shortcoming, we may use deep learning based feature extraction technique(CNN) in various situation.

  • Issues

    • Dashed lane in the far distance from vehicle was not detected well. Because of this, most curve radius of dashed lane has wrong directional curvature. To come up with this I think need to improve the binary threshold function.
    • Updates: I updates threshold binary function based on udacity reviewers suggestions(Yello and White color threshold). The lane detection performance was improved in final result video.
  • Improvements

    • Test different color space (YUV, RGB)
    • Test gradient threshold through CNN extracted feartures. CNN is powerful fearture extractor from images. I think we may have different results from conventional computer vision technique using pretrained CNN feature as inputs to gradient threshold calculator.

reference


In [ ]: