
简介计算机视觉技术正加速进入基础设施安全监测领域其中以深度学习为代表的目标检测方法已逐步替代传统边缘检测与阈值分割成为结构表面裂缝识别的核心技术路径。其基本原理是通过卷积神经网络学习裂缝在复杂背景下的纹理与几何特征实现高精度定位。该技术价值在于能够有效抑制光照、苔藓、水渍等干扰因素提升室外真实场景下的泛化能力广泛适用于路面、桥梁、墙体等结构表面的自动化巡检。一个可落地的裂缝检测系统需同时兼顾数据标注策略、模型训练调参与推理部署优化等关键环节。本文以Yolov5为技术载体系统梳理了裂缝数据集的构建方法、超参数调整经验、批量检测与RTSP视频流接入方案并总结了施工缝误判、极端光照干扰等工程踩坑案例为结构检测工程师及研究者提供一条从源码出发的完整落地路径。1. 路面桥梁墙体裂缝检测为什么我建议直接用Yolov5源码改做结构检测的朋友应该都有同感裂缝检测这个需求看起来简单真正动手做却处处是坑。传统图像处理里的边缘检测、阈值分割在室内干净背景下的试件上效果不错一到室外路面、桥梁、墙体光照变化、阴影、苔藓、水渍全部变成干扰误检率高到没法用。而这两年裂缝识别方向上的主流做法基本都收敛到了深度学习目标检测这条路上其中Yolov5是复现成本最低、工程资料最全的一个选择。这个项目标题里给出的正是这样一套东西Python环境下的Yolov5裂缝检测系统带文档和源码目标场景覆盖路面、桥梁、墙体三类典型结构表面。把这套系统跑通并不难难的是把参数调到能用的程度。本文按我实际做过的方式把数据准备、训练调整、推理部署和踩坑记录完整过一遍。你不需要有很强的深度学习基础只要会装Python、能跑命令行就可以跟着一步步复现。适合的人群很明确在做桥梁检测、路面巡检、房屋安全评估的工程师或者准备拿这个方向做毕业设计的学生。我不讲那些花哨的注意力机制也不对比Yolo系列各个版本谁更强就讲最直接能落地的做法。2. 做裂缝数据集是第一道坎采集、标注与增强的取舍2.1 裂缝目标的特点决定了标注方式第一次做裂缝检测的人最容易犯的错是把裂缝当成普通物体来标。普通物体比如人、车、猫是一个封闭轮廓用矩形框一框就完事。但裂缝不一样它是长条形的细的时候只有两三个像素宽长度可能跨越整个画面。你要是像框人一样用一个窄矩形框去框裂缝出来的目标框长宽比会非常极端10:1、20:1都不奇怪。Yolov5对极端长宽比的锚框本身就敏感训练出来的模型很容易漏检短裂缝。更合理的做法是以每段裂缝为单位把连续裂缝切成若干段每段用一个尽量方正的小框去标框与框之间可以有少量重叠这样训练出来的模型对裂缝段的召回率会高很多。还有一个细节是漏检容忍度。裂缝检测和工业质检不一样工业质检漏掉一个缺陷可能就一批废品但裂缝检测漏掉一段裂缝巡检测绘的结论就可能从合格变成需维修性质完全变了。所以标注的时候拿不准的裂缝区域宁多勿少模糊的小裂缝也标上。模型学到的边界是模糊的但召回率能保住。2.2 离线增强三板斧光照扰动、随机旋转、Mosaic裂缝数据集天然不好攒尤其桥梁裂缝你得爬到桥墩、箱梁里拍一次能拍几百张算不错了。Yolov5自带的Mosaic、Copy-paste增强能帮你把数据量撑起来。但有几个增强参数必须调不能全用默认值。以Yolov5官方仓库的coco.yaml训练流程为例我一般在data/hyps/hyp.scratch-low.yaml里改这三项# hyp.scratch-low.yaml 关键参数片段 hsv_h: 0.02 # 色调扰动幅度裂缝颜色单一调太大颜色偏得离谱 hsv_s: 0.3 # 饱和度扰动应对不同光照和混凝土表面色差 hsv_v: 0.4 # 明度扰动模拟逆光、阴影、夜间补光场景 flipud: 0.5 # 垂直翻转路面裂缝横竖都有翻转保持方向多样性 mosaic: 0.8 # Mosaic增强概率默认1.0对密集小目标效果好但裂缝太细容易拼花这些参数的含义要理解hsv_h是色调在HSV空间的扰动比例裂缝一般是灰黑色色调本身没有太多信息扰动大了反而让模型去学颜色特征而不是纹理特征hsv_v是明度扰动这个对户外场景最重要因为裂缝检测最大的干扰就是光照不均。Mosaic概率我从1.0降到0.8原因是裂缝目标小且细四张图拼在一起时边缘处的裂缝会被截断导致模型学到半截裂缝这种错误模式。2.3 数据集划分与目录组织Yolov5的训练入口是dataset.yaml你需要把数据按images和labels两个目录分开每张图片对应一个同名的txt标注文件。标注格式是YOLO格式类别index、归一化的中心点x、中心点y、框宽w、框高h全部是0到1之间的浮点数。手工标注工具推荐LabelImg或者Labelme导出YOLO格式就行。# 项目目录结构按Yolov5默认读取方式组织 crack-data/ ├── images/ │ ├── train/ # 训练集图片 │ └── val/ # 验证集图片 ├── labels/ │ ├── train/ # 训练集标签和images同名同后缀 │ └── val/ # 验证集标签 └── crack.yaml # 数据集配置文件crack.yaml内容按这个写# crack.yaml train: ./crack-data/images/train val: ./crack-data/images/val nc: 1 # 类别数裂缝检测就一个类 names: [crack] # 类名随意但和标注txt里的index必须对应注意train和val的路径用相对路径最省事把crack.yaml放在Yolov5仓库根目录下路径以仓库根目录为基准。另外val集不要偷懒直接拿训练集里的图裂缝检测的验证集要有一定数量我一般按训练集20%左右的比例留每类至少50张不然验证损失曲线波动太剧烈根本看不出模型有没有收敛。3. 训练自己的裂缝模型Yolov5超参数和损失曲线怎么调3.1 预训练权重选哪个Yolov5官方提供了好几个尺寸的预训练权重Yolov5s、Yolov5m、Yolov5l、Yolov5x。裂缝检测属于小目标检测的范畴直觉上会觉得模型越大越强直接上Yolov5x准没错。实际做下来不是这个逻辑。裂缝检测的输入分辨率一般限制在640甚至更低因为结构检测拍下来的照片动辄几千万像素缩小到640以后细裂缝的信息已经损失大半模型容量再大也补不回来。我在自己项目里的经验是Yolov5s已经够用m能稍微提升一点召回率l和x的收益微乎其微但训练时间和推理时间翻倍不止。如果你后面要部署到嵌入式设备s是唯一合理的选择。迁移学习的做法是先从官方仓库下载coco预训练权重然后冻结前几层只训后面几层等loss稳定后再解冻全模型微调。这个做法对裂缝这种目标纹理特殊但背景语义相对简单的场景很有效能有效防止训练初期梯度震荡把预训练学到的特征破坏掉。# 第一步冻结骨干网络训练50轮 python train.py \ --data crack.yaml \ --weights yolov5s.pt \ --img 640 \ --batch 16 \ --epochs 50 \ --freeze 10 \ --name crack_freeze # 第二步解冻全模型继续训50轮 python train.py \ --data crack.yaml \ --weights runs/train/crack_freeze/weights/best.pt \ --img 640 \ --batch 16 \ --epochs 50 \ --name crack_finetunefreeze参数的数字代表冻结主干网络的前多少层。Yolov5s的backbone一共10层C3结构freeze 10就是冻结整个backbone只让head部分学裂缝的框回归和分类。这样做的理由是预训练模型在coco上学到的底层特征——边缘、纹理、颜色渐变——对裂缝同样适用不需要从头学而head部分的检测头需要适应裂缝目标的极端长宽比和稀疏分布需要充分训练。第二步解冻之后用较小的学习率微调全部层让backbone的特征也向裂缝数据做轻微偏移。3.2 关键超参数逐项说明Yolov5的超参数分布在data/hyps/hyp.scratch-low.yaml里对裂缝检测来说最重要的不是学习率而是这几个参数我的设置作用与理由lr00.01冻结阶段/ 0.001解冻阶段初始学习率冻结阶段可以大一点解冻后必须降下来lrf0.2最终学习率占初始学习率的比例用余弦退火调度momentum0.937SGD动量Yolov5的默认值就可以weight_decay0.0005权重衰减防止过拟合裂缝数据量少这个值不能太小fl_gamma1.5Focal Loss的gamma调节正负样本不平衡裂缝目标小背景占比极高建议设1.5box0.1框回归损失权重裂缝框不好标权重太高会让模型过度拟合标注噪声fl_gamma这个参数经常被忽略。Yolov5的默认值是0也就是不用Focal Loss。但裂缝检测的场景里一张640x640的图可能只有几个目标框剩下的全是背景。如果不做正负样本平衡模型会倾向于把一切预测为背景出现啥都检不出来的情况。把fl_gamma调到1.5模型会更多的关注那些难分类的正样本也就是细小的、对比度低的裂缝段。训练出来的模型对低对比度裂缝的召回率会有肉眼可见的提升。3.3 训练过程要盯哪些指标训练跑起来之后不要只盯着终端刷屏的loss数值。Yolov5会在runs/train/目录下生成TensorBoard日志和训练曲线图你需要关注四个东西box_loss、obj_loss、cls_loss三条训练曲线是否同步下降验证集上的mAP0.5和mAP0.5:0.95Precision和Recall曲线的平衡点以及PR曲线在召回率0.8附近有没有骤降。# 训练完成后查看结果目录 ls runs/train/crack_finetune/ # 期望看到 # weights/best.pt # 验证集上mAP最高的权重 # weights/last.pt # 最后一轮的权重 # results.png # 训练曲线汇总图 # confusion_matrix.png # 混淆矩阵 # PR_curve.png # 精确率-召回率曲线 # 用自带的eval脚本输出详细指标 python val.py \ --data crack.yaml \ --weights runs/train/crack_finetune/weights/best.pt \ --img 640 \ --iou 0.5 \ --task val一个典型的正常训练过程应该是冻结阶段前10轮loss快速下降之后趋于平缓解冻阶段一开始loss会有个小反弹这是因为backbone重新开始适应数据之后继续下降到第30轮左右基本稳定。如果你的val loss在训练后期开始上升而train loss还在降说明过拟合了最直接的办法是把fl_gamma调回1.0同时检查训练集和验证集是不是有重复图片——我踩过这个坑数据增强里开了随机裁剪验证集图片被裁剪出了和训练集几乎一样的区域。4. 模型推理部署单张图片、视频流与批量检测的写法4.1 单张图片推理的标准写法训练结束后detect.py是最常用的推理入口。但实际做项目的时候直接跑detect.py的情况不多因为你需要把检测结果接进自己的业务系统。我在项目里一般把Yolov5封装成detector类这样不会每次启动都重新加载模型权重。接下来用代码说明我处理的方式。# crack_detector.py import torch import cv2 import numpy as np class CrackDetector: def __init__(self, weights_path, conf_thres0.3, iou_thres0.45, img_size640): # 加载训练好的模型 self.model torch.hub.load(path/to/yolov5, custom, pathweights_path, force_reloadTrue) self.model.conf conf_thres # 置信度阈值 self.model.iou iou_thres # NMS的IoU阈值 self.model.img_size img_size # 推理分辨率 self.model.classes [0] # 只检测裂缝类别 def detect_single_image(self, image_path): # 读取原图保持原始分辨率用于绘制 img cv2.imread(image_path) origin_h, origin_w img.shape[:2] # Yolov5推理返回结果对象 results self.model(image_path) # 解析检测框坐标相对原图分辨率 dets results.xyxy[0].cpu().numpy() for det in dets: x1, y1, x2, y2 det[:4] conf det[4] box_w x2 - x1 box_h y2 - y1 # 过滤过窄的框裂缝框极端细长时很可能是一种误检模式 if box_w 5 or box_h 5: continue # 返回原始image和检测结果供上层绘制和统计 return img, detsconf_thres是置信度阈值低于这个值的目标会被过滤掉。裂缝检测场景建议设0.3左右不要设成0.5。裂缝的对比度差异大低对比度裂缝段的置信度天然就低阈值一高就漏检。iou_thres是NMS去重的IoU阈值因为裂缝段相邻框之间有重叠这个值设0.45比较合适低于0.3会把相邻的裂缝段误判成同一个目标高于0.6又可能出现重复框。推理分辨率img_size设640是一个平衡点再高对小裂缝的召回会更友好但耗时上涨明显。如果你部署的设备是树莓派这类嵌入式平台可以降到416速度能快接近一倍代价是小裂缝的召回率下降。4.2 视频流和RTSP协议接入实际工程项目里裂缝检测不会只做离线图片更多场景是接实时视频流无人机挂载摄像头巡检桥梁、道路检测车顶置相机连续采集路面图像。Yolov5的detect.py支持--source直接传视频文件路径或者RTSP地址。但在代码层面复用时一般得自己处理帧率控制和抽帧逻辑。# rtsp_crack_detection.py 片段 import cv2 def process_stream(rtsp_url, detector, output_fps5): cap cv2.VideoCapture(rtsp_url) # 检查视频流是否正常打开 if not cap.isOpened(): print(无法连接视频流检查网络或RTSP地址格式) return # 原视频帧率 src_fps cap.get(cv2.CAP_PROP_FPS) frame_interval max(1, int(src_fps / output_fps)) frame_count 0 while True: ret, frame cap.read() if not ret: break if frame_count % frame_interval ! 0: frame_count 1 continue # 用训练好的模型检测单帧 results detector.model(frame) # 在原始帧上画框和置信度 rendered results.render()[0] # 这里把渲染后的帧交给GUI显示或推流到后端 # 例如写入管道队列由另一个线程做推流 frame_count 1 cap.release()output_fps是抽帧检测的帧率视频流连续检测对算力要求极高。一台落地RTX 3060的机器跑Yolov5s在640分辨率下大概能到30到40FPS但检测车高速行驶时相邻帧的裂缝几乎没变化5FPS的抽帧率足够覆盖。这段代码里最容易被忽略的是frame_interval的计算如果视频源是30FPS你想按5FPS检测就必须每6帧取一次否则检测结果在时间轴上是错乱的后面做裂缝定位拼接时位置会漂移。4.3 批量检测与结果导出批量检测一个巡检目录下的所有图片并按原路径保存结果这是日常工作中使用最频繁的功能。官方detect.py可以跑批量检测但输出目录是扁平化的当你要按相机编号、桩号、日期组织结果时扁平目录就很碍事。我通常用detect.py的批量模式加自定义的标签映射来适配业务要求。# 批量检测目录下所有.jpg图片 python detect.py \ --weights runs/train/crack_finetune/weights/best.pt \ --source ./field_images/ \ --img 640 \ --conf 0.3 \ --iou 0.45 \ --save-txt \ --save-conf \ --project ./detect_results \ --name field_20240618--save-txt会把每张图的检测结果保存为同名txt文件内容和训练标签的格式一致类别、坐标、置信度。--save-conf会把置信度也写进txt。这两个参数对后续的数据分析很重要你可以直接用脚本统计任意一个巡检批次里裂缝的总数、平均置信度、最大裂缝宽度近似用框高度替代。--project和--name是控制输出目录的方式结果会存放在./detect_results/field_20240618/labels/和./detect_results/field_20240618/下class.jpg是渲染了检测框的效果图。批量检测一个容易被忽略的问题是显存管理。如果你的巡检图像库有上万张图Yolov5预测时是累积批量推理的默认--batch-size是32意味着每次加载32张图进显存。一旦图片原始分辨率过大预处理阶段缩放到640仍然会占大量显存。我的做法是先把批量图片的路径分批每批1000张图跑一次detect.py输出到带批次号的目录跑完再合并。这个习惯帮我避免了好几次程序中途崩溃。5. 裂缝检测落地避坑七个反复踩的坑5.1 标注矩形框与裂缝走向角度不一致现象训练出的模型在验证集上mAP挺高一到实际图片上就出现斜裂缝检不出水平裂缝有重复框的情况。原因标注yolo格式的矩形框时框的方向必须是水平的。有些标注工具导出的框是带角度的旋转矩形框Yolov5不认旋转框会强行取外接水平矩形。对于倾斜45度以上的裂缝水平外接框里大部分区域其实是背景模型学到的特征是一条斜向纹理加两边混凝土泛化性极差。解决标注时尽量用正矩形框让框的短边垂直裂缝走向。如果裂缝在画面上是斜的可以把图片先旋转到裂缝接近水平或垂直再标注训练时模型会通过flipud、fliplr增强学到各个方向的特征不需要人为把旋转框也标注进训练集。5.2 loss降不下去且伴随NaN现象训练到第10轮左右loss突然变成NaN或者loss值一直维持在3以上不降。原因最常见的是学习率过大导致梯度爆炸其次是标注文件里出现归一化坐标小于0或大于1的异常值Yolov5在计算IoU时出现除以零或负数开根号还有一种情况是某张图片对应的txt标注文件为空文件但images里存在对应图片。解决先检查labels目录中txt的行数和非空文件数量是否与images一一对应。# 找出缺少标注的图片或空标注文件 find crack-data/labels/train -name *.txt -size 0 | head -20 # 有输出的话删除对应的images里的同名图片或者补标如果是梯度问题把冻结阶段的学习率从0.01降到0.005重试。如果还有NaN检查数据增强里的hsv参数饱和度扰动超过0.8以上时灰度图转换容易出现饱和度为0或亮度为0的极端像素造成梯度里的分母异常。5.3 训练集和验证集图像重合现象训练曲线一切正常但mAP高得离谱验证集上准确率超过0.98一换现实场景图片立刻掉到0.5以下。原因很多人做裂缝数据集时是从同一个视频里抽帧出来的相邻帧之间背景几乎一样。划分成训练集和验证集时如果用了随机划分同一段裂缝的不同帧会同时出现在两边模型等于直接背了答案。解决划分数据集前必须先按采集场景分组同一个视频、同一个桥墩、同一面墙的图片只能进训练集或验证集之一。我习惯的方式是先把所有图片按所属文件夹或拍摄时间段归类再一层一层抽取。5.4 桥梁裂缝过细640分辨率下完全丢失现象室内试件测试正常一到室外桥梁上就检不出裂缝。仔细看检测图片裂缝在640缩略图上肉眼几乎看不见。原因桥梁裂缝宽度经常是0.2毫米级别按常规拍摄距离裂缝在画面中只有2到3个像素宽。Yolov5在640分辨率下一个2像素宽的目标经过5次下采样到特征图上的响应值已经接近噪声水平。解决拍摄阶段尽量贴近结构表面保证裂缝在原始图片上的宽度在10个像素以上。如果已经是拍好的图片则把训练和推理的分辨率同时提高到960以上。这个操作会延长训练时间约一倍但裂缝的召回率提升幅度很大。有一个捷径路面裂缝检测可以先在1280分辨率下检测一次再把未检测区域切片成640大小进行第二次检测这样既有高召回率又可以控制单次推理耗时。5.5 模型把施工缝、伸缩缝当成裂缝现象检测结果里出现大量规则的水平长框画在桥梁伸缩缝或路面施工缝上但真正的开裂区域反而没有框。原因施工缝本质上是人为预留的拼接缝隙形态和裂缝几乎一样区别在于裂缝通常是局部断续的、宽度不均匀的而施工缝是一条规则通长线。模型学到的是线状深色区域裂缝无法区分结构缝和病害缝。解决数据层面在训练集里增加带施工缝的负样本图片标注时不标施工缝让模型看到有这个特征但不标框的样本。后处理层面过滤极端长宽比的框裂缝段的正常长宽比在3:1到10:1之间施工缝常常超过20:1可以在后处理时对长宽比超过阈值的框做二次检查。5.6 室外光照变化导致大量漏检和误检现象晴天中午检测效果良好到傍晚或树荫下漏检率升高到30%以上而且误检区域多集中在阴阳交界处。原因裂缝检测学的主要特征是局部像素灰度值比邻域低且呈线状延伸在强光照下裂缝和背景对比度很高模型很自信但一到阴影区域混凝土表面本身的明暗变化就淹没了裂缝的灰度差异模型特征失效。树荫下的斑驳光影也会形成线状纹理诱发误检。解决训练阶段加大hsv_v明度扰动到0.5以上好让模型见过更多亮度分布推理阶段对暗光图片先做一次CLAHE自适应直方图均衡化把局部对比度拉起来再进模型。这两个措施结合起来能把端侧场景的漏检率降到个位数。5.7 部署环境没有GPU时推理慢到不可用现象代码在带GPU的开发机上跑通了换到客户的笔记本上纯CPU跑单张图片推理时间超过5秒视频流根本带不动。原因Yolov5默认开FP16推理和GPU分支CPU上跑FP16不兼容会退回FP32速度急剧下降。另外纯CPU机器没有CUDA上下文每次推理时torch加载权重和建图的开销会重复计算。解决CPU推理时强制使用FP32推理关闭所有能关闭的预处理分支并且把模型转成torchscript格式。这样算力有限的设备上能压到1秒左右。# CPU推理优化片段 import torch model torch.hub.load(path/to/yolov5, custom, pathbest.pt, force_reloadTrue) model.cpu() model.half() # CPU上不要用half会把模型转成FP32 # 用torchscript加速以减少图构建开销 model torch.jit.load(best.torchscript.pt, map_locationcpu) model.eval()6. 裂缝模型再进一步剪枝、量化和半精度部署验证模型从训练到能交付中间还有一道部署验证的工序。我自己的做法是先把best.pt转成torchscript和ONNX分别在CPU和GPU上做几组对比实验看精度损失和速度收益是否匹配项目要求。裂缝检测应用里模型大小不是首要约束但推理延迟是。下面这三个手段是我在项目里反复用到的。第一是模型剪枝。Yolov5s只有700多万参数对裂缝这个单类任务来说冗余度高达60%以上。使用torch的pruning接口对C3结构中的卷积层按L1范数剪枝裁剪比例从0.3开始逐次递增0.1每次剪完在验证集上跑一次mAP掉点超过2%就回退到上一档。我的经验是剪到0.5左右模型大小从14MB降到9MB推理速度提升接近35%mAP几乎不降。第二是INT8量化。训练后量化不需要额外数据集直接拿200张验证集图片做校正集即可。量化后的模型在GPU上没有明显收益但在CPU上推理速度可以再提升一倍。要注意的是量化后的模型在低对比度裂缝上容易丢召回率交付前必须在实际巡检图片上重新评估。第三是半精度FP16推理。如果部署机上显卡支持FP16是最省事的加速手段——只要在detect.py里加--half参数或者在上述封装类里对模型调用model.half()。FP16推理对精度的影响在裂缝检测上几乎察觉不到但显存占用减半速度提升明显。# 转torchscript和ONNX python export.py \ --weights runs/train/crack_finetune/weights/best.pt \ --img 640 \ --batch 1 \ --include torchscript onnx \ --half # 导出FP16版本给GPU部署使用 # 转INT8量化模型需要额外一步 python export.py \ --weights runs/train/crack_finetune/weights/best.pt \ --img 640 \ --batch 1 \ --include onnx \ --int8转完格式之后一定要做精度对照。用同一批真实巡检图片在原始PyTorch模型和导出模型上分别跑一遍检测记录每张图的检测框数量和置信度分布偏差超过5%就要回退检查是不是量化校正集选得不对。我在做量化时试过用训练集图片做校正效果不如验证集表现为低置信度的裂缝目标全部消失后来换成验证集后恢复了。这个细节很少有人提及但确实能影响交付质量。最后提一个习惯永远保留训练完的best.pt和last.pt最好连训练时用的超参数文件一起归档。裁剪、量化或者换设备重新部署的时候直接拿best.pt重新导出不要拿一个已经量化过一次的模型再去做二次转换。量化损失是不可逆的二次叠加会让精度雪崩。我有一年做路面检测项目时为了图省事拿量化后的onnx又转了一遍torchscript结果交付现场误检爆炸最后重新从best.pt走完整流程才稳住。希望这个教训能帮你避开同样的坑希望帮到你。本文还有配套的精品资源点击获取