openpilot is an open source driver assistance system. openpilot performs the functions of Automated Lane Centering and Adaptive Cruise Control for over 200 supported car makes and models.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

208 lines
7.4 KiB

import numpy as np
import common.filters as filters
from selfdrive.controls.lib.latcontrol import calc_curvature
# Calibration Status
class CalibStatus(object):
INCOMPLETE = 0
VALID = 1
INVALID = 2
def line_intersection(line1, line2, no_int_sub = [0,0]):
xdiff = (line1[0][0] - line1[1][0], line2[0][0] - line2[1][0])
ydiff = (line1[0][1] - line1[1][1], line2[0][1] - line2[1][1])
def det(a, b):
return a[0] * b[1] - a[1] * b[0]
div = det(xdiff, ydiff)
if div == 0:
# since we are in float domain, this should really never happen
return no_int_sub
d = (det(*line1), det(*line2))
x = det(d, xdiff) / div
y = det(d, ydiff) / div
return [x, y]
def points_inside_hit_box(pts, box):
"""Determine which points lie inside a box.
Inputs:
pts: An nx2 array of points to hit test.
box: An array [[x_left, y_top], [x_right, y_bottom]] describing a box to
use for hit testing.
Returns:
A logical array with true for every member of pts inside box.
"""
hits = np.all(np.logical_and(pts > box[0, :], pts < box[1, :]), axis=1)
return hits
def warp_points(pt_s, warp_matrix):
# pt_s are the source points, nx2 array.
pt_d = np.dot(warp_matrix[:, :2], pt_s.T) + warp_matrix[:, 2][:, np.newaxis]
# divide by third dimension for representation in image space.
return (pt_d[:2, :] / pt_d[2, :]).T
class ViewCalibrator(object):
def __init__(self, box_size, big_box_size, vp_r, warp_matrix_start, vp_f=None, cal_cycle=0, cal_status=0):
self.calibration_threshold = 3000
self.box_size = box_size
self.big_box_size = big_box_size
self.warp_matrix_start = warp_matrix_start
self.vp_r = list(vp_r)
if vp_f is None:
self.vp_f = list(vp_r)
else:
self.vp_f = list(vp_f)
# slow filter fot the vanishing point
vp_fr = 0.005 # Hz, slow filter
self.dt = 0.05 # camera runs at 20Hz
self.update_warp_matrix()
self.vp_x_filter = filters.FirstOrderLowpassFilter(vp_fr, self.dt, self.vp_f[0])
self.vp_y_filter = filters.FirstOrderLowpassFilter(vp_fr, self.dt, self.vp_f[1])
self.cal_cycle = cal_cycle
self.cal_status = cal_status
self.cal_perc = int(np.minimum(self.cal_cycle*100./self.calibration_threshold, 100))
def vanishing_point_process(self, old_ps, new_ps, v_ego, steer_angle, VP):
# correct diffs by yaw rate
cam_fov = 23.06*np.pi/180. # deg
curvature = calc_curvature(v_ego, steer_angle, VP)
yaw_rate = curvature * v_ego
hor_angle_shift = yaw_rate * self.dt * self.box_size[0] / cam_fov
old_ps += [hor_angle_shift, 0] # old points have moved in the image due to yaw rate
pos_ps = [None]*len(new_ps)
for ii in range(len(old_ps)):
xo = old_ps[ii][0]
yo = old_ps[ii][1]
yn = new_ps[ii][1]
# don't consider points with low flow in y
if abs(yn - yo) > 1:
if xo > (self.vp_f[0] + 20):
pos_ps[ii] = 'r' # right lane point
elif xo < (self.vp_f[0] - 20):
pos_ps[ii] = 'l' # left lane point
# intersect all the right lines with the left lines
idxs_l = [i for i, x in enumerate(pos_ps) if x == 'l']
idxs_r = [i for i, x in enumerate(pos_ps) if x == 'r']
old_ps_l, new_ps_l = old_ps[idxs_l], new_ps[idxs_l]
old_ps_r, new_ps_r = old_ps[idxs_r], new_ps[idxs_r]
# return None if there is one side with no lines, the speed is low or the steer angle is high
if len(old_ps_l) == 0 or len(old_ps_r) == 0 or v_ego < 20 or abs(steer_angle) > 5:
return None
int_ps = [[None] * len(old_ps_r)] * len(old_ps_l)
for ll in range(len(old_ps_l)):
for rr in range(len(old_ps_r)):
old_p_l, old_p_r, new_p_l, new_p_r = old_ps_l[ll], old_ps_r[
rr], new_ps_l[ll], new_ps_r[rr]
line_l = [[old_p_l[0], old_p_l[1]], [new_p_l[0], new_p_l[1]]]
line_r = [[old_p_r[0], old_p_r[1]], [new_p_r[0], new_p_r[1]]]
int_ps[ll][rr] = line_intersection(
line_l, line_r, no_int_sub=self.vp_f)
# saturate outliers that are too far from the estimated vp
int_ps[ll][rr][0] = np.clip(int_ps[ll][rr][0], self.vp_f[0] - 20, self.vp_f[0] + 20)
int_ps[ll][rr][1] = np.clip(int_ps[ll][rr][1], self.vp_f[1] - 30, self.vp_f[1] + 30)
vp = np.mean(np.mean(np.array(int_ps), axis=0), axis=0)
return vp
def calibration_validity(self):
# this function sanity checks that the small box is contained in the big box.
# otherwise the warp function will generate black spots on the small box
cp = np.asarray([[0, 0],
[self.box_size[0], 0],
[self.box_size[0], self.box_size[1]],
[0, self.box_size[1]]])
cpw = warp_points(cp, self.warp_matrix)
# pixel margin for validity hysteresys:
# - if calibration is good, keep it good until small box is inside the big box
# - if calibration isn't good, then make it good again if small box is in big box with margin
margin_px = 0 if self.cal_status == CalibStatus.VALID else 5
big_hit_box = np.asarray(
[[margin_px, margin_px],
[self.big_box_size[0], self.big_box_size[1] - margin_px]])
cpw_outside_big_box = np.logical_not(points_inside_hit_box(cpw, big_hit_box))
return not np.any(cpw_outside_big_box)
def get_calibration_hit_box(self):
"""Returns an axis-aligned hit box in canonical image space.
Points which do not fall within this box should not be used for
calibration.
Returns:
An array [[x_left, y_top], [x_right, y_bottom]] describing a box inside
which all calibration points should lie.
"""
# We mainly care about feature from lanes, so removed points from sky.
y_filter = 50.
return np.asarray([[0, y_filter], [self.box_size[0], self.box_size[1]]])
def update_warp_matrix(self):
translation_matrix = np.asarray(
[[1, 0, self.vp_f[0] - self.vp_r[0]],
[0, 1, self.vp_f[1] - self.vp_r[1]],
[0, 0, 1]])
self.warp_matrix = np.dot(translation_matrix, self.warp_matrix_start)
self.warp_matrix_inv = np.linalg.inv(self.warp_matrix)
def calibration(self, p0, p1, st, v_ego, steer_angle, VP):
# convert to np array first thing
p0 = np.asarray(p0)
p1 = np.asarray(p1)
st = np.asarray(st)
p0 = p0.reshape((-1,2))
p1 = p1.reshape((-1,2))
# filter out pts with bad status
p0 = p0[st==1]
p1 = p1[st==1]
calib_hit_box = self.get_calibration_hit_box()
# remove all the points outside the small box and above the horizon line
good_idxs = points_inside_hit_box(
warp_points(p0, self.warp_matrix_inv), calib_hit_box)
p0 = p0[good_idxs]
p1 = p1[good_idxs]
# print("unwarped points: {}".format(warp_points(p0, self.warp_matrix_inv)))
# print("good_idxs {}:".format(good_idxs))
# get instantaneous vp
vp = self.vanishing_point_process(p0, p1, v_ego, steer_angle, VP)
if vp is not None:
# filter the vanishing point
self.vp_f = [self.vp_x_filter(vp[0]), self.vp_y_filter(vp[1])]
self.cal_cycle += 1
if not self.calibration_validity():
self.cal_status = CalibStatus.INVALID
else:
# 10 minutes @5Hz TODO: make this threshold function of convergency speed
self.cal_status = CalibStatus.VALID
#self.cal_status = CalibStatus.VALID if self.cal_cycle > self.calibration_threshold else CalibStatus.INCOMPLETE
self.cal_perc = int(np.minimum(self.cal_cycle*100./self.calibration_threshold, 100))
self.update_warp_matrix()