• This repository has been archived on 06/May/2019
  • Stars
    star
    154
  • Rank 242,095 (Top 5 %)
  • Language
    C++
  • License
    MIT License
  • Created over 7 years ago
  • Updated over 6 years ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

A software pipeline using the Model Predictive Control method to drive a car around a virtual track.

INTRODUCTION

This is my turn-in code for one of the project in partial fulfillment of the requirements for Udacity's self-driving car Nanodegree program. In this project, I have implemented a software pipeline using the model predictive control (MPC) method to drive a car around a track in a simulator. There is a 100 millisecond latency between actuation commands on top of the connection latency.

IMPORTANT: Please view my screen recording of using my code for the vehicle to drive around the track for several laps:

  • https://www.youtube.com/watch?v=75ylhM0QsXQ
  • The MPC trajectory path is displayed in green, and the polynomial fitted reference path in yellow.
  • I used 640 x 480 screen resolution, with a graphic quality of fastest
  • Tested in macOS Sierra Version 10.12.4 Macbook Pro Mid 2014. 2.6 GHz Intel Core i5.

MPC

According to wikipedia:

Model predictive controllers rely on dynamic models of the process. The main advantage of MPC is the fact that it allows the current timeslot to be optimized, while keeping future timeslots in account. This is achieved by optimizing a finite time-horizon, but only implementing the current timeslot. MPC has the ability to anticipate future events and can take control actions accordingly.

Here is an overview of the MPC approach as provided by Udacity: MPC MPC

In my own words, the MPC method can anticipate future events because we have an idea of what is probably gonna happen if we do something. This is because we have a model of how things work in our world (like physics for example). We can anticipate future events based on our current plan of action and also anticipate our next plan of action based on the result of the current plan.

How to use

1. Install dependencies

  • Check the dependencies from the repo mentioned above.
  • for example for mac with homebrew here's what I did since I already have most of the dependencies.
brew install ipopt
brew install cppad
brew install openssl libuv cmake zlib
git clone https://github.com/uWebSockets/uWebSockets 
cd uWebSockets
patch CMakeLists.txt < ../cmakepatch.txt
mkdir build
export PKG_CONFIG_PATH=/usr/local/opt/openssl/lib/pkgconfig 
cd build
cmake ..
make 
sudo make install
cd ..
cd ..
sudo rm -r uWebSockets

2. Clone, compile, and run

git clone https://github.com/mithi/mpc
mkdir build && cd build
cc=gcc-6 cmake .. && make
./mpc

3. Install the simulator, open it, and go to the MPC section


THE STATE VARIABLES

The state variables for the vehicle are the following:

px

  • The current location in the x-axis of an arbitrary global map coordinate system

py

  • The current location in the y-axis of an arbitrary global map coordinate system

psi

  • The current orientation / heading of the vehicle

v

  • The current velocity/speed of the vehicle

This is given to us by the simulation every time we ask for it. In addition to this, we are also given a series of waypoints which are points with respect to an arbitrary global map coordinate system we can use to fit a polynomial which is a function that estimates the curve of the road ahead. It is known that a 3rd degree polynomial can be a good estimate of most road curves. The polynomial function is with respect to the vehicle's local coordinate.

CTE AND EPSI

ERROR

We can compute for the errors which is the difference between our desired position and heading and our actual position and heading:

cte

  • This is the cross track error which is the difference between our desired position and actual position. We can use our fitted polynomial at point px = 0 to get the position where we should currently be.
  • cte = f(0)

epsi

  • This is the orientation error which is the difference between our desired heading and actual heading. Our desired orientation is the heading tangent to our road curve. This can be computed using an arctan to the derivative of the fitted polynomial function at point px = 0 to get the angle to which we should be heading.
  • epsi = arctan(f`(0)) where f` is the derivative of f

ACTUATIONS: STEERING, THROTTLE, AND BRAKE

From the state variables, CTE, EPSI, and the kinematic model (see discussion immediately below this section), we can use the MPC method to arrive at our best course of action. There are two modes of actuation we can use to control our vehicle.

delta

  • This is the steering value which represents the angle of which we turn our vehicle, which I suppose is the angle of the vehicle's tires. The angle is restricted to be between -25 and 25 degrees but is mapped to the values between -1 and 1. In my code, I have restricted this to only be between -0.75 and 0.75 as I which to be conservative as opposed to aggressive in our steering. Since we can plan ahead, we shouldn't be needing to suddenly steer a large angle.

a

  • This is the throttle or brake value which represents the acceleration or deceleration of our vehicle. In an actual vehicle, this is controlled by the brake pedal. The simulator expects values between -1 and 1. Negative values represents braking and positive values represents throttle. In my code, I have restricted the range to only be between -0.5 and 1 as ideally we shouldn't be suddenly pressing the brakes all of a sudden since we are able to plan ahead.

KINEMATIC MODEL

So based on physics, here is a simplified version of how the world (with our vehicle in it) works. How the state variables get updated based elapse time dt, the current state, and our actuations delta and a.

px` = px + v * cos(psi) * dt
py` = py + v * sin(psi) ( dt)
psi` = psi + v / Lf * (-delta) * dt
v` = v + a * dt

Lf - this is the length from front of vehicle to its Center-of-Gravity

We can also predict the next cte, and epsi based on our actuations.

cte` = cte - v * sin(epsi) * dt
epsi` = epsi +  v / Lf * (-delta) * dt

Recall from the discussion:

cte = py_desired - py
epsi = psi - psi_desired
py_desired = f(px)
psi_desired = atan(f`(px))
where f is the road curve function
      f` is the derivative of f

Cost Function and Penalty Weights

Given the constraints of our model, we should have a good objective such as a cost function to minimize.

Here are the factors we should consider:

  • We should minimize the cross track error cte, we want to be in our desired position
  • We should minimize our heading error epsi, we want to be oriented to our desired heading
  • If possible, we want to go as fast as we can. I set this to v = 100 but you can play around with this.
  • We don't want to be erratic in our driving IE:
    1. We don't want to steer if we don't really need to
    1. We don't want to accelerate or brake if we don't really need to
    1. We don't want consecutive steering angles to be too different
    1. We don't want consecutive accelerations to be too different

So mathematically it should be like:

cost = A * cte^2 + B * epsi^2 + C * (v - vmax)^2 +
       D * delta^2 + E * a^2 + F * (a` - a)^2 +  G * (delta` - delta)^2

... integrated over all time steps

The penalty weights are again determined through trial-and-error, taking into consideration that our primary objective is to minimize cte and epsi so that should hold the highest weight. I also increased the weight cost for consecutive steering angles when I notice that the path that the vehicle was taking is somehow jagged. The least of our concerns is going as fast as possible. I also had to increase the weight cost a little bit for the consecutive acceleration difference because we don't want to turn too late at road curves.

Ultimately here are the weights I ended up with:

A = 1500
B = 1500
C = 1
D = 10
E = 10
F = 150
G = 15

Timestep Length and Frequency

The timestep length N is how many states we "lookahead" in the future and the time step frequency dt is how much time we expect environment changes. I chose a dt = 0.1 seconds because the that's the latency between actuation commands so it seemed like a good ballpark. If the N is too small, this makes us too short-sighted which defeat the purpose of planning for the future. If the N is too small we might not be able to take advantage of looking ahead to plan for curves that we might not be able to do with simpler and less sophisticated control methods like PID. It doesn't make sense to look too far to the future because that future might not be as we expect it, so we must not calculate too much before getting feedback from the environment. Also, it will take a long time to look for the best move as I have set this to a time limit of 0.5 seconds (The default option from the mpc quizzes the code approach was modeled after). I started with N = 6 because that's the number of waypoints given to us but looking at the displayed green line at the simulator makes too short to plan for curves. At N = 15 I noticed that given the time limit of 0.5 seconds, it seems that the computer was running out of time to find the best variables that have minimized the cost well. With trial-and-error I found that N = 10 was good.

Polynomial Fitting and MPC Preprocessing

The waypoints to estimate the road curves is given at an arbitrary global coordinate system, so I had to transform them to the vehicle's local coordinate system.

 for(int i = 0; i < NUMBER_OF_WAYPOINTS; ++i) {
    const double dx = points_xs[i] - px;
    const double dy = points_ys[i] - py;
    waypoints_xs[i] = dx * cos(-psi) - dy * sin(-psi);
    waypoints_ys[i] = dy * cos(-psi) + dx * sin(-psi);
  }

I used this to get a 3rd order polynomial as an estimate of the current road curve ahead, as it is said that it is a good fit of most roads. Using a smaller order polynomial runs the risk of underfitting, and likewise using a higher order polynomial would be prone to overfitting or an inefficient unnecessary added complexity.

To get the fitted curve, I used a function adapted from here:

We also have to generate the current errors as discussed above.

Here it is in code:

  // current CTE is fitted polynomial (road curve) evaluated at px = 0.0
  // f = K[3] * px0 * px0 + px0 + K[2] * px0 * px0 + K[1] * px0 + K[0];
  const double cte = K[0];
  // current heading error epsi is the tangent to the road curve at px = 0.0
  // epsi = arctan(f') where f' is the derivative of the fitted polynomial
  // f' = 3.0 * K[3] * px0 * px0 + 2.0 * K[2] * px0 + K[1]
  const double epsi = -atan(K[1]);

Model Predictive Control with Latency

Note we have to take the 100ms latency into account, so instead of using the state as churned out to as, we compute the state with the delay factored in using our kinematic model before feeding it to the object that will solve for what we should do next.

  // current state must be in vehicle coordinates with the delay factored in
  // kinematic model is at play here
  // note that at current state at vehicle coordinates:
  // px, py, psi = 0.0, 0.0, 0.0
  // note that in vehicle coordinates it is going straight ahead the x-axis
  // which means position in vehicle's y-axis does not change
  // the steering angle is negative the given value as we have
  // as recall that during transformation we rotated all waypoints by -psi
  
  MPC mpc;

  const double dt = 0.1;

  const double current_px = 0.0 + v * dt;
  const double current_py = 0.0;
  const double current_psi = 0.0 + v * (-delta) / Lf * dt;
  const double current_v = v + a * dt;
  const double current_cte = cte + v * sin(epsi) * dt;
  const double current_epsi = epsi + v * (-delta) / Lf * dt;

  state << current_px, current_py, current_psi, current_v, current_cte, current_epsi;

  mpc.solve(state, K);

  cout << "steering angle:" << mpc.steer
       << "throttle:" << mpc.throttle << endl;

  //K is the coefficients of the 3rd order polynomial that estimates the road curve ahead

More Repositories

1

react-philosophies

๐Ÿง˜ Things I think about when I write React code ๐Ÿง˜
3,518
star
2

robotics-coursework

๐Ÿค– Places where you can learn robotics (and stuff like that) online ๐Ÿค–
2,605
star
3

hexapod-robot-simulator

A hexapod robot simulator built from first principles
Python
768
star
4

hexapod

Blazing fast hexapod robot simulator for the web.
JavaScript
579
star
5

fusion-ukf

An unscented Kalman Filter implementation for fusing lidar and radar sensor measurements.
C++
278
star
6

epic-react-exercises

Practical React exercises with detailed solutions.
JavaScript
159
star
7

rusty-genes

Genetic algorithm implementation in Rust with animated visualizations in Python
Rust
141
star
8

fusion-ekf

An extended Kalman Filter implementation in C++ for fusing lidar and radar sensor measurements.
C++
135
star
9

cpp-resources

A small collection of notes about basic C++
C++
132
star
10

hexy

Code for a hexapod robot
Python
85
star
11

vehicle-tracking-2

A vehicle detection and tracking pipeline with OpenCV, histogram of oriented gradients (HOG), and support vector machines (SVM).
Jupyter Notebook
84
star
12

advanced-lane-detection

An advanced lane-finding algorithm using distortion correction, image rectification, color transforms, and gradient thresholding.
Jupyter Notebook
80
star
13

algorithm-playground

An (old) and unstructured (messy tbh) collection of programming exercises.
Jupyter Notebook
79
star
14

hexapod-irl

A "fork" of Bare-Minimum Hexapod Robot Simulator 2 modified to be able to control a real physical hexapod robot.
JavaScript
72
star
15

point-cloud-clusters

A catkin workspace in ROS which uses DBSCAN to identify which points in a point cloud belong to the same object.
Python
63
star
16

fusion-ekf-python

An extended Kalman Filter implementation in Python for fusing lidar and radar sensor measurements
Jupyter Notebook
63
star
17

arm-ik

An Inverse Kinematics 6DOF Robot Arm Pick and Place Project in ROS.
Jupyter Notebook
62
star
18

simple-cryptography

Scripts that illustrate basic cryptography concepts based on Coursera Standford Cryptography I course and more.
Python
60
star
19

hello-3d-world

Plot 3d points, lines, and polygon on an svg. A demonstration of what you can do with the BareMinimum3d package
TypeScript
57
star
20

point-cloud-filter

Scripts showcasing filtering techniques applied to point cloud data.
Python
49
star
21

particle-filter-prototype

Particle Filter Implementations in Python and C++, with lecture notes and visualizations
Jupyter Notebook
43
star
22

highway-path-planning

My path-planning pipeline to navigate a car safely around a virtual highway with other traffic.
C++
40
star
23

hexapod-kinematics-library

Code you can use to solve forward / inverse kinematics and generate walk sequences of hexapod robots
JavaScript
39
star
24

bare-minimum-3d

A small package to transform declared 3d data (points, polygons, lines) to 2d data.
TypeScript
33
star
25

arduino-basic

Code for an Arduino Boot Camp with emphasis on ditching delay(), basic object-oriented programming, and clean readable code.
C++
30
star
26

semantic-segmentation

A Fully Convolutional Network (FCN) script to label the pixels of a road in images
Jupyter Notebook
27
star
27

deep-blueberry

If you've always wanted to learn about deep-learning but don't know where to start, then you might have stumbled upon the right place!
25
star
28

crypto

Is Bitcoin cloud mining profitable? Check the notebook to find out! (Not Clickbait)
Jupyter Notebook
24
star
29

bare-minimum-2d

An extremely lightweight React component to declaratively (and elegantly) plot shapes on an inline SVG
JavaScript
23
star
30

hello-tiny-box

๐Ÿ“ฆ Manipulate a three dimensional box ๐Ÿ“ฆ
JavaScript
20
star
31

hellobot-raspberry

Code for various mobile robotics projects
C++
18
star
32

digital-garden

๐ŸŒฑ๐ŸŒทMithi's digital garden built with NextJS
TypeScript
16
star
33

traffic-signs-classification

A deep neural network to classify traffic signs, using TensorFlow.
Jupyter Notebook
15
star
34

some-udacity-projects

Some of my projects as a former mentor, reviewer, and beta-tester of Robotics and Self-Driving Car ND
Jupyter Notebook
15
star
35

bossy

๐ŸŽฎ Contains the code that can be used with Bossy controllers and derivatives ๐ŸŽฎ
C++
10
star
36

kingdom-rush-graphql

Simply get Kingdom Rush Tower information through queries in GraphQL
TypeScript
10
star
37

coding-exercises

Coding exercises for fun and profit
JavaScript
8
star
38

beginning-japanese

Self-study plan for learning japanese tailored to me
7
star
39

sdc-talk

A workshop about a problem-solving philosophy, machine learning intuitions, and behavioral cloning in the context of DIY self-driving / self-racing cars.
6
star
40

robotics-blog

My robotics blog
HTML
5
star
41

mithi

My github profile readme
5
star
42

hotels

TypeScript
3
star
43

portal-clone

TypeScript
2
star
44

wordpress-archive

Articles I wrote about hexapod robots and the lego technic 4x4 crawler
2
star