ARTICLE DETAIL

资讯详情

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

航拍路面病害检测数据集解析:VOC/YOLO双格式与YOLOv8训练实战

航拍路面病害检测数据集解析:VOC/YOLO双格式与YOLOv8训练实战 简介面向航拍路面病害检测与智慧交通场景这份数据集提供3302张现场图像的双格式标注包含Pascal VOC和YOLO两种格式覆盖横向裂缝、纵向裂缝、斜向裂缝、坑槽、修复区域等7类常见路面病害适合算法工程师、科研人员以及计算机视觉学习者用于目标检测模型训练、算法验证与精度对比。压缩包内共2000个文件以xml标注文件为主另有使用说明txt整体约166.21MB结构清晰可直接适配主流检测框架省去格式转换环节。目前已有1197人学习下载标注与图像一一对应类别划分明确可支撑裂缝识别、病害分类、区域定位等实验文件按统一编号命名便于脚本批量划分训练集与测试集双格式设计也能满足不同框架的输入要求。整体而言该数据集可降低路面病害样本采集与标注成本适合作为基准数据集开展模型效果评估与调优。1. 航拍裂缝检测为什么绕不开数据集3302张、VOC和YOLO双格式同时给航拍路面病害检测和普通目标检测最大的区别在于裂缝是极端细长的目标分辨率稍微不够模型就把裂缝和阴影、油渍混在一起。我做这类巡检项目时最头疼的不是模型选型而是标注数据——公开数据集里航拍视角的路面病害非常少自己标注一个类别两三百张图就要耗掉好几天。这份路面病害检测数据集解压之后是 3302 张 jpg每张图配一个 VOC 格式的 xml 和一个 YOLO 格式的 txt一共 7 个类别覆盖龟裂、纵裂、斜裂、横裂、坑洞和修补区域。对做道路巡检、无人机巡检、裂缝识别的人来说它最省事的地方在于两种标注格式同时给齐拿到手不用先写转换脚本直接划分训练集就能喂进 YOLOv8。不过拆完这份数据后我得提醒一句标注格式只是第一步类别命名里的坑才是真正的拦路虎。2. 拆开7z包看标注格式VOC XML与YOLO TXT的对应关系和转换脚本2.1 压缩包里的文件构成与命名规律刚解压完看到一串firc_lu_2945.xml这种名字时我先确认了文件清单避免训练到一半发现图片和标注对不上。这份资源里 jpg、xml、txt 三种文件是配套的firc_lu_2945.jpg对应firc_lu_2945.xml和firc_lu_2945.txt前缀firc_lu是采集批次编号后面是序号。用 7z 查看压缩包内部结构可以用这条命令7z l 航拍路面病害检测数据集VOCYOLO格式3302张7类别.7z | grep -E \.(jpg|xml|txt) | head -20grep过滤出三种标注文件head -20只看前 20 行用于确认命名规律。压缩包解压后的构成如下表我在拿到任何数据集时都会先做这一步确认数量等于声明值再往下走。文件类型数量说明jpg3302航拍原图分辨率不统一xml3302Pascal VOC 格式标注像素坐标txt3302YOLO 格式标注归一化坐标VOC 和 YOLO 两种格式差异很大VOC 的 XML 用肉眼就能读出目标和位置YOLO 的 TXT 只存数字 id 和归一化坐标。实际训练时大部分框架只认后者但排查问题时前者更直观。两者概念对照如下维度VOC XMLYOLO TXT坐标体系像素坐标 xmin/ymin/xmax/ymax归一化 x_center/y_center/w/h类别表示字符串名称数字 id可读性高打开就能看低必须配合类别清单训练入口需转换大多数框架直接支持2.2 解析VOC XML把像素坐标和类别名读出来我在正式处理数据前都会先写一个解析脚本把 XML 里的信息还原成结构化对象。Python 标准库的xml.etree.ElementTree就够用不需要装额外依赖import xml.etree.ElementTree as ET from pathlib import Path def parse_voc_xml(xml_path): tree ET.parse(xml_path) root tree.getroot() # filename 是Pascal VOC旧规范里记录的原图文件名 filename root.findtext(filename) # size 节点记录原图宽高转换YOLO坐标时必须要用 size root.find(size) width int(size.findtext(width)) height int(size.findtext(height)) objects [] # iter(object) 遍历所有标注目标一个目标就是一个病害框 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)) objects.append({ name: name, bbox: [xmin, ymin, xmax, ymax] # 像素坐标左上右下 }) return {filename: filename, width: width, height: height, objects: objects} if __name__ __main__: data parse_voc_xml(firc_lu_2945.xml) print(data[filename], data[width], data[height]) print(data[objects][:2]) # 先打印前两个目标确认结构这里返回的bbox是[xmin, ymin, xmax, ymax]顺序后续转 YOLO 时统一按这个顺序处理避免混淆。width和height必须从 XML 的size节点读取而不是自己猜测因为航拍图像尺寸不统一用错了会导致归一化坐标全部偏移。2.3 VOC转YOLO归一化坐标与类别id的换算YOLO 的 TXT 每行五个数字类别 id、中心点 x、中心点 y、宽度 w、高度 h全部除以原图宽高做归一化。这个换算公式每个字段都不能写错我见过很多次x_center和w搞混导致训练时框全部漂到图片角落的翻车def voc_bbox_to_yolo(size, bbox): size: (width, height) 原图尺寸 bbox: [xmin, ymin, xmax, ymax] 像素坐标 返回归一化后的 x_center, y_center, w, h img_w, img_h size xmin, ymin, xmax, ymax bbox # 中心点坐标是 xmin 和 xmax 的中点 x_center (xmin xmax) / 2.0 y_center (ymin ymax) / 2.0 # 宽高是最大值减最小值 w xmax - xmin h ymax - ymin # 除以原图宽高归一化到 [0, 1] return x_center / img_w, y_center / img_h, w / img_w, h / img_h归一化后的值落不到 0 到 1 之间就说明原图尺寸读错了。写进 TXT 时要保留 6 位小数精度不够会在小目标上产生几个像素的偏移。反向转换在校验时有用把归一化坐标还原成像素坐标可以和 VOC 原始标注做对比def yolo_bbox_to_voc(size, yolo_line): yolo_line: class_id x_center y_center w h 字符串 class_id, x_center, y_center, w, h map(float, yolo_line.split()) img_w, img_h size # 反推像素坐标注意这里还原的是浮点取整前先做边界保护 xmin (x_center - w / 2) * img_w ymin (y_center - h / 2) * img_h xmax (x_center w / 2) * img_w ymax (y_center h / 2) * img_h return int(class_id), int(xmin), int(ymin), int(xmax), int(ymax)反向转换主要用于抽查随机挑几张图转回 VOC 后用 OpenCV 画框对比原图一眼就能看出标注有没有错位。我在处理这份数据时抽了 5 张图做可视化框和裂缝轮廓基本贴合说明原始标注质量是靠谱的。2.4 双格式为什么都要留一份很多人在网上找数据集只要其中一个格式就觉得够用了但其实双格式各有用处。VOC 的 XML 是排查问题的入口某个类别的框太多或太少时可以直接读出来统计YOLO 的 TXT 是训练入口数据集作者把两份同时给齐意味着你可以跳过最容易被坑的格式转换环节。做自检时建议先跑一次文件对齐脚本确认三种文件的文件名集合完全一致再进入统计环节from pathlib import Path jpg_files {p.stem for p in Path(images).glob(*.jpg)} xml_files {p.stem for p in Path(labels_voc).glob(*.xml)} txt_files {p.stem for p in Path(labels_yolo).glob(*.txt)} # 差集为空才是正常状态任何多出来的或缺失的都要处理 print(jpg 有 xml 无:, jpg_files - xml_files) print(xml 有 jpg 无:, xml_files - jpg_files) print(jpg 有 txt 无:, jpg_files - txt_files)差集输出为空我才会继续做类别统计这一步花不了 10 秒但能避免后面训练时报一堆image not found。3. 七个类别怎么认清类别含义、分布统计与Obliquecrack合并3.1 七个类别的实际含义和判定边界数据集声明了 7 个类别实际看类别名称时会发现一个细节Oblique crack和Obliquecrack只差一个空格语义上是同一个东西——斜向裂缝但标注时被写成了两个名字。这种命名噪声在真实标注数据里非常常见不处理后训练类别数会变成 8而且两个类共享几乎一样的特征模型会被自己搞糊涂。最好先把每个类别的实际含义对齐类别名中文习惯叫法路面形态特征训练建议Alligator crack龟裂 / 鳄鱼纹裂网状多方向连通裂缝密集小目标建议用大分辨率训练小框特别多Longitudinal crack纵向裂缝沿行车方向近似直线宽高比极端注意 resize 时别把细线直接抹掉Oblique crack斜向裂缝与行车方向成夹角短而斜和 Obliquecrack 合并后使用Obliquecrack斜向裂缝拼写不一致与上一条完全相同必须映射回 Oblique crackPothole坑洞局部脱落凹陷块状形态差异大优先保证样本量Repair修补区域已修复的块状补丁严格说不是病害看项目需求决定是否保留Transverse crack横向裂缝垂直行车方向宽高比极端和纵向裂缝一样要注意 resizeRepair这个类别是最容易被忽略的它表示的是修补过的痕迹不是病害本身。如果你的项目只做病害检测应该把它当成背景过滤掉如果做的是路面养护评估它反而是重要信息。这个决定要提前做因为它直接影响训练时的类别数量。3.2 统计脚本样本量、框数和分辨率一起看拿到数据集先统计再训练是我一直保留的习惯。长尾分布严重的类别mAP 再高也有水分。下面的脚本用 Python 标准库配合collections.Counter统计每个类别的样本数、框数和图片分辨率分布import xml.etree.ElementTree as ET from collections import Counter from pathlib import Path xml_dir Path(labels_voc) # 三个计数器分别统计图片数量、目标框数量、图片分辨率 image_counter Counter() # 某类别出现在多少张图里 box_counter Counter() # 某类别总共有多少个框 resolution_counter Counter() # 图片分辨率分布 for xml_file in xml_dir.glob(*.xml): root ET.parse(xml_file).getroot() # 从 size 节点取宽高用于分辨率分布统计 w int(root.findtext(size/width)) h int(root.findtext(size/height)) resolution_counter[(w, h)] 1 # 用 set 避免同一张图里多个同类框被重复计入图片数 seen set() for obj in root.iter(object): name obj.findtext(name) box_counter[name] 1 seen.add(name) for name in seen: image_counter[name] 1 print(图片数统计:, dict(image_counter)) print(框数统计:, dict(box_counter)) print(常见分辨率:, resolution_counter.most_common(5))输出里最值得关注的是每个类别的box_counter。如果某个类别只有几十个框而其他类别上千训练时模型会对这个类别严重欠拟合。分辨率分布也要看如果图片尺寸跨度很大imgsz参数就要考虑用较大的值否则小图被放大后裂缝会糊成一片。统计完如果发现Obliquecrack和Oblique crack各占一部分就立刻做合并处理。3.3 合并Obliquecrack同步改XML和TXT才算干净合并类别不能只改 XML 里的名字TXT 里的数字 id 也必须同步重建否则训练框架读到的类别索引和你以为的不一样。我一般先确立统一的类别清单再分别改两种标注文件import xml.etree.ElementTree as ET from pathlib import Path # 先定义映射旧的错误拼写 - 统一后的正确名称 rename_map {Obliquecrack: Oblique crack} # 合并后的唯一类别列表按字母序排序保证 id 稳定 final_classes sorted([ Alligator crack, Longitudinal crack, Oblique crack, Pothole, Repair, Transverse crack ]) # 类别名 - 新 id 的映射YOLO txt 里的数字全靠这个 class_to_id {name: idx for idx, name in enumerate(final_classes)} xml_dir Path(labels_voc) txt_dir Path(labels_yolo) for xml_file in xml_dir.glob(*.xml): # 先把旧类别名数组读出来处理XML时重写 tree ET.parse(xml_file) root tree.getroot() for obj in root.iter(object): name_elem obj.find(name) old_name name_elem.text # 命中映射条件才替换不改动其他正常类别 if old_name in rename_map: name_elem.text rename_map[old_name] tree.write(xml_file, encodingutf-8, xml_declarationTrue) # 同步处理同名TXT按旧id转换到新id txt_file txt_dir / (xml_file.stem .txt) if not txt_file.exists(): continue # 旧类别清单必须和原始txt生成时的排序一致 old_classes sorted([ Alligator crack, Longitudinal crack, Oblique crack, Obliquecrack, Pothole, Repair, Transverse crack ]) new_lines [] for line in txt_file.read_text().strip().splitlines(): parts line.split() old_id int(parts[0]) old_name old_classes[old_id] # 经过rename后得到统一名称再查新id new_name rename_map.get(old_name, old_name) new_id class_to_id[new_name] # 保留原本的归一化坐标只替换类别id new_lines.append(f{new_id} .join(parts[1:])) txt_file.write_text(\n.join(new_lines) \n)处理完后再跑一次 3.2 的统计脚本确认类别数从 7 变 6、Obliquecrack完全消失才算干净。这个步骤是训练前的必经流程我自己在这个数据集上踩过坑第一次直接拿原始标注训练loss 降得挺快但验证集的混淆矩阵里斜向裂缝两个类互相串合并之后 mAP50 直接涨了快 3 个点。4. 喂给YOLOv8之前数据集切分、data.yaml与训练参数4.1 按图片切分train/val先定验证集再打乱切分数据集最怕的是同一张图既出现在训练集又出现在验证集还有同一段路连续拍摄的相似图被拆散。航拍数据往往存在大量相邻帧高度相似的图片如果不按场景切分而是按单图随机切验证集会严重高估模型表现。我一般按文件序号排序后每隔固定间隔抽一部分做验证集再打乱训练集顺序import random from pathlib import Path random.seed(42) # 固定随机种子保证每次复现结果一致 img_dir Path(images) imgs sorted(img_dir.glob(*.jpg)) # 按文件名排序让同类场景尽量聚到一起 val_ratio 0.15 val_num int(len(imgs) * val_ratio) # 从排好序的列表中按固定间隔抽取避免连拍帧全部进同一集合 val_set set(imgs[:: len(imgs) // val_num][:val_num]) train_list, val_list [], [] for img in imgs: txt img.with_suffix(.txt) if not txt.exists(): continue # 缺标注的图直接跳过不硬塞进训练集 # 写入绝对路径训练时方便定位文件 if img in val_set: val_list.append(str(img.resolve())) else: train_list.append(str(img.resolve())) Path(train.txt).write_text(\n.join(train_list)) Path(val.txt).write_text(\n.join(val_list)) print(ftrain: {len(train_list)}, val: {len(val_list)})imgs[:: len(imgs) // val_num]这个切片写法等价于等间隔抽样把连续帧尽量分开。切完之后要做一件事单独跑一次类别统计确认验证集里每个类别至少有一个框否则某个类的指标在验证时永远是 0。4.2 data.yaml配置类别顺序不能自己乱排YOLOv8 的data.yaml是训练入口的配置文件类别列表的顺序必须和 TXT 里的数字 id 完全一致。很多人会犯一个错误在names里按自己舒服的顺序排结果 id 对不上训练出来的模型类别全是错的。# 注意这个路径要改成你解压后实际的绝对路径 path: /home/user/datasets/rash_road train: train.txt # 用上一节生成的列表文件 val: val.txt # 类别数必须和 names 列表长度一致 nc: 6 # names 顺序必须严格按照合并后按字母序生成的 id # 0到5分别对应 names: 0: Alligator crack 1: Longitudinal crack 2: Oblique crack 3: Pothole 4: Repair 5: Transverse crack这份配置里的nc是 6因为第 3 章已经把Obliquecrack并进了Oblique crack。如果没做合并这里就是 7但两个近似类会互相干扰我不推荐这么干。path字段建议写绝对路径相对路径在换机器跑训练时经常翻车。4.3 训练命令与关键参数imgsz为什么要开1280裂缝是细长小目标用默认的imgsz640会丢细节。我一般在显存允许的情况下直接开到 1280让裂缝在缩小时保留更多像素信息。训练命令如下yolo detect train \ datadata.yaml \ modelyolov8n.pt \ epochs120 \ imgsz1280 \ batch8 \ patience20 \ seed42各参数的作用modelyolov8n.pt是加载 COCO 预训练权重n 是 nano 版本显存小的机器也能跑epochs120对中型数据集是起步值配合patience20表示连续 20 个 epoch 验证指标不提升就早停imgsz1280是这次训练最关键的参数裂缝的最小边可能只有十几个像素缩到 640 等于直接抹平batch8在 16G 显存上比较稳如果你的卡只有 8G建议降到 4。# 显存不够时用这个组合 yolo detect train \ datadata.yaml \ modelyolov8s.pt \ epochs120 \ imgsz960 \ batch4 \ patience20有人会纠结 YOLOv8 不是 anchor-free 吗锚框参数怎么调YOLOv8 用的是 DFL 形式的解耦检测头其实已经没有传统意义上手动设 anchor 的必要那些到处搜YOLOv8 anchor 怎么改的教程多半是拿旧版 YOLOv5 的经验硬套。真正影响裂缝检测的是imgsz和batch这两个没调对后面全是白费。4.4 训练日志判读loss曲线和mAP分别看什么训练时终端输出的box_loss、cls_loss、dfl_loss三条曲线要分开看box_loss下降说明框的位置在收敛cls_loss下降说明类别判断在变准dfl_loss是分布聚焦损失它不稳时框的边界会抖。如果box_loss下降而cls_loss居高不下九成是类别不平衡回去看 3.2 的统计结果。如果三条 loss 都在降但验证集 mAP 不动大概率是验证集和训练集分布差异过大检查切分逻辑。训练结束后用下面命令在验证集上做评估yolo detect val \ modelruns/detect/train/weights/best.pt \ datadata.yaml输出里的mAP50是 IoU 阈值 0.5 下的平均精度mAP50-95是 0.5 到 0.95 逐档平均。裂缝这种细长目标天然对 IoU 敏感如果mAP50有 70 但mAP50-95只有 35说明框的位置偏向抖考虑加大imgsz或检查有没有大量窄框没做裁剪。5. 避坑记录7z解压失败、类别翻倍与文件名对不齐5.1 7z解压工具版本和密码校验现象用老版本 7-Zip 或 Linux 自带的7z命令解压时提示数据错误或者解到一半就中断还有的人遇到输入密码是正确的但一直报错。原因这份资源是.7z格式旧版本 p7zip 对高压缩比 7z 文件支持不完整密码报错的情况多半是密码里带了不可见字符或者输入法在英文状态下被切成了全角。文件名里有中文时老版本解压还会出现乱码导致文件找不到。解决Windows 下用 7-Zip 19.0 以上版本右键解压Linux 下先升级 p7zip-full再执行解压# Ubuntu / Debian 更新 p7zip-full 后解压 sudo apt update sudo apt install -y p7zip-full 7z x 航拍路面病害检测数据集VOCYOLO格式3302张7类别.7z密码报错时先在记事本里把密码打出来确认没有多余空格再复制粘贴进解压工具。这是最常见的玄学问题十次里有八次是键盘布局或复制时带入了换行。5.2 类别拼写不一致导致训练类别翻倍现象训练启动时日志显示nc8明明声明是 7 个类别。原因Oblique crack和Obliquecrack只差一个空格转类别 id 的工具把它们当成两个独立类。如果你没做第 3 章的合并YOLO txt 里的 id 会多出一个。解决先跑类别统计确认差异再按 3.3 的脚本统一映射并重建 id。校验方法很简单重新统计后类别数必须是 6且混淆矩阵里不再出现两个斜裂缝类互相串扰。5.3 XML和JPG对不齐先跑文件清单比对现象训练中途报错FileNotFoundError或者验证集图片数量比预期少了几张。原因解压过程中文件丢失或者有人用脚本筛选图片时按扩展名误过滤了_开头的隐藏文件。这份数据里三种文件是一一对应的任何一方多出来或少了都会出问题。解决直接用第 2.4 节的对齐脚本跑一次差集输出的差集单独处理。如果是压缩包本身缺文件重新解压一次如果是个别文件确实损坏宁可把它从清单里删掉也不要带着残缺标注硬训练。5.4 bbox越界与窄框转换时要做clip现象可视化时发现某些框超出图片边界或者训练时 loss 正常但 mAP 一直卡在低位。原因标注工具的 bndbox 坐标在图片边缘时偶尔会写出一两个像素的越界值转换脚本没有做边界保护导致归一化坐标等于 1.0 甚至 1.02推理时这些框被浪费。解决写转换脚本时强制裁剪import xml.etree.ElementTree as ET def clamp_bbox(root, img_w, img_h): for obj in root.iter(object): bndbox obj.find(bndbox) xmin float(bndbox.findtext(xmin)) ymin float(bndbox.findtext(ymin)) xmax float(bndbox.findtext(xmax)) ymax float(bndbox.findtext(ymax)) # 拉回边界内同时保证最小宽高为1像素 xmin max(0, min(xmin, img_w - 1)) ymin max(0, min(ymin, img_h - 1)) xmax max(xmin 1, min(xmax, img_w - 1)) ymax max(ymin 1, min(ymax, img_h - 1)) bndbox.find(xmin).text str(int(xmin)) bndbox.find(ymin).text str(int(ymin)) bndbox.find(xmax).text str(int(xmax)) bndbox.find(ymax).text str(int(ymax))窄框的处理另说如果某个框的宽或高小于 8 像素先不要急着删去原图看一眼是不是标注漏了半段裂缝这种框对训练几乎没有正向帮助过滤掉通常更稳。5.5 Repair类别不是病害要不要排除现象模型在刚修补过的路面上频繁误报病害训练指标看着不错但实际巡检结果没法用。原因Repair在类别定义上是修补区域不是病害。航拍视角下新修补的沥青颜色和周边不一样模型学到的其实是颜色差异而不是病害纹理。如果你的业务目标是病害检测把它留在类别里反而误导模型。解决先用 3.2 的统计脚本看Repair的样本量和分布再决定是否保留。做病害检测时我一般直接把它从训练集里过滤类别数从 6 降到 5模型误报率明显下降做养护评估时保留但要在报告中单独说明该类的判定依据。6. 进阶验证bbox筛查脚本与mosaic增强把数据再榨一遍6.1 bbox异常筛查脚本训练前我会再跑一遍异常框筛查把无效框、越界框、极端长宽比框一次性找出来。这个脚本用整数坐标直接判断不依赖任何框架import xml.etree.ElementTree as ET from pathlib import Path xml_dir Path(labels_voc) for xml_file in xml_dir.glob(*.xml): root ET.parse(xml_file).getroot() w int(root.findtext(size/width)) h int(root.findtext(size/height)) 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)) # 无效框宽或高为负说明标注顺序写反了 if xmax xmin or ymax ymin: print(f[无效框] {xml_file.name} {name}) # 越界框坐标超出原图尺寸 if xmin 0 or ymin 0 or xmax w or ymax h: print(f[越界框] {xml_file.name} {name}) # 极窄框少于8像素基本是标注遗漏 if (xmax - xmin) 8 or (ymax - ymin) 8: print(f[极窄框] {xml_file.name} {name}) # 极端长宽比超过15倍大概率是线状裂缝被标注成整条断线 bw, bh xmax - xmin, ymax - ymin if bw / bh 15 or bh / bw 15: print(f[极端长宽比] {xml_file.name} {name})8 像素这个阈值是我在航拍路面场景里的经验值比它更小的框在 1280 分辨率下也只剩不到 10 个像素模型基本学不到有效特征。跑完后除了极端长宽比可以酌情保留其他两类建议都处理掉能省下不少无效训练时间。6.2 用增强参数验证小目标鲁棒性YOLOv8 默认开启 mosaic 增强把四张图拼成一张对小目标检测有明显帮助。航拍路面数据本来就有大量小裂缝这一步值得单独验证。在训练命令里显式控制增强强度yolo detect train \ datadata.yaml \ modelyolov8n.pt \ epochs120 \ imgsz1280 \ batch8 \ mosaic1.0 \ scale0.5 \ fliplr0.5 \ flipud0.5mosaic1.0表示每个 epoch 都启用四图拼接scale0.5限制缩放幅度避免裂缝被放太大或太模糊flipud0.5是上下翻转航拍图片没有上下概念这类数据很适合开。跑完对比不开增强的版本如果 mAP50 掉了反而说明原始数据分布已经不错增强过猛破坏了原有分布。验证时我习惯把best.pt和last.pt都留一份用 best 跑推理、用 last 回放训练后期状态。推理时对细长目标把 NMS 的conf阈值调到 0.15 左右再看一次结果很多裂缝框被默认阈值过滤得太狠——这个技巧救过我不少次。每次拿到新的标注数据集我现在都强制自己走完这一套文件名对齐、类别统计、类别合并、bbox 筛查四个脚本全过才敢启动训练。这套流程帮我躲过了至少三次训练到一半发现类别数量不对的翻车也希望帮到你。本文还有配套的精品资源点击获取
返回列表