-
Notifications
You must be signed in to change notification settings - Fork 1.4k
/
lane_detection.py
186 lines (141 loc) · 6.5 KB
/
lane_detection.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
import numpy as np
import cv2
from Line import Line
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.
"""
# 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, mask
def hough_lines_detection(img, rho, theta, threshold, min_line_len, max_line_gap):
"""
`img` should be the output of a Canny transform.
"""
lines = cv2.HoughLinesP(img, rho, theta, threshold, np.array([]), minLineLength=min_line_len,
maxLineGap=max_line_gap)
return lines
def weighted_img(img, initial_img, α=0.8, β=1., λ=0.):
"""
Returns resulting blend image computed as follows:
initial_img * α + img * β + λ
"""
img = np.uint8(img)
if len(img.shape) is 2:
img = np.dstack((img, np.zeros_like(img), np.zeros_like(img)))
return cv2.addWeighted(initial_img, α, img, β, λ)
def compute_lane_from_candidates(line_candidates, img_shape):
"""
Compute lines that approximate the position of both road lanes.
:param line_candidates: lines from hough transform
:param img_shape: shape of image to which hough transform was applied
:return: lines that approximate left and right lane position
"""
# separate candidate lines according to their slope
pos_lines = [l for l in line_candidates if l.slope > 0]
neg_lines = [l for l in line_candidates if l.slope < 0]
# interpolate biases and slopes to compute equation of line that approximates left lane
# median is employed to filter outliers
neg_bias = np.median([l.bias for l in neg_lines]).astype(int)
neg_slope = np.median([l.slope for l in neg_lines])
x1, y1 = 0, neg_bias
x2, y2 = -np.int32(np.round(neg_bias / neg_slope)), 0
left_lane = Line(x1, y1, x2, y2)
# interpolate biases and slopes to compute equation of line that approximates right lane
# median is employed to filter outliers
lane_right_bias = np.median([l.bias for l in pos_lines]).astype(int)
lane_right_slope = np.median([l.slope for l in pos_lines])
x1, y1 = 0, lane_right_bias
x2, y2 = np.int32(np.round((img_shape[0] - lane_right_bias) / lane_right_slope)), img_shape[0]
right_lane = Line(x1, y1, x2, y2)
return left_lane, right_lane
def get_lane_lines(color_image, solid_lines=True):
"""
This function take as input a color road frame and tries to infer the lane lines in the image.
:param color_image: input frame
:param solid_lines: if True, only selected lane lines are returned. If False, all candidate lines are returned.
:return: list of (candidate) lane lines.
"""
# resize to 960 x 540
color_image = cv2.resize(color_image, (960, 540))
# convert to grayscale
img_gray = cv2.cvtColor(color_image, cv2.COLOR_BGR2GRAY)
# perform gaussian blur
img_blur = cv2.GaussianBlur(img_gray, (17, 17), 0)
# perform edge detection
img_edge = cv2.Canny(img_blur, threshold1=50, threshold2=80)
# perform hough transform
detected_lines = hough_lines_detection(img=img_edge,
rho=2,
theta=np.pi / 180,
threshold=1,
min_line_len=15,
max_line_gap=5)
# convert (x1, y1, x2, y2) tuples into Lines
detected_lines = [Line(l[0][0], l[0][1], l[0][2], l[0][3]) for l in detected_lines]
# if 'solid_lines' infer the two lane lines
if solid_lines:
candidate_lines = []
for line in detected_lines:
# consider only lines with slope between 30 and 60 degrees
if 0.5 <= np.abs(line.slope) <= 2:
candidate_lines.append(line)
# interpolate lines candidates to find both lanes
lane_lines = compute_lane_from_candidates(candidate_lines, img_gray.shape)
else:
# if not solid_lines, just return the hough transform output
lane_lines = detected_lines
return lane_lines
def smoothen_over_time(lane_lines):
"""
Smooth the lane line inference over a window of frames and returns the average lines.
"""
avg_line_lt = np.zeros((len(lane_lines), 4))
avg_line_rt = np.zeros((len(lane_lines), 4))
for t in range(0, len(lane_lines)):
avg_line_lt[t] += lane_lines[t][0].get_coords()
avg_line_rt[t] += lane_lines[t][1].get_coords()
return Line(*np.mean(avg_line_lt, axis=0)), Line(*np.mean(avg_line_rt, axis=0))
def color_frame_pipeline(frames, solid_lines=True, temporal_smoothing=True):
"""
Entry point for lane detection pipeline. Takes as input a list of frames (RGB) and returns an image (RGB)
with overlaid the inferred road lanes. Eventually, len(frames)==1 in the case of a single image.
"""
is_videoclip = len(frames) > 0
img_h, img_w = frames[0].shape[0], frames[0].shape[1]
lane_lines = []
for t in range(0, len(frames)):
inferred_lanes = get_lane_lines(color_image=frames[t], solid_lines=solid_lines)
lane_lines.append(inferred_lanes)
if temporal_smoothing and solid_lines:
lane_lines = smoothen_over_time(lane_lines)
else:
lane_lines = lane_lines[0]
# prepare empty mask on which lines are drawn
line_img = np.zeros(shape=(img_h, img_w))
# draw lanes found
for lane in lane_lines:
lane.draw(line_img)
# keep only region of interest by masking
vertices = np.array([[(50, img_h),
(450, 310),
(490, 310),
(img_w - 50, img_h)]],
dtype=np.int32)
img_masked, _ = region_of_interest(line_img, vertices)
# make blend on color image
img_color = frames[-1] if is_videoclip else frames[0]
img_blend = weighted_img(img_masked, img_color, α=0.8, β=1., λ=0.)
return img_blend