简介:本资源为面向自动驾驶视觉感知方向的图像分割数据集,聚焦车道线虚线、实线语义分割任务,适合从事自动驾驶、道路场景理解及图像分割算法学习与实验的开发者与研究者使用。数据集已完成训练集与验证集划分,训练集约1200张图片及对应mask,验证集约120张,共约1300张样本,涵盖背景、虚线、实线3类分割标签,类别定义可参考classes文件。压缩包共2000个文件,以png与jpg图像为主,另含1个txt说明与1个py脚本,整体约146.87MB。资源附带图像分割可视化脚本,可随机抽取一张图片,展示原始图像、GT图像及GT在原图上的蒙板效果并保存至当前目录,便于快速核验标注质量与数据分布。目前已有303人学习下载,适合作为车道线分割模型训练、验证与可视化分析的实践素材。
1. 车道线语义分割数据集:1300 张 3 类标注到底能训出什么
高速上压线被拍、匝道口错过实线变道、夜间虚线看不清直接骑线——这些场景背后其实是同一个技术问题:车端要实时知道「哪条线是实线、哪条是虚线、哪里根本没线」。图像分割数据集里,专门针对车道线的语义分割数据一直是稀缺品,通用自动驾驶数据集(比如常被提到的 bdd100k)虽然大,但车道线像素级标注往往只有几类粗标,虚线和实线经常被合并成一类,训出来的模型分不清能不能变道。这个标题指向的就是一个更聚焦的资产:约 1300 张图像,3 类语义分割标签,类别通常落在「实线 / 虚线 / 背景」这个划分上。
它解决的不是「检测车道线在哪」,而是「逐像素判断这条线属于哪一类」。适合谁?做 ADAS 前视感知的算法工程师、想用 yolov8-seg 或 FCN 系语义分割模型跑通车道线任务的开发者、以及需要一个小规模干净数据集做算法验证和课程设计的人。1300 张不算多,但车道线场景本身纹理单一、结构重复,只要标注质量过关,这个量级足够把一条 baseline 跑通并看到类别区分效果。下面从数据本身讲到训练、评估和踩坑,全部按可复现的路子来。
2. 拆开这 1300 张:3 类标签的像素分布与目录组织
拿到一个语义分割数据集,第一件事不是急着喂模型,而是先搞清楚它的标签长什么样、类别是否平衡、掩码用什么格式存。车道线数据集有个典型特征:背景像素占比极高,实线和虚线加起来可能不到全图的 5%。这个先验直接决定了后面损失函数怎么选、评估指标怎么看。
2.1 三类标签的定义与掩码编码方式
常见的 3 类划分是:
| 类别 ID | 名称 | 含义 | 典型像素占比 |
|---|---|---|---|
| 0 | background | 路面、天空、车辆等非车道线区域 | 90% 以上 |
| 1 | solid_line | 实线,禁止变道 | 2%~5% |
| 2 | dashed_line | 虚线,允许变道 | 1%~3% |
掩码一般有两种存法:一种是单通道 PNG,像素值直接是 0/1/2;另一种是灰度图映射到 0/128/255。这两种在读取时处理方式不同,读错了会导致类别全乱。我一般先写个脚本统计每张图的唯一像素值,确认编码方式再往下走。
import numpy as np from PIL import Image import os mask_dir = "dataset/masks" for name in os.listdir(mask_dir)[:5]: m = np.array(Image.open(os.path.join(mask_dir, name))) uniq = np.unique(m) print(name, m.shape, uniq)这段代码的作用是抽样打印掩码的形状和所有出现过的像素值。如果输出是[0 1 2],说明是直接类别编码;如果是[0 128 255],就需要在 Dataset 里做一次映射。参数上注意m.shape,如果是(H, W, 3)说明存成了 RGB,得先转单通道,否则np.unique会给出三个通道混在一起的结果,看着像有几百个类别,其实是假象。
2.2 目录结构与训练/验证划分
一个能直接跑的结构通常长这样:
dataset/ ├── images/ │ ├── train/ │ └── val/ ├── masks/ │ ├── train/ │ └── val/ └── classes.txtclasses.txt里按行写background、solid_line、dashed_line,行号即类别 ID。划分比例上,1300 张我一般按 8:2 切,训练 1040 张、验证 260 张。车道线场景连续帧之间高度相似,如果按随机切分,相邻帧可能一张在训练集一张在验证集,导致验证指标虚高。更稳的做法是按采集片段切分,同一段路的图整段进训练或整段进验证。这一点在只有 1300 张时尤其关键,否则你会看到一个漂亮但没意义的 mIoU。
import os, random, shutil random.seed(42) imgs = sorted(os.listdir("dataset/images/all")) random.shuffle(imgs) split = int(len(imgs) * 0.8) for i, name in enumerate(imgs): phase = "train" if i < split else "val" shutil.copy(f"dataset/images/all/{name}", f"dataset/images/{phase}/{name}") shutil.copy(f"dataset/masks/all/{name}", f"dataset/masks/{phase}/{name}")逻辑说明:固定随机种子保证可复现,图像和掩码同名同步搬运。参数0.8是训练占比,样本量小的时候可以调到 0.85 多留点训练数据,但验证集别低于 200 张,否则指标抖动大。搬完后务必抽查几张,确认图和掩码一一对应,我见过因为文件名后缀不一致(.jpg对.png)导致掩码全读成空的翻车案例。
3. 用 yolov8-seg 跑通车道线分割的最小闭环
车道线语义分割可以走两条路:一条是纯语义分割模型(FCN、U-Net、DeepLab 系),输出每像素类别;另一条是用实例分割框架(yolov8-seg)把每条线当实例,再按类别归并。前者更贴合「语义分割」的定义,后者工程上更省事、部署链路成熟。这里给一条能快速看到效果的 yolov8-seg 路线,同时说明它和纯语义分割的差别。
3.1 把语义掩码转成 yolov8-seg 需要的多边形标注
yolov8-seg 不吃单通道掩码,它要的是每张图对应的 txt,每行是class x1 y1 x2 y2 ...(归一化多边形)。所以第一步是把语义掩码转多边形。用 OpenCV 找轮廓即可。
import cv2 import numpy as np import os def mask_to_polygons(mask_path, out_txt, img_w, img_h): m = cv2.imread(mask_path, cv2.IMREAD_GRAYSCALE) lines = [] for cls_id in [1, 2]: # 0 是背景,不输出 binary = (m == cls_id).astype(np.uint8) * 255 contours, _ = cv2.findContours(binary, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) for c in contours: if cv2.contourArea(c) < 30: # 过滤噪点 continue c = c.reshape(-1, 2).astype(float) c[:, 0] /= img_w c[:, 1] /= img_h coords = " ".join(f"{x:.6f} {y:.6f}" for x, y in c) lines.append(f"{cls_id - 1} {coords}") # yolov8 类别从 0 开始 with open(out_txt, "w") as f: f.write("\n".join(lines))逻辑说明:对每个前景类别单独二值化再找外轮廓,RETR_EXTERNAL只取最外层,避免车道线内部空洞产生多余多边形。参数cv2.contourArea(c) < 30是面积阈值,车道线细长,太小会把断裂的碎点也当目标,太大又会漏掉远处细线,30 像素是个经验起点,按分辨率调整。归一化用图像真实宽高,别用掩码尺寸,两者不一致时坐标会整体偏移。类别映射cls_id - 1是因为 yolov8 的类别索引从 0 开始,而掩码里 0 是背景,容易在这里错位。
3.2 训练配置与关键超参
生成data.yaml:
path: ./dataset train: images/train val: images/val names: 0: solid_line 1: dashed_line启动训练:
yolo segment train data=data.yaml model=yolov8n-seg.pt epochs=100 imgsz=640 batch=8参数说明:yolov8n-seg.pt是最小的分割预训练权重,1300 张数据量下从它微调比从零训稳得多。imgsz=640是输入尺寸,车道线细,分辨率太低虚线会糊成一片,如果显存够可以上 960。batch=8是保守值,显存 8G 以内基本能跑。epochs=100配合早停,实际往往 60~80 轮就收敛。训练时重点看mask mAP50而不是box mAP50,车道线任务里框的指标意义不大。
3.3 语义分割路线:U-Net 的最小实现差异
如果你要的是严格逐像素语义分割,U-Net 更直接。核心差别在损失函数:车道线前景极少,交叉熵会被背景淹没,必须用带权重的损失或 Dice。
import torch import torch.nn as nn class WeightedCE(nn.Module): def __init__(self, weights): super().__init__() self.weights = torch.tensor(weights, dtype=torch.float32) def forward(self, logits, target): return nn.functional.cross_entropy(logits, target, weight=self.weights.to(logits.device))逻辑说明:weights按类别像素占比的倒数来设,比如[0.1, 1.0, 1.5],给背景低权重、给虚线高权重。参数不是拍脑袋,先统计训练集三类像素总数再取反比归一化。用这个损失后,模型才不会偷懒把所有像素判成背景还拿到 95% 准确率。评估时也别只看 accuracy,要看每类的 IoU,尤其是虚线那一类。
4. 车道线分割的避坑与排查:5 个真实翻车点
这个任务看着简单,实际坑集中在数据编码、类别不平衡和评估口径上。下面 5 条都是我在类似数据集上踩过的。
现象一:训练 loss 正常下降,但预测图全黑。原因:掩码读取时没做类别映射,模型学到的是 0/128/255 三个值,而你的损失函数按 0/1/2 算,标签和预测对不上。解决:在 Dataset 的__getitem__里统一做mask[mask == 128] = 1; mask[mask == 255] = 2,并在训练前打印一个 batch 的标签唯一值确认。
现象二:验证 mIoU 很高,实车测试虚线全丢。原因:验证集和训练集来自同一段路,光照、路面材质高度相似,模型过拟合到场景而非车道线本身。解决:按采集片段切分,或者刻意把不同天气、不同路段的图放进验证集,宁可验证指标难看也要真实。
现象三:虚线被预测成实线,反之亦然。原因:两类形态接近,1300 张里虚线样本又少,模型区分不开。解决:提高虚线类损失权重,同时做针对性增强——对虚线图做随机遮挡,模拟虚线被磨损、被前车挡住的情况,逼模型学虚线本身的断续特征而不是长度。
现象四:远处车道线断裂、不连续。原因:输入分辨率不够,远处一条虚线在 640 尺寸下只剩几个像素。解决:提高imgsz,或者用滑窗/多尺度推理。代价是推理变慢,要权衡。
现象五:多边形标注转换后目标数量暴涨。原因:掩码里有细小噪点或车道线断裂,findContours把每个碎片都当独立目标。解决:加面积过滤,或者先对掩码做形态学闭运算把断线连起来再找轮廓。闭运算核大小按线宽设,一般 3×3 或 5×5。
提示:每次改完数据编码或划分方式,先跑 5 个 epoch 看 loss 和预测可视化,别等 100 轮跑完才发现标签是错的,那才是真的后悔药没处买。
5. 把 3 类分割用起来:从指标验证到类别合并的进阶技巧
训完模型只是开始,真正决定这个数据集值不值得投入的,是你能不能把它的输出接到实际判断上。车道线分割的下游通常是「可行驶区域」和「变道可行性」判断,这里有两个我常用的技巧。
第一个是按类别做后处理连通性检查。语义分割输出的是像素类别,但车道线在物理上应该是连续的。可以对虚线类别的预测做一次纵向连通域分析:如果一条虚线在近处连续、远处断开,大概率是分辨率问题而非真实断线,可以用形态学膨胀补上;如果实线出现断裂,那更可能是遮挡,不该强行补。这个区分直接决定后处理策略。
import cv2 import numpy as np def check_continuity(pred_mask, cls_id=2): binary = (pred_mask == cls_id).astype(np.uint8) num, labels = cv2.connectedComponents(binary) sizes = [np.sum(labels == i) for i in range(1, num)] return num - 1, sorted(sizes, reverse=True)[:5]逻辑说明:connectedComponents统计每类前景的连通块数量和大小分布。虚线正常应该有多个中等大小的块,如果出现一个巨大块加一堆碎点,说明模型把实线误判成虚线了。参数cls_id切换类别,分别对实线和虚线跑一遍,对比块数量是否符合预期。
第二个技巧是类别合并的降级策略。如果实测发现虚线和实线实在分不开,别硬扛,先把 3 类合并成「车道线 / 背景」2 类,保证线在哪是对的,再单独训一个二分类器判断虚实。这种分阶段方案在数据量不足时比强行 3 类更靠谱。1300 张做 3 类分割是够的,但前提是标注质量过关;如果标注本身虚实边界就模糊,合并反而是止损。
| 方案 | 类别数 | 适用场景 | 风险 |
|---|---|---|---|
| 直接 3 类分割 | 3 | 标注清晰、虚实分明 | 虚线样本少易混淆 |
| 先分割再分类 | 2+1 | 数据量小、虚实难分 | 链路长、误差累积 |
| 合并为 2 类 | 2 | 只关心线位置 | 丢失变道语义 |
验证方法上,除了 mIoU,我习惯再算一个「按线的 IoU」:把预测和真值都做连通域,匹配最近的线对,算匹配线上的重叠率。这个指标比像素 mIoU 更贴近「这条线有没有被正确识别」的工程感受。做法不复杂,但能暴露像素指标掩盖的问题,比如一条线整体偏移几个像素,像素 IoU 还行,但按线看就错位了。
最后说个习惯:每次拿到新的车道线数据集,我都会先花半小时把掩码可视化叠到原图上,肉眼过 20 张。这一步能提前发现 80% 的编码和标注问题,比任何自动化检查都直接。1300 张的规模,人工抽检完全可行,别省这个时间。希望帮到你。
本文还有配套的精品资源,点击获取