简介面向自动驾驶场景的路面图像分类数据集合计约50,000张已标注图片覆盖冰、干燥沥青轻微/严重、潮湿沥青、湿混凝土等27种路面状态贴合真实道路场景数据已经过预处理可直接作为分类网络输入。资源内部划分了训练集与测试集同类图片按目录集中存放加载后即可开展训练与评估json文件记录类别标签编号与映射关系Python脚本支持一键可视化样本方便快速检查图像质量与类别分布图片文件名还带有路面状态标识便于筛选与调试。整个压缩包共2000个文件采用7z格式封装主要包括1998张jpeg图片、1个py可视化脚本和1个json配置文件整体大小约763.1MB目录结构清晰适合结合PyTorch、TensorFlow等主流深度学习框架开展路面状态识别实验。该数据集可用于训练图像分类模型、进行迁移学习与算法对比也能为自动驾驶感知中的路面状态分析提供数据准备训练集与测试集划分明确可避免自行随机划分带来的数据泄漏问题。目前已有70人学习适合正在从事自动驾驶感知、路面状态分类或计算机视觉研究的开发者与研究人员下载使用同时可作为数据预处理、训练管线搭建与模型评估的实用参考。1. 路面图像分类数据集五万张已标注图片的实际价值做自动驾驶感知的人迟早要面对一个很现实的问题车端摄像头实时拍回来的路面图怎么快速判断当前是干燥、湿滑、积水还是积雪结冰。这个判断直接决定车辆要不要降速、四驱扭矩怎么分配、紧急制动距离怎么估算。这套约50,000张已标注的路面图像分类数据集解决的就是“路面识别”这个环节它是感知层里最接地气的那一块训练素材。适合正在做ADAS、自动驾驶仿真、道路养护检测的算法工程师也适合在校学生做图像分类项目。规模不算大但胜在类别明确、标注齐全拿到手不用做清洗直接划分就能进模型。2. 数据集结构与标注体系先看清文件夹和标签再动手2.1 五万张图的组织方式与常见目录结构拿到数据集后先不要急着写训练脚本。我接手过很多公开数据集多数情况下标注格式比想象中要杂有的是直接把类别名当文件夹名有的是一个CSV文件拉着图片路径和标签有的干脆是JSON。这套路面分类数据集按常规组织方式大概率是“目录即标签”的结构像下面这样dataset/ ├── train/ │ ├── dry/ │ │ ├── 00001.jpg │ │ └── ... │ ├── wet/ │ ├── snow/ │ ├── ice/ │ └── pothole/ ├── val/ └── test/这里dry、wet、snow、ice、pothole五个文件夹分别对应干燥、湿滑、积雪、结冰和破损五种路面状态。为什么要先确认目录结构因为后续的数据划分、标签读取、类别权重计算全都依赖这个基础。如果实际包里的类别名是别的写法比如用0、1、2、3、4的数字目录那你需要自己维护一个类别映射表训练代码里才能把数字索引转回可读的类别名。类别目录标签ID典型路面特征自动驾驶中的影响dry0路面干燥无明显反光正常行驶轮胎抓地力良好wet1有明显水膜反光制动距离变长需降低车速snow2路面覆盖积雪白色纹理易打滑需启用雪地模式ice3透明或半透明冰层强反光极易失控需谨慎制动pothole4坑洞或裂缝路面破损需绕障或减速通过这份映射表是我训练前必建的一个锚点后面生成混淆矩阵、写评估报告都要用同一套ID。中途换映射是我踩过的坑图表、日志、代码里的数字全对齐不了排查了半天才发现是类别ID对不上白白浪费一晚上。拿到手的第一件事我一般会写一个分布统计脚本看每个类别的样本量是否均衡import os from collections import Counter data_dir dataset/train counts Counter() for cls_name in os.listdir(data_dir): cls_path os.path.join(data_dir, cls_name) if os.path.isdir(cls_path): counts[cls_name] len(os.listdir(cls_path)) print(counts) print(总样本数:, sum(counts.values()))这段代码做的事只有一个统计每个类别文件夹里的图片数量。很多人下载完数据集直接开训训到一半发现某个类别只有几百张另一个类别一万多张整个模型被大类带偏所以分布统计这步不要省。输出结果里如果某个类别数量明显偏少那后面做类别权重或者过采样就要提前准备。提示如果实际解压后类别数目或目录名与这里不完全一致一切以你手里的版本为准训练代码里的num_classes也要改成对应数字不要照抄网上的固定值。2.2 标注可靠性的基本验证抽看而不是盲信“已标注”不等于“标注全对”。公开数据集里最常见的标注问题有三类一是边界情况标错比如路面有少量水渍被标成干燥二是文件名和标签对不上解压过程丢文件三是类别定义不统一比如某些图上既有雪又有冰标谁的都有道理。我会按每个类别抽二十到三十张图用一张网格画布拼出来人工过一眼。import matplotlib.pyplot as plt from PIL import Image import os def preview_class(data_dir, cls_name, cols5, rows4): cls_path os.path.join(data_dir, cls_name) files sorted(os.listdir(cls_path))[:cols * rows] fig, axes plt.subplots(rows, cols, figsize(12, 12)) for i, fname in enumerate(files): img Image.open(os.path.join(cls_path, fname)) ax axes[i // cols][i % cols] ax.imshow(img) ax.set_title(f{cls_name}-{i}) ax.axis(off) plt.tight_layout() plt.show() for cls in [dry, wet, snow, ice, pothole]: preview_class(dataset/train, cls)这个脚本里值得关注的参数cols和rows决定一屏看多少张默认5乘4等于一次看20张足够你快速判断这一批的标注风格是否统一。如果你发现同一类照片里出现大量“半干半湿”的过渡路面说明标注粒度比较粗后续训练时应考虑把这类样本单独分出来做验证或者先剔除否则模型很难收敛。抽看这个动作能避免一个很尴尬的开发事故交付模型时才发现验证集里标签本来就是错的精度上不去不是模型的问题是数据的问题。所以我一直把这步和统计分布并列成必做项宁可多花十分钟也不让坏标签在后边浪费你一整天。3. 训练路面分类模型ResNet50、数据加载和超参数配置3.1 模型选型为什么从ResNet50起步而不是直接上ViT路面分类是一个典型的细粒度图像分类任务干燥和湿滑的区分往往只靠反光、纹理和颜色差异。直接上Vision Transformer不是不行但在五万张这种中等规模数据上它的收敛速度和调参成本对新手并不友好。常规做法是从ResNet50起步预训练权重在ImageNet上已经学会了边缘、纹理、颜色等底层特征微调到路面场景只需要更新高层语义部分训练时间短效果可控。用PyTorch实现时我会在torchvision里加载带预训练权重的ResNet50然后把最后的全连接层替换成和类别数一致的输出import torch import torch.nn as nn from torchvision import models, transforms num_classes 5 # dry / wet / snow / ice / pothole model models.resnet50(weightsmodels.ResNet50_Weights.IMAGENET1K_V1) model.fc nn.Linear(model.fc.in_features, num_classes) transform_train transforms.Compose([ transforms.Resize((224, 224)), transforms.RandomHorizontalFlip(p0.5), transforms.RandomRotation(10), transforms.ColorJitter(brightness0.2, contrast0.2), transforms.ToTensor(), transforms.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]), ]) transform_val transforms.Compose([ transforms.Resize((224, 224)), transforms.ToTensor(), transforms.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]), ])训练集用随机水平翻转、旋转和颜色抖动来增强泛化性验证集不能做随机增强否则每次评估结果都会上下抖动没法横向对比实验。Resize到224乘224是ResNet系列的标准输入尺寸Normalize的均值和标准差直接用ImageNet的统计值不需要自己重新统计。ColorJitter里的brightness和contrast我一般不会调太大路面识别的核心特征就是颜色和纹理过强的颜色抖动反而会把wet和ice的差异抹平。提示torchvision版本较旧时预训练权重参数名可能是pretrainedTrue新版本里应使用weights两种写法不兼容跑之前先确认你本地的版本。3.2 数据加载与训练循环采样器、学习率和Loss的坑数据量五万张实际能用的训练集大概四万张出头用DataLoader批量喂给模型时num_workers这个参数会影响数据读取瓶颈。先给一个完整的训练骨架batch_size按你机器显存调整from torch.utils.data import DataLoader from torchvision.datasets import ImageFolder from torch.optim import AdamW import torch.nn as nn train_dataset ImageFolder(dataset/train, transformtransform_train) val_dataset ImageFolder(dataset/val, transformtransform_val) train_loader DataLoader(train_dataset, batch_size64, shuffleTrue, num_workers4, pin_memoryTrue) val_loader DataLoader(val_dataset, batch_size64, shuffleFalse, num_workers4, pin_memoryTrue) optimizer AdamW(model.parameters(), lr1e-4, weight_decay1e-2) scheduler torch.optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max30) criterion nn.CrossEntropyLoss() for epoch in range(30): model.train() total_loss, correct, total 0, 0, 0 for images, labels in train_loader: images, labels images.cuda(), labels.cuda() optimizer.zero_grad() outputs model(images) loss criterion(outputs, labels) loss.backward() optimizer.step() total_loss loss.item() * images.size(0) _, preds torch.max(outputs, 1) correct (preds labels).sum().item() total labels.size(0) scheduler.step() train_acc correct / total print(fEpoch {epoch1}: loss{total_loss/total:.4f}, acc{train_acc:.4f})几个参数按实际显卡调整batch_size为64时约占用10GB显存8GB显卡就降到32AdamW的默认学习率1e-4结合CosineAnnealingLR三十个epoch内通常能收敛到稳定水平。CrossEntropyLoss在类别不均衡时不会自动按样本数加权这一点和有些框架里的默认行为不一样如果类别数量差距悬殊参考第4章里的显式权重方案。有些人的翻车点在于不冻结骨干网络直接全量微调学习率又设得太高导致预训练权重被冲掉。我习惯把backbone的学习率调成分类头的十分之一做法是给不同参数组配不同lr。上面代码为了简洁没有做分层学习率但实际项目里我强烈建议加上尤其是数据量只有几万张时。3.3 保存checkpoint与早停策略别让最后一轮毁掉整个模型训练30轮下来最后一个epoch不一定是最优的可能在20轮就已经收敛、后面开始过拟合。我在训练循环里会同时跟踪验证集精度只在验证精度创新高时保存权重训练结束再加载最优权重用于评估而不是直接用最后一轮的结果best_val_acc 0.0 best_model_path best_resnet50_rgb.pth for epoch in range(30): # 训练循环结束后追加验证逻辑 model.eval() val_correct, val_total 0, 0 with torch.no_grad(): for images, labels in val_loader: images, labels images.cuda(), labels.cuda() outputs model(images) _, preds torch.max(outputs, 1) val_correct (preds labels).sum().item() val_total labels.size(0) val_acc val_correct / val_total print(fEpoch {epoch1} val_acc{val_acc:.4f}) if val_acc best_val_acc: best_val_acc val_acc torch.save(model.state_dict(), best_model_path) print(f保存最优模型: {best_model_path} (val_acc{val_acc:.4f}))这段代码的核心价值是避免“最后一次迭代的权重”代表不了“整个训练过程的最优”。保存时只存state_dict而不是整个模型对象文件更小加载时也需要先把模型结构定义出来再load_state_dict。如果你还想做早停可以加一个计数器连续5个epoch验证精度不上升就break能省下不少算力。4. 避坑指南标注噪声、类别不均衡和训练翻车实录4.1 类别严重不均衡精度虚高但实际不可用现象训练完看整体accuracy有96%觉得稳了结果打开混淆矩阵发现模型把所有图都预测成了dry其他类别几乎不被识别。原因dry类别占了近一半模型只要学会“蒙dry”就能拿到不错的整体准确率。交叉熵损失在样本量差距大的情况下很难充分约束模型关注少数类。解决第一步显式给少数类更高损失权重第二步对少数类做过采样。我动手前先跑分布统计脚本把每个类别的图片数量打到日志里按类别数量反比计算权重from sklearn.utils.class_weight import compute_class_weight import numpy as np labels [img[1] for img in train_dataset.samples] classes train_dataset.classes weights compute_class_weight(class_weightbalanced, classesnp.array(classes), ynp.array(labels)) class_weights torch.tensor(weights, dtypetorch.float32).cuda() criterion nn.CrossEntropyLoss(weightclass_weights)compute_class_weight的balanced模式按n_samples除以类别数和各类别样本数的乘积自动计算权重样本越少的类别权重越大。这一步能快速压低大类主导的问题但也不是万能。如果某个类别实在少到只有两三百张光调权重不够得配合过采样在DataLoader里让这个类别的样本重复出现常见做法是用WeightedRandomSampler按权重抽样。4.2 数据集里混入错误标签验证集精度上不去的元凶现象训练loss下降正常验证accuracy卡在88%左右换模型换学习率都无效损失曲线和精度曲线像两条不相关的线。原因标注文件里有错标或者某张图同时包含两种路面状态被随机归到一边。模型学到的真实分布和标注分布不一致验证集上那些错标样本永远被预测错精度被死死压住。解决把训练好的模型在验证集上跑一遍输出预测置信度和预测类别置信度最低的100张单独存文件夹人工过目import os import shutil import torch model.eval() low_conf_dir low_confidence_picks os.makedirs(low_conf_dir, exist_okTrue) conf_scores [] for idx, (images, labels) in enumerate(val_loader): images images.cuda() with torch.no_grad(): prob torch.softmax(model(images), dim1) conf, pred torch.max(prob, 1) for j in range(images.size(0)): sample_idx idx * images.size(0) j path val_dataset.samples[sample_idx][0] conf_scores.append((conf[j].item(), pred[j].item(), path)) conf_scores.sort(keylambda x: x[0]) for rank, (conf, pred, path) in enumerate(conf_scores[:100]): shutil.copy(path, os.path.join(low_conf_dir, frank{rank}_conf{conf:.2f}_pred{pred}.jpg))这里有个容易踩的细节val_loader的batch_size不为1时不能简单拿样本号去索引val_dataset.samples需要把batch内的坐标换算成全局索引。上面代码里用idx乘batch_size加j来算前提是最后一批没有丢样本。如果想省事直接把batch_size设为1代价是验证速度慢不少。看这100张图时重点不是找“模型认错的”而是找“人看着也犹豫的”。这类数据集里最大的标注噪声来自wet和ice因为湿沥青路面的反光和薄冰反光在单张图片上很难区分。遇到这种样本要么修正标签要么从验证集里挪走单独做边界分析。4.3 训练和验证的数据分布不一致现象训练集精度很快就到99%验证集始终在85%附近震荡两者差距越拉越大。原因做了数据增强的训练集和原始验证集分布不同这是正常现象。但差距过大的时候要怀疑数据处理流程里是不是有bug比如Resize不一致、Normalize参数用错、灰度图混进RGB通道。解决先检查数据加载时的transform是否一致再检查图像通道。路面图像数据里经常混入单通道灰度图RGB模型跑的时候会报错或自动复制三通道复制后的数值范围如果没归一化会引入大量异常样本。我习惯在数据加载器里显式统一转RGB三通道from PIL import Image import os def check_and_fix_image(img_path): img Image.open(img_path) if img.mode ! RGB: img img.convert(RGB) img.save(img_path) return img这个修正脚本建议在数据集预处理阶段跑一遍。黑白图和彩色图混在一起模型底层特征会被污染最终精度差一到两个百分点是常有的事。4.4 训练到一半显存溢出或数据加载卡死现象跑第10个epoch左右报CUDA out of memory或者DataLoader进度条卡在原地不动。原因显存溢出往往是batch_size太大、输入图片尺寸意外变成非224或者有超大图没有缩放数据卡死多半是num_workers设置过高在Windows上还可能是因为没有把训练代码放进ifname main保护里导致多进程反复启动。解决batch_size减半或者开启AMP混合精度训练num_workers在Windows上设为0Linux设4到8数据加载路径加上pin_memoryTrue配合cuda()。如果还卡就把数据集复制到本地固态硬盘不要在机械硬盘或网络挂载盘上直接训练否则每轮迭代都被IO拖死。用AMP的方式一般是torch.cuda.amp.autocast包住前向和loss计算再加GradScaler缩放梯度能节约近一半显存实测在同样batch_size下训练速度还更快。5. 进阶验证用混淆矩阵和分层指标判断模型真实水平到了这一步模型已经能跑到一个不错的loss和accuracy但你还不清楚它在每个类上表现如何更不知道它适合落地的场景边界在哪。这份数据集已经按类目分好文件夹拿到压缩包解压后就能直接套用上面的流程不用再花几周去收集和清洗路面图。我会把评估拆成三层整体精度、混淆矩阵、分场景抽样验证。第一层看整体精度简单直接但容易骗人。第二层看混淆矩阵能看出哪两类最容易互相搞混。路面分类里最常见的混淆是wet和ice因为它们都带反光颜色都偏灰偏冷仅凭单帧图像确实难分。如果混淆矩阵里这两类互相错认的比例超过10%你要考虑用多帧时序信息来辅助判断比如连续三帧里两帧都预测为ice再输出结果。from sklearn.metrics import confusion_matrix, classification_report import seaborn as sns import matplotlib.pyplot as plt all_preds, all_labels [], [] model.eval() with torch.no_grad(): for images, labels in val_loader: images images.cuda() outputs model(images) _, preds torch.max(outputs, 1) all_preds.extend(preds.cpu().numpy()) all_labels.extend(labels.numpy()) cm confusion_matrix(all_labels, all_preds) report classification_report(all_labels, all_preds, target_namesval_dataset.classes) print(report) plt.figure(figsize(8, 6)) sns.heatmap(cm, annotTrue, fmtd, cmapBlues, xticklabelsval_dataset.classes, yticklabelsval_dataset.classes) plt.xlabel(Predicted) plt.ylabel(True) plt.savefig(confusion_matrix.png, dpi150)classification_report分别给出precision、recall、F1和support这才是评估路面分类模型的正确视角。pothole这类破损路面如果recall只有70%意味着三分之一的坑洞没被认出来这在真实自动驾驶场景里不可接受。只看精准率不看召回率很容易漏报真实危险路段。第三层分场景抽样验证把验证集按图像亮度、对比度切几个子集分别测精度。比如模拟夜间低照度环境把图片亮度降到原来的0.5倍再测如果精度掉得厉害说明模型对光照敏感部署前要加入低照度训练样本。我习惯在训练结束后强制跑一遍这个流程不管是五万张的数据集还是自己攒的一千张私有数据。从那以后每次交付模型我都附上混淆矩阵和分层验证结果不再只丢一个accuracy数字。它能让人一眼看出模型在哪个场景能用、哪个场景不能这对自动驾驶这种安全敏感的落地场景尤其重要。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?