news 2026/10/11 22:47:04

沥青路面缺陷检测:labelme转YOLO全流程与避坑指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
沥青路面缺陷检测:labelme转YOLO全流程与避坑指南

简介:面向智慧交通与道路养护领域的算法工程师、科研人员,这份沥青路面缺陷目标检测数据集可支撑缺陷检测模型的训练与评估。针对当前普遍存在的标注数据稀缺、缺陷类别不均衡等问题,数据集中包含裂缝、裂缝修补、坑洞、坑洞修补、井盖及其他共六类关键目标,第一部分提供两千个JSON标注文件,对应两千张路面图像的标注信息,全部六千张完整数据可进一步扩展使用。压缩包采用7z格式,整体大小为五百六十三点三MB,文件采用数字编号命名,便于与原始图像一一配对。资源目前已有三百八十五人学习,适合需要高质量道路缺陷标注数据的算法研发场景,可帮助研究人员缓解数据瓶颈,提升模型在真实道路环境下的泛化能力,也为智慧交通与道路养护应用提供了可直接使用的数据基础。

1. 沥青路面缺陷目标检测数据集到手:6000张labelme标注图能直接开训吗

拿到一套6000张的沥青路面缺陷目标检测数据集,标注格式是labelme JSON,很多人的第一反应是直接把整个文件夹丢进YOLO训练脚本。我劝你先停一下:这里面的问题不是“能不能跑”,而是“跑出来的模型能不能用”。6000张听着不少,但对道路病害这种小目标、长条目标占比极高的场景,真正决定模型上限的不是图片数量,而是标注质量、类别分布和格式处理。

这套part1数据集的典型内容是巡检车在高速或市政道路上拍下的路面图像,标注对象通常是裂缝、坑槽、修补、龟裂这几类。labelme产出的JSON文件里记录的是多边形或多点坐标,而YOLO训练要的是归一化中心点加宽高的txt文件,中间还隔着类别定义、数据集划分和训练参数三关。这篇文章就按我处理这类数据集的完整流程来写:先体检,再转换,然后训练,最后讲清楚那些容易让人翻车的坑。适合正在做道路病害检测、无人机巡检缺陷识别或打算从零搭建路面检测方案的工程师参考。

2. 数据体检先行:这6000张图的类别分布与标注质量到底行不行

2.1 文件结构:一套标准的labelme目标检测数据集长什么样

用labelme标注完一张图,会得到一张同名但后缀为.json的文件。文件里记录了三块关键信息:imagePath指向原始图片名,imageWidth和imageHeight是像素尺寸,shapes数组里则是每一处缺陷的标注结果。每个shape包含label(类别名)、points(多边形顶点坐标)、shape_type(polygon、rectangle或circle)。这套结构本身很清晰,但你从别人手里拿到数据集时,最容易踩的坑是文件结构不统一。

我收到这份6000张part1数据集后,第一步不是写训练脚本,而是先列目录。

tree -L 2 dataset/ # 期望看到: # dataset/ # images/ # 6000 张 jpg 或 png 原图 # labels/ # 6000 个同名 json # classes.txt # 类别名,一行一个

如果labels里的json数量少于images里的图片数,先别急着训练,有图没标注意味着这些样本会被YOLO当成纯背景。如果json数量多于图片数,说明存在重复标注或者同名文件覆盖问题。另外,labelme本身是可以通过conda安装的桌面标注工具,任何时候想人工抽查标注,装一个起来看图是最快的办法。

conda create -n labelme python=3.9 -y conda activate labelme pip install labelme

这套数据如果是从别人那里拷来的,我建议你顺手在labelme里打开三五张看看原图与框的贴合程度。很多数据集的问题一眼就能看出来:框把人行道也框进去了、裂缝只标了一半、坑槽的外接矩形把路沿石也包进去。这些视觉缺陷在后面的训练指标里很难被定位,因为损失函数会认为模型已经学得很好了。

2.2 体检脚本:类别统计、异常坐标与图片尺寸核查

在转换格式之前,先跑一个体检脚本,把类别分布、越界坐标、过小框这些基础问题全部暴露出来。这个脚本很简单,但能把后面几个小时的排错时间省下来。

import json from pathlib import Path json_dir = Path("labels") # 指向 json 文件夹 stats = {} errors = [] for jf in sorted(json_dir.glob("*.json")): with open(jf, encoding="utf-8") as f: data = json.load(f) img_w = data.get("imageWidth", 0) img_h = data.get("imageHeight", 0) if not data["shapes"]: errors.append((jf.name, "空标注", "该图没有任何缺陷框")) continue for shape in data["shapes"]: label = shape["label"] stats[label] = stats.get(label, 0) + 1 pts = shape["points"] xs = [p[0] for p in pts] ys = [p[1] for p in pts] x1, y1, x2, y2 = min(xs), min(ys), max(xs), max(ys) if x1 < 0 or y1 < 0 or x2 > img_w or y2 > img_h: errors.append((jf.name, label, "坐标越界")) if x2 - x1 < 5 or y2 - y1 < 5: errors.append((jf.name, label, "疑似过小框")) print("类别统计:", stats) print(f"异常记录 {len(errors)} 条,前 20 条:") for e in errors[:20]: print(e)

这段脚本的逻辑很简单:遍历所有JSON,统计每个类别的目标数量,同时检查两种常见异常。坐标越界说明标注时图片尺寸信息与真实图片不一致,或者标注时图片被resize过但labelme没有更新imageWidth。过小框则往往是误标或噪声,对目标检测模型来说,一个5乘5像素的框基本学不到有效特征。

参数上,过小框的阈值我一般设成5像素,你可以按任务调整。裂缝这类目标本身可能是细长的,宽度只有几个像素,但长度通常有几十像素,所以宽和高的最小值分开判断更合理:把x2 - x1 < 5 or y2 - y1 < 5改成(x2 - x1) < 5 or (y2 - y1) < 5是保守判断,想更严格可以写成(x2 - x1) < 10 and (y2 - y1) < 10。跑完这个脚本,你会对这套part1数据的真实质量有一个明确认识。

2.3 为什么labelme的JSON不能直接喂给YOLO

YOLO系列训练时读取的标注格式是每张图对应一个txt文件,每行五个数字:类别ID、归一化中心点x、归一化中心点y、归一化宽度、归一化高度。而labelme的JSON里存的是多个顶点的绝对像素坐标,这两者之间不是简单改后缀的关系。

最核心的差异有两点。第一,坐标基准不同。labelme的points是“绝对像素”,YOLO要的是“0到1之间的相对值”,转换时必须要除以图片宽高。第二,目标表示方式不同。labelme里同一个缺陷可能是用四个角点围成的旋转框,也可能是用几十个点围成的多边形,而YOLO的矩形框只有两组坐标信息,转换时必须决定如何从多边形里提取出矩形。

常见做法是取外接正矩形,也就是polygon所有顶点里x和y的最小最大值。这种转换对裂缝、龟裂这类不规则目标会损失一部分形状信息,但对目标检测来说完全够用。如果你发现某些缺陷形状极度不规则,矩形框内背景占比太高,那后续要考虑的是切片或实例分割方案,而不是继续在检测框上做文章。

3. 从labelme到YOLO:JSON转TXT的转换脚本与数据集划分细节

3.1 转换脚本:把多边形与旋转框统一转成YOLO txt

明确了格式差异之后,接下来是写转换脚本。这里我直接给一份能跑的版本,兼顾了多边形、矩形和旋转框三类情况。

import json from pathlib import Path json_dir = Path("labels") out_dir = Path("yolo_labels") out_dir.mkdir(exist_ok=True) with open("classes.txt", encoding="utf-8") as f: class_names = [line.strip() for line in f.readlines() if line.strip()] for jf in sorted(json_dir.glob("*.json")): with open(jf, encoding="utf-8") as f: data = json.load(f) img_w = data.get("imageWidth", 0) img_h = data.get("imageHeight", 0) txt_lines = [] for shape in data["shapes"]: label = shape["label"] if label not in class_names: continue cls_id = class_names.index(label) pts = shape["points"] xs = [p[0] for p in pts] ys = [p[1] for p in pts] # 多边形或四点框:统一取外接矩形 x1, y1 = min(xs), min(ys) x2, y2 = max(xs), max(ys) # 归一化到 0~1 cx = (x1 + x2) / 2.0 / img_w cy = (y1 + y2) / 2.0 / img_h bw = (x2 - x1) / img_w bh = (y2 - y1) / img_h txt_lines.append(f"{cls_id} {cx:.6f} {cy:.6f} {bw:.6f} {bh:.6f}") out_path = out_dir / (jf.stem + ".txt") out_path.write_text("\n".join(txt_lines), encoding="utf-8") print(f"转换完成,共处理 {len(list(json_dir.glob('*.json')))} 个 json")

这段脚本有几个关键决策点。第一,img_w和img_h优先从JSON里读,但如果JSON缺失这两个字段,脚本会得到0,归一化则出现除零异常。稳妥做法是当imageWidth为0时从原图读取,可以在循环开头加一个判断,调用PIL去取实际图片尺寸。第二,所有shape无论标注成多边形还是矩形,都折成外接正框,这样虽然会引入少量背景,但保证模型收敛稳定。

第三,也是容易被忽略的:shape_type为“circle”时,points里只有圆心坐标和另一个边缘点坐标,你的脚本要做特殊处理。我一般遇到circle类型就取圆心和半径画外接正方形,避免直接套min/max逻辑得到异常框。这套part1数据如果是纯目标检测标注,通常不会出现circle,但保险起见在转换前先跑一遍类别检测,把不认识的shape_type打出来。

3.2 train/val/test划分:避免同源照片串集

转换完txt之后要做数据集划分。很多人随手用random.shuffle切分,这在小数据集上没问题,但路面缺陷数据集有一个特性:同一批巡检车数据往往是从同一条路上连续拍摄的。如果随机打乱,模型训练集里看到的道路纹理和验证集高度相似,验证指标会虚高,真实场景表现则大幅缩水。

我处理这套6000张part1数据时,优先按文件名的前缀或拍摄批次来分组。比如文件名以日期和路段编号开头,就按这些批次划分,保证同一个路段的连续帧全部落在同一个集合里。如果文件名没有规律,退而求其次是按一定间隔挑验证集,比如每隔10张取一张做验证,这样时间上相邻的帧不会同时出现在训练集和验证集。

import shutil from pathlib import Path from collections import defaultdict image_dir = Path("images") label_dir = Path("yolo_labels") out_root = Path("dataset") batch_map = defaultdict(list) # 按文件名前 8 位分桶,模拟按路段划分 for img in sorted(image_dir.glob("*.jpg")): batch_key = img.stem[:8] # 根据你的命名规则调整 batch_map[batch_key].append(img) items = list(batch_map.values()) # 按桶划分,而不是按单张图片划分 train_buckets = items[: int(len(items) * 0.8)] val_buckets = items[int(len(items) * 0.8): int(len(items) * 0.9)] test_buckets = items[int(len(items) * 0.9):] for split, buckets in [("train", train_buckets), ("val", val_buckets), ("test", test_buckets)]: for bucket in buckets: for img in bucket: img_out = out_root / "images" / split / img.name img_out.parent.mkdir(parents=True, exist_ok=True) shutil.copy(img, img_out) lab = label_dir / (img.stem + ".txt") if lab.exists(): lab_out = out_root / "labels" / split / lab.name lab_out.parent.mkdir(parents=True, exist_ok=True) shutil.copy(lab, lab_out)

这段脚本的核心是按“桶”划分,而不是按单张划分。batch_key用的是文件名前8位,你需要根据这套part1数据的实际命名规则去改。如果文件名没有任何批次信息,那至少要在划分前把重复出现的道路背景考虑进去,宁可让训练集少几张,也不要让验证集和训练集出现同一条路上的连续帧。

3.3 目录约定与类别文件:YOLO训练前的最后一步

划分完成后,目录结构必须严格按YOLO的约定来组织。以常见做法为例,最终数据集应该长成下面这样:

dataset/ images/ train/ # 约 4800 张 val/ # 约 600 张 test/ # 约 600 张 labels/ train/ # 与 images/train 一一对应的 txt val/ test/ data.yaml # 数据集配置文件

这里有一个容易犯错的地方:labels目录下放的是转换后的txt,不是labelme的json。训练脚本会按图片名去找同名txt,如果目录里混着json文件,多数框架会报警告然后跳过,导致一批图片变成无标签样本。我一般转换脚本输出和原json目录分开,yolo_labels目录独立管理,避免混淆。

data.yaml的内容是下一步训练直接要用的,先看一眼:

path: /absolute/path/to/dataset train: images/train val: images/val test: images/test names: 0: crack 1: pothole 2: patch

names里的顺序必须和前面转换脚本里classes.txt的顺序一致,训练和推理阶段都依赖这个索引。如果你后续想增删类别,务必重新跑一遍转换脚本,不然类别ID错位,模型的loss曲线会乱成一团。

4. 用YOLO跑通沥青路面缺陷检测:训练配置与参数调整

4.1 数据配置文件与预训练权重的选择

格式转换和数据集划分完成后,接下来是训练。现在主流做法是用ultralytics的YOLOv8或YOLOv11,这两套框架对自定义数据集的训练体验最顺,配置文件写好后,一条命令就能跑起来。我一般用YOLOv8起步,因为它在小数据集上的收敛稳定性和文档完备度都很好,等基线出来后再考虑换YOLOv11或做结构改进。

训练前要做两件事。第一,确认data.yaml里的path是绝对路径或相对路径都可用,但train和val字段只写子目录名,不写绝对路径前缀。第二,选择合适的预训练权重。6000张数据足够让模型学出路面缺陷的特征,但直接用COCO预训练权重做迁移学习仍然是最靠谱的起点,模型已经具备通用的边缘和纹理感知能力,收敛速度明显快于随机初始化。显存有限就选yolov8n,显存够用且对裂缝细节要求高就选yolov8m。

4.2 训练命令与参数:imgsz、batch、epochs怎么设才不玄学

下面是基础训练命令,我在多个路面检测项目里用的就是这套参数,表现稳定。

yolo detect train \ data=asphalt.yaml \ model=yolov8n.pt \ epochs=120 \ imgsz=640 \ batch=16 \ patience=20 \ cache=True

逐个说参数。imgsz=640是目前检测任务的通用默认值,但对路面缺陷这种场景,我强烈建议你试imgsz=1280。沥青路面缺陷里的裂缝宽度往往只有2到5个像素,640的输入尺寸意味着下采样后裂缝可能只剩下1个像素宽,特征几乎丢失。如果你的显卡显存不够跑1280,可以先用640跑通流程,再用1280微调,或者走切片方案。

batch=16是通用值,设多大主要取决于显存。显存不够就把batch降到8或4,同时适当减少epochs。epochs=120对于6000张数据来说是一个合理起点,配合早停机制,模型通常在60到80轮就收敛。patience=20表示连续20轮验证集指标不提升就提前结束训练,这个设置能省下不少无意义的训练时间。

训练时的输出日志里,重点看三行:box_loss、cls_loss、dfl_loss。正常情况是三者都稳定下降,最后趋于平缓。如果box_loss降了但cls_loss纹丝不动,大概率是类别定义有问题,比如两类缺陷在标注时边界模糊。如果所有loss都在降但PR曲线很差,问题出在标注质量而非训练参数。

4.3 结果评估:从PR曲线到裂缝漏检的调优思路

训练完成后,先用验证集做一次正式评估。

yolo detect val \ model=runs/detect/train/weights/best.pt \ data=asphalt.yaml

输出会包含mAP@0.5、mAP@0.5:0.95、precision和recall。我的经验是:路面缺陷检测里,recall比precision更值得关注。漏检一条裂缝的代价远大于误检一个背景区域,因为实际落地时你还会加后处理去过滤误报,但漏检是模型层面的硬伤。

如果recall偏低,优先做两件事。第一,检查是不是小目标占比太高导致下采样后特征丢失,把imgsz从640翻到1280,往往recall直接涨三到五个点。第二,检查类别分布是否失衡,比如裂缝占了5000张而坑槽只有300张,此时模型对坑槽的recall天然偏低,需要做copy-paste数据增强或针对少样本类别加权。如果precision偏低,说明模型把很多阴影、水渍、修补痕迹当成了缺陷,这类问题靠调参数解决不了,要回到数据层面补负样本或重新标注。

5. 常见问题与排查:沥青路面缺陷检测的4个高频翻车点

5.1 现象:loss一直掉,mAP却上不去,原因多半是标注框过大

这是我从多个路面数据集上观察到的最高频问题。训练日志里box_loss和cls_loss都在正常下降,但验证集mAP@0.5死活卡在50%以下,随手抽几张预测结果一看,模型把整片破损区域框成一个巨大的矩形,而真实标注只框了其中一小块裂缝。

原因在于标注规范不统一:把裂缝、坑槽、修补都画成外接大框,导致正样本里包含大量背景。模型学到的不是“裂缝长什么样”,而是“破损区域长什么样”。解决方法是回到labelme里抽查原标注,如果确实存在大框问题,需要用脚本把过大的标注框做收缩或重标。经验阈值是:标注框面积超过整张图面积30%的样本,基本都需要复查。

5.2 现象:裂缝检测结果碎成一段段,长条目标的切片与锚框问题

裂缝检测的典型失败模式是一条完整的横向裂缝被识别成三段五段的碎片,置信度还都不低。这背后的原因通常是输入分辨率不够,模型只看到了裂缝的局部,没看到整体走向。

解决思路有两条。第一,把训练和推理的imgsz提高到1280,让模型在更大尺度上感受裂缝的连续性。第二,对原始大图做切片推理,把4000乘3000的大图切成四块或九块,分别检测,然后合并坐标。切片时相邻切片要有10%到20%的重叠,否则恰好跨在边界上的裂缝会被截断。切片后检测碎片的概率会明显下降,因为模型是在完整的目标尺度上做判断。

5.3 现象:坑槽误检率偏高,阴影、水渍与修补痕迹全是干扰源

模型在验证集上把路面的阴影、水渍、老旧修补痕迹识别成坑槽,这是路面缺陷检测里最让人头疼的问题。坑槽的视觉特征是边缘锐利的凹陷区域,而阴影和水渍在灰度图像上同样呈现深色区域,仅靠外观特征确实容易混淆。

原因有两层。第一,训练数据里缺少坑槽的负样本,阴影和水渍如果没被标注为“背景”,模型就没有机会学习它们的差异。第二,坑槽和修补在标注时被混为一谈,本来应该分两个类,却统一标成了坑槽。处理方法一是收集一批没有缺陷的干净路面和只含阴影/水渍的路面图片加入训练集,二是把坑槽与修补拆成两个独立类别,让模型分别学习各自特征。

5.4 现象:验证集指标漂亮,现场一测就翻车,数据同源导致的假象

训练时mAP@0.5到0.9,现场拿巡检车拍的新数据一测,漏检率飙升。这种翻车不是模型训练出了问题,而是数据集划分时埋下的隐患:训练集和验证集来自同一条路、同一台相机、同一个时间段,背景纹理和光照条件高度一致,验证指标自然好看。真实巡检场景可能换了路面材质、换了拍摄角度、遇到逆光或树影,模型泛化能力立刻现形。

解决方法是必须留出一批“陌生数据”作为最终评估集。建议按拍摄日期或路段严格隔离,训练阶段完全不接触这批数据,只在模型定型后用它做终测。如果你只有这套part1,建议先按时间序列把最后一批数据扣下来,等后续part2或part3拿到后再验证。这套流程执行下来,你的模型在线下测试和现场部署之间的差距会小很多。

6. 让6000张数据发挥更大价值:半自动预标注与模型验证的实操技巧

6.1 用训练好的模型给剩余数据做预标注

6000张数据训练出来的模型已经具备初步检测能力,这时候用它来加速后续标注效率是回报率最高的用法。常见做法是把模型跑一遍所有未标注图片,输出带置信度的检测框,再把框转回labelme格式,人工只需要修正框的位置和删除误检,而不是从零开始画多边形。

yolo predict \ model=runs/detect/train/weights/best.pt \ source=/path/to/unlabeled_images \ save_txt=True \ save_conf=True \ conf=0.35

预标注时置信度阈值我一般设在0.3到0.4之间。设太高会漏掉大量模型没把握但真实存在的缺陷,设太低又会产生一堆噪声框,人工删除更耗时。保存的txt是YOLO格式,转回labelme JSON时需要把归一化坐标乘以图片尺寸,按矩形框写入points,shape_type设置为rectangle即可。人工复核时重点看两类:模型标出的区域是不是真的缺陷,以及漏掉的缺陷模型为什么没标出来。

6.2 离线验证三板斧:留出集、PR曲线与现场抽帧

模型是否值得投入,不能只看训练日志的loss曲线,我习惯用三板斧来验证。第一是把前面扣下的签证数据跑一遍推理,统计漏检率和误检率。第二是画出每个类别的PR曲线,看曲线右下角的面积,如果曲线在低置信度区间断崖式下跌,说明模型对该类别的召回能力不行。第三是现场抽帧测试,找几段实际部署场景的视频或照片,覆盖不同光照、不同路面材质,在图片上直接画框看效果。

这三板斧跑完,模型能不能用已有结论。我早期做路面检测时就是忽略了留出集,被验证集指标骗了两个月,后来把模型部署到巡检车上才发现裂缝漏检率高得离谱,那段排查经历里尽是血泪。从那以后,我的习惯是先扣数据、再谈训练,数据划分规范了,后面的训练和调参才有意义。

希望这套流程能帮你在沥青路面缺陷检测这条路上少走些弯路。

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

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

数据库系统概论期末备考:用试题文档做知识体检与复习路线

简介&#xff1a;一套数据库系统概论期末考试试题&#xff0c;面向高校计算机专业学生及数据库备考者&#xff0c;覆盖数据库系统基础、关系数据库、结构化查询语言、数据库设计、数据安全、事务处理、并发控制与数据库恢复等核心模块&#xff0c;适合期末复习、考研巩固和教学…

作者头像 李华
网站建设 2026/10/11 22:46:03

大模型Agent技能系统实战:可插拔、可组合的工具调用架构

作为一个常年折腾AI应用开发的工程师&#xff0c;我最近在做一个叫“agent-skills”的项目&#xff0c;核心就一件事&#xff1a;给AI智能体搭一套可扩展的技能系统&#xff0c;让大模型不只会聊天&#xff0c;还能真正动手干活。这个项目解决了一个很实际的痛点——很多开发者…

作者头像 李华
网站建设 2026/10/11 22:40:40

微表情识别实战:基于CASME2与注意力机制的完整方案

简介&#xff1a;使用CASME2微表情数据集训练而来的识别系统&#xff0c;提供完整Python源码与详细文档说明&#xff0c;支持接入摄像头进行实时检测&#xff0c;也可对静态图片及视频文件完成微表情识别。资源面向需要完成毕业设计、期末大作业或课程设计的计算机专业学生&…

作者头像 李华
网站建设 2026/10/11 22:39:08

【全域智能营销实战】3、OpenClaw 架构源码深度解析:Gateway、Agent、Skill、Memory 四大模块完全拆解与 TaoToken 统一接入实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华