<jupyter_start><jupyter_text>Advanced Lane Finding ProjectThe 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.--- First, I'll compute the camera calibration using chessboard images<jupyter_code>import numpy as np
import cv2
import glob
import matplotlib.pyplot as plt
import matplotlib.image as mpimg
%matplotlib qt

# Define a class to receive the characteristics of each line detection
class Line():
    def __init__(self):
        # was the line detected in the last iteration?
        self.detected = False
        # was the line detected in the last iteration?
        self.detectedCounter = 0  
        # x values of the last n fits of the line
        self.recent_xfitted = [] 
        #average x values of the fitted line over the last n iterations
        self.bestx = None     
        #recent good average x values of the fitted line over the last n iterations
        self.recent_bestx = None     
        #polynomial coefficients averaged over the last n iterations
        self.best_fit_list = None  
        #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 for the last n iterations
        self.curvatures = None 
        #radius of curvature of the line in some units
        self.radius_of_curvature = None 
        #distance in meters of vehicle center from the line
        self.line_base_pos = None 
        #difference in fit coefficients between last and new fits
        self.diffs = np.array([0,0,0], dtype='float') 
        #x values for detected line pixels
        self.allx = None  
        #y values for detected line pixels
        self.ally = None  

# Define a class to receive the characteristics of each line detection
class globalVar():
    def __init__(self):
        # frame counter
        self.value = 0
# prepare object points, like (0,0,0), (1,0,0), (2,0,0) ....,(6,5,0)
objp = np.zeros((6*9,3), np.float32)
objp[:,:2] = np.mgrid[0:9,0:6].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
images = glob.glob('camera_cal/calibration*.jpg')

# Step through the list and search for chessboard corners
for fname in images:
    img = cv2.imread(fname)
    gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)

    # Find the chessboard corners
    ret, corners = cv2.findChessboardCorners(gray, (9,6),None)

    # If found, add object points, image points
    if ret == True:
        objpoints.append(objp)
        imgpoints.append(corners)
        #print(objpoints, ', ', imgpoints)

        # Draw and display the corners
        img = cv2.drawChessboardCorners(img, (9,6), corners, ret)
        cv2.imshow('img',img)
        cv2.waitKey(500)

# Use cv2.calibrateCamera() and cv2.undistort()
ret, mtx, dist, rvecs, tvecs = cv2.calibrateCamera(objpoints, imgpoints, img.shape[1::-1], None, None)
print(mtx, dist)
cv2.destroyAllWindows()
#Functions for Pipline

#undistort the input image with the known parameter mtx and dist
def undistortImg(img, mtx, dist):
    imgUndistort = cv2.undistort(img, mtx, dist, None, mtx)
    return imgUndistort

#warp the preprocessed binary image
def warpImg(img,X,Y):

    src = np.float32([[(1-X)/2*img.shape[1],Y*img.shape[0]],[0,img.shape[0]],
                      [img.shape[1],img.shape[0]],[(1+X)/2*img.shape[1],Y*img.shape[0]]])
    #print(img.shape)
    dst = np.float32([[0,0],[0,img.shape[0]],[img.shape[1],img.shape[0]],[img.shape[1],0]])
    
    M = cv2.getPerspectiveTransform(src, dst)
    
    warped = cv2.warpPerspective(img, M, (img.shape[1], img.shape[0]), flags=cv2.INTER_LINEAR)
    #delete the next two lines
    return warped, M

# Edge Detection
def edgeDetection(img, s_thresh=(170, 255), sx_thresh=(20, 100)):
    img = np.copy(img)
    # Convert to HLS color space and separate the V channel
    hls = cv2.cvtColor(img, cv2.COLOR_RGB2HLS)
    l_channel = hls[:,:,1]
    s_channel = hls[:,:,2]
    # Sobel x
    sobelx = cv2.Sobel(l_channel, cv2.CV_64F, 1, 0) # Take the derivative in x
    abs_sobelx = np.absolute(sobelx) # Absolute x derivative to accentuate lines away from horizontal
    scaled_sobel = np.uint8(255*abs_sobelx/np.max(abs_sobelx))
    
    # Threshold x gradient
    sxbinary = np.zeros_like(scaled_sobel)
    sxbinary[(scaled_sobel >= sx_thresh[0]) & (scaled_sobel <= sx_thresh[1])] = 1
    
    # Threshold color channel
    s_binary = np.zeros_like(s_channel)
    s_binary[(s_channel >= s_thresh[0]) & (s_channel <= s_thresh[1])] = 1
    # Stack each channel
    color_binary = np.dstack(( np.zeros_like(sxbinary), sxbinary, s_binary)) * 255
    return color_binary

def find_lane_pixels(binary_warped):
    # Take a histogram of the bottom half of the image
    histogram = np.sum(binary_warped[binary_warped.shape[0]//2:,:], axis=0)
    # Create an output image to draw on and visualize the result
    out_img = np.dstack((binary_warped, binary_warped, binary_warped))
    # Find the peak of the left and right halves of the histogram
    # These will be the starting point for the left and right lines
    midpoint = np.int(histogram.shape[0]//2)
    leftx_base = np.argmax(histogram[:midpoint])
    rightx_base = np.argmax(histogram[midpoint:]) + midpoint

    # HYPERPARAMETERS
    # Choose the number of sliding windows
    nwindows = 9
    # Set the width of the windows +/- margin
    margin = 100
    # Set minimum number of pixels found to recenter window
    minpix = 50

    # Set height of windows - based on nwindows above and image shape
    window_height = np.int(binary_warped.shape[0]//nwindows)
    # Identify the x and y positions of all nonzero pixels in the image
    nonzero = binary_warped.nonzero()
    nonzeroy = np.array(nonzero[0])
    nonzerox = np.array(nonzero[1])
    # Current positions to be updated later for each window in nwindows
    leftx_current = leftx_base
    rightx_current = rightx_base

    # 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 = binary_warped.shape[0] - (window+1)*window_height
        win_y_high = binary_warped.shape[0] - window*window_height
        ### TO-DO: Find the four below boundaries of the window ###
        win_xleft_low = leftx_current-margin  # Update this
        win_xleft_high = leftx_current+margin  # Update this
        win_xright_low = rightx_current-margin  # Update this
        win_xright_high = rightx_current+margin  # Update this
        
        # 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) 
        
        ### TO-DO: 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)
        
        #init find lane line flag and set them to false
        fLeftFound = False
        fRightFound = False
        ### TO-DO: If you found > minpix pixels, recenter next window ###
        if (len(good_left_inds)>minpix):
            leftx_current = np.int(np.mean(nonzerox[good_left_inds]))
            fLeftFound = True
            
        if (len(good_right_inds)>minpix):
            rightx_current = np.int(np.mean(nonzerox[good_right_inds]))
            fRightFound = True
            
        ### (`right` or `leftx_current`) on their mean position ###
        #pass # Remove this when you add your function

    # Concatenate the arrays of indices (previously was a list of lists of pixels)

    try:
        left_lane_inds = np.concatenate(left_lane_inds)
        right_lane_inds = np.concatenate(right_lane_inds)
    except ValueError:
        # Avoids an error if the above is not implemented fully
        pass
    
    # 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]

    return leftx, lefty, rightx, righty, out_img, fLeftFound, fRightFound


def fit_polynomial(binary_warped):
    # Find our lane pixels first
    leftx, lefty, rightx, righty, out_img, fLeftFound, fRightFound = find_lane_pixels(binary_warped)

    ### TO-DO: Fit a second order polynomial to each using `np.polyfit` ###
    left_fit = np.polyfit(lefty, leftx, 2)
    right_fit = np.polyfit(righty, rightx, 2)

    # Generate x and y values for plotting
    ploty = np.linspace(0, binary_warped.shape[0]-1, binary_warped.shape[0] )
    try:
        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]
    except TypeError:
        # Avoids an error if `left` and `right_fit` are still none or incorrect
        print('The function failed to fit a line!')
        left_fitx = 1*ploty**2 + 1*ploty
        right_fitx = 1*ploty**2 + 1*ploty

    ## Visualization ##
    # Colors in the left and right lane regions
    out_img[lefty, leftx] = [255, 0, 0]
    out_img[righty, rightx] = [0, 0, 255]

    # Plots the left and right polynomials on the lane lines
    plt.plot(left_fitx, ploty, color='yellow')
    plt.plot(right_fitx, ploty, color='yellow')

    return out_img, left_fitx, right_fitx, left_fit, right_fit, ploty, fLeftFound, fRightFound

def fit_poly(img_shape, leftx, lefty, rightx, righty):
    ### TO-DO: Fit a second order polynomial to each with np.polyfit() ###
    left_fit = np.polyfit(lefty, leftx, 2)
    right_fit = np.polyfit(righty, rightx, 2)
    # Generate x and y values for plotting
    ploty = np.linspace(0, img_shape[0]-1, img_shape[0])
    ### TO-DO: Calc both polynomials using ploty, left_fit and right_fit ###
    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]
    
    return left_fitx, right_fitx, ploty, left_fit, right_fit

def search_around_poly(binary_warped, left_fit_last, right_fit_last):
    # HYPERPARAMETER
    # Choose the width of the margin around the previous polynomial to search
    # The quiz grader expects 100 here, but feel free to tune on your own!
    margin = 100

    # Grab activated pixels
    nonzero = binary_warped.nonzero()
    nonzeroy = np.array(nonzero[0])
    nonzerox = np.array(nonzero[1])
    
    ### TO-DO: Set the area of search based on activated x-values ###
    ### within the +/- margin of our polynomial function ###
    ### Hint: consider the window areas for the similarly named variables ###
    ### in the previous quiz, but change the windows to our new search area ###
    left_lane_inds = ((nonzerox > (left_fit_last[0]*nonzeroy**2 + left_fit_last[1]
    *nonzeroy + left_fit_last[2] - margin)) & (nonzerox < (left_fit_last[0]*nonzeroy
    **2 + left_fit_last[1]*nonzeroy + left_fit_last[2] + margin)))
    right_lane_inds = ((nonzerox > (right_fit_last[0]*nonzeroy**2 + right_fit_last[1]
    *nonzeroy + right_fit_last[2] - margin)) & (nonzerox < (right_fit_last[0]*nonzeroy
    **2 + right_fit_last[1]*nonzeroy + right_fit_last[2] + margin)))
    
    # Again, 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 new polynomials
    left_fitx, right_fitx, ploty, left_fit, right_fit = fit_poly(binary_warped.shape, leftx, lefty, rightx, righty)
    if(len(leftx)<10000):
        left_fit = left_fit_last
        fLeftFound = False
    else:
        fLeftFound = True
        
    if(len(rightx)<10000):
        right_fit = right_fit_last
        fRightFound = False
    else:
        fRightFound = True
    ## Visualization ##
    # Create an image to draw on and an image to show the selection window
    out_img = np.dstack((binary_warped, binary_warped, binary_warped))*255
    window_img = np.zeros_like(out_img)
    # Color in left and right line pixels
    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]

    # Generate a polygon to illustrate the search window area
    # And recast the x and y points into usable format for cv2.fillPoly()
    left_line_window1 = np.array([np.transpose(np.vstack([left_fitx-margin, ploty]))])
    left_line_window2 = np.array([np.flipud(np.transpose(np.vstack([left_fitx+margin, 
                              ploty])))])
    left_line_pts = np.hstack((left_line_window1, left_line_window2))
    right_line_window1 = np.array([np.transpose(np.vstack([right_fitx-margin, ploty]))])
    right_line_window2 = np.array([np.flipud(np.transpose(np.vstack([right_fitx+margin, 
                              ploty])))])
    right_line_pts = np.hstack((right_line_window1, right_line_window2))

    # Draw the lane onto the warped blank image
    cv2.fillPoly(window_img, np.int_([left_line_pts]), (0,255, 0))
    cv2.fillPoly(window_img, np.int_([right_line_pts]), (0,255, 0))
    result = cv2.addWeighted(out_img, 1, window_img, 0.3, 0)
    
    # Plot the polynomial lines onto the image
    plt.plot(left_fitx, ploty, color='yellow')
    plt.plot(right_fitx, ploty, color='yellow')
    ## End visualization steps ##
    
    return result, left_fitx, right_fitx, left_fit, right_fit, ploty, fLeftFound, fRightFound
#smooth function for 2 D array
def lineSmooth(xlist, x, n):
    xlist = np.append(xlist, [x], axis=0)
    if(len(xlist) > n):
        xlist = np.delete(xlist, 0, axis=0)
    return xlist

#smooth function for list
def simpleSmooth(xlist, x, n):
    #print(xlist.shape)
    xlist = np.append(xlist, x)
    if(len(xlist) > n):
        xlist = np.delete(xlist, 0)
    return xlist
def measure_curvature_real(ploty, left_fit_cr, right_fit_cr):
    '''
    Calculates the curvature of polynomial functions in meters.
    '''
    # Define conversions in x and y from pixels space to meters
    ym_per_pix = 30/720 # meters per pixel in y dimension
    xm_per_pix = 2.9/700 # meters per pixel in x dimension
    
    # Define y-value where we want radius of curvature
    # We'll choose the maximum y-value, corresponding to the bottom of the image
    y_eval = np.max(ploty)
    
    #calculation of R_curve (radius of curvature) #####
    left_curverad = (1+(2*left_fit_cr[0]*y_eval*ym_per_pix+left_fit_cr[1])**2)**1.5/np.abs(2*left_fit_cr[0])  ## Implement the calculation of the left line here
    right_curverad = (1+(2*right_fit_cr[0]*y_eval*ym_per_pix+right_fit_cr[1])**2)**1.5/np.abs(2*right_fit_cr[0])  ## Implement the calculation of the right line here
    
    return left_curverad, right_curverad
#draw the detected lines back on the original image
def drawLines(undist, warped, left_fitx, right_fitx, ploty, X, Y, color):
    # Create an image to draw the lines on
    warp_zero = np.zeros_like(warped).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]), color)
    
    dst = np.float32([[(1-X)/2*img.shape[1],Y*img.shape[0]],[0,img.shape[0]],
                      [img.shape[1],img.shape[0]],[(1+X)/2*img.shape[1],Y*img.shape[0]]])

    src = np.float32([[0,0],[0,img.shape[0]],[img.shape[1],img.shape[0]],[img.shape[1],0]])
    
    Minv = cv2.getPerspectiveTransform(src, dst)

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

#calculate lane center offset based on the left and right lane line position
def laneCenterCalc(left_fitx, right_fitx, imgWidth):
    xm_per_pix = 2.9/700 # meters per pixel in x dimension
    #use the 100 pixels close the bottom side to get the average position of left and right lane lines
    offset = round((imgWidth - np.mean(left_fitx[-100:]) - np.mean(right_fitx[-100:]))/2*xm_per_pix, 2)
    #print((-np.mean(left_fitx[-100:]) + np.mean(right_fitx[-100:]))*xm_per_pix)
    return offset
#compare two detected lane lines
def sanity_check(cur_L, cur_R, xfit_L, xfit_R, left_co, right_co, initCounter):
    #if the curvature of the lane line is above this limit, it is very close to straight line, no need to check similiraty of curvature
    cur_total_upper_lim = 10000
    #tuned this parameter based on standard high way width
    xm_per_pix = 2.9/700 # meters per pixel in x dimension
    #standard high way width in the US is 3.7m
    lane_width_standard = 3.7 #unit meter
    
    #threshold for the similar curvature, lane width and lane line parallel
    lim_cur = 100
    lim_width = 30
    lim_para = 200
    
    #calculate the difference between left cur and right cur in percentage
    cur_offset = np.abs(cur_L-cur_R)/cur_R*100
    #calculate the lane width and difference with standard lane width
    lane_width = (-np.mean(xfit_L[-100:]) + np.mean(xfit_R[-100:]))*xm_per_pix
    lane_width_offset = np.abs(lane_width-lane_width_standard)/lane_width_standard*100
    
    #calc if the two lines are roughly parallel
    para_offset = ((np.abs(left_co[0]-right_co[0])/right_co[0]) + (np.abs(left_co[1]-right_co[1])/right_co[1]) + 
     (np.abs(left_co[2]-right_co[2])/right_co[2]))*100
    
    if ((cur_L + cur_R)>cur_total_upper_lim):
        if (lane_width_offset > lim_width):
            initCounter += 1
        else:
            initCounter = 0
    else:
        if ((cur_offset>lim_cur)or(lane_width_offset > lim_width)or(para_offset>lim_para)):
            initCounter += 1
        else:
            initCounter = 0

    #print(zSanityCounter.value)
    return initCounter
def process_image(image):
    #print current frame number
    print('Current frame: ', zFrame.value)
    
    #define warp parameter X, Y
    warpX = 0.17
    warpY = 0.63
    
    #define n for detection result smooth
    n = 20
    
    #undistort the image with camera calibration results
    image_undistort = undistortImg(image, mtx, dist)
    
    #detect edge of the frame with combined gradient and color method
    image_edge = edgeDetection(image_undistort, s_thresh=(170, 255), sx_thresh=(20, 100))
    
    #warp the edge detection result frame to get a "bird view" picture
    image_warped, M = warpImg(image_edge,warpX,warpY)
    
    #convert the warped image to gray scale
    image_gray = cv2.cvtColor(image_warped, cv2.COLOR_RGB2GRAY)

    #normal mode: detect lane lines with known poly fit
    if(left_Line.detected and right_Line.detected and zSanityCounter.value < n):
        print('Normal Mode')
        result, left_fitx, right_fitx, left_fit, right_fit, ploty, fLeftFound, fRightFound = search_around_poly(image_gray, 
        left_Line.best_fit, right_Line.best_fit)
        
    #initilization mode: detect with slide window method
    else:
        print('Init Mode')
        result, left_fitx, right_fitx, left_fit, right_fit, ploty, fLeftFound, fRightFound = fit_polynomial(image_gray)
    
    # was the line detected in the last iteration?
    left_Line.detected = fLeftFound
    right_Line.detected = fRightFound
    
    # x values of the last n fits of the line
    left_Line.recent_xfitted = lineSmooth(left_Line.recent_xfitted, left_fitx, n)
    right_Line.recent_xfitted = lineSmooth(right_Line.recent_xfitted, right_fitx, n)
    
    #average x values of the fitted line over the last n iterations
    left_Line.bestx = np.mean(left_Line.recent_xfitted, axis=0)
    right_Line.bestx = np.mean(right_Line.recent_xfitted, axis=0)
    
    #polynomial coefficients averaged over the last n iterations
    left_Line.best_fit_list = lineSmooth(left_Line.best_fit_list, left_fit, n)
    right_Line.best_fit_list = lineSmooth(right_Line.best_fit_list, right_fit, n)
    left_Line.best_fit = np.mean(left_Line.best_fit_list, axis=0)
    right_Line.best_fit = np.mean(right_Line.best_fit_list, axis=0)
    
    #polynomial coefficients for the most recent fit: by pass

    #left_Line.radius_of_curvature, right_Line.radius_of_curvature = measure_curvature_pixels(ploty, left_Line.best_fit, right_Line.best_fit)
    left_Line.radius_of_curvature, right_Line.radius_of_curvature = measure_curvature_real(ploty, left_fit, right_fit)
    left_Line.curvatures = simpleSmooth(left_Line.curvatures, left_Line.radius_of_curvature, n)
    left_Line.radius_of_curvature = np.mean(left_Line.curvatures)
    right_Line.curvatures = simpleSmooth(right_Line.curvatures, right_Line.radius_of_curvature, n)
    right_Line.radius_of_curvature = np.mean(right_Line.curvatures)

    #distance in meters of vehicle center from the line, negative means vechile center on the left side of the lane center
    left_Line.line_base_pos = laneCenterCalc(left_Line.bestx, right_Line.bestx, image.shape[1])
    right_Line.line_base_pos = laneCenterCalc(left_Line.bestx, right_Line.bestx, image.shape[1])
    
    #sanity check
    zSanityCounter.value = sanity_check(left_Line.radius_of_curvature, right_Line.radius_of_curvature,
                 left_Line.bestx, right_Line.bestx, left_Line.best_fit, right_Line.best_fit, zSanityCounter.value)
    
    #when init lane detection, draw detection area with red color
    if (left_Line.detected and right_Line.detected and zSanityCounter.value < n):
        color = (0,255,0)
    else:
        color = (255,0,0)
    
    #draw the detected/take over lane line on the original undistort image
    result = drawLines(image_undistort, image_gray, left_Line.bestx, right_Line.bestx, ploty, warpX, warpY, color)

    #GUI Info
    #define the text and corresponding info
    txt_curvature = 'lane radius: ' + str(round((left_Line.radius_of_curvature+right_Line.radius_of_curvature)/2, 2)) + 'm'
    txt_offset = 'offset: ' + str(left_Line.line_base_pos) + 'm'
    txt_sanity = 'sanity check fail: ' + str(zSanityCounter.value)
    
    org_curvature = (20, 50)
    org_offset = (20, 100)
    org_sanity = (20, 150)
    
    font = cv2.FONT_HERSHEY_SIMPLEX
    fontScale = 1
    color = (255,255,255)
    thickness = 2
    
    result = cv2.putText(result, txt_offset, org_offset, font, fontScale, color, thickness, cv2.LINE_AA) 
    result = cv2.putText(result, txt_curvature, org_curvature, font, fontScale, color, thickness, cv2.LINE_AA) 
    result = cv2.putText(result, txt_sanity, org_sanity, font, fontScale, color, thickness, cv2.LINE_AA) 
    
    #frame counter add 1
    zFrame.value += 1
    
    return result
# Import everything needed to edit/save/watch video clips
from moviepy.editor import VideoFileClip
from IPython.display import HTML

#init frame number count, for debug use
zFrame = globalVar()
zFrame.value = 1

#init sanity check counter
zSanityCounter = globalVar()
zSanityCounter.value = 0

#init left and right lane line classes
left_Line = Line()
right_Line = Line()

#init array to store left and right poly coefficients for smooth calculation
left_Line.best_fit_list = np.empty([0,3])
right_Line.best_fit_list = np.empty([0,3])

#init array to store left and right fitted x values for smooth calculation
left_Line.recent_xfitted = np.empty([0,720])
right_Line.recent_xfitted = np.empty([0,720])

#init list to store left and right curvatures for smooth calculation
left_Line.curvatures = list()
right_Line.curvatures = list()

#name the output video file
white_output = 'project_video_out.mp4'

#define path and name of the input video file
clip1 = VideoFileClip("project_video.mp4")#.subclip(0,5)

#process the input video with the pipeline function
white_clip = clip1.fl_image(process_image) #NOTE: this function expects color images!!
%time white_clip.write_videofile(white_output, audio=False)
HTML("""
<video width="640" height="360" controls>
  <source src="{0}">
</video>
""".format(white_output))<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.10051113972453081, 'fraction_numerical': 0.01989933278707714, 'mean_word_length': 3.5226751367566616, 'pattern_counts': {'":': 0, '<': 20, '<?xml version=': 0, '>': 26, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '1', 'dir': 'jupyter-structured-clean-dedup', 'id': '596734', 'license': 'MIT', 'n_tokens_mistral': 8943, 'n_tokens_neox': 8583, 'n_words': 2349, 'path': 'Advanced Detection.ipynb', 'repo_name': 'dlh2so4/CarND-Advanced-Lane-Lines', 'size': '25629'}