Let's say you have a bunch of lines and you would like to extrapolate (guess data points beyond the range of the data set) them.
I had to figure this out for the Udacity Self-driving Car Nanodegree P1 Line Detection task.
For simplicity, I am going to use these lines.
import matplotlib.pyplot as plt
import numpy as np
lines = [
(50, 50, 40, 35),
(52, 52, 42, 37),
(38, 30, 25, 15),
]
for x1, y1, x2, y2 in lines:
plt.plot((x1, x2), (y1, y2), 'g')
plt.axis([0, 60, 0, 60])
plt.show()
The goal is to have one straight line from the top right corner to all the way down.
First, separate x
and y
points.
x = []
y = []
for x1, y1, x2, y2 in lines:
x += [x1, x2]
y += [y1, y2]
Then we can use np.polyfit
to fit a line to these points. A straight line can be represented with y = mx + b
which is a polynomial of degree 1
.
z = np.polyfit(x, y, 1)
print(z)
We'll get
[ 1.40241735 -21.23284749]
which are the coeficients for y = mx + b
, so m=1.40241735
and b=-21.23284749
.
m, b = z
Let's plot this line.
for i in range(min(x), max(x)):
plt.plot(i, i * m + b, 'go')
plt.show()
numpy has a handy function np.poly1d
which can do the y = mx + b
calculation for us.
z = np.polyfit(x, y, 1)
f = np.poly1d(z)
for i in range(min(x), max(x)):
plt.plot(i, f(i), 'go')
plt.show()
Instead of using range
, we could also use numpy's np.linspace
to generate a number of points for us.
x_new = np.linspace(min(x), max(x), 10).astype(int)
y_new = f(x_new).astype(int)
points_new = list(zip(x_new, y_new))
print(points_new) # [(25, 13), (28, 18), (31, 22), (34, 26), (37, 30), (40, 34), (43, 39), (46, 43), (49, 47), (52, 51)]
print(len(points_new)) # 10
for x, y in points_new:
plt.plot(x, y, 'ro')
plt.show()
We could plot these points as lines as follows:
for i in range(1, len(points_new)):
px, py = points_new[i-1]
cx, cy = points_new[i]
plt.plot((px, cx), (py, cy), 'r')
plt.show()
Or make it just one straight line because that's what we ultimately want.
px, py = points_new[0]
cx, cy = points_new[-1]
plt.plot((px, cx), (py, cy), 'r')
plt.show()
If return to the original question, how do we extrapolate the lines?
Since we got a straight line, we can simply plug in points that are outside of our data set.
z = np.polyfit(x, y, 1)
f = np.poly1d(z)
plt.plot((0, max(x)), (f(0), f(max(x))), 'r')
plt.axis([0, 60, 0, 60])
plt.show()
When applied to the original task, it would like like this.
By the way, np.polyfit
can also fit more complex lines. This time we need at least a polynomial of degree 3
.
x = [10, 30, 50, 80, 100]
y = [30, 45, 40, 20, 40]
for x1, y1 in zip(x, y):
plt.plot(x1, y1, 'ro')
z = np.polyfit(x, y, 3)
f = np.poly1d(z)
for x1 in np.linspace(0, 110, 110):
plt.plot(x1, f(x1), 'b+')
plt.axis([0, 110, 0, 60])
plt.show()