
简介面向目标检测学习者与算法研究者的车道线/虚线检测数据集内含1659张已标注图像可直接用于YOLO v5、v7、v8、v9、v10、yolo11等主流目标检测算法的训练、验证与测试。压缩包已提前划分好数据子集并附带data.yaml配置文件省去自行整理目录和编写配置的流程。标签同时提供YOLO格式txt和VOC格式xml两种形式分别存放在独立文件夹中YOLO格式以class、x_center、y_center、width、height五个字段记录目标框中心点坐标与宽高比适配常见训练管线VOC格式便于在需要XML标注的框架中继续使用。资源共2000个文件其中1161个xml、839个txt压缩包整体约74.72MB目录组织清晰适合直接开展批量训练与结果验证。已有79人学习下载可作为车道线检测、虚线识别等场景的入门或进阶训练数据基础帮助减少标注成本、快速验证模型效果、缩短项目迭代时间。1. 拿到这份车道线—虚线检测数据集先回答“能不能直接训练”“yolo算法-车道线-虚线检测道数据集-1659张图像带标签-车道.zip”这个压缩包名字已经把大部分信息写在明面上了1659张道路图像每张都配了YOLO格式的txt标签目的是训练或微调一个能检测车道线、尤其是虚线路段的车道线检测模型。它和你常见的车道线语义分割数据集不同这里提供的是目标检测框不是像素级掩码所以不能直接拿去跑分割网络也不能指望它输出完整的车道线轮廓。适合谁想做ADAS视觉感知验证的算法工程师、需要在自有场景快速试跑yolo检测的从业者、以及准备用yolov8训练自己的数据集但缺标注样本的同学。反直觉的一点是这类虚线目标又细又长标签框看着“很准”训练时却最容易因为下采样和增强策略丢掉。2. 解压后先别跑训练从标签坐标还原这套数据的真实面貌很多人在解压后第一件事是拖进训练脚本结果要么报“no labels found”要么loss飘成nan。车道线检测数据集和COCO这类通用目标数据集不一样它的标签质量、坐标范围、类别ID含义直接决定了你后面两天是调参还是调数据。所以我拿到压缩包的第一件事永远是先看标签再谈训练。2.1 用unzip解压再按文件类型统计images与labels配比先把压缩包解压到英文目录里避免后续脚本路径踩到中文编码问题mkdir -p lane_dataset unzip 车道线-虚线检测道数据集-1659张图像带标签-车道.zip -d lane_datasetLinux下直接unzip中文文件名大概率没问题但如果你是在macOS或某些旧版zip工具上解压会出现文件名乱码。我一般会先解压到一个临时目录确认images和labels文件夹都完整再复制到工作目录。解压后先看文件类型分布find lane_dataset -type f | sed s/.*\.// | sort | uniq -c这一步能快速告诉你压缩包里到底有什么。正常的车道线检测数据集应该有两类文件图片文件和txt标签文件。如果出现了json、xml、csv说明原数据集可能是COCO或LabelMe格式只是被某脚本转成了YOLO格式如果出现py文件那可能是转换工具被一起打包了。常见做法是图片集中在images目录标签集中在labels目录两者靠文件名一一对应。文件名可能是纯数字也可能带时间戳或路段编号。先统计一下图片和标签数量echo jpg数量: $(find lane_dataset -name *.jpg | wc -l) echo txt数量: $(find lane_dataset -name *.txt | wc -l)如果图片数1659、txt数也是1659这是最理想的情况。但现实里常常有少量txt是空的、或者图片后缀是jpeg/png导致数量对不上。这里有个很容易被忽略的坑很多YOLO脚本默认只找jpg图片如果你的数据里混了jpeg训练时会被静默跳过然后你看着“1659张图”训练实际情况却少了三百张。2.2 逐行读YOLO标签归一化坐标如何换算成虚线框YOLO格式的标签文件是纯文本每一行代表一个目标框格式为class_id cx cy bw bh其中cx、cy是目标框中心点的相对坐标bw、bh是框的相对宽高。所谓相对就是除以图片本身的宽和高所以这四个数理论上都在0到1之间。比如一行0 0.5828 0.7312 0.0421 0.1087表示第0类目标框中心在图片横向58.28%、纵向73.12%的位置宽度占图片宽度4.21%高度占图片高度10.87%。我用一段小脚本把标签解析出来看类别ID分布from pathlib import Path from collections import Counter root Path(lane_dataset) label_files list(root.rglob(*.txt)) print(标签数量:, len(label_files)) class_counter Counter() empty_labels 0 for lf in label_files: lines [line.strip() for line in lf.read_text(encodingutf-8).splitlines() if line.strip()] if not lines: empty_labels 1 continue for line in lines: parts line.split() if len(parts) ! 5: continue class_counter[int(parts[0])] 1 print(各类别ID统计:, dict(class_counter)) print(空标签文件数:, empty_labels)这段代码不依赖任何第三方库就是纯文本解析。为什么要看类别ID分布因为标题里写了“车道线-虚线检测”但压缩包里到底标了几类只有统计标签才知道。常见情况有两种只有class 0所有车道线统一标成一类虚线检测靠框的形态后续判断有class 0和class 1分别对应实线和虚线比如0solid1dashed。如果统计出来有两类恭喜你这个数据集确实把虚线和实线分开了训练目标就是二分类检测。如果只有一类那你后面就要考虑是否自己按框的位置或连通域拆分标注否则模型只能回答“哪里有线”不能回答“哪里是虚线”。2.3 画框回原图确认“虚线”到底标到多细只看坐标数字还不够我会把标签画回原图用视觉检查标注粒度。这一步非常关键尤其是“虚线检测”这个需求。import cv2 from pathlib import Path img_path Path(lane_dataset/images/00001.jpg) label_path img_path.with_suffix(.txt) img cv2.imread(str(img_path)) if img is None: raise FileNotFoundError(f无法读取图片: {img_path}) h, w img.shape[:2] for line in label_path.read_text(encodingutf-8).strip().splitlines(): cls, cx, cy, bw, bh map(float, line.split()) x1 int((cx - bw / 2) * w) y1 int((cy - bh / 2) * h) x2 int((cx bw / 2) * w) y2 int((cy bh / 2) * h) cv2.rectangle(img, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.putText(img, str(int(cls)), (x1, max(0, y1 - 5)), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 0, 255), 2) cv2.imwrite(check.jpg, img)这里的核心是cv2.rectangle的坐标换算。YOLO标签是归一化坐标必须乘以图片宽高才能得到像素坐标。x1、y1是左上角x2、y2是右下角。画完之后你会看到两种典型结果。一种是每个虚线小段都单独有一个细长框框与框之间有间隔这说明标注粒度已经细到了“虚线段”模型训练出来能直接检测每个虚线段的位置。另一种是整条车道线一个大矩形把虚线间断全部包在一个框里这只能做车道线存在性检测不能回答虚线从哪里开始、到哪里结束。这个粒度直接决定你的任务边界。如果发现是后一种标注而你又要做虚线检测需要先按连通域拆分标注框或者改用旋转框、线段检测方案。不要指望YOLO自己从一个大框里学到虚线断点它只会觉得“这一片都是目标”。3. 把1659张图整理成YOLO能吃的目录train/val划分与data.yaml数据集解压完标签也检查过下一步就是把原始目录整理成YOLO训练的标准形态。这一步做不好后面训练命令写得再漂亮也没用。3.1 标准目录结构images和labels必须一一对应YOLO系列训练时的默认目录约束是图片在images/train、images/val下标签在labels/train、labels/val下标签文件名和图片文件名相同只是后缀从.jpg变成.txt。不要把1659张图平铺在一个目录里也不要把标签和图片混在一起。我先按8:2划分训练集和验证集训练集约1327张验证集约332张。这个比例对这个体量比较稳妥import random from pathlib import Path import shutil root Path(lane_dataset) images sorted([p for p in root.rglob(*.jpg)]) random.seed(20240610) random.shuffle(images) val_n int(len(images) * 0.2) val_imgs images[:val_n] train_imgs images[val_n:] for split_name, split_imgs in [(train, train_imgs), (val, val_imgs)]: img_dir root / images / split_name lbl_dir root / labels / split_name img_dir.mkdir(parentsTrue, exist_okTrue) lbl_dir.mkdir(parentsTrue, exist_okTrue) for imp in split_imgs: shutil.move(str(imp), str(img_dir / imp.name)) lbp imp.with_suffix(.txt) if lbp.exists(): shutil.move(str(lbp), str(lbl_dir / lbp.name))注意这段代码用shutil.move会把图片和标签从原目录挪进新目录。如果你希望保留原始压缩包解压出来的结构改成shutil.copy2即可。我在实际项目里倾向于先复制一份原始数据再整理这样训练参数玩崩了还能重来算是给自己留后悔药。划分时设置随机种子是为了可复现。deep learning很多玄学问题都来自随机性固定seed之后至少数据划分这一环是可复现的。.with_suffix(.txt)的意思是把图片后缀换成txt得到对应标签路径。这个做法假设你的图片都是jpg。如果原图有png/jpeg这套脚本会漏掉它们。所以在大规模整理前先跑一行确认find lane_dataset -type f \( -name *.jpg -o -name *.jpeg -o -name *.png \) | wc -l如果三种后缀都有建议用PIL统一转成jpg再做划分。统一后缀比改脚本简单得多。3.2 写对data.yaml的nc和names训练才不白跑整理完目录写data.yaml。这是YOLO训练时数据集的“身份证”。后面所有训练命令都靠它找数据# lane_dataset/data.yaml path: /data/lane_dataset train: images/train val: images/val nc: 2 names: 0: solid_line 1: dashed_linepath推荐写绝对路径省去很多相对路径带来的困扰。train和val相对于path写。nc是类别总数如果你的数据只有一类就写1并把names改成一个名字。names里的顺序必须和标签txt里的class_id一一对应否则训练不会报错但最后画出来的混淆矩阵和per-class AP会张冠李戴。如果前面统计标签时发现只有class 0data.yaml应该长这样nc: 1 names: 0: lane类别名本身只影响日志和可视化不影响loss计算。真正影响训练的是nc。如果你写了nc2但标签里最大的class_id是1没问题如果标签里最大的class_id是2训练会报index out of range。这也解释了为什么第2章要先统计类别ID分布。写完data.yaml后先不要急着训练用YOLO的校验接口快速验证数据能不能被正确加载。一个常用的做法是跑短训练前先调用model.val或直接跑一个epoch看日志。另一种更快的方法是写一个小脚本数一下train和val里图片数量echo train images: $(find lane_dataset/images/train -type f | wc -l) echo train labels: $(find lane_dataset/labels/train -type f | wc -l) echo val images: $(find lane_dataset/images/val -type f | wc -l) echo val labels: $(find lane_dataset/labels/val -type f | wc -l)图片和标签数量不一致时优先检查是不是有图片没有标签。YOLO训练时会把没有对应txt的图片直接跳过并且在日志里提示。3.3 虚线和实线数量悬殊时的分层划分策略如果数据集里有实线/虚线两类最常见的坑是一整条虚线被标注成几十个框而实线标注框数量少得多导致类别严重不均衡。8:2随机划分很可能会让某些类别在验证集里占比过高出现“训练时见过很多虚线验证时虚线却不够”的尴尬情况。我一般会按图像包含的类别组合做分层划分而不是纯随机from collections import defaultdict def get_classes_of_image(img_path: Path) - tuple: lbp img_path.with_suffix(.txt) if not lbp.exists(): return () classes set() for line in lbp.read_text(encodingutf-8).strip().splitlines(): if line.strip(): classes.add(int(line.split()[0])) return tuple(sorted(classes)) buckets defaultdict(list) for img in images: buckets[get_classes_of_image(img)].append(img) val_set set() for classes, img_list in buckets.items(): random.shuffle(img_list) val_count max(1, int(len(img_list) * 0.2)) val_set.update(img_list[:val_count]) train_imgs [img for img in images if img not in val_set] val_imgs [img for img in images if img in val_set]这里get_classes_of_image先读每个图片对应的txt提取里面出现的class_id组合。比如有的图只有实线类别组合是(0,)有的图实线虚线都有组合是(0,1)。然后按组合分别取20%做验证集。这样做比纯随机多一步但能保证验证集里每条类别组合都有代表性。否则可能出现验证集里只有实线模型预测虚线全部漏检而你还在那儿怀疑是训练参数出了问题。如果某个类别的图像数量实在少比如虚线样本只有几十张我会选择在训练前对少类样本做离线过采样把它们复制两份到训练目录里。复制不是新增信息但能调整训练时的采样比例配合后续数据增强效果不错。别去改loss权重那个参数在小数据上调控难度大不如直接控样本分布直观。4. 用yolov8训练自己的车道线数据集参数、损失与anchors目录整理好data.yaml写对接下来进入真正训练环节。我用的是YOLOv8它在小目标检测场景下有更友好的训练流程并且自动完成了anchor设置。标题里的算法是“yolo算法”车道线检测任务用YOLOv8或YOLOv5都行但当前环境下yolov8训练自己的数据集社区的坑和教程都更全。4.1 从预训练权重起步还是从头训练这个问题几乎每次都会被问。1659张图在目标检测领域只能算小数据集尤其车道线这种细长目标可学习的纹理模式和场景上下文都很单一。我的建议是除非你想发论文证明特定结构否则一律从预训练权重起步。常见的做法是下载YOLOv8官方预训练权重比如yolov8n.pt或yolov8s.pt。这个文件可以在官方Release页面直接下载下载后放到当前工作目录。第一次运行训练脚本时会自动匹配这个权重文件如果没找到它会尝试联网拉取网速不理想时可能卡很久。训练入口可以有两种写法。命令行直接跑yolo detect train \ datalane_dataset/data.yaml \ modelyolov8n.pt \ epochs100 \ imgsz960 \ batch16 \ patience20或者用Python脚本from ultralytics import YOLO model YOLO(yolov8n.pt) results model.train( datalane_dataset/data.yaml, epochs100, imgsz960, batch16, lr00.005, patience20, save_period10, )两个写法本质一致。模型选yolov8n是因为nano版本参数量小1659张数据不容易过拟合训练速度也快。如果你的显卡显存充足又希望感知能力更强可以换yolov8s但在这个数据量下提升有限。4.2 三个必调参数与YOLO损失函数的观察方式不要照着默认参数直接开跑。车道线数据有它自己的特点下面三个参数至少必须过一遍参数默认值建议值原因imgsz640960或1280车道线细长下采样后容易消失batch168~16按显存调batch太小BN统计不稳定lr00.010.005小数据集下0.01偏高容易震荡imgsz是最影响结果的一个参数。640px输入下一条宽度4px的虚线框缩到输入分辨率后可能只剩2~3px很容易在特征提取阶段被当成噪声丢掉。调到960需要更多显存但对虚线召回率提升立竿见影。batch不建议为了显存强行调到4以下。YOLOv8默认训练使用BatchNormbatch过小时BN统计量抖动剧烈前几个epoch就可能让loss飘起来。如果显卡只有8G显存imgsz960下batch设8再不行就降到640。训练开始后你能不能看懂日志决定了你能不能判断模型有没有学歪。YOLO损失函数主要由三部分组成box_loss、cls_loss、dfl_loss。box_loss负责让预测框和真实框的重合度更高cls_loss负责分类正确dfl_loss负责框的边界精细度。训练时终端每轮会打印这三项。我一般只看两件事。第一box_loss有没有稳步下降。如果下降但cls_loss纹丝不动一般不是模型问题是类别太简单或者正负样本太不均衡。第二loss降到某个值后突然反弹说明lr到了平台期可以把lr0再调小。针对类别不均衡可以开label_smoothing0.1。这相当于给标签做了软标签平滑让模型不要对某个类别过于自信。注意这里的“软标签”指的是训练策略层面的不是让你去把txt里的0改成0.9YOLO标签文件本身就是硬标签不要手动改。4.3 细长目标与mosaic增强为什么不能照搬默认配置YOLOv8默认开启Mosaic增强把四张图拼成一张训练这在COCO这类普通目标数据上效果很好但车道线数据集上Mosaic会把细长框缩到原图的四分之一目标更小模型更难学。这是很多人在车道线数据集上训练后mAP上不去的主要原因。先统计一下你的标签框宽高比import numpy as np from pathlib import Path from PIL import Image ratios [] for lf in Path(lane_dataset/labels/train).rglob(*.txt): img_path lf.with_suffix(.jpg) if not img_path.exists(): continue w, h Image.open(img_path).size for line in lf.read_text(encodingutf-8).strip().splitlines(): parts line.split() if len(parts) ! 5: continue _, cx, cy, bw, bh map(float, parts) if bh 0: continue ratios.append((bw * w) / (bh * h)) ratios np.array(ratios) print(宽高比 p50 / p90 / max:, np.percentile(ratios, [50, 90, 100]))如果结果里p50在5以上、p90在20以上说明大量目标框是细长条。这种形态下我建议在训练参数里显式关闭或降低Mosaicmodel.train( datalane_dataset/data.yaml, epochs100, imgsz960, batch16, lr00.005, mosaic0.2, close_mosaic10, copy_paste0.1, )mosaic0.2表示只有20%的概率使用Mosaic增强close_mosaic10表示最后10个epoch关闭Mosaic让模型在接近真实分布的数据上做收敛。copy_paste对细长目标更有帮助它能把一张图里的虚线目标复制到另一张图上天然适合车道线这种重复纹理目标。关于anchorsYOLOv8已经是anchor-free框架不需要手动设置anchor大小。但如果你从YOLOv5迁移过来还是会习惯性想调anchor。YOLOv5在训练时会自动聚类anchor参数anchor_scale或auto_anchorTrue控制。车道线数据集上最需要关心的是不要因为框宽高比过大而被聚类结果带偏我在YOLOv5上遇到过聚类出来的anchor宽高比从1到50都有最终mAP反而下降。YOLOv8省掉了这个负担所以后面我只推荐用YOLOv8系列跑这个任务。5. 车道线数据集落地避坑5个常见问题与排查记录这个标题的数据集来源很杂解压、格式、标注质量都会出问题。下面五条是我在这类数据上反复踩过的坑每一条都按“现象、原因、解决”的结构记录。5.1 中文文件名解压乱码导致标签对不上现象压缩包解压后images目录下文件名是乱码labels目录下文件名倒是正常两者无法通过with_suffix匹配上模型训练时报标签文件不存在。原因zip压缩包是用Windows中文编码创建的文件名在Linux/Unix下被错误解码。尤其是标题里本身就带中文“车道线-虚线检测道数据集”文件名长、含空格、含中文最容易触发这个问题。解决不要直接用unzip解压中文zip用Python的zipfile解压并在解压过程中对文件名做手动替换from pathlib import Path from zipfile import ZipFile zip_path Path(车道线-虚线检测道数据集-1659张图像带标签-车道.zip) out_root Path(lane_dataset_fixed) out_root.mkdir(exist_okTrue) with ZipFile(zip_path) as zf: for name in zf.namelist(): new_name name.replace( , _) target out_root / new_name target.parent.mkdir(parentsTrue, exist_okTrue) with zf.open(name) as src, open(target, wb) as dst: dst.write(src.read())这里把空格替换成下划线能避免后续命令行和配置文件解析路径时出问题。中文部分如果已经乱码需要先看实际文件名再做批量替换。给一个建议解压后立即把所有文件名统一成00001.jpg这样的纯数字命名彻底远离编码问题。5.2 标签里出现零宽零高框训练直接飘现象训练前几个epoch的loss剧烈波动有时直接出现nan检查标签文件发现某一行是0 0.5000 0.5000 0.0000 0.0000。原因标注工具或转换脚本在处理某些退化目标时导出了宽或高为0的框。这类框没有物理意义但在loss计算中会产生除零或极端值。解决训练前写脚本过滤掉所有无效框并且把坐标超过[0,1]的框也一起清掉from pathlib import Path for split in [train, val]: lbl_dir Path(lane_dataset/labels) / split for lf in lbl_dir.glob(*.txt): keep_lines [] for line in lf.read_text(encodingutf-8).strip().splitlines(): parts line.split() if len(parts) ! 5: continue cls, cx, cy, bw, bh map(float, parts) if bw 0 or bh 0: continue if not (0 cx 1 and 0 cy 1 and 0 bw 1 and 0 bh 1): continue keep_lines.append(line) lf.write_text(\n.join(keep_lines), encodingutf-8)注意这里不要直接覆盖原始压缩包最好是先备份。很多数据集是别人打包后二次导出的过滤后可能从1659张图变成1648张能用这个数字变化是正常的不要为了凑数勉强保留坏标签。5.3 没有标签的图片混进训练目录现象训练日志里显示train images: 1327但实际参与训练的图片只有900多张另外几百张被跳过日志里出现过WARNING但没有明确说是哪些文件。原因有些图片文件名是.jpeg而标签转换脚本只生成了.jpg对应的txt有些图片本身是数据集作者漏标了还有可能是标签文件名后缀写成了.txt.txt。解决先找出没有标签的图片cd lane_dataset for f in images/train/*.jpg images/train/*.jpeg images/train/*.png; do [ -f $f ] || continue lb${f%.*}.txt if [ ! -f $lb ]; then echo $f fi done把输出重定向到文件里逐个看是无标签还是后缀不匹配。如果只是图片后缀问题用rename统一改成jpg。如果确实漏标把漏标图片移到unlabeled目录防止训练时被误当成负样本。YOLO训练时如果你的图片没有对应txt该图片不会被当作负样本参与训练而是直接被跳过所以不会破坏训练稳定性但会导致有效数据量缩水。1659张图缩水到1400张差距不小尤其是某些路段的虚线样本本来就少。5.4 训练中loss变NaN先怀疑BN崩溃还是标签越界现象训练到第3到第10个epoch之间loss变成nan后续所有epoch的输出都是nan保存的权重文件也无法正常推理。原因两类常见原因。第一是batch太小加上lr0太高BatchNorm的统计量发散俗称BN崩溃第二是标签坐标里有cx或cy大于1的越界框loss在回归分支算出异常值。解决先查标签from pathlib import Path bad_files [] for lf in Path(lane_dataset/labels).rglob(*.txt): for line in lf.read_text(encodingutf-8).strip().splitlines(): parts line.split() if len(parts) ! 5: bad_files.append((str(lf), 列数不对)) continue cls, cx, cy, bw, bh map(float, parts) if not (0 cx 1 and 0 cy 1): bad_files.append((str(lf), 中心点越界)) break if bw 0 or bh 0: bad_files.append((str(lf), 宽高无效)) break print(bad_files[:20])标签没有异常再调训练参数。把lr0从0.01降到0.001batch至少8起步。BN崩溃不是玄学它来自统计量在前期不收敛小数据集上尤其敏感。遇到nan不要急着改网络结构先查这两处十次有八次能解决。5.5 mAP不低虚线还是漏先回到标注粒度找原因现象训练完mAP50到了0.88看起来不错但在连续视频帧上测试时虚线框断续非常严重一会儿检测到一会丢尤其是远处虚线段全漏。原因如果验证集和训练集来自同一路段、同一标注风格mAP虚高并不奇怪。更隐蔽的问题是标注粒度。如果一个虚线段的标注框高度只有3个像素960输入下还能勉强保留但到了模型下采样32倍后特征图上只剩不到1个像素这个目标从特征角度已经不存在了。解决统计一下训练集中所有框的像素宽高import numpy as np from pathlib import Path from PIL import Image widths, heights [], [] for lf in Path(lane_dataset/labels/train).rglob(*.txt): img_path lf.with_suffix(.jpg) if not img_path.exists(): continue w, h Image.open(img_path).size for line in lf.read_text(encodingutf-8).strip().splitlines(): parts line.split() if len(parts) ! 5: continue _, _, _, bw, bh map(float, parts) widths.append(bw * w) heights.append(bh * h) print(目标宽度像素 p50/p90:, np.percentile(widths, [50, 90])) print(目标高度像素 p50/p90:, np.percentile(heights, [50, 90]))如果大部分高度不到5像素说明不是模型问题是这个任务本身就接近小目标检测极限。解决方向有两个继续把imgsz调到1280但显存消耗显著上升或者放弃直接检测远距离虚线改成中近距离检测加跟踪关联用时序补全虚线消失的帧。6. 验证比训练更花时间的那个技巧把预测框画回原图再谈部署训练完best.pt之后不要只看验证集面板上那个mAP50数字。mAP是统计指标它对“漏了远处的小虚线”和“把阴影误检成实线”这两类错误惩罚不够直观。我每次训练完都会先做一件事把验证集里几十张图的预测结果画回原图一张一张看。from ultralytics import YOLO model YOLO(runs/detect/train/weights/best.pt) results model.predict( sourcelane_dataset/images/val, conf0.25, imgsz960, saveTrue, )预测结果里重点看三类图片远处车道线密集的图、桥梁接缝和阴影明显的图、以及虚线断裂处与实线并存的图。YOLOv8自带的plot结果里会画框、类别和置信度足以判断漏检是来自标注粒度还是模型学习。确认模型可用后再跑一次model.val()看per-class AP和混淆矩阵。这里有一个容易被忽略的细节YOLO混淆矩阵打印出来的数字是比例同一行之和不是固定1因为它按不同置信度阈值统计别把“总和不为1”当成bug。你要看的是虚线这一类的recall是否远低于实线。如果虚线AP比实线低太多我会收集几个漏检片段做增量标注单独再训10~20个epoch。这个做法比盲目加大全网数据量更省钱。部署阶段我一般会导出成ONNX格式方便后续转TensorRT或放服务端推理model.export(formatonnx, imgsz960)导出的ONNX输入输出都是固定尺寸推理时如果原图不是960宽需要做好letterbox预处理别直接resize否则框坐标会有系统性偏移。车道线检测的置信度阈值不要跟COCO一样用0.5我常用的区间是0.25到0.35太低会引入大量虚检太高远处虚线全丢。我自己有个习惯不管mAP多好看都要抽一段没有参与训练的连续视频按帧跑一遍看输出。因为mAP衡量的是“单张图的检测能力”而车道线虚线检测最终要落在连续帧上漏检是不是集中在特定距离段只有视频能给你答案。当年我在一个标注粒度很粗的数据集上被mAP0.9骗过模型上测试视频才现原形希望帮你省下这个弯路。本文还有配套的精品资源点击获取