<reponame>gcjyzdd/CarND-Capstone
#!/usr/bin/env python3
import os.path
import tensorflow as tf
from tensorflow.contrib.layers import flatten
import helper
import warnings
from distutils.version import LooseVersion
# import project_tests as tests
import time
import datetime
from moviepy.editor import VideoFileClip

ts = time.time()
st = datetime.datetime.fromtimestamp(ts).strftime('%Y_%m_%d__%H_%M_%S')

# Check TensorFlow Version
assert LooseVersion(tf.__version__) >= LooseVersion(
    '1.0'), 'Please use TensorFlow version 1.0 or newer.  You are using {}'.format(tf.__version__)
print('TensorFlow Version: {}'.format(tf.__version__))

# Check for a GPU
if not tf.test.gpu_device_name():
    warnings.warn('No GPU found. Please use a GPU to train your neural network.')
else:
    print('Default GPU Device: {}'.format(tf.test.gpu_device_name()))


def load_vgg(sess, vgg_path):
    """
    Load Pretrained VGG Model into TensorFlow.
    :param sess: TensorFlow Session
    :param vgg_path: Path to vgg folder, containing "variables/" and "saved_model.pb"
    :return: Tuple of Tensors from VGG model (image_input, keep_prob, layer3_out, layer4_out, layer7_out)
    """
    # TODO: Implement function
    #   Use tf.saved_model.loader.load to load the model and weights
    vgg_tag = 'vgg16'
    vgg_input_tensor_name = 'image_input:0'
    vgg_keep_prob_tensor_name = 'keep_prob:0'
    vgg_layer3_out_tensor_name = 'layer3_out:0'
    vgg_layer4_out_tensor_name = 'layer4_out:0'
    vgg_layer7_out_tensor_name = 'layer7_out:0'

    tf.saved_model.loader.load(sess, [vgg_tag], vgg_path)
    graph = tf.get_default_graph()

    w1 = graph.get_tensor_by_name(vgg_input_tensor_name)
    keep = graph.get_tensor_by_name(vgg_keep_prob_tensor_name)
    # l3 = graph.get_tensor_by_name(vgg_layer3_out_tensor_name)
    # l4 = graph.get_tensor_by_name(vgg_layer4_out_tensor_name)
    l7 = graph.get_tensor_by_name(vgg_layer7_out_tensor_name)

    return w1, keep, l7  # l3, l4,


# tests.test_load_vgg(load_vgg, tf)
# print("load_vgg done.")


def layers(vgg_layer7_out, keep_prob, n_classes):
    """
    Create the layers for a fully convolutional network.  Build skip-layers using the vgg layers.
    :param vgg_layer3_out: TF Tensor for VGG Layer 3 output
    :param vgg_layer4_out: TF Tensor for VGG Layer 4 output
    :param vgg_layer7_out: TF Tensor for VGG Layer 7 output
    :param num_classes: Number of classes to classify
    :return: The Tensor for the last layer of output
    """
    # TODO: Implement function
    mu = 0
    sigma = 0.1

    # SOLUTION: Flatten. Input = 5x5x16. Output = 400.
    fc0 = flatten(vgg_layer7_out)

    # SOLUTION: Layer 3: Fully Connected. Input = 400. Output = 120.
    fc1_W = tf.Variable(tf.truncated_normal(shape=(196608, 100), mean=mu, stddev=sigma))
    fc1_b = tf.Variable(tf.zeros(100))
    fc1 = tf.matmul(fc0, fc1_W) + fc1_b

    # SOLUTION: Activation.
    fc1 = tf.nn.relu(fc1)

    # Dropout
    fc1 = tf.nn.dropout(fc1, keep_prob)

    # SOLUTION: Layer 4: Fully Connected. Input = 120. Output = 84.
    fc2_W = tf.Variable(tf.truncated_normal(shape=(100, 40), mean=mu, stddev=sigma))
    fc2_b = tf.Variable(tf.zeros(40))
    fc2 = tf.matmul(fc1, fc2_W) + fc2_b

    # SOLUTION: Activation.
    fc2 = tf.nn.relu(fc2)

    # Dropout
    fc2 = tf.nn.dropout(fc2, keep_prob)

    # SOLUTION: Layer 5: Fully Connected. Input = 84. Output = 43.
    fc3_W = tf.Variable(tf.truncated_normal(shape=(40, n_classes), mean=mu, stddev=sigma))
    fc3_b = tf.Variable(tf.zeros(n_classes))
    logits = tf.matmul(fc2, fc3_W) + fc3_b

    return logits


def optimize(logits, correct_label, learning_rate, num_classes):
    """
    Build the TensorFLow loss and optimizer operations.
    :param nn_last_layer: TF Tensor of the last layer in the neural network
    :param correct_label: TF Placeholder for the correct label image
    :param learning_rate: TF Placeholder for the learning rate
    :param num_classes: Number of classes to classify
    :return: Tuple of (logits, train_op, cross_entropy_loss)
    """
    # TODO: Implement function
    cross_entropy = tf.nn.softmax_cross_entropy_with_logits(labels=correct_label, logits=logits)
    loss_operation = tf.reduce_mean(cross_entropy)
    optimizer = tf.train.AdamOptimizer(learning_rate=learning_rate)
    train_op = optimizer.minimize(loss_operation)

    # validation
    correct_prediction = tf.equal(tf.argmax(logits, 1), tf.argmax(correct_label, 1))
    accuracy_operation = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))

    return logits, train_op, loss_operation, accuracy_operation  # cross_entropy_loss


def train_nn(sess, epochs, batch_size, get_batches_fn, train_op, acc_op, input_image,
             y, keep_prob, learning_rate):
    """
    Train neural network and print out the loss during training.
    :param sess: TF Session
    :param epochs: Number of epochs
    :param batch_size: Batch size
    :param get_batches_fn: Function to get batches of training data.  Call using get_batches_fn(batch_size)
    :param train_op: TF Operation to train the neural network
    :param cross_entropy_loss: TF Tensor for the amount of loss
    :param input_image: TF Placeholder for input images
    :param correct_label: TF Placeholder for label images
    :param keep_prob: TF Placeholder for dropout keep probability
    :param learning_rate: TF Placeholder for learning rate
    """
    # TODO: Implement function
    for epoch in range(epochs):
        print("epoch {} ".format(epoch))
        loss = 0
        i = 0
        for image, label in get_batches_fn(batch_size):
            #
            print("{}, ".format(i), end='')
            i += 1
            sess.run(train_op,
                     feed_dict={input_image: image, y: label, keep_prob: 0.5, learning_rate: 1e-3})
            loss += sess.run(acc_op,
                             feed_dict={input_image: image, y: label, keep_prob: 0.5, learning_rate: 1e-3})
        print("\nAvg loss =  {} ".format(loss/i))


def run():
    num_classes = 3
    image_shape = (192, 256)
    data_dir = './data_tl'
    runs_dir = './runs'
    # tests.test_for_kitti_dataset(data_dir)

    # Download pretrained vgg model
    # helper.maybe_download_pretrained_vgg(data_dir)

    # OPTIONAL: Train and Inference on the cityscapes dataset instead of the Kitti dataset.
    # You'll need a GPU with at least 10 teraFLOPS to train on.
    #  https://www.cityscapes-dataset.com/

    with tf.Session() as sess:
        # Path to vgg model
        vgg_path = os.path.join(data_dir, 'vgg')
        # Create function to get batches
        get_batches_fn = helper.gen_batch_function(os.path.join(data_dir, 'training'), image_shape)

        # OPTIONAL: Augment Images for better results
        #  https://datascience.stackexchange.com/questions/5224/how-to-prepare-augment-images-for-neural-network

        # TODO: Build NN using load_vgg, layers, and optimize function
        input_image, keep_prob, layer7_out = load_vgg(sess, vgg_path)
        logits = layers(layer7_out, keep_prob, num_classes)

        EPOCHS = 10
        BATCH_SIZE = 8

        y = tf.placeholder(tf.int32, (None))
        correct_label = tf.one_hot(y, num_classes)

        learning_rate = tf.placeholder(tf.float32)

        logits, train_op, cross_entropy_loss, acc_op = optimize(logits, correct_label, learning_rate, num_classes)

        # TODO: Train NN using the train_nn function
        sess.run(tf.global_variables_initializer())
        train_nn(sess, EPOCHS, BATCH_SIZE, get_batches_fn, train_op, acc_op, input_image, y,
                 keep_prob, learning_rate)

        saver = tf.train.Saver()
        saver.save(sess, './mdl_' + st)
        print("Model saved.")
        # TODO: Save inference data using helper.save_inference_samples
        helper.save_inference_samples(runs_dir, data_dir, sess, image_shape, logits, keep_prob, input_image)

        # OPTIONAL: Apply the trained model to a video


def runTest():
    num_classes = 3
    image_shape = (192, 256)
    data_dir = './data_tl'
    runs_dir = './runs'
    with tf.Session() as sess:
        # Path to vgg model
        vgg_path = os.path.join(data_dir, 'vgg')

        # OPTIONAL: Augment Images for better results
        #  https://datascience.stackexchange.com/questions/5224/how-to-prepare-augment-images-for-neural-network

        # TODO: Build NN using load_vgg, layers, and optimize function
        input_image, keep_prob, layer7_out = load_vgg(sess, vgg_path)
        logits = layers(layer7_out, keep_prob, num_classes)

        y = tf.placeholder(tf.int32, (None))
        correct_label = tf.one_hot(y, num_classes)

        learning_rate = tf.placeholder(tf.float32)

        logits, train_op, cross_entropy_loss = optimize(logits, correct_label, learning_rate, num_classes)

        saver = tf.train.Saver()
        saver.restore(sess, tf.train.latest_checkpoint('./'))
        helper.save_inference_samples(runs_dir, data_dir, sess, image_shape, logits, keep_prob, input_image)

def process_video():
    num_classes = 2
    image_shape = (160, 576)
    data_dir = './data'
    write_output = './output.mp4'
    clip1 = VideoFileClip("./project_video.mp4")  # .subclip(18,48)

    with tf.Session() as sess:
        # Path to vgg model
        vgg_path = os.path.join(data_dir, 'vgg')

        # TODO: Build NN using load_vgg, layers, and optimize function
        input_image, keep_prob, layer3_out, layer4_out, layer7_out = load_vgg(sess, vgg_path)
        layer_output = layers(layer3_out, layer4_out, layer7_out, num_classes)

        correct_label = tf.placeholder(tf.float32, (None, image_shape[0], image_shape[1], num_classes))
        learning_rate = tf.placeholder(tf.float32)

        logits, train_op, cross_entropy_loss = optimize(layer_output, correct_label, learning_rate, num_classes)

        saver = tf.train.Saver()
        saver.restore(sess, tf.train.latest_checkpoint('./'))

        white_clip = clip1.fl_image(
            lambda im: helper.gen_output_per_img(sess, logits, keep_prob, input_image, im, image_shape))
        white_clip.write_videofile(write_output, audio=False)


if __name__ == '__main__':
    #run()
    runTest()
    # process_video()

#!/usr/bin/env python3
import os.path
import tensorflow as tf
from tensorflow.contrib.layers import flatten
import helper
import warnings
from distutils.version import LooseVersion
# import project_tests as tests
import time
import datetime
from moviepy.editor import VideoFileClip

ts = time.time()
st = datetime.datetime.fromtimestamp(ts).strftime('%Y_%m_%d__%H_%M_%S')

# Check TensorFlow Version
assert LooseVersion(tf.__version__) >= LooseVersion(
    '1.0'), 'Please use TensorFlow version 1.0 or newer.  You are using {}'.format(tf.__version__)
print('TensorFlow Version: {}'.format(tf.__version__))

# Check for a GPU
if not tf.test.gpu_device_name():
    warnings.warn('No GPU found. Please use a GPU to train your neural network.')
else:
    print('Default GPU Device: {}'.format(tf.test.gpu_device_name()))


def load_vgg(sess, vgg_path):
    """
    Load Pretrained VGG Model into TensorFlow.
    :param sess: TensorFlow Session
    :param vgg_path: Path to vgg folder, containing "variables/" and "saved_model.pb"
    :return: Tuple of Tensors from VGG model (image_input, keep_prob, layer3_out, layer4_out, layer7_out)
    """
    # TODO: Implement function
    #   Use tf.saved_model.loader.load to load the model and weights
    vgg_tag = 'vgg16'
    vgg_input_tensor_name = 'image_input:0'
    vgg_keep_prob_tensor_name = 'keep_prob:0'
    vgg_layer3_out_tensor_name = 'layer3_out:0'
    vgg_layer4_out_tensor_name = 'layer4_out:0'
    vgg_layer7_out_tensor_name = 'layer7_out:0'

    tf.saved_model.loader.load(sess, [vgg_tag], vgg_path)
    graph = tf.get_default_graph()

    w1 = graph.get_tensor_by_name(vgg_input_tensor_name)
    keep = graph.get_tensor_by_name(vgg_keep_prob_tensor_name)
    # l3 = graph.get_tensor_by_name(vgg_layer3_out_tensor_name)
    # l4 = graph.get_tensor_by_name(vgg_layer4_out_tensor_name)
    l7 = graph.get_tensor_by_name(vgg_layer7_out_tensor_name)

    return w1, keep, l7  # l3, l4,


# tests.test_load_vgg(load_vgg, tf)
# print("load_vgg done.")


def layers(vgg_layer7_out, keep_prob, n_classes):
    """
    Create the layers for a fully convolutional network.  Build skip-layers using the vgg layers.
    :param vgg_layer3_out: TF Tensor for VGG Layer 3 output
    :param vgg_layer4_out: TF Tensor for VGG Layer 4 output
    :param vgg_layer7_out: TF Tensor for VGG Layer 7 output
    :param num_classes: Number of classes to classify
    :return: The Tensor for the last layer of output
    """
    # TODO: Implement function
    mu = 0
    sigma = 0.1

    # SOLUTION: Flatten. Input = 5x5x16. Output = 400.
    fc0 = flatten(vgg_layer7_out)

    # SOLUTION: Layer 3: Fully Connected. Input = 400. Output = 120.
    fc1_W = tf.Variable(tf.truncated_normal(shape=(196608, 100), mean=mu, stddev=sigma))
    fc1_b = tf.Variable(tf.zeros(100))
    fc1 = tf.matmul(fc0, fc1_W) + fc1_b

    # SOLUTION: Activation.
    fc1 = tf.nn.relu(fc1)

    # Dropout
    fc1 = tf.nn.dropout(fc1, keep_prob)

    # SOLUTION: Layer 4: Fully Connected. Input = 120. Output = 84.
    fc2_W = tf.Variable(tf.truncated_normal(shape=(100, 40), mean=mu, stddev=sigma))
    fc2_b = tf.Variable(tf.zeros(40))
    fc2 = tf.matmul(fc1, fc2_W) + fc2_b

    # SOLUTION: Activation.
    fc2 = tf.nn.relu(fc2)

    # Dropout
    fc2 = tf.nn.dropout(fc2, keep_prob)

    # SOLUTION: Layer 5: Fully Connected. Input = 84. Output = 43.
    fc3_W = tf.Variable(tf.truncated_normal(shape=(40, n_classes), mean=mu, stddev=sigma))
    fc3_b = tf.Variable(tf.zeros(n_classes))
    logits = tf.matmul(fc2, fc3_W) + fc3_b

    return logits


def optimize(logits, correct_label, learning_rate, num_classes):
    """
    Build the TensorFLow loss and optimizer operations.
    :param nn_last_layer: TF Tensor of the last layer in the neural network
    :param correct_label: TF Placeholder for the correct label image
    :param learning_rate: TF Placeholder for the learning rate
    :param num_classes: Number of classes to classify
    :return: Tuple of (logits, train_op, cross_entropy_loss)
    """
    # TODO: Implement function
    cross_entropy = tf.nn.softmax_cross_entropy_with_logits(labels=correct_label, logits=logits)
    loss_operation = tf.reduce_mean(cross_entropy)
    optimizer = tf.train.AdamOptimizer(learning_rate=learning_rate)
    train_op = optimizer.minimize(loss_operation)

    # validation
    correct_prediction = tf.equal(tf.argmax(logits, 1), tf.argmax(correct_label, 1))
    accuracy_operation = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))

    return logits, train_op, loss_operation, accuracy_operation  # cross_entropy_loss


def train_nn(sess, epochs, batch_size, get_batches_fn, train_op, acc_op, input_image,
             y, keep_prob, learning_rate):
    """
    Train neural network and print out the loss during training.
    :param sess: TF Session
    :param epochs: Number of epochs
    :param batch_size: Batch size
    :param get_batches_fn: Function to get batches of training data.  Call using get_batches_fn(batch_size)
    :param train_op: TF Operation to train the neural network
    :param cross_entropy_loss: TF Tensor for the amount of loss
    :param input_image: TF Placeholder for input images
    :param correct_label: TF Placeholder for label images
    :param keep_prob: TF Placeholder for dropout keep probability
    :param learning_rate: TF Placeholder for learning rate
    """
    # TODO: Implement function
    for epoch in range(epochs):
        print("epoch {} ".format(epoch))
        loss = 0
        i = 0
        for image, label in get_batches_fn(batch_size):
            #
            print("{}, ".format(i), end='')
            i += 1
            sess.run(train_op,
                     feed_dict={input_image: image, y: label, keep_prob: 0.5, learning_rate: 1e-3})
            loss += sess.run(acc_op,
                             feed_dict={input_image: image, y: label, keep_prob: 0.5, learning_rate: 1e-3})
        print("\nAvg loss =  {} ".format(loss/i))


def run():
    num_classes = 3
    image_shape = (192, 256)
    data_dir = './data_tl'
    runs_dir = './runs'
    # tests.test_for_kitti_dataset(data_dir)

    # Download pretrained vgg model
    # helper.maybe_download_pretrained_vgg(data_dir)

    # OPTIONAL: Train and Inference on the cityscapes dataset instead of the Kitti dataset.
    # You'll need a GPU with at least 10 teraFLOPS to train on.
    #  https://www.cityscapes-dataset.com/

    with tf.Session() as sess:
        # Path to vgg model
        vgg_path = os.path.join(data_dir, 'vgg')
        # Create function to get batches
        get_batches_fn = helper.gen_batch_function(os.path.join(data_dir, 'training'), image_shape)

        # OPTIONAL: Augment Images for better results
        #  https://datascience.stackexchange.com/questions/5224/how-to-prepare-augment-images-for-neural-network

        # TODO: Build NN using load_vgg, layers, and optimize function
        input_image, keep_prob, layer7_out = load_vgg(sess, vgg_path)
        logits = layers(layer7_out, keep_prob, num_classes)

        EPOCHS = 10
        BATCH_SIZE = 8

        y = tf.placeholder(tf.int32, (None))
        correct_label = tf.one_hot(y, num_classes)

        learning_rate = tf.placeholder(tf.float32)

        logits, train_op, cross_entropy_loss, acc_op = optimize(logits, correct_label, learning_rate, num_classes)

        # TODO: Train NN using the train_nn function
        sess.run(tf.global_variables_initializer())
        train_nn(sess, EPOCHS, BATCH_SIZE, get_batches_fn, train_op, acc_op, input_image, y,
                 keep_prob, learning_rate)

        saver = tf.train.Saver()
        saver.save(sess, './mdl_' + st)
        print("Model saved.")
        # TODO: Save inference data using helper.save_inference_samples
        helper.save_inference_samples(runs_dir, data_dir, sess, image_shape, logits, keep_prob, input_image)

        # OPTIONAL: Apply the trained model to a video


def runTest():
    num_classes = 3
    image_shape = (192, 256)
    data_dir = './data_tl'
    runs_dir = './runs'
    with tf.Session() as sess:
        # Path to vgg model
        vgg_path = os.path.join(data_dir, 'vgg')

        # OPTIONAL: Augment Images for better results
        #  https://datascience.stackexchange.com/questions/5224/how-to-prepare-augment-images-for-neural-network

        # TODO: Build NN using load_vgg, layers, and optimize function
        input_image, keep_prob, layer7_out = load_vgg(sess, vgg_path)
        logits = layers(layer7_out, keep_prob, num_classes)

        y = tf.placeholder(tf.int32, (None))
        correct_label = tf.one_hot(y, num_classes)

        learning_rate = tf.placeholder(tf.float32)

        logits, train_op, cross_entropy_loss = optimize(logits, correct_label, learning_rate, num_classes)

        saver = tf.train.Saver()
        saver.restore(sess, tf.train.latest_checkpoint('./'))
        helper.save_inference_samples(runs_dir, data_dir, sess, image_shape, logits, keep_prob, input_image)

def process_video():
    num_classes = 2
    image_shape = (160, 576)
    data_dir = './data'
    write_output = './output.mp4'
    clip1 = VideoFileClip("./project_video.mp4")  # .subclip(18,48)

    with tf.Session() as sess:
        # Path to vgg model
        vgg_path = os.path.join(data_dir, 'vgg')

        # TODO: Build NN using load_vgg, layers, and optimize function
        input_image, keep_prob, layer3_out, layer4_out, layer7_out = load_vgg(sess, vgg_path)
        layer_output = layers(layer3_out, layer4_out, layer7_out, num_classes)

        correct_label = tf.placeholder(tf.float32, (None, image_shape[0], image_shape[1], num_classes))
        learning_rate = tf.placeholder(tf.float32)

        logits, train_op, cross_entropy_loss = optimize(layer_output, correct_label, learning_rate, num_classes)

        saver = tf.train.Saver()
        saver.restore(sess, tf.train.latest_checkpoint('./'))

        white_clip = clip1.fl_image(
            lambda im: helper.gen_output_per_img(sess, logits, keep_prob, input_image, im, image_shape))
        white_clip.write_videofile(write_output, audio=False)


if __name__ == '__main__':
    #run()
    runTest()
    # process_video()
#!/usr/bin/env python3 # import project_tests as tests # Check TensorFlow Version # Check for a GPU Load Pretrained VGG Model into TensorFlow.     :param sess: TensorFlow Session     :param vgg_path: Path to vgg folder, containing "variables/" and "saved_model.pb"     :return: Tuple of Tensors from VGG model (image_input, keep_prob, layer3_out, layer4_out, layer7_out) # TODO: Implement function #   Use tf.saved_model.loader.load to load the model and weights # l3 = graph.get_tensor_by_name(vgg_layer3_out_tensor_name) # l4 = graph.get_tensor_by_name(vgg_layer4_out_tensor_name) # l3, l4, # tests.test_load_vgg(load_vgg, tf) # print("load_vgg done.") Create the layers for a fully convolutional network.  Build skip-layers using the vgg layers.     :param vgg_layer3_out: TF Tensor for VGG Layer 3 output     :param vgg_layer4_out: TF Tensor for VGG Layer 4 output     :param vgg_layer7_out: TF Tensor for VGG Layer 7 output     :param num_classes: Number of classes to classify     :return: The Tensor for the last layer of output # TODO: Implement function # SOLUTION: Flatten. Input = 5x5x16. Output = 400. # SOLUTION: Layer 3: Fully Connected. Input = 400. Output = 120. # SOLUTION: Activation. # Dropout # SOLUTION: Layer 4: Fully Connected. Input = 120. Output = 84. # SOLUTION: Activation. # Dropout # SOLUTION: Layer 5: Fully Connected. Input = 84. Output = 43. Build the TensorFLow loss and optimizer operations.     :param nn_last_layer: TF Tensor of the last layer in the neural network     :param correct_label: TF Placeholder for the correct label image     :param learning_rate: TF Placeholder for the learning rate     :param num_classes: Number of classes to classify     :return: Tuple of (logits, train_op, cross_entropy_loss) # TODO: Implement function # validation # cross_entropy_loss Train neural network and print out the loss during training.     :param sess: TF Session     :param epochs: Number of epochs     :param batch_size: Batch size     :param get_batches_fn: Function to get batches of training data.  Call using get_batches_fn(batch_size)     :param train_op: TF Operation to train the neural network     :param cross_entropy_loss: TF Tensor for the amount of loss     :param input_image: TF Placeholder for input images     :param correct_label: TF Placeholder for label images     :param keep_prob: TF Placeholder for dropout keep probability     :param learning_rate: TF Placeholder for learning rate # TODO: Implement function # # tests.test_for_kitti_dataset(data_dir) # Download pretrained vgg model # helper.maybe_download_pretrained_vgg(data_dir) # OPTIONAL: Train and Inference on the cityscapes dataset instead of the Kitti dataset. # You'll need a GPU with at least 10 teraFLOPS to train on. #  https://www.cityscapes-dataset.com/ # Path to vgg model # Create function to get batches # OPTIONAL: Augment Images for better results #  https://datascience.stackexchange.com/questions/5224/how-to-prepare-augment-images-for-neural-network # TODO: Build NN using load_vgg, layers, and optimize function # TODO: Train NN using the train_nn function # TODO: Save inference data using helper.save_inference_samples # OPTIONAL: Apply the trained model to a video # Path to vgg model # OPTIONAL: Augment Images for better results #  https://datascience.stackexchange.com/questions/5224/how-to-prepare-augment-images-for-neural-network # TODO: Build NN using load_vgg, layers, and optimize function # .subclip(18,48) # Path to vgg model # TODO: Build NN using load_vgg, layers, and optimize function #run() # process_video()