<jupyter_start><jupyter_text>**Run the cell below to import some packages.  If you get an `import error` for a package you've already installed, try changing your kernel (select the Kernel menu above --> Change Kernel).  Still have problems?  Try relaunching Jupyter Notebook from the terminal prompt.  Also, consult the forums for more troubleshooting tips.**    Import Packages<jupyter_code>#importing some useful packages
import matplotlib.pyplot as plt
import matplotlib.image as mpimg
import numpy as np
import cv2
%matplotlib inline<jupyter_output><empty_output><jupyter_text>Read in an Image<jupyter_code>#reading in an image
image = mpimg.imread('test_images/solidWhiteRight.jpg')

#printing out some stats and plotting
print('This image is:', type(image), 'with dimensions:', image.shape)
plt.imshow(image)  # if you wanted to show a single color channel image called 'gray', for example, call as plt.imshow(gray, cmap='gray')<jupyter_output>This image is: <class 'numpy.ndarray'> with dimensions: (540, 960, 3)<jupyter_text>Helper Functions<jupyter_code>import math

def grayscale(img):
    """Applies the Grayscale transform
    This will return an image with only one color channel
    but NOTE: to see the returned image as grayscale
    (assuming your grayscaled image is called 'gray')
    you should call plt.imshow(gray, cmap='gray')"""
    return cv2.cvtColor(img, cv2.COLOR_RGB2GRAY)
    # Or use BGR2GRAY if you read an image with cv2.imread()
    # return cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    
def canny(img, low_threshold, high_threshold):
    """Applies the Canny transform"""
    return cv2.Canny(img, low_threshold, high_threshold)

def gaussian_blur(img, kernel_size):
    """Applies a Gaussian Noise kernel"""
    return cv2.GaussianBlur(img, (kernel_size, kernel_size), 0)

def region_of_interest(img, vertices):
    """
    Applies an image mask.
    
    Only keeps the region of the image defined by the polygon
    formed from `vertices`. The rest of the image is set to black.
    `vertices` should be a numpy array of integer points.
    """
    #defining a blank mask to start with
    mask = np.zeros_like(img)   
    
    #defining a 3 channel or 1 channel color to fill the mask with depending on the input image
    if len(img.shape) > 2:
        channel_count = img.shape[2]  # i.e. 3 or 4 depending on your image
        ignore_mask_color = (255,) * channel_count
    else:
        ignore_mask_color = 255
        
    #filling pixels inside the polygon defined by "vertices" with the fill color    
    cv2.fillPoly(mask, vertices, ignore_mask_color)
    
    #returning the image only where mask pixels are nonzero
    masked_image = cv2.bitwise_and(img, mask)
    return masked_image

def draw_lines(img, lines, color=[255, 0, 0], thickness=10):
    """
    This function draws `lines` with `color` and `thickness`.    
    Lines are drawn on the image inplace (mutates the image).
    """

    # getting list of slopes
    slopes = list(filter(lambda x: x not in [float('-inf'), float('inf')],
                         [ (y2 - y1) / (x2 - x1) for [[x1, y1, x2, y2]] in lines ]))

    # building histogram of slopes
    histo = np.histogram(slopes, bins = 'auto')

    # initializing bins
    bins = {}

    # for each line
    for line in lines:
        
        # determing slope
        x1, y1, x2, y2 = line[0]
        slope = (y2-y1) / (x2-x1)
        
        # digitizing with slope
        line_bin = np.digitize([slope], histo[1])[0]
        
        # appending line to appropiate bin 
        bins.setdefault(line_bin, []).append(line[0])

    # getting top two bins (lanes)
    side_split_lines = sorted(list(bins.values()), key=len, reverse=True)[:2]

    # defining functions to convert between pixel and cartesian coordinates
    cart_y_scale = lambda y: 1 - (y / img.shape[0])
    pixel_y_scale = lambda y: (1 - y) * img.shape[0]

    # for each lane
    for i, side in enumerate(side_split_lines):
        
        # combine x and y points into their own lists
        x = [ l[0] for l in side ] + [ l[2] for l in side ]
        y = [ l[1] for l in side ] + [ l[3] for l in side ]
        
        # map pixel coordinates to cartesian
        y = list(map(cart_y_scale, y))

        # fit the points to a single line
        m, b = np.polyfit(x, y, 1)

        # create x and y values for the lines
        line = np.vectorize(lambda x: (m * x) + b)
        xp = np.arange(0, img.shape[1])
        yp = line(xp)
        
        # place the line on the image
        cv2.line(img, (xp[0], int(pixel_y_scale(yp[0]))), (xp[-1], int(pixel_y_scale(yp[-1]))), color, thickness)

def hough_lines(img, rho, theta, threshold, min_line_len, max_line_gap):
    """
    `img` should be the output of a Canny transform.
        
    Returns an image with hough lines drawn.
    """
    lines = cv2.HoughLinesP(img, rho, theta, threshold, np.array([]), minLineLength=min_line_len, maxLineGap=max_line_gap)
    line_img = np.zeros((img.shape[0], img.shape[1], 3), dtype=np.uint8)
    draw_lines(line_img, lines)
    return line_img

# Python 3 has support for cool math symbols.

def weighted_img(img, initial_img, α=0.8, β=1., γ=0.):
    """
    `img` is the output of the hough_lines(), An image with lines drawn on it.
    Should be a blank image (all black) with lines drawn on it.
    
    `initial_img` should be the image before any processing.
    
    The result image is computed as follows:
    
    initial_img * α + img * β + γ
    NOTE: initial_img and img must be the same shape!
    """
    return cv2.addWeighted(initial_img, α, img, β, γ)<jupyter_output><empty_output><jupyter_text>Image Proccessing Pipeline<jupyter_code># canny parameters
min_threshold = 150
max_threshold = 250

# region of interest parameters
vectors = lambda ys, xs: np.array([[(0, ys), (xs * .48, ys * .59), (xs * .52, ys * .59), (xs , ys)]], dtype=np.int32)

# hough line parameters
rho = 2
theta = np.pi / 180
threshold = 15
min_line_len = 20
max_line_gap = 20

def process_image(image):
    result = grayscale(image)
    result = gaussian_blur(result, 5)
    result = canny(result, min_threshold, max_threshold)
    result = region_of_interest(result, vectors(image.shape[0], image.shape[1]))
    result = hough_lines(result, rho, theta, threshold, min_line_len, max_line_gap)
    result = region_of_interest(result, vectors(image.shape[0], image.shape[1]))
    result = weighted_img(result, image)
    return result<jupyter_output><empty_output><jupyter_text>Single Image Test<jupyter_code>import os
test_images = os.listdir("test_images/")
image = mpimg.imread('test_images/' + test_images[5])
result = process_image(image)
plt.imshow(result)<jupyter_output><empty_output><jupyter_text>Test on Videos**Note: if you get an import error when you run the next cell, try changing your kernel (select the Kernel menu above --> Change Kernel). Still have problems? Try relaunching Jupyter Notebook from the terminal prompt. Also, consult the forums for more troubleshooting tips.****If you get an error that looks like this:**```NeedDownloadError: Need ffmpeg exe. You can download it by calling: imageio.plugins.ffmpeg.download()```**Follow the instructions in the error message and check out [this forum post](https://discussions.udacity.com/t/project-error-of-test-on-videos/274082) for more troubleshooting tips across operating systems.**<jupyter_code># Import everything needed to edit/save/watch video clips
from moviepy.editor import VideoFileClip
from IPython.display import HTML<jupyter_output><empty_output><jupyter_text>solidWhiteRight.mp4<jupyter_code>white_output = 'test_videos_output/solidWhiteRight.mp4'
##clip1 = VideoFileClip("test_videos/solidWhiteRight.mp4").subclip(0,5)
clip1 = VideoFileClip("test_videos/solidWhiteRight.mp4")
white_clip = clip1.fl_image(process_image) #NOTE: this function expects color images!!
%time white_clip.write_videofile(white_output, audio=False)<jupyter_output>[MoviePy] >>>> Building video test_videos_output/solidWhiteRight.mp4
[MoviePy] Writing video test_videos_output/solidWhiteRight.mp4<jupyter_text>Play the video inline, or if you prefer find the video in your filesystem (should be in the same directory) and play it in your video player of choice.<jupyter_code>HTML("""
<video width="960" height="540" controls>
  <source src="{0}">
</video>
""".format(white_output))<jupyter_output><empty_output><jupyter_text>solidYellowLeft.mp4<jupyter_code>yellow_output = 'test_videos_output/solidYellowLeft.mp4'
##clip2 = VideoFileClip('test_videos/solidYellowLeft.mp4').subclip(0,5)
clip2 = VideoFileClip('test_videos/solidYellowLeft.mp4')
yellow_clip = clip2.fl_image(process_image)
%time yellow_clip.write_videofile(yellow_output, audio=False)
HTML("""
<video width="960" height="540" controls>
  <source src="{0}">
</video>
""".format(yellow_output))<jupyter_output><empty_output><jupyter_text>challenge.mp4<jupyter_code>challenge_output = 'test_videos_output/challenge.mp4'
##clip3 = VideoFileClip('test_videos/challenge.mp4').subclip(0,5)
clip3 = VideoFileClip('test_videos/challenge.mp4')
challenge_clip = clip3.fl_image(process_image)
%time challenge_clip.write_videofile(challenge_output, audio=False)
HTML("""
<video width="960" height="540" controls>
  <source src="{0}">
</video>
""".format(challenge_output))<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.1102429626458311, 'fraction_numerical': 0.018944664454671946, 'mean_word_length': 4.045356371490281, 'pattern_counts': {'":': 0, '<': 49, '<?xml version=': 0, '>': 56, 'https://': 1, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 3, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '10', 'dir': 'jupyter-structured-clean-dedup', 'id': '625898', 'license': 'MIT', 'n_tokens_mistral': 3226, 'n_tokens_neox': 3048, 'n_words': 917, 'path': 'P1.ipynb', 'repo_name': 'Lesaun/udacity-car-p1-lane-detection', 'size': '9343'}