十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

基于YOLOv5与DeepLabv3+的自动驾驶车辆感知系统:从数据标注到模型融合实战

基于YOLOv5与DeepLabv3+的自动驾驶车辆感知系统:从数据标注到模型融合实战 简介本资源是一套基于Python的车辆行驶障碍物与可通行区域识别检测系统源码面向智能驾驶算法初学者、计算机视觉研究者及自动驾驶方向开发者解决真实道路场景下目标车辆检测、车道线提取与可通行区域分割三大核心问题。压缩包共44个文件总计39.73MB包含26幅PNG/JPG实拍道路图像含BDD100K风格样本、5个核心Python脚本如export_onnx.py、main.py、demo.py、1个ONNX模型文件yolop.onnx、配套names与txt配置文件、Markdown文档README、INFO等及LICENSE开源许可结构完整支持模型导出、推理与结果可视化。已有351人学习下载提供从数据加载、预处理、YOLOPv2模型调用到车道线拟合与可通行区域掩码生成的全流程实现代码模块清晰含utils工具包、result输出目录附带多张标注效果图与运行示例便于快速复现与二次开发。1. 项目缘起从“看见”到“理解”的自动驾驶感知基础最近在整理过往的项目代码翻到了一个几年前做的车辆感知原型系统。这个项目的核心目标很明确让计算机像司机一样在行驶过程中不仅能“看见”前方的障碍物还要能“理解”哪些区域是车辆可以安全通行的。听起来像是自动驾驶的入门课对吧确实无论是做自动驾驶、辅助驾驶还是做移动机器人导航这都是一个绕不开的基础问题。很多人一上来就想搞复杂的决策规划但感知这块地基要是没打牢后面的高楼大厦都是空中楼阁。这个项目完全基于Python实现从数据预处理、模型训练到最后的可视化部署形成了一套完整的流程。当时选择Python一方面是生态丰富各种CV库和深度学习框架用起来顺手另一方面也是想验证一下在原型验证阶段用纯Python的“胶水”特性能否快速整合不同模块跑通一个端到端的感知demo。事实证明这条路是可行的而且对于理解整个技术栈的脉络非常有帮助。今天我就把这个项目的设计思路、核心实现以及踩过的那些坑系统地梳理一遍。我们不会只停留在调用某个API的层面而是会深入到算法选型、数据处理的细节以及如何将“障碍物检测”和“可通行区域分割”这两个看似独立的任务巧妙地融合在一个轻量化的框架里。无论你是刚接触计算机视觉的学生还是想从传统图像处理转向深度学习应用的工程师相信都能从中获得一些可以直接“抄作业”的实操经验。2. 核心任务拆解障碍物与可通行区域到底是什么在动手写代码之前我们必须把问题定义清楚。车辆行驶环境感知具体到我们这个项目主要解决两个子问题2.1 障碍物检测找到“不能碰”的东西这里的障碍物是一个广义概念。在结构化道路比如高速公路、城市道路上它主要指其他车辆、行人、非机动车、锥桶、掉落物等动态或静态的物体。在非结构化环境比如园区、野外下可能还包括石块、灌木、坑洼等。检测的目标是给出这些物体的边界框和类别。注意障碍物检测不等于目标检测。传统目标检测关注“是什么”而行驶障碍物检测更关注“会不会撞上”。因此像远处的、在路外的、或者虽然在路上但与本车轨迹无冲突的物体其处理优先级是不同的。但在原型阶段我们通常先解决“有无”和“在哪”的问题。2.2 可通行区域识别找到“能走”的路这比障碍物检测更抽象。它不关心具体的物体是什么只关心图像中每一个像素点是否属于车辆可以安全行驶的区域。在结构化道路上这通常就是车道线内的区域在非结构化区域则可能是平坦的、无阻碍的草地或土路。这个任务的输出是一张分割掩码为每个像素打上“可通行”或“不可通行”的标签。这两个任务的关系非常紧密互补性障碍物检测可能会漏检一些难以定义类别的障碍比如一堆垃圾而可通行区域分割可以从区域整体性上发现这些异常。校验性一个被检测为“车辆”的障碍物其底部接触的区域在可通行区域分割图中理应是非可通行的。两者可以相互校验提升系统鲁棒性。信息融合最终给下游规划模块的应该是一份融合了“障碍物列表可通行区域地图”的综合性描述。我们的设计目标就是构建一个能同时完成这两项任务且效率足够在边缘设备如Jetson系列上实时运行的轻量级模型。3. 技术选型与架构设计为什么是YOLODeepLab的混合体确定了任务接下来就是技术选型。这是项目成败的关键选错了方向后面代码写得再漂亮也事倍功半。我们逐一分析。3.1 障碍物检测模型YOLOv5的权衡之选当时可选的目标检测框架很多Faster R-CNN、SSD、YOLO系列等。我最终选择了YOLOv5主要基于以下几点考量速度与精度的平衡YOLO系列是单阶段检测器的代表其“You Only Look Once”的设计哲学在速度和精度间取得了很好的平衡。对于行驶场景实时性30 FPS是硬性要求YOLOv5相比两阶段的Faster R-CNN有巨大速度优势。工程化友好YOLOv5的代码库由PyTorch实现结构清晰文档和社区支持极其活跃。它提供了从YOLOv5s小型到YOLOv5x大型多个预训练模型方便我们根据计算资源进行选择。其数据准备格式YOLO格式的txt文件也非常简单。易于部署YOLOv5原生支持导出为ONNX、TorchScript等格式便于后续部署到不同的推理引擎如TensorRT、OpenVINO。这对于从原型走向实际应用至关重要。为什么不选更新的YOLOv8或YOLOv9在项目当时v5是最成熟稳定的选择。即使现在v5的生态和资料丰富度对于学习和原型开发来说依然是非常好的起点。我们的策略是用成熟的v5快速验证流程其思想和方法完全适用于后续升级到更先进的版本。3.2 可通行区域识别模型DeepLabv3的语义分割能力对于像素级的分类任务语义分割模型是标准答案。在U-Net、PSPNet、DeepLab等模型中我选择了DeepLabv3。强大的上下文感知DeepLabv3通过Atrous Spatial Pyramid Pooling模块能够捕获多尺度的上下文信息。这对于可通行区域识别非常重要——判断一片区域能否通行不仅看局部纹理是否是沥青还要看全局结构是否在两条车道线之间。细节保持其Decoder部分有助于恢复在编码过程中丢失的边界细节使得生成的可通行区域掩码边缘更清晰。丰富的预训练模型通常使用在ImageNet和COCO上预训练的ResNet或MobileNet作为Backbone。我们可以选择MobileNetv2作为backbone来保证速度或者选择ResNet-50/101来追求更高的精度。3.3 整体架构设计双分支并行推理最直接的架构就是让两个模型独立运行一个YOLOv5处理图像输出检测框一个DeepLabv3处理同一张图像输出分割掩码。但这里有两个优化点共享Backbone特征提取器理论上两个任务都需要从图像中提取特征。我们可以让它们共享一个初始的特征提取网络比如都使用同一个ResNet的前几层然后再分叉到各自的任务头。这样可以减少计算量。但在原型阶段为了灵活性和调试方便我选择了松耦合的并行方式即两个独立的模型。这样调整其中一个模型的架构或训练数据时不会影响另一个。在实际部署时再考虑模型轻量化和共享Backbone的紧耦合设计。后处理融合这是产生最终感知结果的关键。架构流程如下输入单帧RGB图像。分支A障碍物检测图像输入YOLOv5得到一系列检测框[x1, y1, x2, y2, confidence, class_id]。分支B可通行区域分割图像输入DeepLabv3得到一张与输入同宽高的单通道掩码图像素值0代表背景/不可通行1代表可通行区域。融合模块 a.投影将YOLOv5的检测框从图像坐标系映射到BEV鸟瞰图坐标系需要标定参数原型阶段我们简化处理仍在图像坐标系下融合。 b.区域修正遍历每一个检测框将该框覆盖区域内在分割掩码图中的对应像素强制设置为“不可通行”即使分割模型可能误判为可通行。这是利用检测的高置信度结果来修正分割。 c.冲突检测检查分割出的“可通行区域”内部是否存在未被YOLO检测到的、连片的“不可通行”小区域通过连通域分析。这些区域可能是漏检的障碍物可以作为一个补充的异常区域输出。输出融合后的障碍物列表带类别和框 修正后的可通行区域二值掩码。这个架构清晰、模块化便于单独训练和调试两个模型最后通过简单的逻辑进行融合。4. 数据准备与处理80%的工作量在这里模型架构是骨架数据才是血肉。对于监督学习没有高质量的数据一切免谈。4.1 数据来源与标注公开数据集障碍物检测COCO数据集是万能起点包含常见的车辆、行人等类别。更专业的还有BDD100K、KITTI它们提供了驾驶场景的图像和标注。可通行区域分割Cityscapes数据集提供了精细的像素级语义标注其中“road”类别可以直接作为可通行区域。BDD100K也提供了驾驶场景的分割标注。自定义数据公开数据集场景有限。我使用行车记录仪采集了部分本地道路数据并使用LabelImg用于目标检测标注和LabelMe用于语义分割标注进行手工标注。这是一项极其耗时但无法绕过的工作。4.2 数据标注的实践细节障碍物标注使用YOLO格式。每个图像对应一个.txt文件每行格式为class_id x_center y_center width height坐标和宽高都是归一化后的值除以图像宽高。类别ID需要自己定义映射例如0: car, 1: person, 2: cyclist。可通行区域标注标注工具会生成JSON文件其中包含多边形的点集。我们需要编写脚本将这些多边形渲染成二值掩码图0为背景255为可通行区域。这里有一个关键点如何定义“可通行”对于本车所在车道这很明确。但对于对向车道、路边缓冲带是否算可通行在项目初期我采用了严格定义仅本车当前车道和允许变道的相邻车道为可通行区域。这需要在标注指南中写清楚保证标注一致性。4.3 数据增强策略数据增强是提升模型泛化能力、防止过拟合的利器。我构建了一个统一的数据增强流水线供两个模型训练使用import albumentations as A from albumentations.pytorch import ToTensorV2 def get_train_transform(): return A.Compose([ A.RandomResizedCrop(height640, width640, scale(0.8, 1.0)), # 随机裁剪缩放 A.HorizontalFlip(p0.5), # 水平翻转 A.RandomBrightnessContrast(p0.2), # 亮度对比度扰动 A.HueSaturationValue(p0.2), # 色相饱和度扰动 A.Blur(blur_limit3, p0.1), # 轻微模糊 A.CLAHE(p0.1), # 限制对比度自适应直方图均衡化 A.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]), # ImageNet归一化 ToTensorV2(), ], bbox_paramsA.BboxParams(formatyolo, label_fields[class_labels])) def get_val_transform(): return A.Compose([ A.Resize(height640, width640), A.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]), ToTensorV2(), ])提示使用albumentations库时要确保增强操作同时适用于图像和其对应的标注边界框或掩码。上述示例中的bbox_params确保了边界框随图像一起变换。对于分割掩码A.Compose会自动处理。4.4 制作统一的数据加载器为了让两个模型能方便地使用同一套数据我设计了一个UnifiedDataset类。它的核心思想是每次迭代返回同一张图片以及对应的两种标注检测的txt路径和分割的掩码图路径由后续的训练脚本决定使用哪一种。import cv2 import torch from torch.utils.data import Dataset import os class UnifiedDataset(Dataset): def __init__(self, img_dir, label_dir, mask_dir, transformNone, taskdetection): self.img_dir img_dir self.label_dir label_dir # 存放YOLO格式txt的文件夹 self.mask_dir mask_dir # 存放分割掩码png的文件夹 self.transform transform self.task task # detection 或 segmentation self.img_names [f for f in os.listdir(img_dir) if f.endswith((.jpg, .png))] def __len__(self): return len(self.img_names) def __getitem__(self, idx): img_name self.img_names[idx] img_path os.path.join(self.img_dir, img_name) image cv2.imread(img_path) image cv2.cvtColor(image, cv2.COLOR_BGR2RGB) if self.task detection: # 加载检测标签 label_path os.path.join(self.label_dir, img_name.replace(.jpg, .txt).replace(.png, .txt)) boxes, labels self._parse_yolo_label(label_path, image.shape) target {boxes: boxes, labels: labels} else: # 加载分割掩码 mask_name img_name.replace(.jpg, .png).replace(.jpeg, .png) mask_path os.path.join(self.mask_dir, mask_name) mask cv2.imread(mask_path, cv2.IMREAD_GRAYSCALE) # 将掩码二值化例如128视为可通行 mask (mask 128).astype(uint8) target mask if self.transform: if self.task detection: transformed self.transform(imageimage, bboxesboxes, class_labelslabels) image transformed[image] boxes torch.tensor(transformed[bboxes], dtypetorch.float32) labels torch.tensor(transformed[class_labels], dtypetorch.int64) target {boxes: boxes, labels: labels} else: transformed self.transform(imageimage, maskmask) image transformed[image] target transformed[mask].long() # 转换为LongTensor用于交叉熵损失 return image, target def _parse_yolo_label(self, label_path, img_shape): boxes, labels [], [] if os.path.exists(label_path): with open(label_path, r) as f: lines f.readlines() for line in lines: parts line.strip().split() if len(parts) 5: cls_id, x_c, y_c, w, h map(float, parts) # 将归一化坐标转换为绝对坐标假设后续transform会resize这里先按原图计算 h_img, w_img img_shape[:2] x1 (x_c - w/2) * w_img y1 (y_c - h/2) * h_img x2 (x_c w/2) * w_img y2 (y_c h/2) * h_img boxes.append([x1, y1, x2, y2]) labels.append(int(cls_id)) return boxes, labels这个设计使得数据管理变得清晰也为后续可能的多任务学习一个模型同时输出检测和分割结果留下了接口。5. 模型训练与调优不只是跑通代码有了数据和架构就可以开始训练了。这里面的门道很多直接决定模型性能。5.1 障碍物检测模型YOLOv5训练环境配置直接克隆YOLOv5官方仓库按照其requirements.txt安装依赖。建议使用Python虚拟环境。数据配置创建data/custom.yaml文件定义路径和类别。# data/custom.yaml path: ../datasets/custom_det # 数据集根目录 train: images/train # 训练集图像相对路径 val: images/val # 验证集图像相对路径 nc: 3 # 类别数例如 car, person, cyclist names: [car, person, cyclist] # 类别名称训练命令与关键参数python train.py --img 640 --batch 16 --epochs 100 --data data/custom.yaml --weights yolov5s.pt --project runs/detect --name custom_exp--img 640输入图像尺寸。更大的尺寸精度可能更高但速度更慢显存占用更大。640是速度和精度的一个较好平衡点。--batch 16批次大小。根据你的GPU显存调整。如果出现CUDA out of memory错误就减小这个值或使用--batch-size。--epochs 100训练轮数。通常需要观察验证集损失和mAP曲线提前停止。--weights yolov5s.pt使用预训练的YOLOv5s权重进行迁移学习这是收敛快、效果好的关键。--project和--name指定输出日志和模型的保存路径。训练监控与调优使用TensorBoard查看损失曲线、精度mAP0.5等指标。训练启动后在另一个终端运行tensorboard --logdir runs/detect。重点看什么训练损失平稳下降验证损失也同步下降且未明显上升防止过拟合。mAP值随着训练轮数提升。常见问题损失NaN学习率可能太高尝试减小--lr0初始学习率。mAP不升反降可能是过拟合。增加数据增强的强度或使用更小的模型如yolov5n或添加正则化如权重衰减--weight-decay。验证集效果远差于训练集数据分布不一致检查训练集和验证集的数据是否来自不同场景确保它们同分布。5.2 可通行区域分割模型DeepLabv3训练这里我使用PyTorch官方Torchvision库中的DeepLabv3实现它非常易于使用。模型定义import torchvision.models.segmentation as segmentation def create_deeplabv3(num_classes2, backbonemobilenet_v3_large): # 使用预训练模型将分类头改为我们的类别数2类背景/可通行 if backbone mobilenet_v3_large: model segmentation.deeplabv3_mobilenet_v3_large(pretrainedTrue, progressTrue) model.classifier[4] torch.nn.Conv2d(256, num_classes, kernel_size(1, 1)) elif backbone resnet50: model segmentation.deeplabv3_resnet50(pretrainedTrue, progressTrue) model.classifier[4] torch.nn.Conv2d(256, num_classes, kernel_size(1, 1)) else: raise ValueError(fUnsupported backbone: {backbone}) return model将最后的分类层输出通道数改为2二分类问题。训练循环关键代码import torch.nn as nn import torch.optim as optim model create_deeplabv3(num_classes2, backbonemobilenet_v3_large).to(device) criterion nn.CrossEntropyLoss(ignore_index255) # 忽略某些像素如果有 optimizer optim.Adam(model.parameters(), lr1e-4) scheduler optim.lr_scheduler.ReduceLROnPlateau(optimizer, max, patience5) # 根据IoU调整学习率 for epoch in range(num_epochs): model.train() for images, masks in train_loader: # masks是LongTensor, 值为0或1 images, masks images.to(device), masks.to(device) outputs model(images)[out] loss criterion(outputs, masks) optimizer.zero_grad() loss.backward() optimizer.step() # 验证阶段计算IoU val_iou evaluate_iou(model, val_loader, device) scheduler.step(val_iou) # 根据验证集IoU调整学习率评价指标对于分割任务常用的指标是交并比。二分类的IoU计算如下def compute_iou(pred_mask, true_mask): # pred_mask和true_mask都是二值化后的张量0或1 intersection (pred_mask true_mask).sum().float() union (pred_mask | true_mask).sum().float() iou (intersection 1e-6) / (union 1e-6) # 加平滑项防止除零 return iou.item()在验证集上平均IoU能达到0.85以上模型就算表现不错了。5.3 训练中的经验与坑学习率预热对于迁移学习开始几轮使用很小的学习率如初始学习率的十分之一进行“预热”有助于稳定训练。YOLOv5内置了热身但自己写训练循环时别忘了。类别不平衡处理可通行区域的像素通常远多于障碍物或不可通行区域。在分割任务中可以在CrossEntropyLoss中设置weight参数给少数类别不可通行更高的权重。混合精度训练使用torch.cuda.amp进行自动混合精度训练可以大幅减少显存占用加快训练速度且通常不会损失精度。模型保存策略不要只保存最后一个epoch的模型。保存验证集指标如mAP或IoU最好的那个模型以及每隔一定epoch保存一次作为检查点。6. 推理部署与融合后处理让模型真正跑起来模型训练好之后我们要写一个推理脚本将两个模型串联起来并实现之前设计的融合逻辑。6.1 单个模型推理封装首先分别封装两个模型的推理过程。import torch import cv2 import numpy as np from models.experimental import attempt_load # YOLOv5的模型加载函数 from torchvision import transforms class ObstacleDetector: def __init__(self, weights_path, devicecuda, conf_thres0.5, iou_thres0.45): self.device device self.conf_thres conf_thres self.iou_thres iou_thres # 加载YOLOv5模型 self.model attempt_load(weights_path, devicedevice) self.model.eval() self.stride int(self.model.stride.max()) def detect(self, image): 输入BGR格式的numpy图像返回检测框列表 img_rgb cv2.cvtColor(image, cv2.COLOR_BGR2RGB) # 预处理resize, 归一化, 转tensor img_input self._preprocess(img_rgb) with torch.no_grad(): pred self.model(img_input)[0] # YOLOv5输出 # 非极大值抑制 detections self._non_max_suppression(pred) return detections[0].cpu().numpy() if detections[0] is not None else np.array([]) # [x1, y1, x2, y2, conf, cls] def _preprocess(self, img): # 简化的预处理实际应与训练时一致 img_resized cv2.resize(img, (640, 640)) img_normalized img_resized / 255.0 img_tensor torch.from_numpy(img_normalized).float().permute(2,0,1).unsqueeze(0).to(self.device) return img_tensor class DrivableAreaSegmentor: def __init__(self, model_path, devicecuda): self.device device self.model torch.load(model_path, map_locationdevice)[model] # 加载自定义训练的模型 self.model.eval() self.transform transforms.Compose([ transforms.ToTensor(), transforms.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]), ]) def segment(self, image): 输入BGR图像返回二值化掩码0背景/不可通行1可通行 img_rgb cv2.cvtColor(image, cv2.COLOR_BGR2RGB) img_tensor self.transform(img_rgb).unsqueeze(0).to(self.device) with torch.no_grad(): output self.model(img_tensor)[out] if isinstance(output, dict): output output[out] pred_mask torch.argmax(output, dim1).squeeze().cpu().numpy() # 取概率最大的类别 # 将预测结果resize回原图尺寸 h, w image.shape[:2] pred_mask_resized cv2.resize(pred_mask.astype(np.uint8), (w, h), interpolationcv2.INTER_NEAREST) return pred_mask_resized6.2 融合后处理逻辑实现这是整个系统的“大脑”负责综合两个模型的输出。class PerceptionFusion: def __init__(self, det_model_path, seg_model_path, devicecuda): self.detector ObstacleDetector(det_model_path, device) self.segmentor DrivableAreaSegmentor(seg_model_path, device) self.device device def process_frame(self, frame_bgr): 处理单帧图像。 返回 obstacles: list of dict, 每个dict包含 bbox, confidence, class_name drivable_mask: numpy array, 与原图同尺寸0/1二值掩码 fused_vis: numpy array, 可视化结果图像 # 并行推理 detections self.detector.detect(frame_bgr.copy()) seg_mask self.segmentor.segment(frame_bgr.copy()) # 1. 用检测结果修正分割掩码 corrected_mask seg_mask.copy() h, w seg_mask.shape for det in detections: x1, y1, x2, y2, conf, cls_id map(int, det[:6]) # 确保坐标在图像范围内 x1, y1 max(0, x1), max(0, y1) x2, y2 min(w-1, x2), min(h-1, y2) # 将检测框内的区域设置为不可通行0 corrected_mask[y1:y2, x1:x2] 0 # 2. 从修正后的掩码中查找可能的漏检障碍可选高级功能 # 可以通过查找连通域将面积大于阈值且非矩形的不可通行区域作为疑似障碍物上报 potential_obstacles self._find_potential_obstacles(corrected_mask, seg_mask) # 3. 格式化输出 obstacles [] for det in detections: x1, y1, x2, y2, conf, cls_id det[:6] obstacles.append({ bbox: [int(x1), int(y1), int(x2), int(y2)], confidence: float(conf), class_name: self.detector.model.names[int(cls_id)] # 获取类别名 }) # 4. 可视化用于调试 vis_img self._visualize(frame_bgr, obstacles, corrected_mask) return obstacles, corrected_mask, vis_img def _find_potential_obstacles(self, corrected_mask, original_mask): 一个简单的示例查找被修正的区域即原来被误判为可通行但被检测框覆盖后改为不可通行的区域 # 这里仅作示例实际逻辑更复杂可能涉及形态学操作和连通域分析 diff_mask (original_mask 1) (corrected_mask 0) # 找出diff_mask中的连通域面积大于阈值的视为潜在漏检障碍 # 使用OpenCV的findContours contours, _ cv2.findContours(diff_mask.astype(np.uint8), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) potential_boxes [] for cnt in contours: area cv2.contourArea(cnt) if area 100: # 面积阈值 x, y, w, h cv2.boundingRect(cnt) potential_boxes.append([x, y, xw, yh]) return potential_boxes def _visualize(self, frame, obstacles, drivable_mask): vis frame.copy() # 绘制可通行区域半透明绿色 color_mask np.zeros_like(vis) color_mask[drivable_mask 1] [0, 255, 0] # 绿色 cv2.addWeighted(color_mask, 0.3, vis, 0.7, 0, vis) # 绘制障碍物框 for obj in obstacles: x1, y1, x2, y2 obj[bbox] label f{obj[class_name]} {obj[confidence]:.2f} cv2.rectangle(vis, (x1, y1), (x2, y2), (0, 0, 255), 2) cv2.putText(vis, label, (x1, y1-10), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0,0,255), 2) return vis6.3 主循环与性能考量最后写一个主循环来读取视频流或图像序列并运行整个流程。def main(video_path0): # 0代表摄像头 fusion_system PerceptionFusion(runs/detect/custom_exp/weights/best.pt, runs/segment/best_model.pth, devicecuda if torch.cuda.is_available() else cpu) cap cv2.VideoCapture(video_path) while cap.isOpened(): ret, frame cap.read() if not ret: break # 处理帧 obstacles, drivable_area, vis_frame fusion_system.process_frame(frame) # 显示结果 cv2.imshow(Perception Output, vis_frame) if cv2.waitKey(1) 0xFF ord(q): break # 这里可以将obstacles和drivable_area发送给其他模块如规划控制 # print(fDetected {len(obstacles)} obstacles.) cap.release() cv2.destroyAllWindows() if __name__ __main__: main(test_video.mp4)关于性能在GTX 1660 Ti上YOLOv5s推理一张640x640图像约7msDeepLabv3 with MobileNetV3约15ms加上后处理和可视化一帧总耗时约25-30ms可以达到30 FPS满足实时性要求。如果部署到Jetson等边缘设备需要进行模型量化、TensorRT加速等优化这是另一个话题了。7. 踩坑实录与进阶思考这个项目从零搭建到跑通遇到了不少典型问题这里分享几个印象深刻的7.1 数据标注不一致导致的模型冲突初期分割模型的数据集中“可通行区域”包含了本车车道和对向车道。而检测模型的数据集中对向车道的车辆也被标注为“car”。这就导致了一个矛盾检测模型框出了对向车道的车融合模块会把这些区域在分割掩码中设为不可通行。但实际上对向车道本身是可通行的只是当前有车我们的车也不会开到对向车道去。这造成了感知结果的混乱。解决方案重新定义任务边界。对于结构化道路我们将“可通行区域”严格定义为本车当前及可安全变入的车道区域。对于检测模型我们只关心对本车构成潜在威胁的障碍物对于对向车道远端的车辆可以通过简单的逻辑过滤如基于图像水平位置掉。这要求数据标注和任务定义必须从一开始就保持逻辑一致性。7.2 光照与天气变化的挑战在晴天数据上训练好的模型遇到阴天、黄昏或夜晚性能会急剧下降。分割模型可能将阴影大片区域误判为不可通行检测模型则可能漏检亮度较低的车辆。解决方案数据层面尽可能收集不同时间、不同天气、不同光照条件下的数据。如果难以获取使用数据增强来模拟如随机调整Gamma值、添加随机阴影块、模拟雨滴噪声等。模型层面考虑在模型前端加入一个光照不变性特征提取模块或者使用在更大规模、更多样化数据集上预训练的模型作为backbone。系统层面引入时序信息。单帧感知是不稳定的可以结合多帧结果进行滤波如对检测框做卡尔曼滤波跟踪对分割区域做多帧投票能有效平滑噪声提升鲁棒性。7.3 边缘设备部署的优化在PC上跑得流畅不代表在嵌入式设备上也能行。Jetson Nano的算力有限。优化方向模型轻量化将YOLOv5s替换为YOLOv5n或更小的自定义网络。将DeepLabv3的backbone从ResNet50换为MobileNetV2甚至更小的结构。推理引擎优化使用TensorRT或ONNX Runtime进行推理。将PyTorch模型导出为ONNX然后使用TensorRT生成高度优化的引擎可以获得数倍的推理速度提升。这个过程涉及层融合、精度校准INT8量化等有一定门槛但收益巨大。输入分辨率降低将输入图像从640x640降至320x320速度会显著提升但需要评估精度损失是否在可接受范围内。7.4 从感知到决策的鸿沟这个项目产出的结果障碍物列表可通行区域图还只是感知层的输出。如何用于实际的车辆控制这里涉及坐标转换。图像中的像素坐标需要转换到以车辆为中心的世界坐标系或车身坐标系中这需要相机标定内参、外参和逆透视变换。生成“可通行区域”的俯视图后规划算法才能在此基础上进行路径搜索。这部分内容超出了本项目的范围但它是感知结果真正产生价值的必经之路。回过头看这个基于Python的车辆行驶感知原型系统就像搭积木把目标检测、语义分割、数据融合这些技术模块组合在一起解决一个具体的应用问题。它的价值不在于用了多先进的算法而在于提供了一个完整的、可复现的实践框架。你可以用它作为起点替换更快的模型如YOLOv8 RT-DETR尝试更精巧的融合算法如注意力机制或者集成到机器人操作系统ROS中。希望这次梳理能帮你避开我踩过的那些坑更顺畅地开启自己的视觉感知项目。本文还有配套的精品资源点击获取
返回列表