简介:面向道路与桥梁病害巡检场景,这份YOLOv7裂缝检测资源集成了训练好的模型权重、千余张标注图像及配套训练代码,适合有一定深度学习基础、希望快速复现或二次开发裂缝检测方案的读者,也便于在路桥养护项目中直接部署使用。资源共1252个文件,压缩包约822.81MB,其中jpg样本图占1117个,yaml配置与Python脚本分别用于定义检测参数和训练、推理流程,另有pt权重文件、xml与txt两种标签格式、ipynb示例以及Dockerfile、shell脚本等部署辅助文件,目录结构清晰,可快速完成环境配置。已有1564人学习下载。借助PyTorch框架下的完整代码,可轻松完成数据加载、模型训练、验证与导出;配套的检测结果参考能直观对比模型效果,免去从零标注和调参的繁琐过程,适合作为裂缝检测课题的起点。
1. 直接用YOLOv7做道路裂缝检测:为什么说“开箱即用”是个伪命题
道路裂缝检测在视觉检测里属于典型的“小目标、低对比度、复杂纹理背景”任务。沥青路面上的裂缝宽度往往只有几个像素到十几个像素,光照不均、油渍、修补痕迹、车道线都会成为干扰项。拿通用目标检测的思维去做,很容易出现漏检率居高不下、误检一堆“假裂缝”的结果。YOLOv7 在这种情况下依然是性价比最高的选择——它在COCO上的mAP和推理速度平衡得很好,单张1080Ti就能跑到60 FPS以上,而且社区里已经有人把道路裂缝数据集和预训练权重整理出来了,省掉从零标数据、从零训练的大半时间。
不过这也不等于下载个权重就能直接上线。预训练模型是在特定数据集上拟合出来的,换一条路况、换一套摄像头安装角度,精度都会变化。真正能复现的路径是:拿开源裂缝数据集做基准,跑通YOLOv7的训练-验证闭环,再用自己的数据做微调。下面这套方案,我从数据、训练到部署一条线讲清楚,命令和参数都是可以直接抄的级别。
2. 裂缝检测数据集的选择与预处理:标注格式、类别设计和数据增强
2.1 裂缝数据集里最关键的不是数量,是标注一致性
常见的公开裂缝数据集有CFD(复旦大学的路面裂缝数据集)、CrackForest、GAPs、DeepCrack等,数量从几百张到几千张不等。CFD有118张,GAPs有509张,DeepCrack有537张。单看数量都不大,但用它们做YOLOv7的预训练-微调基线是够用的,因为裂缝的结构特征高度重复。
标注一致性是这里最容易翻车的地方。同一个裂缝,有人标成一条连续的线,有人按裂缝的岔路切成好几段;有人把龟裂的网状区域整体框成一个框,有人把每条小裂纹单独框。YOLOv7对标注框非常敏感——如果同一个目标在训练集里有时标成细长条、有时标成大方框,损失函数会被来回拉扯,模型学出来的边界框会偏向“平均形状”,结果就是框不准。拿到数据集第一件事不是训练,而是用标注可视化脚本把bbox画到原图上,人工抽检50张以上,确认标注风格一致。
标注格式方面,YOLOv7原生支持两种:一种是ImageNet格式的目录结构+txt标注,另一种是COCO格式的JSON。个人习惯是先转成COCO JSON,因为它自带的pycocotools可以直接算mAP,而且用Label Studio或Roboflow标注完导出COCO格式是最省事的。下面是COCO转YOLO格式的脚本核心逻辑:
import json import os def coco_to_yolo(coco_json_path, output_dir, img_dir): with open(coco_json_path) as f: data = json.load(f) # 建立图片id -> 文件名的映射 img_map = {img['id']: img['file_name'] for img in data['images']} # 建立类别id -> 索引的映射(YOLO从0开始) cat_map = {cat['id']: idx for idx, cat in enumerate(data['categories'])} for img in data['images']: img_id = img['id'] h, w = img['height'], img['width'] txt_path = os.path.join(output_dir, img_map[img_id].replace('.jpg', '.txt')) lines = [] for ann in data['annotations']: if ann['image_id'] != img_id: continue cat_id = ann['category_id'] x, y, bw, bh = ann['bbox'] # COCO的bbox是左上角坐标+宽高 # 转成YOLO格式:中心点坐标+宽高,全部归一化到0~1 cx = (x + bw / 2) / w cy = (y + bh / 2) / h nw, nh = bw / w, bh / h lines.append(f"{cat_map[cat_id]} {cx:.6f} {cy:.6f} {nw:.6f} {nh:.6f}") with open(txt_path, 'w') as f: f.write('\n'.join(lines))这个脚本里有个细节值得注意:COCO的bbox字段是[x, y, width, height],而YOLO格式是[class, cx, cy, width, height],其中cx和cy是归一化后的中心点坐标。很多人直接拿COCO的x和y当作YOLO的中心点,坐标就偏了半个框,训练出来的模型定位偏差特别大。另外,裂缝的width通常只有几个像素,归一化后会变成0.001级别的小数,转成txt时保留6位小数是底线,保留4位可能直接让宽度变成0,触发YOLO的维度过滤。
2.2 数据增强策略:Mosaic是双刃剑
YOLOv7默认开启Mosaic增强——把4张图随机裁剪拼接成1张,这能大幅提升模型对遮挡和尺度变化的鲁棒性。但裂缝检测里Mosaic有个副作用:4张图拼接后,裂缝被缩小到原来的1/4甚至更小,本来就只有十几个像素宽的裂缝可能变成三五个像素,人眼都看不清。模型在增强后的图上如果学不到裂缝特征,等于白训练。
我一般会在裂缝任务里调整YOLOv7的增强参数,把Mosaic的概率从默认的1.0降到0.5,同时把copy_paste关掉——复制粘贴增强适合物体边界清晰的场景,裂缝这种细长目标复制进去会显得非常假。另外一个重要的增强是随机旋转和随机亮度对比度调整。裂缝检测对光照变化极度敏感,阴影里的裂缝和阳光直射下的裂缝灰度分布差异很大,亮度对比度增强能强迫模型去学边缘纹理特征,而不是简单地匹配灰度值。
具体修改data/hyp.scratch.custom.yaml里的参数:
# 基于YOLOv7默认hyp.scratch.yaml调整 mosaic: 0.5 # 降低mosaic概率,避免裂缝被过度缩小 mixup: 0.0 # 裂缝不适合mixup,混出来的图裂缝纹理完全丢失 copy_paste: 0.0 # 关闭复制粘贴 hsv_h: 0.01 # 色调变化调低,路面色彩本来就单一 hsv_s: 0.5 # 饱和度变化 hsv_v: 0.4 # 亮度变化,这个很关键,模拟不同光照 degrees: 15.0 # 随机旋转,摄像头安装角度不完全水平 translate: 0.1 # 小幅平移 scale: 0.3 # 缩放这里重点说下degrees和scale的取舍。裂缝虽然是任意方向都有,但大部分是横向或纵向延伸的,旋转角度过大导致目标旋转后边界框需要覆盖大量背景区域,拉低IoU。15度够用,不需要更大。scale调成0.3的意思是允许训练时图像缩放到原图的70%到130%之间,这能模拟摄像头远近造成的尺度变化,但对于几像素宽的裂缝,缩放本身就是一种破坏性操作——放大后裂缝变成“宽缝”,缩小后裂缝直接消失。实际训练时如果发现验证集的召回率上不去,优先怀疑增强参数设置过于激进。
3. YOLOv7模型训练与调优:预训练权重、超参数选择和损失变化的解读
3.1 用预训练权重还是从头训练:边界条件要先想清楚
YOLOv7的官方仓库提供了在COCO上预训练好的权重文件,yolov7.pt、yolov7x.pt、yolov7-w6.pt分别对应标准版、高精度版和超大输入版。裂缝检测领域里没有像COCO那样大规模的预训练模型,最靠谱的做法是拿COCO权重做初始化,把最后的检测头输出类别数从80改成自己的类别数,然后全量微调。
有人问能不能直接用已有的裂缝检测权重推理,不训练。这取决于权重是从哪个数据集上来的,以及你的图片和它训练时的分布差异有多大。如果权重是用上千张同一条路段的图片训练的,你的摄像头安装位置和角度相似,直接推理可能有个七八成的可用度;如果路段差异大,直接推理的漏检率可能高到没法用。所以预训练权重的定位是“少训练几千轮”,不是“零训练”。
开始训练前,还有一件事要做:确认anchor是否匹配你的裂缝尺寸。YOLOv7在训练时会自动计算anchor,但如果你的数据集里裂缝都是细长条——宽高比普遍在1:5以上——默认的COCO anchor(偏向正方形物体)就不合适了。用官方仓库里的tools/kmeans_anchors.py跑一下自动聚类anchor尺寸:
python tools/kmeans_anchors.py --data data/crack.yaml --img-size 640 --n 9这个脚本会输出9组anchor的宽高值,把它们替换到模型配置文件的anchors字段。自动计算anchor跑一次就行,不需要每次训练都重算。裂缝检测的anchor一般会聚出少数几个极端的细长比例,比如[3, 15, 5, 42]这种,这是正常现象,不用觉得不对劲。另外,如果换成anchor后第一轮训练的loss反而比原来高,先跑50轮再看趋势——anchor是聚类的初始值,模型会自己在训练中微调,头几轮loss波动不代表anchor选错了。
3.2 训练命令的完整参数解释:batch、epoch和输入尺寸怎么取舍
训练命令的骨架长这样:
python train.py \ --data data/crack.yaml \ --weights yolov7.pt \ --batch-size 16 \ --img-size 640 \ --epochs 200 \ --hyp data/hyp.scratch.custom.yaml \ --device 0 \ --workers 8 \ --project runs/train \ --name crack_exp1参数逐个说。--batch-size 16是建立在24GB显存的卡上的选择,如果是12GB的卡就降到8,否则显存溢出直接OOM。YOLOv7在batch size=16和batch size=8之间的精度差距,可以通过多跑50个epoch补回来,没必要为省显存用极端小的batch然后祈祷模型能收敛。--img-size 640是YOLOv7官方预训练时的输入尺寸,改成1280能提升小目标检测能力,但显存消耗会变成原来的4倍,推理速度也会掉到原来的1/4甚至更多。裂缝宽度只有几个像素,提升输入分辨率确实比改任何其他参数都有效,但我一般建议先在640上把整个训练-评估流程跑通,再在1280上做二次微调。
--epochs这个参数值得多说两句。裂缝数据集的体量通常在几百到几千张,在这种小数据集上,200个epoch足够看到收敛趋势。但小数据集上模型很容易过拟合——训练loss降到很低,验证集mAP却停滞甚至下降。跑的时候要盯着results.png里的验证集mAP曲线,如果发现训练loss还在降但val mAP连续20个epoch不动了,直接停掉,保存之前的最佳权重。YOLOv7支持--patience参数启用早停机制:
python train.py \ --patience 50 \ --save-period 10 \ ...--save-period 10的意思是每10个epoch保存一次权重,这样如果最后几个epoch过拟合了,还能回头选中间某个检查点继续用。这是个很实用的小技巧——不要默认最后一次保存的权重就是最好的,YOLOv7训练时会把验证集上mAP最高的那个epoch另存为best.pt,用那个文件做推理就行。
3.3 训练过程中的loss曲线:什么样的曲线代表模型真的在学
YOLOv7的loss由三部分组成:box回归损失(CIoU)、目标性损失(obj,衡量框里是否有目标)、分类损失(cls,判断框里目标的类别)。单类别裂缝检测里,分类损失全收敛到0附近不代表模型就好——因为只有一个类别,模型只要学会“框住裂缝”就行。真正要盯的是box损失和obj损失。
健康的loss曲线应该是:前10个epoch快速下降(从十几降到三四),之后进入缓慢下降阶段,在100到150个epoch之间趋于平稳。如果训练到一半loss突然跳高,往往是学习率策略的问题。YOLOv7默认用余弦退火学习率调度,初始学习率0.01。如果数据集特别小,0.01的初始学习率可能太大了,造成前期动荡。这时候把超参文件里的lr0改成0.005甚至0.001,训练会更稳,代价是收敛速度慢一点。
另一种常见问题是loss稳步下降但验证集mAP始终在低位徘徊。这种时候别急着调模型结构,先看两个东西:一是验证集的ground truth框是否完整——有没有漏标的裂缝在验证集里被模型正确检测出来了,却被mAP计算当作“误检”惩罚;二是推理时的置信度阈值是不是设得过高。mAP计算用的是0.001的置信度下限,而实际应用里你可能会把阈值提到0.5以上,这之间的差距会让“mAP看着不错”和“实际用起来漏检很多”并存。检查方法很简单:推理时把置信度阈值设到0.25,画出来的框如果位置基本正确,说明模型本身没大问题,问题出在应用阈值设置。
4. 用训练好的模型做道路裂缝推理部署
4.1 PyTorch直接推理:几行代码验证权重可用性
训练好的best.pt可以直接用PyTorch加载推理。这里注意一个坑:YOLOv7官方仓库的模型定义依赖models/yolo.py里的Detect层,不能像加载纯torchvision模型那样裸加载torch.load,得走官方仓库的detect.py,或者用torch.hub.load加载整个仓库。
最稳妥的验证方式是用官方推理脚本:
python detect.py \ --weights runs/train/crack_exp1/weights/best.pt \ --source test_images/ \ --img-size 640 \ --conf-thres 0.25 \ --iou-thres 0.45 \ --device 0--conf-thres是置信度阈值,低于这个值的检测结果会被过滤掉;--iou-thres是NMS的IoU阈值,两个重叠框的IoU超过这个值就合并。裂缝检测里conf-thres建议调到0.2以下试试——裂缝的特征是“边缘清晰但颜色不明显”,模型可能给出0.3到0.4的置信度,这在别的任务里属于偏低,但在裂缝检测里可能已经是个有效检测了。
如果在自己的图像上验证效果不理想,先别急着重新训练。用一个简单的前处理手段——将图像转为灰度图后再输入模型——往往能立竿见影。YOLOv7默认按3通道RGB输入,它自己在训练时已经用颜色增强模拟过灰度变化,所以推理阶段直接传灰度图给RGB通道(RGB(B=B, G=B, R=B))反而能减少路面颜色干扰。
4.2 ONNX Runtime部署:摆脱PyTorch环境,提升推理速度
PyTorch原生推理依赖完整的深度学习框架环境,部署到现场设备时动不动就是2GB以上的依赖项。ONNX Runtime可以把模型转成通用的ONNX格式,推理时只需要安装onnxruntime这一个Python包或C++库。
用官方仓库的导出脚本转ONNX:
python export.py \ --weights runs/train/crack_exp1/weights/best.pt \ --img-size 640 \ --batch-size 1 \ --simplify--simplify参数很关键,它调用onnx-simplifier对计算图做常量折叠和冗余节点消除,能减少10%到30%的推理耗时。导出完成后可以用onnxruntime跑一次推理验证:
import cv2 import numpy as np import onnxruntime as ort # 创建ONNX Runtime推理会话 session = ort.InferenceSession("best.onnx", providers=["CUDAExecutionProvider", "CPUExecutionProvider"]) # 预处理:resize + 归一化 + 维度调整 img = cv2.imread("road_crack.jpg") img_resized = cv2.resize(img, (640, 640)) img_norm = img_resized.astype(np.float32) / 255.0 input_tensor = img_norm.transpose(2, 0, 1)[None, ...] # HWC -> NCHW # 推理 input_name = session.get_inputs()[0].name outputs = session.run(None, {input_name: input_tensor})[0] # outputs shape: [1, 25200, 6],最后一维是 [x1, y1, x2, y2, conf, class_id]ONNX Runtime的输出格式和PyTorch略有不同——官方detect.py里做了后处理(置信度过滤+NMS),而导出的ONNX模型输出的是密集预测结果,需要自己写后处理逻辑。这一步的常见错误是直接从outputs里取检测结果,不经过NMS,导致同一个裂缝被框出十几次。下面是精简版的NMS后处理:
def non_max_suppression(output, conf_thres=0.25, iou_thres=0.45): pred = output[0] # [25200, 6] # 过滤低置信度 mask = pred[:, 4] > conf_thres pred = pred[mask] if len(pred) == 0: return np.array([]) # 按置信度从高到低排序 order = np.argsort(-pred[:, 4]) pred = pred[order] keep = [] while len(pred) > 0: best = pred[0] keep.append(best) if len(pred) == 1: break # 计算其余框与最高置信度框的IoU ious = compute_iou(best[:4], pred[1:, :4]) pred = pred[1:][ious < iou_thres] return np.array(keep)compute_iou函数用标准IoU公式实现即可。ONNX推理的一个性能要点是:输入分辨率固定为640×640,如果图片不是这个尺寸,resize会改变目标的大小和宽高比。裂缝的宽高比极高,resize时如果用纯拉伸(把宽拉成640、高拉成640),细长的裂缝会被扭曲成不同的角度,影响检测精度。推荐的做法是用letterbox技术——等比缩放后用灰色填充剩余区域,保持图像不变形。YOLOv7官方仓库的letterbox函数可以直接复用,不要自己去写resize。
5. 部署到边缘设备与工程化落地:TensorRT加速和模型量化
5.1 TensorRT FP16推理:把单张推理压到20ms以内
ONNX Runtime在GPU上的性能已经不错,但和TensorRT比起来仍有差距。TensorRT能做到多数优化工作自动化,YOLOv7的模型结构能够完整映射到TensorRT的层上,不会有某个算子不支持导致全链路失败的尴尬。
先把ONNX模型转成TensorRT引擎:
import tensorrt as trt logger = trt.Logger(trt.Logger.WARNING) builder = trt.Builder(logger) network = builder.create_network(1 << int(trt.NetworkDefinitionCreationFlag.EXPLICIT_BATCH)) parser = trt.OnnxParser(network, logger) with open("best.onnx", "rb") as f: parser.parse(f.read()) config = builder.create_builder_config() config.set_memory_pool_limit(trt.MemoryPoolType.WORKSPACE, 1 << 30) # 1GB workspace config.set_flag(trt.BuilderFlag.FP16) # 启用FP16精度 engine = builder.build_serialized_network(network, config) with open("best_fp16.engine", "wb") as f: f.write(engine)FP16精度下,TensorRT的推理速度和精度能达到一个平衡。实测中,一款主流的嵌入式GPU(如Jetson Orin Nano)上跑YOLOv7在FP16下能做到单张640×640输入推理时间在20到40毫秒之间,已经满足实时处理需求。
这里要提醒一个部署时的实操问题:TensorRT引擎是绑定具体GPU型号的,换卡需要重新生成引擎。生成引擎的过程通常要经历一次模型优化,可能耗时一两分钟,在工业生产中往往会让第一帧卡住。解决办法是把引擎文件序列化保存到本地,后续直接反序列化加载,不再走解析和优化流程。不过引擎文件不能跨设备使用——不同代显卡的算子优化策略不同,硬加载会直接报错,而且报错信息特别隐晦,比如Assertion failed: enginePtr之类的,排查一小时可能才发现就是换GPU了。
5.2 量化到INT8时的精度保护措施
把模型压缩到INT8能进一步提速,但代价是精度下降。裂缝检测对微小细节敏感,INT8量化后如果发现召回率掉了5%以上,常规操作是设置每通道量化(per-channel quantization)而不是每张量量化(per-tensor)。另外,TensorRT INT8需要提供校准数据集——一般从测试集里抽样200到500张包含裂缝的图片,喂给模型统计激活值的分布。注意校准数据集里应该同时包含裂缝密集和裂缝稀疏的图片,如果只拿裂缝密集的图片校准,模型量化后遇到裂缝稀疏的图段会判断失常。
一个容易忽略的实际方案:部署时给极端难例开优先级。裂缝检测出口不只是“检测到裂缝”,还有“裂缝宽度分级”——细裂缝早期修复成本低,粗裂缝可能需要封路维修。这个分级逻辑如果放在YOLOv7里学,需要额外标数据,工程量大;简单做法是推理后处理里加一个像素级宽度估计步骤:在检测框内做二值化分割,统计裂缝像素宽度,再做分级。这一步虽然增加了单个目标的处理时间(不超过5ms),但让整个系统从单纯检测升级到了检测+量化评估,也避开了为分级重新标注训练数据的麻烦。
5.3 帧率与延迟的取舍:视频流处理不要每帧都做全图推理
如果你要处理的是连续的视频流(道路巡检车、监控摄像头),一个常见的性能误区是逐帧全图推理。实际上相邻帧之间的背景差异极小,裂缝的移动(如果摄像头装在移动载体上)也有限。可以用跳帧策略——每3帧推理一次,中间2帧直接沿用上一帧的检测结果,必要时用光流法做微调。这种方式能把整体吞吐量提升近3倍,而对裂缝这种目标,漏检率几乎不受影响。
另一个实用策略是感兴趣区域(ROI)剪裁。摄像头画面里,道路区域通常只占下半部分,上半部分是天空、树木或者建筑物。把固定ROI坐标保存下来(用cv2.selectROI交互式选定一次),推理前先做一次裁剪再送入模型,能把模型有效输入从640×640降到640×320甚至更小,降低无效背景的干扰,也能顺带提升一点精度——因为模型无需在目标之外浪费注意力。
6. 训练好模型后期怎么持续调优:伪标签迭代和自适应阈值
训练完一版模型、部署上线后,真正的调优才刚开始。
第一个实用技巧是伪标签(pseudo-labeling)迭代。模型在真实路段上跑一段时间后,会采集到大量未被训练集覆盖的场景——比如雨天反光路面、桥面伸缩缝、阴影里的裂缝。这些新图片没有人工标注,但它们本身就是最好的训练数据。拿当前模型对这些图片跑一次推理,置信度高于0.7的检测结果作为伪标签,置信度在0.3到0.7之间的暂时丢弃,然后把“新图片+伪标签”并入原始训练集,再微调一轮。
这个迭代有几个参数要注意。伪标签置信度阈值不能太低,否则模型会把错误检测当正确答案继续强化;但也不能太高,否则收集到的都是模型原本就有把握检测到的简单样本,对提升边界情况帮助有限。0.7是一个相对稳妥的起始值。另外,每轮迭代建议只加入新数据,不要让旧数据占比被稀释,否则模型会逐渐遗忘原始分布。
第二个实用技巧是自适应置信度阈值。固定用0.25或0.5做推理阈值会损失一部分召回率,但统一调低又会增加误检。一个行之有效的办法是采集一小段正常路段的视频(不含裂缝),用模型跑一遍,统计置信度分布,取分布中前5%高分数的均值,作为该场景的“动态误检基线”。正式推理时的置信度阈值设为这个基线值乘以1.2到1.5,既能过滤掉该环境下的背景干扰,又不会让真正裂缝被卡掉。同一套模型在晴天、阴天、雨后三个场景下分别计算基线,得到三个阈值,按时间或光照传感器自动切换——这种轻量级自适应机制,提升的精度往往比重新训练一个模型更立竿见影。
本文还有配套的精品资源,点击获取