车道偏离预警系统(LDWS)的Python实现通常依赖计算机视觉库(如OpenCV)和数值计算库(如NumPy)。其核心流程遵循**图像采集与预处理、车道线检测与拟合、车辆状态估计与偏离判断、预警触发**的技术路线[ref_4]。以下是一个完整的、可直接运行的示例项目,它使用OpenCV处理视频流,模拟一个基础的LDWS。
### **一、 项目结构与核心模块**
整个项目包含一个主程序文件,集成了图像处理、车道检测和预警逻辑。我们将分模块构建代码。
```python
# ldws_simulator.py
import cv2
import numpy as np
import matplotlib.pyplot as plt
class LaneDepartureWarningSystem:
"""
车道偏离预警系统仿真类。
处理输入视频帧,检测车道线,计算车辆偏移,并在必要时触发预警。
"""
def __init__(self, warning_threshold_pixels=50, lane_width_pixels=500):
"""
初始化系统参数。
Args:
warning_threshold_pixels (int): 触发预警的横向像素偏移阈值。
lane_width_pixels (int): 预估的车道宽度(像素),用于计算车道中心。
"""
self.warning_threshold = warning_threshold_pixels
self.lane_width = lane_width_pixels
self.left_lane_coeffs = None # 存储左车道线拟合系数 [A, B, C] for y = Ax^2 + Bx + C
self.right_lane_coeffs = None # 存储右车道线拟合系数
self.warning_state = "SAFE" # 预警状态: "SAFE", "LEFT_WARNING", "RIGHT_WARNING"
def preprocess_image(self, img):
"""
图像预处理:转换为灰度图,进行高斯模糊和Canny边缘检测。
Args:
img: 输入的BGR图像。
Returns:
edges: 边缘检测后的二值图像。
"""
# 1. 转换为灰度图,减少计算量[ref_4]
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 2. 高斯模糊,降噪
blur = cv2.GaussianBlur(gray, (5, 5), 0)
# 3. Canny边缘检测,突出车道线边缘[ref_4]
edges = cv2.Canny(blur, 50, 150)
return edges
def region_of_interest(self, img):
"""
定义鸟瞰图变换的源点和目标点,并进行透视变换。
将图像转换为鸟瞰图,便于车道线拟合[ref_4]。
Args:
img: 边缘检测后的图像。
Returns:
warped: 透视变换后的鸟瞰图。
Minv: 逆变换矩阵,用于将结果绘制回原图。
"""
height, width = img.shape
# 定义源点 (在原图像中选取一个梯形区域)
src = np.float32([
[width * 0.45, height * 0.65], # 左上
[width * 0.55, height * 0.65], # 右上
[width * 0.90, height * 0.90], # 右下
[width * 0.10, height * 0.90] # 左下
])
# 定义目标点 (在鸟瞰图中形成一个矩形)
dst = np.float32([
[width * 0.25, 0], # 左上
[width * 0.75, 0], # 右上
[width * 0.75, height], # 右下
[width * 0.25, height] # 左下
])
# 计算透视变换矩阵和逆矩阵
M = cv2.getPerspectiveTransform(src, dst)
Minv = cv2.getPerspectiveTransform(dst, src)
# 执行透视变换
warped = cv2.warpPerspective(img, M, (width, height), flags=cv2.INTER_LINEAR)
return warped, Minv
def detect_lane_lines(self, warped_img):
"""
在鸟瞰图中检测并拟合左右车道线。
使用滑动窗口法和二次多项式拟合[ref_1]。
Args:
warped_img: 鸟瞰图(二值边缘图像)。
Returns:
left_fit, right_fit: 左右车道线的二次多项式系数。
out_img: 用于可视化的图像。
"""
# 1. 计算图像下半部分的直方图,找到左右车道线的起点
histogram = np.sum(warped_img[warped_img.shape[0]//2:, :], axis=0)
midpoint = histogram.shape[0] // 2
leftx_base = np.argmax(histogram[:midpoint])
rightx_base = np.argmax(histogram[midpoint:]) + midpoint
# 2. 滑动窗口参数
nwindows = 9
margin = 100
minpix = 50
window_height = warped_img.shape[0] // nwindows
# 3. 识别非零像素的x和y位置
nonzero = warped_img.nonzero()
nonzeroy = np.array(nonzero[0])
nonzerox = np.array(nonzero[1])
leftx_current = leftx_base
rightx_current = rightx_base
left_lane_inds = []
right_lane_inds = []
out_img = np.dstack((warped_img, warped_img, warped_img)) * 255
# 4. 迭代每个窗口,收集窗口内的像素索引
for window in range(nwindows):
win_y_low = warped_img.shape[0] - (window + 1) * window_height
win_y_high = warped_img.shape[0] - window * window_height
win_xleft_low = leftx_current - margin
win_xleft_high = leftx_current + margin
win_xright_low = rightx_current - margin
win_xright_high = rightx_current + margin
# 在可视化图像上绘制窗口
cv2.rectangle(out_img, (win_xleft_low, win_y_low), (win_xleft_high, win_y_high), (0, 255, 0), 2)
cv2.rectangle(out_img, (win_xright_low, win_y_low), (win_xright_high, win_y_high), (0, 255, 0), 2)
# 识别当前窗口内属于左右车道线的像素
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)
# 如果找到的像素数大于minpix,则更新下一个窗口的中心x位置
if len(good_left_inds) > minpix:
leftx_current = int(np.mean(nonzerox[good_left_inds]))
if len(good_right_inds) > minpix:
rightx_current = int(np.mean(nonzerox[good_right_inds]))
# 5. 将像素索引数组合并
left_lane_inds = np.concatenate(left_lane_inds)
right_lane_inds = np.concatenate(right_lane_inds)
# 6. 提取左右车道线像素位置
leftx = nonzerox[left_lane_inds]
lefty = nonzeroy[left_lane_inds]
rightx = nonzerox[right_lane_inds]
righty = nonzeroy[right_lane_inds]
# 7. 使用二次多项式拟合左右车道线:y = Ax^2 + Bx + C
left_fit = np.polyfit(lefty, leftx, 2) if len(leftx) > 0 else None
right_fit = np.polyfit(righty, rightx, 2) if len(rightx) > 0 else None
self.left_lane_coeffs = left_fit
self.right_lane_coeffs = right_fit
# 在可视化图像上标记车道线像素
out_img[lefty, leftx] = [255, 0, 0]
out_img[righty, rightx] = [0, 0, 255]
return left_fit, right_fit, out_img
def calculate_vehicle_offset(self, img_shape):
"""
计算车辆中心相对于车道中心的横向偏移(像素)。
假设相机安装在车辆中心。
Args:
img_shape: 图像尺寸 (height, width)。
Returns:
offset_pixels: 偏移量(像素),正值为偏右,负值为偏左。
"""
if self.left_lane_coeffs is None or self.right_lane_coeffs is None:
return 0
# 在图像底部(y_max)评估左右车道线的x位置
y_eval = img_shape[0] - 1
left_x = self.left_lane_coeffs[0] * y_eval**2 + self.left_lane_coeffs[1] * y_eval + self.left_lane_coeffs[2]
right_x = self.right_lane_coeffs[0] * y_eval**2 + self.right_lane_coeffs[1] * y_eval + self.right_lane_coeffs[2]
# 计算车道中心
lane_center = (left_x + right_x) / 2.0
# 计算车辆中心(假设为图像中心)
vehicle_center = img_shape[1] / 2.0
# 计算偏移量
offset_pixels = vehicle_center - lane_center # 车辆中心 - 车道中心
return offset_pixels
def check_departure_and_warn(self, offset):
"""
根据横向偏移判断是否发生车道偏离并更新预警状态。
Args:
offset: 车辆中心相对于车道中心的横向偏移。
"""
# 预警逻辑:当偏移量绝对值超过阈值,且方向明确时触发预警[ref_1]
if offset > self.warning_threshold:
self.warning_state = "LEFT_WARNING" # 车辆中心在车道中心左侧(offset为正),可能向右偏离
elif offset < -self.warning_threshold:
self.warning_state = "RIGHT_WARNING" # 车辆中心在车道中心右侧(offset为负),可能向左偏离
else:
self.warning_state = "SAFE"
def visualize(self, original_img, warped_img, lane_img, offset):
"""
将检测结果、预警信息可视化到原图像上。
Args:
original_img: 原始帧。
warped_img: 鸟瞰图。
lane_img: 带车道线检测的可视化鸟瞰图。
offset: 计算的横向偏移。
Returns:
result: 合成后的可视化结果图像。
"""
height, width = original_img.shape[:2]
result = original_img.copy()
# 1. 如果检测到车道线,将车道区域绘制回原图
if self.left_lane_coeffs is not None and self.right_lane_coeffs is not None:
# 为透视变换回原图生成点
ploty = np.linspace(0, warped_img.shape[0]-1, warped_img.shape[0])
left_fitx = self.left_lane_coeffs[0]*ploty**2 + self.left_lane_coeffs[1]*ploty + self.left_lane_coeffs[2]
right_fitx = self.right_lane_coeffs[0]*ploty**2 + self.right_lane_coeffs[1]*ploty + self.right_lane_coeffs[2]
# 创建要填充的多边形点集
pts_left = np.array([np.transpose(np.vstack([left_fitx, ploty]))])
pts_right = np.array([np.flipud(np.transpose(np.vstack([right_fitx, ploty])))])
pts = np.hstack((pts_left, pts_right))
# 将点变换回原始图像空间
_, Minv = self.region_of_interest(np.zeros_like(warped_img))
pts = cv2.perspectiveTransform(pts.astype(np.float32), Minv).astype(np.int32)
# 在原图上绘制绿色的车道区域
cv2.fillPoly(result, [pts], (0, 255, 0))
# 在原图上绘制车道线
cv2.polylines(result, [pts_left.astype(np.int32).reshape((-1,1,2))], False, (255, 0, 0), thickness=10)
cv2.polylines(result, [pts_right.astype(np.int32).reshape((-1,1,2))], False, (0, 0, 255), thickness=10)
# 2. 在图像顶部添加信息文本
info_y_start = 30
line_height = 30
cv2.putText(result, f"Lane Departure Warning System", (10, info_y_start), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (255, 255, 255), 2)
cv2.putText(result, f"Offset: {offset:.1f} px", (10, info_y_start + line_height), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (255, 255, 255), 2)
# 3. 根据预警状态显示不同颜色的警示文本
if self.warning_state == "SAFE":
cv2.putText(result, f"State: {self.warning_state}", (10, info_y_start + 2*line_height), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 0), 2)
elif self.warning_state == "LEFT_WARNING":
cv2.putText(result, f"State: {self.warning_state}!", (10, info_y_start + 2*line_height), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 0, 255), 2)
# 添加一个红色的警示框
cv2.rectangle(result, (width-200, 10), (width-10, 60), (0, 0, 255), -1)
cv2.putText(result, "WARNING!", (width-180, 45), cv2.FONT_HERSHEY_SIMPLEX, 0.8, (255, 255, 255), 2)
elif self.warning_state == "RIGHT_WARNING":
cv2.putText(result, f"State: {self.warning_state}!", (10, info_y_start + 2*line_height), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 0, 255), 2)
cv2.rectangle(result, (width-200, 10), (width-10, 60), (0, 0, 255), -1)
cv2.putText(result, "WARNING!", (width-180, 45), cv2.FONT_HERSHEY_SIMPLEX, 0.8, (255, 255, 255), 2)
# 4. 将鸟瞰图、检测过程图等作为子图插入
# 调整鸟瞰图大小并放置在右上角
warped_resized = cv2.resize(warped_img, (width//4, height//4))
lane_img_resized = cv2.resize(lane_img, (width//4, height//4))
# 将灰度鸟瞰图转换为BGR以便拼接
if len(warped_resized.shape) == 2:
warped_resized = cv2.cvtColor(warped_resized, cv2.COLOR_GRAY2BGR)
# 在结果图右上角放置两个小图
result[10:10+warped_resized.shape[0], width-warped_resized.shape[1]-10:width-10] = warped_resized
result[20+lane_img_resized.shape[0]:20+2*lane_img_resized.shape[0], width-lane_img_resized.shape[1]-10:width-10] = lane_img_resized
cv2.putText(result, "Bird's Eye", (width-warped_resized.shape[1]-5, 5), cv2.FONT_HERSHEY_SIMPLEX, 0.4, (255,255,255), 1)
cv2.putText(result, "Lane Detect", (width-lane_img_resized.shape[1]-5, 15+lane_img_resized.shape[0]), cv2.FONT_HERSHEY_SIMPLEX, 0.4, (255,255,255), 1)
return result
def process_frame(self, frame):
"""
处理单帧图像的完整流水线。
Args:
frame: 输入的视频帧 (BGR格式)。
Returns:
output_frame: 带有可视化结果的帧。
"""
# 步骤1: 图像预处理
edges = self.preprocess_image(frame)
# 步骤2: 透视变换获取鸟瞰图
warped, _ = self.region_of_interest(edges)
# 步骤3: 检测并拟合车道线
left_fit, right_fit, lane_detection_img = self.detect_lane_lines(warped)
# 步骤4: 计算车辆横向偏移
offset = self.calculate_vehicle_offset(frame.shape)
# 步骤5: 检查偏离并更新预警状态
self.check_departure_and_warn(offset)
# 步骤6: 可视化所有结果
output_frame = self.visualize(frame, warped, lane_detection_img, offset)
return output_frame
def main():
"""
主函数:初始化系统并处理视频流。
"""
# 初始化LDWS系统,可调整阈值以适应不同分辨率的视频
ldws = LaneDepartureWarningSystem(warning_threshold_pixels=60, lane_width_pixels=450)
# 选择输入源:摄像头或视频文件
# cap = cv2.VideoCapture(0) # 使用摄像头
cap = cv2.VideoCapture('test_video.mp4') # 使用视频文件,请确保文件存在
if not cap.isOpened():
print("错误:无法打开视频源。")
return
print("按 'q' 键退出。")
while True:
ret, frame = cap.read()
if not ret:
print("视频结束或读取失败。")
break
# 处理当前帧
output_frame = ldws.process_frame(frame)
# 显示结果
cv2.imshow('Lane Departure Warning System', output_frame)
# 按'q'退出
if cv2.waitKey(1) & 0xFF == ord('q'):
break
cap.release()
cv2.destroyAllWindows()
if __name__ == "__main__":
main()
```
### **二、 代码运行与测试说明**
#### **1. 环境依赖**
运行此代码需要安装以下Python库:
```bash
pip install opencv-python numpy matplotlib
```
#### **2. 输入数据准备**
* **视频文件**:将主函数 `main()` 中的 `cv2.VideoCapture('test_video.mp4')` 替换为你的车道线清晰的行车记录仪视频路径。
* **摄像头**:若使用摄像头,将上述行改为 `cv2.VideoCapture(0)`。
#### **3. 核心算法流程与关键参数**
下表总结了代码中每个步骤的核心方法和可调参数:
| 处理步骤 | 对应方法 | 核心算法/技术 | 关键可调参数与说明 |
| :--- | :--- | :--- | :--- |
| **图像预处理** | `preprocess_image` | 灰度化、高斯模糊、Canny边缘检测 | `canny_thresholds=(50,150)`:影响边缘检测的灵敏度。 |
| **视角变换** | `region_of_interest` | 透视变换(逆透视变换) | `src` 和 `dst` 点:决定了鸟瞰图的视野范围,需根据相机安装位置和镜头参数标定[ref_4]。 |
| **车道线检测** | `detect_lane_lines` | 滑动窗口搜索、二次多项式拟合 | `nwindows=9`:窗口数量;`margin=100`:窗口宽度;`minpix=50`:最小像素数,影响跟踪稳定性。 |
| **偏移计算** | `calculate_vehicle_offset` | 基于拟合曲线计算横向偏移 | 假设相机安装在车辆中心。更精确的系统需要相机-车辆坐标系标定。 |
| **预警决策** | `check_departure_and_warn` | 阈值比较法 | `warning_threshold_pixels`:预警阈值(像素)。需根据实际像素-米转换比例调整,或直接使用世界坐标系下的距离(如0.3米)[ref_1]。 |
#### **4. 系统优化与扩展方向**
上述代码是一个基础演示,要提升其鲁棒性和实用性,可以考虑以下优化,这些也是实际LDWS开发中的关键技术点[ref_1][ref_4]:
1. **相机标定与畸变校正**:在第一步前加入相机标定,校正镜头畸变,这是保证后续几何计算准确性的前提[ref_4]。
```python
# 伪代码示例
ret, mtx, dist, rvecs, tvecs = cv2.calibrateCamera(...)
undistorted_frame = cv2.undistort(frame, mtx, dist)
```
2. **高级车道线检测**:使用**颜色空间转换(如HSV)** 和**梯度方向阈值**结合,能更好地处理不同光照和路面条件下的车道线[ref_4]。
```python
# 结合Sobel算子和颜色通道(如S饱和度通道)进行二值化
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
s_channel = hsv[:,:,1]
sobelx = cv2.Sobel(s_channel, cv2.CV_64F, 1, 0) # x方向梯度
# ... 应用阈值生成二值图像
```
3. **更鲁棒的预警算法**:采用 **TLC(Time to Lane Crossing)** 算法替代简单的偏移阈值[ref_1]。TLC考虑了车辆横向速度,能更早、更准确地预测偏离。
```python
# 伪代码:TLC = (到车道线的距离) / (横向速度)
# 需要估计或从CAN总线获取横向速度vy
lateral_speed = vy_estimated # 需要额外传感器或状态估计器
distance_to_lane = abs(offset) * meters_per_pixel
if lateral_speed != 0:
tlc = distance_to_lane / abs(lateral_speed)
if tlc < TLC_THRESHOLD: # 例如1.5秒
trigger_warning()
```
4. **滤波与跟踪**:对检测到的车道线参数(多项式系数)和计算的偏移量使用**卡尔曼滤波器**,可以平滑输出,减少抖动,提高稳定性[ref_1]。
5. **处理弯道与虚线**:当前算法对急弯和虚线车道线可能不稳定。可以通过增加拟合多项式阶数(如三次)、或引入车道线跟踪(基于前一帧结果的搜索)来改善[ref_1]。
运行此代码后,你将看到一个可视化窗口,实时显示原始视频、鸟瞰图、检测到的车道线、车辆偏移量以及预警状态。通过调整阈值和透视变换参数,可以使其适应不同的道路场景。这个仿真系统为理解LDWS的工作原理和进行算法实验提供了一个坚实的基础框架。