1. 基于车道线检测的路径规划基础
任务目标
本案例实现一个最小可运行的视觉车道线检测与路径点生成流水线:输入单张道路图像,输出左右车道线的拟合多项式系数,并基于车道中心生成一组可供下游控制模块使用的路径点序列(车辆坐标系下)。
环境与依赖
需要 Python 3.8+,并安装以下库:
pip install opencv-python numpy matplotlib
实现思路与关键步骤
算法按以下流水线处理单帧图像,不涉及视频序列或时序平滑:
- 透视变换:将前视摄像头图像转换为鸟瞰图,使车道线近似平行,便于多项式拟合。
- 二值化:结合颜色空间(HLS的S通道)与梯度阈值(Sobelx),提取车道线像素。
- 滑窗搜索:从图像底部直方图峰值确定左右车道起始点,用滑动窗口跟踪车道像素。
- 多项式拟合:对左右车道像素分别做二阶多项式拟合,得到
f(y) = a·y² + b·y + c。 - 路径点生成:在鸟瞰图坐标系下,按固定纵向间隔取左右车道中点,再经逆透视变换映射回车辆坐标系,输出路径点序列。
完整代码
import cv2
import numpy as np
class LaneDetector:
def __init__(self):
# 透视变换矩阵(假设输入图像尺寸 720x1280)
src = np.float32([[580, 460], [700, 460], [1050, 720], [230, 720]])
dst = np.float32([[320, 0], [960, 0], [960, 720], [320, 720]])
self.M = cv2.getPerspectiveTransform(src, dst)
self.Minv = cv2.getPerspectiveTransform(dst, src)
self.nwindows = 9
self.margin = 100
self.minpix = 50
self.y_eval = 720 # 车辆坐标系原点在图像底边中心
def _binary(self, img):
"""生成二值图:S通道阈值 + Sobelx梯度阈值"""
hls = cv2.cvtColor(img, cv2.COLOR_BGR2HLS)
s_channel = hls[:, :, 2]
s_binary = np.zeros_like(s_channel)
s_binary[(s_channel >= 170) & (s_channel <= 255)] = 1
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
sobelx = cv2.Sobel(gray, cv2.CV_64F, 1, 0, ksize=5)
abs_sobelx = np.absolute(sobelx)
scaled_sobel = np.uint8(255 * abs_sobelx / np.max(abs_sobelx))
sx_binary = np.zeros_like(scaled_sobel)
sx_binary[(scaled_sobel >= 50) & (scaled_sobel <= 255)] = 1
combined = np.zeros_like(sx_binary)
combined[(s_binary == 1) | (sx_binary == 1)] = 1
return combined
def _find_lane_pixels(self, binary_warped):
"""滑窗搜索左右车道像素坐标"""
histogram = np.sum(binary_warped[binary_warped.shape[0]//2:, :], axis=0)
midpoint = histogram.shape[0] // 2
leftx_base = np.argmax(histogram[:midpoint])
rightx_base = np.argmax(histogram[midpoint:]) + midpoint
window_height = binary_warped.shape[0] // self.nwindows
nonzero = binary_warped.nonzero()
nonzeroy = np.array(nonzero[0])
nonzerox = np.array(nonzero[1])
left_lane_inds = []
right_lane_inds = []
leftx_current = leftx_base
rightx_current = rightx_base
for window in range(self.nwindows):
win_y_low = binary_warped.shape[0] - (window + 1) * window_height
win_y_high = binary_warped.shape[0] - window * window_height
win_xleft_low = leftx_current - self.margin
win_xleft_high = leftx_current + self.margin
win_xright_low = rightx_current - self.margin
win_xright_high = rightx_current + self.margin
good_left_inds = ((nonzeroy >= win_y_low) & (nonzeroy < win_y_high) &
(nonzerox >= win_xleft_low) & (nonzerox < win_xleft_high)).nonzero()[0]
good_right_inds = ((nonzeroy >= win_y_low) & (nonzeroy < win_y_high) &
(nonzerox >= win_xright_low) & (nonzerox < win_xright_high)).nonzero()[0]
left_lane_inds.append(good_left_inds)
right_lane_inds.append(good_right_inds)
if len(good_left_inds) > self.minpix:
leftx_current = int(np.mean(nonzerox[good_left_inds]))
if len(good_right_inds) > self.minpix:
rightx_current = int(np.mean(nonzerox[good_right_inds]))
left_lane_inds = np.concatenate(left_lane_inds)
right_lane_inds = np.concatenate(right_lane_inds)
leftx = nonzerox[left_lane_inds]
lefty = nonzeroy[left_lane_inds]
rightx = nonzerox[right_lane_inds]
righty = nonzeroy[right_lane_inds]
return leftx, lefty, rightx, righty
def detect(self, img):
"""主检测函数:返回左右多项式系数和车辆坐标系路径点"""
binary = self._binary(img)
warped = cv2.warpPerspective(binary, self.M, (img.shape[1], img.shape[0]))
leftx, lefty, rightx, righty = self._find_lane_pixels(warped)
if len(leftx) < 10 or len(rightx) < 10:
return None, None, None
left_fit = np.polyfit(lefty, leftx, 2)
right_fit = np.polyfit(righty, rightx, 2)
# 生成路径点(鸟瞰图坐标)
ploty = np.linspace(0, warped.shape[0]-1, 20).astype(int)
left_fitx = left_fit[0]*ploty**2 + left_fit[1]*ploty + left_fit[2]
right_fitx = right_fit[0]*ploty**2 + right_fit[1]*ploty + right_fit[2]
center_x = (left_fitx + right_fitx) / 2.0
# 逆透视到车辆坐标系(x=图像列-中心,y=图像底边-行)
pts = np.vstack([center_x, ploty]).T.astype(np.float32)
vehicle_pts = cv2.perspectiveTransform(pts.reshape(-1, 1, 2), self.Minv).reshape(-1, 2)
vehicle_pts[:, 0] -= img.shape[1] / 2.0
vehicle_pts[:, 1] = img.shape[0] - vehicle_pts[:, 1]
return left_fit, right_fit, vehicle_pts
# 使用示例
if __name__ == "__main__":
detector = LaneDetector()
img = cv2.imread("test_image.jpg") # 输入道路图像
if img is None:
print("请提供 test_image.jpg")
else:
left_fit, right_fit, path_pts = detector.detect(img)
if path_pts is not None:
print("左车道系数:", left_fit)
print("右车道系数:", right_fit)
print("路径点序列 (x, y) 米/像素坐标系:")
print(path_pts)
else:
print("未检测到足够车道线像素")
运行与验证
运行方式:将代码保存为 lane_detector.py,准备一张包含清晰车道线的道路图片命名为 test_image.jpg 放在同目录,执行:
python lane_detector.py
通过条件:程序正常输出左右车道各3个多项式系数,以及20个路径点坐标。路径点应呈现从近到远沿车道中心延伸的分布,且 x 坐标大致在图像中心附近(车辆坐标系原点),y 坐标从0(车头位置)递增到约720(远处)。若输出 None,说明图像中车道线对比度过低或弯道过急,可调整 _binary 中的S通道阈值(170)或Sobel梯度阈值(50)。
常见问题
- 检测不到车道线:检查图像是否包含清晰的车道线;光照过强或过暗时,可降低S通道下限至150或提高Sobel下限至80。
- 路径点抖动:本案例为单帧检测,未做时序平滑。实际应用中可对多项式系数做指数移动平均。
- 透视矩阵不匹配:代码中的
src点针对1280x720图像标定。若使用其他分辨率,需重新标定或先resize到1280x720。