🌟 项目简介
本文带你从零实现一个辅助驾驶视觉系统,核心功能包括:
车道线分割:使用传统图像处理算法在道路图像上绘制绿色车道线。
目标检测与距离估计:识别前方车辆和行人,估算距离并显示在画面中。
碰撞预警:当距离过近时,在画面显示红色警告并在控制台模拟“嘀嘀嘀”报警声。
本项目的最大特点是完全离线运行,不需要深度学习框架(PyTorch/TensorFlow),也不需要联网下载模型,非常适合作为计算机视觉入门项目或课程作业。
🗂️ 数据集说明
我们使用的是Kaggle上的BDD100K YOLO格式数据集(由用户a7madmostafa上传)。
数据内容:包含行车记录仪视角的道路图片。
标注格式:YOLO格式(
.txt文件),每行包含class_id x_center y_center width height(归一化坐标)。关注类别:车辆(car/truck/bus)、行人(person)等交通参与者。
数据集挂载路径(Kaggle环境):
text
/kaggle/input/datasets/a7madmostafa/bdd100k-yolo/test/images
📦 完整代码(可直接运行)
python
import cv2 import numpy as np import os import matplotlib.pyplot as plt # ============================================================ # 1. 路径配置(根据不同环境修改此处) # ============================================================ DATA_ROOT = "/kaggle/input/datasets/a7madmostafa/bdd100k-yolo" IMAGE_DIR = os.path.join(DATA_ROOT, "test/images") LABEL_DIR = os.path.join(DATA_ROOT, "test/labels") # 若不存在则尝试其他路径 # 类别映射表 (BDD100K标准) CLASS_NAMES = { 0: 'car', 1: 'truck', 2: 'bus', 3: 'traffic light', 4: 'traffic sign', 5: 'person', 6: 'bicycle', 7: 'motorcycle', 8: 'train', 9: 'rider' } # 只关心这些类别(车辆和行人) INTERESTED_CLASSES = [0, 1, 2, 3, 5, 6, 7, 9] # ============================================================ # 2. 车道线检测(前端展示:绿色线条) # ============================================================ def detect_lanes(image): """ 传统OpenCV车道线检测流程: 灰度化 -> 高斯模糊 -> Canny边缘检测 -> ROI掩码 -> 霍夫直线检测 """ gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) blur = cv2.GaussianBlur(gray, (5, 5), 0) edges = cv2.Canny(blur, 50, 150) height, width = edges.shape # 定义梯形/三角形感兴趣区域(只关注前方路面) mask = np.zeros_like(edges) polygon = np.array([[(0, height), (width, height), (width // 2, height // 2)]], np.int32) cv2.fillPoly(mask, polygon, 255) masked_edges = cv2.bitwise_and(edges, mask) # 霍夫变换检测直线 lines = cv2.HoughLinesP(masked_edges, rho=1, theta=np.pi/180, threshold=30, minLineLength=80, maxLineGap=30) lane_img = np.copy(image) if lines is not None: for line in lines: x1, y1, x2, y2 = line[0] # 【前端】绘制绿色粗线 cv2.line(lane_img, (x1, y1), (x2, y2), (0, 255, 0), 4) return lane_img # ============================================================ # 3. 距离估计(基于目标面积) # ============================================================ def estimate_distance(area, frame_area): """ 利用目标在画面中的面积占比粗略估算距离。 原理:面积越大 -> 距离越近 (反比关系)。 假设:面积占比0.01时距离为10米。 """ if area < 500: # 忽略噪声小点 return 99.0 ratio = area / frame_area if ratio < 0.001: # 距离太远忽略 return 99.0 return min(10.0 * (0.01 / ratio) ** 0.5, 80.0) # ============================================================ # 4. 解析YOLO标注文件(后端数据处理) # ============================================================ def get_boxes_from_label(label_path, img_shape): """ 读取txt标注文件,将归一化坐标转为像素坐标。 格式:class_id x_center y_center width height (归一化0~1) """ height, width = img_shape[:2] boxes = [] if not os.path.exists(label_path): return boxes with open(label_path, 'r') as f: lines = f.readlines() for line in lines: parts = line.strip().split() if len(parts) != 5: continue cls_id = int(parts[0]) if cls_id not in INTERESTED_CLASSES: continue # 解析归一化坐标 x_c, y_c, w_n, h_n = map(float, parts[1:]) # 还原为像素坐标 (左上角x, y, 宽度, 高度) x = int((x_c - w_n/2) * width) y = int((y_c - h_n/2) * height) w = int(w_n * width) h = int(h_n * height) # 边界裁剪,防止溢出画面 x = max(0, x) y = max(0, y) w = min(width - x, w) h = min(height - y, h) if w > 0 and h > 0: boxes.append((cls_id, x, y, w, h)) return boxes # ============================================================ # 5. 图像处理主流程(前端渲染 + 后端计算) # ============================================================ def process_image(img_path, label_path): # 读取图片并缩放到统一尺寸(加速处理) img = cv2.imread(img_path) if img is None: return None img = cv2.resize(img, (800, 450)) orig_h, orig_w = img.shape[:2] frame_area = orig_w * orig_h # 1. 车道线检测(前端绘制绿线) result = detect_lanes(img) # 2. 读取标注框(后端数据) boxes = get_boxes_from_label(label_path, img.shape) warning_triggered = False min_distance = 99.0 # 3. 遍历每个检测框 for cls_id, x, y, w, h in boxes: area = w * h dist = estimate_distance(area, frame_area) min_distance = min(min_distance, dist) # 根据距离着色:>8米绿色安全,<8米红色危险 color = (0, 255, 0) if dist > 8 else (0, 0, 255) # 【前端】绘制矩形框和距离文本 cv2.rectangle(result, (x, y), (x+w, y+h), color, 2) label = f"{CLASS_NAMES.get(cls_id, 'obj')} {dist:.1f}m" cv2.putText(result, label, (x, y-10), cv2.FONT_HERSHEY_SIMPLEX, 0.5, color, 2) # 触发预警逻辑 if dist < 8.0: warning_triggered = True # 4. 【前端】显示预警信息 if warning_triggered: cv2.putText(result, "!!! WARNING: Collision Risk !!!", (30, 50), cv2.FONT_HERSHEY_SIMPLEX, 0.9, (0, 0, 255), 3) # 控制台模拟蜂鸣声(\a为ASCII响铃符) print("🔊 嘀嘀嘀!前方距离过近!", end=" ") if min_distance < 99: cv2.putText(result, f"Min Dist: {min_distance:.1f}m", (30, 100), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (255, 255, 0), 2) # 转为RGB格式供matplotlib显示 return cv2.cvtColor(result, cv2.COLOR_BGR2RGB) # ============================================================ # 6. 主程序入口(模拟视频流播放) # ============================================================ if __name__ == "__main__": # 检查目录是否存在 if not os.path.exists(IMAGE_DIR): print(f"❌ 错误:图片目录不存在!请检查路径。") exit() # 获取前20张图片(数据量大,只取部分演示) image_files = sorted([f for f in os.listdir(IMAGE_DIR) if f.endswith(('.jpg', '.jpeg', '.png'))])[:20] print(f"✅ 共加载 {len(image_files)} 张图片,开始处理...") # 使用matplotlib交互模式动态显示 fig, ax = plt.subplots(figsize=(12, 6)) plt.ion() # 打开交互模式 for img_file in image_files: img_path = os.path.join(IMAGE_DIR, img_file) label_file = img_file.rsplit('.', 1)[0] + '.txt' label_path = os.path.join(LABEL_DIR, label_file) result_rgb = process_image(img_path, label_path) if result_rgb is None: continue # 【前端】刷新显示 ax.clear() ax.imshow(result_rgb) ax.set_title(f"Processing: {img_file}") ax.axis('off') plt.draw() plt.pause(0.3) # 每0.3秒切换一张,模拟视频帧率 plt.ioff() plt.show() print("\n✅ 全部处理完成!")🔍 代码核心原理解析
1. 车道线检测(detect_lanes)
Canny边缘检测:提取图像中梯度变化剧烈的像素点(即边缘)。
ROI掩码:由于车道线通常只出现在图像下方区域,我们用一个三角形掩码过滤掉天空、树木等无关区域,大幅减少误检。
霍夫变换(HoughLinesP):在边缘图像中寻找直线段。参数
threshold=30控制检测灵敏度,minLineLength=80过滤短线段。
2. YOLO标注解析(get_boxes_from_label)
YOLO格式的坐标是归一化的(0~1),需要乘以图片的宽/高才能还原为像素值。注意这里的x_center, y_center是中心点,转换为左上角坐标时需减半宽高。
3. 距离估计算法(estimate_distance)
这是一种单目视觉的简化近似,假设物体在图像中的面积占比与真实距离平方成反比。虽然不如双目视觉或激光雷达精准,但足以满足演示和预警逻辑的需求。实际项目中若需精准值,需要进行相机标定。
4. 预警逻辑
距离阈值:设定为8米,当任意感兴趣目标小于该值时触发。
前端反馈:画面顶部显示红色英文警告。
后端反馈:控制台打印
嘀嘀嘀(利用\a转义符可触发系统蜂鸣声)。
🧩 前端与后端的分工
| 模块 | 类型 | 具体代码 | 作用 |
|---|---|---|---|
| 车道线绘制 | 前端 | cv2.line(..., (0,255,0), 4) | 将检测到的直线渲染为绿色 |
| 目标框绘制 | 前端 | cv2.rectangle()/cv2.putText() | 可视化检测结果和距离 |
| 预警文字 | 前端 | cv2.putText(..., (0,0,255)) | 显示红色预警提示 |
| 控制台输出 | 前端 | print("嘀嘀嘀...") | 模拟蜂鸣报警声 |
| 图像显示 | 前端 | plt.imshow()/plt.pause() | 在Notebook内展示图片序列 |
| 边缘检测 | 后端 | cv2.Canny()/cv2.GaussianBlur() | 图像预处理和特征提取 |
| 直线拟合 | 后端 | cv2.HoughLinesP() | 计算车道线几何参数 |
| 坐标转换 | 后端 | 归一化坐标转像素坐标 | 解析标注数据 |
| 距离计算 | 后端 | estimate_distance() | 根据面积计算距离数值 |
⚠️ 踩坑与排障指南
1. 路径找不到(FileNotFoundError)
原因:Kaggle数据集的挂载路径因用户而异。
解决:运行
os.listdir('/kaggle/input')查看实际根目录名称,修改DATA_ROOT变量。
2. 找不到标签文件
原因:部分数据集版本只有图片,没有
labels文件夹。解决:若缺少标注,可去掉
get_boxes_from_label调用,仅保留车道线检测功能。或者尝试遍历os.walk()查找.txt文件位置。
3. Matplotlib无法动态显示
原因:
plt.ion()在某些Jupyter内核中不稳定。解决:改用
cv2.imshow()(需要GUI环境)或IPython.display.clear_output()刷新图片。
4. 内存溢出(Kaggle Kernel Died)
原因:图片尺寸过大或一次性加载过多。
解决:使用
cv2.resize()统一缩小至800x450;仅读取前20张而非全量数据。
🚀 扩展与改进方向
引入YOLOv8模型:替换标注文件读取,实现端到端的目标检测,提升准确率。
车道偏离预警:计算车辆中心与车道线的横向偏移,当偏离时触发警报。
测距标定:使用棋盘格标定相机内参,将像素面积映射为真实物理距离(米)。
多线程加速:使用OpenMP或CUDA加速图像处理流水线。
💬 结语
本文完整实现了一个轻量级的辅助驾驶视觉系统,无需昂贵的GPU,无需深度学习,仅靠OpenCV和简单的几何逻辑即可运行。适合作为计算机视觉课程的期末作业或入门项目。希望本文能帮助你理解图像处理的基础流程,并激发你对自动驾驶技术的兴趣!
如果你在运行过程中遇到任何问题,欢迎在评论区留言交流。
项目环境:Python 3.10 + OpenCV 4.8 + Matplotlib 3.7
数据集来源:Kaggle - BDD100K YOLO Format Dataset