news 2026/9/28 15:47:41

雨雪路面数据集:结冰湿滑识别与YOLOv8训练实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
雨雪路面数据集:结冰湿滑识别与YOLOv8训练实战

简介:面向自动驾驶、智能交通与路面状态监测场景,这份雨雪天气路面状况数据集提供了结冰路面、雪地、下雨湿滑、干燥路面四种典型状况的原始图片,并配套VOC格式XML标注文件,适合用于目标检测、图像分类等模型的训练与评测。压缩包共1294个文件,包含646张jpg原图与646个xml标注文件,另有2个txt说明文件,包体总大小27.09MB。数据图片均保持原始拍摄状态,未做额外处理,便于研究者直接用于模型训练和算法验证。目前已有823人学习下载,适用于计算机视觉初学者熟悉VOC标注流程,也适合工程师快速构建路面状态识别原型系统。通过该数据集可完成从数据整理、标注解析到模型训练验证的完整闭环,为恶劣天气下的行车安全预警提供基础数据支撑。

1. 雨雪天气路面状况数据集是什么:它为结冰与湿滑识别补上了哪块缺口

在辅助驾驶与道路结冰预警这两类场景里,干燥路面的检测相对成熟,真正让模型整体翻车的是结冰路面、雪地和下雨湿滑这三类带“水”的状态。这个雨雪天气路面状况数据集正好补上这处数据缺口:四类路面状态统一采用原始图片,没有合成纹理和平台水印,并用VOC格式给出目标框标注,能直接对接YOLO系检测训练。适合它的读者很具体——做路面状态识别、道路湿滑预警、辅助驾驶感知算法的一线工程师,以及需要在自采数据上复现同类流程的团队。下面按我处理多个同类数据集的习惯,从解析VOC标注讲到训练、避坑和验证。

2. 解析voc标记的标注逻辑:从BBox分布到YOLO训练格式

2.1 用标准库脚本扫描XML,统计四类的框尺寸与长宽比

拿到任何VOC格式数据集,我的第一个动作是扫描标注文件,而不是直接配训练脚本。原因很简单:标注习惯会直接影响输入分辨率和模型度量,特别是路面数据集,干燥路面通常是覆盖整条路的大框,冰面可能只框住桥墩阴影下一小块,长宽比分布跨度极大。不先统计就训练,后面会在imgsz设置上反复试错,纯属浪费时间。

import os import xml.etree.ElementTree as ET from collections import defaultdict xml_dir = "./Annotations" stats = defaultdict(lambda: {"count": 0, "w": [], "h": [], "ratio": []}) for fname in os.listdir(xml_dir): if not fname.endswith(".xml"): continue tree = ET.parse(os.path.join(xml_dir, fname)) root = tree.getroot() for obj in root.iter("object"): name = obj.findtext("name") bndbox = obj.find("bndbox") xmin = float(bndbox.findtext("xmin")) ymin = float(bndbox.findtext("ymin")) xmax = float(bndbox.findtext("xmax")) ymax = float(bndbox.findtext("ymax")) w = xmax - xmin h = ymax - ymin stats[name]["count"] += 1 stats[name]["w"].append(w) stats[name]["h"].append(h) stats[name]["ratio"].append(w / h) for name, s in stats.items(): avg_w = sum(s["w"]) / len(s["w"]) avg_h = sum(s["h"]) / len(s["h"]) avg_ratio = sum(s["ratio"]) / len(s["ratio"]) print(f"{name}: 框数={s['count']}, 平均宽={avg_w:.1f}, 平均高={avg_h:.1f}, 平均宽高比={avg_ratio:.2f}")

这段脚本用Python标准库里的xml.etree.ElementTree遍历每个标注文件,把object节点下的name和bndbox取出来,按类别记录宽、高和宽高比。输出的数字直接告诉你四类框的形态差异:如果dry平均宽高比在3:1以上,说明标注的是整条道路;如果icy平均宽高比接近1:1,说明标注的是局部冰面区域。看到这个分布后再决定输入分辨率,而不是盲目套640×640。

参数上要注意,xml.etree.ElementTree解析几千张文件没问题,数据集超过2万张时建议换成lxml,接口一致但速度快很多。avg_ratio这个值还影响后面的训练策略:框长宽比集中在2到4之间,输入尺寸设成640×384或768×448这种长方形,比正方形更适合路面检测。我处理过的路面数据集中,结冰路面经常只出现在一处,同一张图里干燥路面和结冰区域并存,标注框形态差异非常大,这一步统计能帮你提前发现这个问题。

2.2 VOC转YOLO坐标时最容易翻车的三个点

路面数据集的落地路径目前最成熟的是YOLO系列,但YOLO不读XML标注。转换这一步是所有后续流程的地基,它决定了模型学到的是正确路面位置还是噪声。我一般这样处理:

import os import cv2 import xml.etree.ElementTree as ET class_names = ["icy", "snow", "wet", "dry"] # 顺序必须与训练配置一致 def voc_to_yolo(xml_path, img_path, out_txt_path): img = cv2.imread(img_path) img_h, img_w = img.shape[:2] tree = ET.parse(xml_path) root = tree.getroot() lines = [] for obj in root.iter("object"): name = obj.findtext("name") if name not in class_names: continue cls_id = class_names.index(name) bndbox = obj.find("bndbox") xmin = float(bndbox.findtext("xmin")) ymin = float(bndbox.findtext("ymin")) xmax = float(bndbox.findtext("xmax")) ymax = float(bndbox.findtext("ymax")) x_center = (xmin + xmax) / 2.0 / img_w y_center = (ymin + ymax) / 2.0 / img_h width = (xmax - xmin) / img_w height = (ymax - ymin) / img_h # 防止标注越界导致训练报错 x_center = max(0.0, min(1.0, x_center)) y_center = max(0.0, min(1.0, y_center)) width = max(0.0, min(1.0, width)) height = max(0.0, min(1.0, height)) lines.append(f"{cls_id} {x_center:.6f} {y_center:.6f} {width:.6f} {height:.6f}") with open(out_txt_path, "w", encoding="utf-8") as f: f.write("\n".join(lines)) voc_dir = "./Annotations" yolo_dir = "./labels" img_dir = "./JPEGImages" os.makedirs(yolo_dir, exist_ok=True) for xml_name in os.listdir(voc_dir): if not xml_name.endswith(".xml"): continue stem = os.path.splitext(xml_name)[0] xml_path = os.path.join(voc_dir, xml_name) img_path = os.path.join(img_dir, stem + ".jpg") out_txt = os.path.join(yolo_dir, stem + ".txt") if not os.path.exists(img_path): print(f"跳过缺少图片的标注: {xml_name}") continue voc_to_yolo(xml_path, img_path, out_txt)

逻辑说明:脚本把VOC里的像素坐标除以图片的实际宽高,得到0到1之间的归一化值,这是YOLO训练能直接读的格式。这里我坚持用cv2.imread读图后的shape来取宽高,而不是信XML里size节点记录的值。原因在路面数据集中很常见:部分图片被第三方工具压缩过,或EXIF方向旋转后长宽互换,XML里的宽高和实际像素对不上。用实际读图值,坐标转换的误差最小。

参数说明:class_names列表的排列顺序就是训练时模型输出层的类别顺序,后面road.yaml里names也要按这个顺序写,错一位就全错。代码里加的四行clip是防越界的,雨雪天气的标注偶尔会把框画到图像边缘外,YOLO遇到负坐标或大于1的坐标会直接报错,极端情况下loss计算会出NaN。如果你发现转换后大量框被截断,说明原始标注质量有问题,要回去人工核对,而不是靠clip掩盖。

这里有个血泪经验:XML里的filename字段不可全信。有次数据集的filename写的是相对路径,还有含空格的字符,os.path.join拼出来的路径直接不存在。所以我加了图片存在性检查,缺失的单独记日志排查,而不是让整个转换流程崩在中间。Windows用户还要注意,train.txt和val.txt的编码必须统一用UTF-8无BOM,否则用记事本保存一次就可能在训练时读出一堆乱码路径。

2.3 按路段分组切分训练与验证,防止同源帧泄露

路面数据集的采集方式决定了它的一个特性:同一路段连续拍摄的几十帧高度相似。如果训练集和验证集随机打乱,这些相似帧两边都有,模型相当于提前见过验证集答案,mAP虚高,真实换路测试立刻打回原形。

import os import random from collections import defaultdict img_dir = "./JPEGImages" grouped = defaultdict(list) for fname in os.listdir(img_dir): # 假设文件名形如: 路段编号_时间戳.jpg,取前缀做分组 group_key = fname.split("_")[0] grouped[group_key].append(fname) groups = list(grouped.keys()) random.shuffle(groups) val_size = max(1, int(len(groups) * 0.2)) val_groups = set(groups[:val_size]) train_groups = set(groups[val_size:]) train_txt = open("./train.txt", "w") val_txt = open("./val.txt", "w") for g, fnames in grouped.items(): for f in fnames: img_path = os.path.join(img_dir, f) if g in val_groups: val_txt.write(img_path + "\n") else: train_txt.write(img_path + "\n") train_txt.close() val_txt.close()

逻辑说明:代码按文件名里的路段字段分组,同一路段所有帧只会被整体分到训练集或验证集,不会两边同时出现。这样验证集换到一条新路段时,模型的表现才接近真实部署。train.txt和val.txt是图片路径列表,YOLO的数据配置可以直接读。

参数说明:val_size取20%是检测任务里的常见比例,如果你的数据集中结冰或雪地样本特别少,建议先按路段分组后统计每组的类别分布,再做分层抽组,保证验证集里四类都有一定比例。group_key用split("_")[0]只是示例,实际命名可能是连字符或日期前缀,先看文件名再定规则。

这一小节核心一句话:检查文件名、按场景分组、再随机切分。这个细节决定你评估出的mAP是真实水平还是自欺欺人。

3. 用YOLOv8训练自己的路面数据集:从目录布置到参数选择

3.1 为什么用检测而不是分类:结冰区域定位才是业务要的

有人把路面状态当成整图分类问题,输入ResNet输出四类概率。这个方案在“画面里只有一种路面”的截图场景勉强能用,但真实行车记录仪画面经常是干燥路面占七成、前方桥面有一片结冰。分类网络只能说整张图“大概率有冰”,没法告诉下游系统冰面在哪个位置,辅助驾驶的预警决策根本用不上这种结果。分割网络能做像素级定位,但像素级路面标注成本高,训练开销也大,对一个只需要触发“前方结冰”预警的项目来说投入产出比太低。检测框架是中间最优解——用边界框把异常路面区域定位出来,标注时只需要画框,采集这套VOC标注的经济性也体现在这里。

我在几个路面状态识别项目里都采用YOLOv8做检测,模型结构稳定,训练命令简单,换数据集成本低。从路面检测的实际需求来说,类别间纹理差异细微,但边界框尺寸大,YOLOV8的多尺度特征融合对这类场景天生适配。你要处理的是大尺寸目标和细微的纹理差异,这正好是检测头的强项,而不是抠图或语义分割的强项。

3.2 按ultralytics的结构整理数据集:目录、软链接和data.yaml

用ultralytics跑YOLOv8,目录结构必须严格按它的约定布置,否则训练时标签和图片匹配不上。很多新手在这一节就开始迷路。其实只要四样东西:图片、标签、训练列表、验证列表。

for split in train val; do mkdir -p dataset_root/images/$split mkdir -p dataset_root/labels/$split while read img_path; do fname=$(basename "$img_path") stem="${fname%.jpg}" cp "$img_path" "dataset_root/images/$split/$fname" cp "labels/$stem.txt" "dataset_root/labels/$split/$stem.txt" done < "$split.txt" done

逻辑说明:脚本按train.txt和val.txt里的图片路径,把图片和对应txt标签分别复制到images/train、images/val、labels/train、labels/val四个目录。YOLO找标签的逻辑是:给定一张图在images/train下的路径,把路径里的images换成labels,后缀换成txt,同名匹配。所以目录名必须严格是images和labels,多一层少一层都没用。

参数说明:这里用的是cp复制,如果原始图片单张有几十MB,建议改成ln -s建软链接,能省一半磁盘空间和大量I/O时间。stem变量是从图片名去掉.jpg后得到的名称主体,如果数据集里有.png或.jpeg后缀,需要相应调整。

然后写数据集配置文件road.yaml:

path: dataset_root # 数据集根目录 train: images/train # 训练图片相对路径 val: images/val # 验证图片相对路径 names: 0: icy 1: snow 2: wet 3: dry

逻辑说明:names的列表顺序必须和第2章转换脚本里的class_names保持一致。yaml里的train和val是相对path的相对路径,字符串里不需要开头的斜杠。YOLOv8会自动生成anchor或者使用anchor-free策略,不需要在yaml里手写anchor尺寸,这是它比YOLOv5省心的一个点。

如果你做的是旋转目标检测,比如车载前视摄像头拍到的倾斜道路区域,也可以考虑mmrotate训练DOTA格式数据,但这类雨雪路面数据集绝大多数是水平的矩形框,YOLOv8足够,不需要引入旋转框的复杂度。

3.3 四个必调参数:epochs、imgsz、batch和样本重采样

训练命令很简单,难点在参数选择。以下几个参数直接影响路面数据的收敛效果,每个都值得单独说。

# 基础训练命令 yolo detect train data=road.yaml model=yolov8m.pt epochs=100 imgsz=640 batch=16

epochs。路面数据集普遍依靠人工采集,总体样本量不大,每类几百到几千张之间。我习惯先跑100个epoch看收敛曲线,再决定是否延长。训练集不足两千张时,盲目跑300个epoch会过拟合到雨雪纹理上,验证集mAP反而下降。保存的best.pt在哪一个epoch出现,是判断该不该加时的直接依据。

imgsz。这是最容易被忽略的参数。如果第2章统计出的框平均宽高比在3:1到5:1,设成正方形640会浪费大量像素。处理方法是用长方形输入,640×384或768×448配合YOLOv8的rect模式,模型会自动按图片比例分组padding,训练速度更快,结冰和湿滑这种纹理差本来就小的类别也更容易区分。记住,输入分辨率要匹配你数据里bbox的几何形态,而不是匹配自己的经验值。

batch。batch太小,BN层统计量不稳定,路面光照变化本来就剧烈,模型会学得很飘。太大又撑爆显存。常见做法是batch=16起步,观察GPU显存占用后调整。如果batch=8还跑不动,优先降imgsz而不是继续调小batch,因为过小的batch在路面这类弱纹理任务上表现很不稳定。

样本重采样。雨雪天气数据集中干燥路面样本往往占绝大多数,不处理的话模型会偏向把一切路面判成干燥,结冰和雪地几乎不输出。先用小脚本统计标签分布:

from collections import Counter counter = Counter() for img_path in open("train.txt"): img_path = img_path.strip() label_path = img_path.replace("/images/", "/labels/").replace(".jpg", ".txt") try: with open(label_path) as f: for line in f: cls_id = int(line.split()[0]) counter[cls_id] += 1 except FileNotFoundError: print("缺失标签:", label_path) print(counter)

逻辑说明:统计train.txt里每张图的标签文件,按类别ID累加框数,输出分布后能直观看到哪类样本被淹没。这一步属于训练前的体检,解决类别不平衡问题最直接的手段不是改loss函数,而是重采样训练列表。

参数说明:如果输出显示dry有5000个框,icy只有400个,把train.txt中含icy图片的路径复制两遍或三遍,另存为train_balanced.txt,再在road.yaml里把train字段改成train_balanced.txt即可。重采样后要盯住验证集精度,稀少类样本被重复采样超过三轮时容易过拟合,合并在验证集上重新评估后及时调整拷贝次数。

4. 雨雪路面数据集训练的5个常见坑:从zip伪加密到类别淹没

4.1 zip伪加密:解压时弹出密码框,但数据集根本没设密码

现象:在Windows下右键解压zip,弹出“需要密码”对话框,找数据发布者确认,对方却说没设置任何密码。

原因:部分打包工具写入的是伪加密标志。ZIP文件头的General Purpose Bit Flag第0位如果被置1,解压软件会认为文件加密,但实际上文件内容并未加密处理,这种就是所谓的zip伪加密。

解决:改用7-Zip打开,很多情况下7-Zip能识别伪加密并直接列出文件内容,这时正常解压即可。也可以在解压工具里选择忽略密码报错继续释放文件。需要说明的是,不要用所谓密码移除工具去暴力破解,这既慢又没必要,伪加密只是标志位问题,不是真加密。

4.2 转换脚本报“找不到图片”,卡在filename字段和中文路径上

现象:第2章的转换脚本运行时,大量XML日志打印“缺少图片”,程序跑完但生成的标签文件少一半。

原因:VOC标注里的filename字段有时write了相对路径、完整磁盘路径,甚至带前导空格,直接拿去拼路径就对不上。Windows下还有一个问题是zip解压到含中文的目录后,训练脚本读路径时编码不一致,报错或静默跳过文件。

解决:不要信XML里的filename字段,改成遍历images目录,用文件名主干建立索引后再做转换。Windows解压时把zip解到纯英文路径,目录名不要带中文和空格,从源头绕开编码问题。这一步处理完,转换脚本的日志输出量会明显减少。

4.3 wet和dry在中低光照下互相混淆

现象:白天验证集上wet和dry的mAP都正常,换上夜间真实路段实测,大量干燥路面被判成下雨湿滑,误报率明显偏高。

原因:夜间的干燥路面在路灯照射下有反光带,视觉纹理和下雨湿滑状态高度相似。训练集里的wet样本大部分来自白天雨天,夜间样本占比太少,模型学到的是“暗背景下的反光”而非真正的湿滑特征。

解决:给wet类补充夜间雨后的数据,或者对wet类训练样本做随机曝光扰动增强,模拟傍晚和夜间光照后再训练。验证时不要只看整体mAP,把验证集按白天和夜间拆开,分别输出wet的混淆矩阵,能暴露单一指标掩饰的问题。

4.4 干燥路面样本过多,模型成了“一切皆干”

现象:训练loss正常下降,但验证输出里dry框占了95%以上,icy和snow几乎没有输出,mAP曲线在低位徘徊。

原因:多数路面数据集按自然分布采集,干燥天气的样本天然远超冰雪天气。模型在类别权重上大幅偏向多数的干燥类。

解决:按第3.3节的统计脚本看一下各类框数,如果dry占比超过70%,就做样本重采样,把含icy、snow的图片路径复制两到三遍追加到新的train列表里,让模型在一个epoch内均匀看到这四类。同时你可以把置信度阈值提高到0.5来过滤掉大量低分的干燥框,但不能只靠阈值解决问题,根子还是在数据分布上。重采样后要监控验证集mAP,防止少数类过拟合。

4.5 原始图片分辨率太大,预处理阶段直接拖垮内存

现象:训练启动后CPU占用率打满,内存持续上涨,卡在第一个epoch迟迟不结束,严重时机器直接OOM。

原因:这个数据集的图片采用原始图片,可能来自行车记录仪或运动相机,单张分辨率达到4K级别。YOLO预处理时要逐张读整图再缩放,高分辨率图配合多进程DataLoader,内存很快吃满。

解决:先做一次批处理缩放,把长边压到2000像素内再用缩略图训练,同时保留原图用于最后的路测推理验证。

import os import cv2 src_dir = "images" dst_dir = "images_work" os.makedirs(dst_dir, exist_ok=True) for fname in os.listdir(src_dir): img = cv2.imread(os.path.join(src_dir, fname)) h, w = img.shape[:2] scale = min(1.0, 2000.0 / max(h, w)) if scale < 1.0: new_w, new_h = int(w * scale), int(h * scale) resized = cv2.resize(img, (new_w, new_h), interpolation=cv2.INTER_AREA) else: resized = img cv2.imwrite(os.path.join(dst_dir, fname), resized, [cv2.IMWRITE_JPEG_QUALITY, 90])

逻辑说明:逐张读取图片,判断长边是否超过2000,超过的等比缩放,用INTER_AREA插值保证缩小后纹理不过度模糊。缩放后的图在训练时能明显降低预处理耗时,内存占用也回到正常范围。

参数说明:长边阈值2000是经验值,配合YOLOv8的imgsz=640输入,训练阶段没有信息损失风险。JPEG质量设90,比默认95小一点但对模型训练影响不大,磁盘占用却能少不少。缩放后的数据集单独放一个目录,原图不要覆盖,后面做路测推理或微调还能用上。

5. 路面模型的验证不能只看mAP:混淆矩阵和落帧测试

5.1 用验证集输出混淆矩阵,盯着wet和icy这对易混类别

训练完成后,大部分人只看mAP就收工,但在路面状态检测里这远远不够。mAP是一个平均值,它会把wet和icy之间的严重混淆平均掉。我每次训练完第一件事是跑验证并拉出混淆矩阵。

yolo detect val data=road.yaml model=runs/detect/train/weights/best.pt plots=True

这里plots=True会在输出目录生成confusion_matrix.png和confusion_matrix_normalized.png两张图。看的时候重点看矩阵的非对角线区域:如果真实标签为icy的样本被大量预测为wet,或者wet被预测为dry,说明模型在两类之间没有找到稳定边界。噪声的表现形式往往是某两类的混淆在白天和夜间都稳定存在,而不是随机误差。真实结冰和湿滑在视觉上确实容易混淆,但这张矩阵至少能告诉你混淆比例高到哪一步、该不该补数据。

如果是用mmrotate或旋转框模型训练的场景,混淆矩阵的查看方式类似,区别在于评测脚本要同时计算旋转框的IoU,混淆程度会比水平框更严重,但路面数据集中这种需求不常见。

5.2 落帧测试:一帧一帧看类别跳变,而不是只看短视频结论

验证集mAP再高,也替代不了对实际视频流的测试。路面状态在物理世界中不会在一两秒内反复横跳,但模型单帧输出很容易出现上一帧wet、下一帧dry、再下一帧wet的抖动。我习惯把一段真实路测视频跑一遍,统计每类在多少帧中出现,同时观察类别切换频率。

from ultralytics import YOLO import cv2 model = YOLO("runs/detect/train/weights/best.pt") cap = cv2.VideoCapture("night_road.mp4") class_counter = {"icy": 0, "snow": 0, "wet": 0, "dry": 0} frames = 200 for i in range(frames): ret, frame = cap.read() if not ret: break results = model.predict(frame, conf=0.4, verbose=False) present = set() for r in results: for box in r.boxes: cls_id = int(box.cls[0]) present.add(model.names[cls_id]) for c in class_counter: if c in present: class_counter[c] += 1 print(class_counter)

逻辑说明:逐帧读取200帧测试视频,每帧做一次推理,把出现的类别记入计数器。输出结果是每个类别在多少帧中出现过。如果icy出现了120帧但分散成每两三帧一次断断续续的片段,那就是典型的帧间不稳定。

参数说明:conf=0.4是推理置信度阈值,比训练时的验证阈值略低一点,目的是在不漏检的前提下暴露单帧预测的抖动,这正好是下一章帧融合要解决的问题。

5.3 部署时的阈值取舍:conf_thres按场景分开设

部署推理时的conf阈值不要全场景套一个值,路面状态的四类在风险等级上完全不同。

场景类别置信度阈值建议理由
结冰预警0.3到0.4漏报结冰的代价远高于误报,宁可多触发后用帧融合过滤
雪地0.4到0.5雪地视觉特征相对清晰,阈值中等即可
下雨湿滑0.4到0.5容易和干燥夜间混淆,不宜太高
干燥路面0.6到0.7属于背景常规状态,不需要高召回

NMS的IoU阈值一般保持默认0.5到0.6即可,雨水天里框之间有重叠是正常的,框重叠过多时才需要适当调高到0.7。路面检测没有特别复杂的后处理逻辑,阈值这块的核心思想是:高风险类别宁可误报,低风险背景类别宁可漏报,加权之后再做多帧决策才合理。

6. 连续帧窗口投票:让结冰检测结果不再一闪而过

单帧模型最烦人的问题就是状态跳变,前一帧结冰、后一帧消失、第三帧又出现。物理世界中,路面结冰不会在几百毫秒内凭空出现又消失,这种跳变纯粹是模型单帧预测的不稳定。解决办法是加一个多帧缓冲区,用最近几帧的结果投票决定最终输出。

class RoadStateBuffer: def __init__(self, size=5, min_votes=3, conf_thr=0.5): self.size = size self.min_votes = min_votes self.conf_thr = conf_thr self.history = [] def update(self, frame_result): # frame_result: {"icy": 0.82, "wet": 0.45, "dry": 0.12} self.history.append(frame_result) if len(self.history) > self.size: self.history.pop(0) votes = {} for h in self.history: for cls, conf in h.items(): if conf >= self.conf_thr: votes[cls] = votes.get(cls, 0) + 1 return [c for c, v in votes.items() if v >= self.min_votes]

逻辑说明:线程每帧更新一次,维护最近5帧的类别置信度列表。只有某一类在最近5帧里至少被投票3次才输出,这样偶尔的单帧误检会被周围帧拉平。代码返回的是通过投票的路面状态列表,可直接交给预警模块。

参数说明:size=5帧对应在25帧每秒视频里约0.2秒的观察窗口,min_votes=3意味着需要连续0.12秒的稳定判断才触发,这个量级对结冰预警足够灵敏。如果实际场景对误报容忍度更低,可以调成size=7、min_votes=5,代价是响应时间变长。conf_thr建议和部署阈值保持一致,比如结冰类设0.35,干燥类设0.65。

我做这类路面识别项目时,习惯把单帧模型和帧融合当作一个整体去调参,而不是分开调。因为连续帧决策才是部署时真正交付给驾驶系统的结果,单帧mAP只是中间指标。帧融合的窗口长度、置信度阈值和单帧模型本身一起调优,才能找到误报率与响应速度之间的平衡点。

希望这些处理雨雪天气路面数据的经验能帮到你。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/28 15:46:40

STM32 HAL库驱动MAX30102心率血氧传感器完整教程

作为一个经常折腾STM32的嵌入式爱好者&#xff0c;今天想和你分享一个我最近调试通过的项目——用STM32的HAL库驱动MAX30102心率血氧传感器。这颗传感器在可穿戴设备里非常常见&#xff0c;像是手环、指夹式血氧仪基本都用它。网上关于它的资料不少&#xff0c;但大多是寄存器版…

作者头像 李华
网站建设 2026/9/28 15:46:10

多模态视频理解如何量化视频叙事节奏——CaelisVideo原理与实战

先说结论&#xff1a;CaelisVideo不是什么“票房预测神器”&#xff0c;也不是给视频打分的玄学工具。它本质上是一套带反馈闭环的多模态视频理解系统&#xff0c;专门用来回答一个问题——一个视频到底靠什么让人从头看到尾&#xff1f;这个“什么”&#xff0c;落到工程上&am…

作者头像 李华
网站建设 2026/9/28 15:46:08

C++哈希表从原理到手写实现:彻底看懂unordered_map的O(1)

哈希表&#xff08;Hash Table&#xff09;在C里的存在感很不均衡&#xff1a;刚学的时候觉得它就是个std::unordered_map&#xff0c;会用就行&#xff1b;等真正遇到性能问题&#xff0c;或者被面试官问一句“为什么unordered_map查找是O(1)”&#xff0c;很多人一下子就卡住…

作者头像 李华
网站建设 2026/9/28 15:46:06

AI本地部署、Agent工程化与AI短剧制作:2026年AI落地实战全解析

1. 今日AI速览&#xff1a;2026年9月19日&#xff0c;圈内人都在聊什么今天早上打开工作群&#xff0c;发现大家转得最多的一条是关于AI编程工具链的实测对比。看起来今年下半年的主线任务已经相当清晰&#xff1a;能落地的AI大模型、能进产线的AI Agent、能直接出片的AI视频工…

作者头像 李华
网站建设 2026/9/28 15:46:05

AD转OrCAD完整实操指南:原理图迁移、封装修复与踩坑速查

做硬件的老哥们应该都遇到过这种尴尬&#xff1a;手头有一套完整的Altium Designer工程&#xff0c;原理图、封装、网络表调得明明白白&#xff0c;结果客户或者合作工厂那边只认OrCAD Capture&#xff0c;要么就是公司并购、部门整合&#xff0c;整个团队从AD切到Cadence平台&…

作者头像 李华
网站建设 2026/9/28 15:46:02

舵机串联设计如何让ALPHA 1Pro跳出灵动舞步

很多人第一眼看到优必选ALPHA 1Pro跳舞的视频&#xff0c;第一反应都是“这玩意儿怎么这么灵活”&#xff0c;第二反应才是“我能不能也搞一台研究研究”。作为一台面向入门级用户和创客群体的双足人形机器人&#xff0c;ALPHA 1Pro最值得琢磨的地方并不是它用了多高级的AI算法…

作者头像 李华