ARTICLE DETAIL

资讯详情

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

Unet++车道线分割实战:数据集、完整代码与预训练权重全流程

Unet++车道线分割实战:数据集、完整代码与预训练权重全流程 简介面向自动驾驶视觉入门者与语义分割研究者的完整实战资源基于 Unet 网络实现车道线分割内含约 3200 张自动驾驶道路场景图像及标注划分训练/验证集覆盖不同时段与帧可直接训练自有数据。代码全部为手写结构清晰按 README 摆放数据即可运行训练环节支持 Adam/SGD/RMSProp 优化器、BCE 损失并提供恒定、余弦退火、step 三种学习率策略便于对比实验。整个压缩包内共 2000 个文件以 1859 张 PNG 标注图与 133 张 JPG 原图为主另有 5 个 Python 脚本、2 个配置文件及 README压缩包约 489MB30 个 epoch 下全局像素准确度 0.995、精确度 0.907、召回率 0.908、Dice 0.91增大轮次仍有提升空间。资源已训练生成最优与最终权重附带预处理可视化、Dice/Loss 曲线及训练日志方便直接评估微调目前已有 654 人学习适合毕业设计、课程实验或算法落地前验证。1. 用 Unet 做车道线分割数据集、完整代码与预训练权重的落地路径车道线分割在自动驾驶感知里看起来是个标准二分类任务但真正动手做过的人都有体会普通 Unet 到了弯道、阴影、雨夜场景输出 mask 要么断裂要么把路面接缝一起框进来。这份基于 Unet 的车道线分割实战资源把数据集、完整代码和训练好的权重打包在一起拿到手不用从零开始标数据直接训练或者加载权重就能出结果。它适合正在跑自动驾驶数据集落地、想对比 Unet 和 Unet 效果差异、或者只想快速产出一个可演示分割结果的工程师。全流程按工程交付标准整理数据划分、损失函数、checkpoint 保存、推理脚本都在照着跑就能复现指标不用再花时间把散落的代码拼起来。2. 训练前准备Unet 结构回顾、车道线数据集目录与依赖环境2.1 Unet 与车道线分割的匹配点为什么不是普通 Unet 或 DeepLab车道线目标有两个明显特征细长、占比小。一张 512×512 的图里车道线通常只占不到 5% 的像素一旦下采样次数太多浅层的线条细节很容易被直接丢掉。普通 Unet 的跳跃连接只是把 encoder 的特征图原样拼到 decoder浅层信息虽然保住了但缺少跨层融合遇到阴影、裂缝、低对比度路面时模型很容易把纹理相似但语义完全不同的区域一起分出来。Unet 的核心改动是把简单跳跃连接换成了嵌套的密集卷积块。每一层 decoder 的输入都不只来自上一层上采样结果还同时接收前面多个层级的特征图这种设计让网络在多个尺度上反复校准分割边界对车道线这类细长目标更友好。另一个实用点是深度监督deep supervision训练时 Unet 会从多个子网络分支同时计算损失权重由深到浅按 [1, 0.5, 0.25, 0.125] 递减。说白了就是让浅层分支也直接接受监督信号而不是只靠最后一层反传训练早期收敛明显更快。如果你在普通 Unet 和 DeepLab v3 之间纠结我的建议很直接DeepLab 擅长大物体语义分割但对车道线这种强边缘、长条状目标空洞卷积的采样间距容易把细线拉断Unet 结构简单、好调但特征复用不够。Unet 属于两者之间最稳的选择模型复杂度可控训练技巧要求也不高。这份资源里默认用 ResNet34 做 encoder你也可以在配置里换成 ResNet50代价是显存涨一截。对比项UnetDeepLab v3Unet跳跃连接直接拼接无明确跳跃嵌套密集卷积块多尺度融合一般强强对细长车道线一般容易断裂好训练收敛速度慢中快深度监督显存占用低中中高2.2 数据集目录结构、标签格式与训练/验证划分这份资源里的数据集是典型的自动驾驶场景车道线图片原始图像为 RGB标签是单通道二值 mask背景为 0车道线为 1。工程目录大致是下面这个样子下载后建议先按这个结构核对一遍避免后面训练脚本找不到路径。data/lane/ ├── images/ # 原始图像jpg/png 均可 ├── masks/ # 与 images 同名的标签图png 格式 ├── train.txt # 训练集文件名列表每行一个文件名不含扩展名 ├── val.txt # 验证集文件名列表 └── test.txt # 测试集文件名列表拿到数据后第一件事不是直接开训而是检查train.txt和val.txt的划分是否和 images、masks 目录一致。如果不一致可以用下面这段脚本重新划分核心是固定随机种子保证每次划分结果可复现。import os import random random.seed(42) root ./data/lane images sorted([ f for f in os.listdir(os.path.join(root, images)) if f.lower().endswith((.jpg, .jpeg, .png)) ]) random.shuffle(images) split_idx int(len(images) * 0.85) with open(os.path.join(root, train.txt), w) as f: f.write(\n.join(images[:split_idx])) with open(os.path.join(root, val.txt), w) as f: f.write(\n.join(images[split_idx:]))这段脚本的逻辑很简单先列出 images 目录下所有图片文件名打乱顺序后按 85%/15% 切分成训练集和验证集。训练比例可以按数据量调整数据量少于 500 张时建议把比例拉到 0.9否则验证集会因为样本太少而出现指标抖动。注意脚本只写入了文件名没有写入目录前缀训练脚本里读取train.txt时一般会拼上images/和masks/前缀所以你不需要在 txt 里写完整路径。2.3 环境依赖PyTorch、CUDA 版本与一键安装车道线分割训练最好有一块 8GB 显存以上的 GPU我建议的配置是 Python 3.8、PyTorch 1.13 或 2.0、CUDA 11.8。这套组合在大多数工作机上不会遇到兼容性问题。如果 CUDA 版本不同安装命令也要对应调整。pip install torch1.13.1 torchvision0.14.1 --index-url https://download.pytorch.org/whl/cu118 pip install -r requirements.txtrequirements.txt里一般包含 opencv-python、numpy、albumentations、tqdm、tensorboard 这几个常用库。albumentations 负责数据增强建议保留不要为了省安装时间把它注释掉后面训练阶段会用随机翻转、亮度抖动、轻微旋转来提升模型对光照变化的鲁棒性。如果机器上没有 GPUCPU 也能跑推理但训练 120 个 epoch 会非常煎熬不建议尝试。装完环境后顺手跑一句python -c import torch; print(torch.cuda.is_available())输出 True 再继续。很多训练翻车其实不是模型问题而是 PyTorch 装了 CPU 版模型一直跑在 CPU 上训练速度慢到你以为代码死循环了。3. 完整训练超参数、损失函数与训练日志解读3.1 训练入口与核心参数配置这份资源的主训练脚本是train.py所有关键参数都可以通过命令行覆盖。第一次复现建议直接用下面这条命令不要一上来就改网络结构。python train.py \ --arch unet_plus_plus \ --data ./data/lane \ --input-size 512 \ --epochs 120 \ --lr 1e-4 \ --batch-size 8 \ --device 0--arch指定网络结构这里必须传unet_plus_plus。--input-size是训练时的输入分辨率512 是速度和精度的平衡点如果你跑 1080Ti 或显存只有 11GB降到 384 可以显著降低显存占用精度损失大约在 1 到 2 个 IoU 点。--lr初始学习率用 1e-4 起步不要在刚开训时用 1e-2Unet 系列对学习率很敏感调大之后 loss 大概率直接放飞。--batch-size在 8GB 显存卡上建议设 416GB 可以设 8具体数值根据 OOM 报错调整。训练脚本的数据加载部分一般会做三件事读取train.txt里的文件名列表、从 images/masks 目录读图、再做随机裁剪和归一化。归一化的 mean/std 通常使用 ImageNet 统计值 [0.485, 0.456, 0.406] 和 [0.229, 0.224, 0.225]。这里最常见的问题是图像读到网络前没有除以 255导致输入范围在 0 到 255 而不是 0 到 1loss 会在一开始就异常偏大。3.2 损失函数BCE Dice Loss 的组合与深度监督权重车道线分割的样本极度不均衡背景像素远多于车道线像素单独用 BCE 会让模型倾向于把一切预测为背景因为这样 loss 仍然很低。实践中更稳的组合是 BCE 加 Dice LossDice Loss 直接优化目标区域的重叠度对类别不均衡天然不敏感。import torch import torch.nn.functional as F def dice_loss(pred, target, smooth1e-6): pred pred.contiguous().view(pred.size(0), -1) target target.contiguous().view(target.size(0), -1) intersection (pred * target).sum(dim1) dice (2.0 * intersection smooth) / ( pred.sum(dim1) target.sum(dim1) smooth ) return 1.0 - dice.mean() def bce_dice_loss(pred, target): bce F.binary_cross_entropy_with_logits(pred, target) dice dice_loss(torch.sigmoid(pred), target) return bce dicedice_loss里smooth1e-6是为了防止分母为 0这个值不能省也不能改成整数 1否则在训练初期预测概率接近 0 时损失会被异常拉高。bce_dice_loss中 BCE 输入的是 logits所以前面没有接 sigmoid而 Dice 计算时传入的是torch.sigmoid(pred)两者处理的分布不同这是很多人在复现时会忽略的细节。由于 Unet 有深度监督分支每个分支都会输出一个预测图。常规做法是每个分支都过一遍bce_dice_loss然后乘上对应的权重再求和。权重默认 [1, 0.5, 0.25, 0.125]意思是越靠近原始输入分辨率的浅层分支权重越低越靠近最终输出的深层分支权重越高。3.3 训练日志、可视化与 checkpoint 保存策略训练开始后控制台会持续打印当前 epoch、loss、IoU、lr 等信息。正常的收敛规律大致是前 5 个 epoch loss 从 0.8 左右快速降到 0.3 以内后续 20 到 40 个 epoch 缓慢下降IoU 逐步从 0.4 爬到 0.8 以上。如果 10 个 epoch 后 IoU 还不到 0.2不要急着继续跑先回去检查数据加载和标签是否对齐。python train.py \ --arch unet_plus_plus \ --data ./data/lane \ --resume ./checkpoints/last.pth训练中断是常态主流训练脚本都会在每轮结束保存两个文件last.pth和best_iou.pth。last.pth用于断点续训恢复时保持优化器状态、当前 epoch 和 learning ratebest_iou.pth只在验证集 IoU 创新高时覆盖保存。续训命令如上注意--resume和从头训练的区别从头训练会重新初始化优化器续训则把优化器状态也加载回来学习率调度器也会从上次位置继续否则中断恢复后学习率回到初始值模型效果会明显退步。日志建议用 tensorboard 或者直接保存到 csv 文件不要只用 print 输出。等到训练结束再翻终端滚动记录你会后悔没有留结构化日志。工程里常见的落盘格式是每行一个 json包含 epoch、train_loss、val_iou、lr 四个字段后面画曲线和调参都方便。4. 避坑指南Unet 车道线分割训练与推理的 5 个实战问题4.1 训练第一个 epoch 就出现 Loss 为 NaN现象loss 在几轮迭代内从正常值直接变成 NaN且之后无法恢复控制台开始爆出大量 RuntimeError。原因最常见的是学习率过大导致梯度爆炸其次是标签图是 0 到 255 的灰度图但代码里没有除以 255BCE 在 logits 和 target 范围不一致时计算出的梯度会非常不稳定。另一个少见但同样致命的原因是数据里存在坏图有某张 mask 是全黑Dice 分母处理不当直接溢出。解决先把学习率降到 1e-4如果还 NaN就在数据加载处加上mask mask / 255.0。检查 train.txt 里是否有文件名和实际目录对不上一旦数据加载器读到空白图模型学到的全是噪声。最后确认dice_loss里有smooth并且pred和target都做了contiguous().view()避免维度不对导致的隐性问题。4.2 显存不足batch-size8 直接 OOM现象训练脚本启动后不久CUDA 报out of memory有时在第一个 epoch 结束前就崩掉。原因Unet 相比普通 Unet 多了大量密集卷积块和深度监督分支前向传播会同时保存多个中间特征图显存占用比想象中高。输入尺寸 512、batch-size 8、ResNet34 encoder 的组合在 8GB 显卡上基本没戏。解决先降到 batch-size 4 或者 2确认能跑通再逐步往上加。其次是配合 PyTorch 的 AMP 混合精度训练显存能省下将近一半。再不行就把输入尺寸从 512 降到 384。我也见过有人直接在训练脚本里加torch.cuda.empty_cache()其实这只是把显存碎片清理一下解决不了根本的占用问题不要指望它在训练循环里力挽狂澜。4.3 预测 mask 错位车道线整体偏移或断裂现象训练指标不差val IoU 有 0.75 以上但可视化的预测图层叠到原图上后车道线和真实位置对不齐有些路段断成几截。原因这是训练和推理阶段预处理不一致造成的。训练时用了随机裁剪、随机旋转推理时没有对齐中心裁剪和缩放参数另一个高发原因是数据增强里随机旋转角度设置过大比如 ±30 度车道线是有明确方向性的目标旋转太狠会让模型学到错误的几何关系。解决把 albumentations 里的旋转角度控制在 ±5 度以内不要用通用分割里常用的 ±30 度。推理管线必须和训练管线保持一致训练时如果 resize 到 512×512推理时也要先做同样的 resize之后再对 mask 做反向 resize 回到原图尺寸。很多复现项目只看指标不看图等真正可视化时才发现这一点。4.4 加载训练好的权重时报 missing keys 或 unexpected keys现象model.load_state_dict(torch.load(best_iou.pth))直接报错提示缺少deep_supervision相关的键或者多出不该有的键。原因Unet 在训练阶段会保留深度监督分支推理时如果代码把deep_supervision设置为 False模型结构里就不存在那些分支state_dict 自然对不上。另一个原因是保存权重时直接存了整个 model 而没有存 state_dict。解决加载权重时先打印state_dict的 key确认模型结构开关是否一致。推理脚本里建议固定使用deep_supervisionFalse构建模型再用torch.load并去掉以deep_supervision开头的键或者直接用load_state_dict(weights, strictFalse)。这样虽然不会报错但一定要自己确认缺失的键确实是深度监督分支而不是主分支被悄悄改名了。4.5 可视化结果颜色混乱BGR 和 RGB 通道对调现象分割结果形状正确但叠加到原图后整体色调发蓝发红像是颜色被反转了。原因OpenCV 读取图片默认是 BGR 通道顺序训练时如果用的是 PIL 或 matplotlib 读取就是 RGB一旦两个流程混用模型看到的输入分布和训练分布不一致预测结果自然不对。解决统一在数据加载入口做一次cv2.cvtColor(img, cv2.COLOR_BGR2RGB)。推理可视化时再转换回来叠加。最简单的方式是推理脚本全部用 OpenCV 读图转成 RGB 后进模型输出的 mask 是单通道二值图叠加时直接用 OpenCV 的addWeighted不要再用 matplotlib 中转能少踩一次颜色坑。5. 推理验证与交付加载训练好的权重可视化并校准端到端时延训练和避坑都过了之后最后一步是把模型从 Jupyter 实验环境挪到真实推理链路里。我用的是这个工程自带的predict.py加载best_iou.pth对单张图片做前向推理并统计端到端耗时。这里的端到端指的是从图片读入内存、预处理、模型推理到输出 mask 的完整时间不包含后处理拟合车道线方程的部分。import cv2 import torch import time model build_unet_plusplus(num_classes1).cuda().eval() model.load_state_dict(torch.load(weights/best_iou.pth)) img cv2.imread(samples/road.jpg) x cv2.cvtColor(img, cv2.COLOR_BGR2RGB) x torch.from_numpy(x).permute(2, 0, 1).unsqueeze(0).float().cuda() / 255.0 t0 time.time() with torch.no_grad(): out model(x) logits out[0] if isinstance(out, list) else out mask torch.sigmoid(logits).squeeze().cpu().numpy() 0.5 latency (time.time() - t0) * 1000 overlay img.copy() overlay[mask 0] (0, 0, 255) cv2.imwrite(result.jpg, overlay) print(fend-to-end latency: {latency:.2f} ms)这段代码的关键在out[0] if isinstance(out, list) else out因为 Unet 在推理时模型的deep_supervision开关一旦关闭输出就不再是列表直接取主分支 logits。mask 0.5是二值化阈值0.5 对大多数场景够用如果预测结果偏保守、车道线断断续续可以降到 0.35 再试但阈值太低会把路面阴影带进来。我习惯把端到端时延压到 32.8 毫秒这个量级作为验收分界线。这个数字不是凭空定的单帧图像从送入网络到输出车道线 mask 的延迟压在 33 毫秒以内才能给上层规划留出足够决策余量。实测这份权重在 RTX 3090 上接近这个值换到较低分辨率的边缘设备时需要结合 TensorRT 再做一次加速但作为 CPU 上的项目交付验证这个指标足够说服团队进入下一阶段。自己在做验收时吃过一次亏当时只看 IoU 高就以为可以上车验证结果可视化后发现模型在强光路段不断把路肩裂缝识别成车道线。那之后我每次都强制走完一遍全流程读图、转 RGB、resize 对齐、推理、反变换到原图、叠加可视化、统计时延任何一个环节不过关都不算交付完成。这份资源里已经有训练好的权重和验证脚本你只需要把数据路径换成自己的场景图跑一遍这段流程就能确认模型边界在哪里。希望帮到你。本文还有配套的精品资源点击获取
返回列表