资讯动态

基于U-Net的路面裂缝检测识别系统设计与工程部署实践

发布时间:2026/9/12 2:30:39 来源:尧图企业网站定制
简介这是一套基于MATLAB的路面裂缝检测识别系统设计资源面向深度学习、图像处理方向的工程实践与课程设计可用于道路病害自动化检测和算法验证。压缩包内共18个文件以14个.m源码为主覆盖主程序、图像处理函数与裂缝判断模块另有1个.fig图形界面文件、测试图像和xls结果表整体大小仅268KB轻量易部署。项目中综合运用图像增强、直方图均衡化、噪声去除与分割预处理并构建卷积神经网络完成裂缝特征提取与类型判断从数据准备、模型训练与验证到GUI展示环环相扣。已有1053人学习代码模块化清晰Gui_Main等核心脚本可直接运行配套样例图片方便快速验证。对于希望掌握MATLAB深度学习应用、图像预处理流程及裂缝识别算法的读者是一份值得动手实践的完整参考。1. 路面裂缝检测识别系统设计先看像素再看模型市政道路巡检一天拍下几万张图像人工复核窗口里的裂缝往往只占几十个像素漏检率随疲劳快速上升。路面裂缝检测识别系统设计很容易被误带成“目标检测任务”但裂缝宽度在图上常常只有两到五个像素检测框与真实裂缝之间的IoU天然很小算出的指标和现场手感对不上。更稳健的路径是像素级分割加形态学后处理分割网络输出每个像素的裂缝概率后处理再提取骨架、计算长度与平均宽度。这条路径适合道路养护巡检、桥梁检测、智慧城市平台也适合拿到打包好的权重组、需要离线接入脚本的后端工程师。我把整个设计拆成数据准备、模型训练、后处理与zip交付、现场量化验证四段来讲参数给到可以直接替换的范围。2. 路面裂缝检测识别系统的数据准备与模型选型2.1 先用正样本占比决定训练策略拿到第一批标注数据先别急着写训练代码。裂缝是典型的小目标很多标注图的mask几乎全黑直接训练会得到一个“什么也不输出”的高准确率模型。最快验证数据质量的办法是统计正样本像素占比import cv2 import glob mask_list glob.glob(labels/train/*.png) for fp in mask_list[:30]: mask cv2.imread(fp, 0) ratio (mask 0).mean() print(f{fp}: {ratio * 100:.4f}%)mask 0把标注二值化mean()在这个二值图上等价于正样本比例。路面裂缝正常情况在0.05%到2%之间如果抽样统计普遍低于0.5%训练损失就不要用普通二分类交叉熵改为后面要讲的Dice组合损失否则模型只需把全图预测成背景就能骗到很高的准确率。这个比例同样决定输入分辨率。把1920×1080原图直接resize到256×256两像素宽的裂缝在输入里只剩亚像素信息分割网络不可能学出连续mask。我一般会先在原图上量10到20条裂缝的平均像素宽度再按输入尺寸与原始尺寸的缩放比例换算保证训练输入里裂缝至少保留一到两个像素。如果显存不允许就用随机crop代替整图resize。2.2 经典边缘检测与分割模型的边界在哪里在固定机位、固定光照的隧道或桥梁场景Canny加形态学处理仍然能跑而且零标注、推理快。但道路巡检车的光照、树影、水渍和标线残影会快速击穿固定阈值。深模型虽然需要标注和训练成本但对光照变化的鲁棒性明显更好。选型结论我常按下面这张表收拢方案适用场景标注需求主要问题Canny/阈值分割固定机位、夜间补光无阴影和水渍误报高目标检测YOLO等龟裂、坑槽等块状病害800张以上细裂缝IoU失真U-Net分割细裂缝与宽度统计300~500张后处理和部署链路长目标检测最大的麻烦不是精度而是输出格式。养护报告要写“某路段有一条长3米、平均宽2毫米的裂缝”检测框给不出宽度只能给一个与裂缝斜交的矩形。分割模型输出与像素对应的概率图骨架提取后能直接换算物理长度这是工程上选分割模型更常见的理由。2.3 最小可复现的数据目录与增强配置管线里的目录结构必须稳定否则训练脚本、打包脚本、推理脚本各指各的路径。最小结构如下crack_data/ ├── images/ │ ├── train/ │ ├── val/ │ └── test/ └── labels/ ├── train/ ├── val/ └── test/目录稳定后增强配置建议用albumentations统一管理避免手写numpy变换在训练和推理时行为不一致import albumentations as A from albumentations.pytorch import ToTensorV2 train_tf A.Compose([ A.Resize(288, 288), A.HorizontalFlip(p0.5), A.RandomBrightnessContrast( brightness_limit0.15, contrast_limit0.15, p0.3), A.RandomGamma(gamma_limit(80, 120), p0.3), A.GaussNoise(var_limit(10.0, 30.0), p0.2), A.Normalize(mean(0.485, 0.456, 0.406), std(0.229, 0.224, 0.225)), ToTensorV2(), ])亮度、对比度和噪声都模拟光照与相机传感器的波动横向翻转保留裂缝的方向语义不要随意做90度旋转否则横缝纵缝在模型里会被混成一团。最容易被忽略的是切分方式同一路段连续几帧图像相关性极高按图像随机切分会让验证集“记忆”训练集。要按路段ID或采集时间分组切分才能评估模型对新路面的泛化能力。另一个实战操作是把无裂缝图像按20%到30%比例混进训练集让模型见过足够多的负样本降低标线和路缘石的误报。3. 基于 U-Net 的路面裂缝检测识别系统训练与推理3.1 轻量 U-Net 的最小可运行结构路面裂缝是低对比度小目标完整模型结构可以很轻。与原始U-Net从64通道起步不同我一般从32通道起步减少显存占用并加快迭代。最小核心结构如下import torch import torch.nn as nn class DoubleConv(nn.Module): def __init__(self, in_ch, out_ch): super().__init__() self.conv nn.Sequential( nn.Conv2d(in_ch, out_ch, 3, padding1), nn.BatchNorm2d(out_ch), nn.ReLU(inplaceTrue), nn.Conv2d(out_ch, out_ch, 3, padding1), nn.BatchNorm2d(out_ch), nn.ReLU(inplaceTrue), ) def forward(self, x): return self.conv(x) class UNet(nn.Module): def __init__(self, in_ch3, out_ch1): super().__init__() self.enc1 DoubleConv(in_ch, 32) self.enc2 DoubleConv(32, 64) self.enc3 DoubleConv(64, 128) self.pool nn.MaxPool2d(2) self.bridge DoubleConv(128, 256) self.up3 nn.ConvTranspose2d(256, 128, 2, stride2) self.dec3 DoubleConv(256, 128) self.up2 nn.ConvTranspose2d(128, 64, 2, stride2) self.dec2 DoubleConv(128, 64) self.up1 nn.ConvTranspose2d(64, 32, 2, stride2) self.dec1 DoubleConv(64, 32) self.outc nn.Conv2d(32, out_ch, 1) def forward(self, x): e1 self.enc1(x) e2 self.enc2(self.pool(e1)) e3 self.enc3(self.pool(e2)) b self.bridge(self.pool(e3)) d3 self.dec3(torch.cat([self.up3(b), e3], dim1)) d2 self.dec2(torch.cat([self.up2(d3), e2], dim1)) d1 self.dec1(torch.cat([self.up1(d2), e1], dim1)) return self.outc(d1)padding1让卷积不改变特征图尺寸编码器和解码器在concat时尺寸对齐最后一层输出单通道logits在损失函数里做sigmoid不要在这里提前做阈值化。三层下采样的感受野对于288×288输入足够覆盖裂缝周围的纹理区域若用512×512输入可以保持相同深度不必再加池化层。3.2 损失函数与评价指标怎么配这类分割任务常用bce加dice的组合下面这段可以直接用import torch.nn.functional as F def dice_loss(pred, target, smooth1e-5): p torch.sigmoid(pred) intersection (p * target).sum() union p.sum() target.sum() return 1 - (2 * intersection smooth) / (union smooth) def combined_loss(pred, target): bce F.binary_cross_entropy_with_logits(pred, target) return bce dice_loss(pred, target)smooth1e-5只是防除零不要设成0.1这类大值否则小目标裂缝的梯度会被平滑项稀释。intersection用概率与二值标签相乘得到相当于软统计比硬阈值方式更平滑训练早期不会因为一个像素的翻转带来剧烈梯度。训练过程中别只看loss分割任务要同时看IoU和F1这两个指标才能反映细裂缝召回情况def prediction_metrics(pred_mask, gt_mask, thr0.4): p pred_mask thr inter (p gt_mask).sum() union (p | gt_mask).sum() iou inter / max(float(union), 1e-6) f1 2.0 * inter / max(float(p.sum() gt_mask.sum()), 1e-6) return iou, f1pred_mask thr产生布尔数组后直接做位运算max(..., 1e-6)是为了防止全零mask出现除零。评估用的阈值需要和后续推理保持一致最好在验证集上先用0.3到0.5之间搜索F1最高点再固定为报告阈值。3.3 训练参数表与收敛观察点一个最小训练命令如下参数含义落在表里python train.py --data crack_data --img-size 288 --batch-size 16 --epochs 100 --lr 1e-3参数推荐范围说明img-size288 ~ 512小于224容易丢细裂缝大于512显存开销大batch-size8 ~ 16按显存降级低于4时先调小输入尺寸lr1e-3 ~ 3e-4Adam搭配余弦退火epochs60 ~ 120数据量小反而要多跑配合早停看val_f1这里要特别提醒前10到20个epoch的验证IoU波动很正常不用急着调参如果10个epoch内F1一直贴着0优先检查标签对齐和数据集切分而不是模型结构。另一个高频问题是验证时用固定阈值0.5而推理时实际用0.35到0.45前后不一致导致报告的指标不可信。提示prediction_metrics里的thr要和实际推理阈值为同一个值评估脚本里不要写第二份阈值逻辑减少维护成本。3.4 推理脚本滑窗切图与重叠拼接整图resize到288在4K图上会丢掉细裂缝推理时我一般用滑窗切图、重叠拼接平均。核心逻辑如下import cv2 import numpy as np import torch def pred_patch(model, patch, device): t torch.from_numpy(patch).permute(2, 0, 1).unsqueeze(0).float() / 255.0 with torch.no_grad(): out torch.sigmoid(model(t.to(device))) return out.squeeze().cpu().numpy() def tile_infer(model, image, device, tile512, stride384, thr0.4): h, w image.shape[:2] pad_h (tile - h % tile) % tile pad_w (tile - w % tile) % tile if pad_h or pad_w: image cv2.copyMakeBorder( image, 0, pad_h, 0, pad_w, cv2.BORDER_REFLECT) H, W image.shape[:2] prob np.zeros((H, W), dtypenp.float32) weight np.zeros((H, W), dtypenp.float32) ys list(range(0, H - tile, stride)) [H - tile] xs list(range(0, W - tile, stride)) [W - tile] for y0 in ys: for x0 in xs: patch image[y0:y0 tile, x0:x0 tile] prob[y0:y0 tile, x0:x0 tile] pred_patch(model, patch, device) weight[y0:y0 tile, x0:x0 tile] 1 valid weight 0 prob[valid] / weight[valid] mask (prob thr).astype(np.uint8) return mask[:h, :w]stride小于tile是刻意制造重叠区重叠处多次平均后可以避免分块边界出现明显接缝。ys和xs末尾追加H - tile和W - tile是为了保证图像尾部不会被漏掉。对细裂缝建议tile512, stride384对龟裂等大块病害可以把stride提高到tile - 64减少重复计算。BORDER_REFLECT用于填充边缘最后mask[:h, :w]把填充部分裁掉。4. 路面裂缝检测识别系统的后处理与 zip 交付工程4.1 骨架提取与裂缝长度、平均宽度统计养护业务要的长度、宽度不能从概率图直接读需要先做骨架提取。骨架的每个白点可以理解为长度方向上的一个像素把骨架点计数作为长度像素数再除以裂缝面积得到平均宽度的像素值。import cv2 import numpy as np def crack_metrics(mask, pixel_per_mm5.0): binary (mask 0).astype(np.uint8) * 255 skeleton cv2.ximgproc.thinning(binary) length_px int((skeleton 0).sum()) area_px int((binary 0).sum()) avg_width_px area_px / max(length_px, 1) return { length_mm: length_px / pixel_per_mm, area_mm2: area_px / (pixel_per_mm ** 2), avg_width_mm: avg_width_px / pixel_per_mm, }cv2.ximgproc.thinning需要安装opencv-contrib-python普通的opencv-python里没有 ximgproc 模块。pixel_per_mm来自相机标定常见路面检测相机在3米拍摄高度下约为3到8像素/毫米它不是固定值需要在现场用棋盘格或已知尺寸的标定线换算。面积除以长度得到的是平均宽度如果裂缝分叉很多骨架长度会被展开平均宽度会比主缝实际值偏小这时最好额外统计宽度分布而不是只看均值。4.2 形态学后处理的三个常用参数经验分割输出直接过骨架提取会带出大量噪点后处理顺序建议先开运算去掉孤立点再按连通域过滤小目标最后闭运算连接断缝。顺序不能颠倒先做闭运算会把小噪点也连进裂缝区域。参数常用范围作用踩坑点开运算kernel3×3 ~ 7×7消除孤立噪点大于7会吃掉细裂缝连通域面积阈值10 ~ 50 px过滤撒点状误检细裂缝断点处易被误删闭运算kernel3×3 ~ 5×5连接小间断过大把两条平行缝连成一条处理时可以与推理阈值联动当推理阈值在0.35附近时误检点多开运算kernel选大一些比如5×5或7×7当阈值在0.45以上时误检点少kernel退回到3×3。业务只关注长度超标的裂缝时也可以在连通域阶段加一个长度下限少于20像素的目标直接丢弃减少后续骨架计算量。4.3 用 zip 把模型、配置与脚本打成可交付包现场环境常常断网或不允许git clone因此zip是更常见的交付载体。交付前在干净目录里收拢模型权重、配置和推理脚本不要把训练数据也打包进去。打包流程如下mkdir -p crack_system/{model_weight,config,scripts} cp exp/best_model.pt crack_system/model_weight/ cp config/inference.yaml crack_system/config/ cp infer.py crack_system/scripts/ find crack_system -name __pycache__ -type d -exec rm -rf {} \; zip -r crack_system_v1.0.zip crack_systemzip -r递归压缩目录加-0可以关闭压缩模型权重本身已经是压缩格式现场解压速度要求高时关闭压缩能明显缩短搬迁时间。交付包内要带requirements.txttorch、opencv-contrib-python等包版本至少锁主版本避免现场和训练环境行为不一致。目标机安装和部署可以一条命令链走完python -m venv venv source venv/bin/activate pip install -r requirements.txt unzip -q crack_system_v1.0.zip -d /opt/crack_system cd /opt/crack_system/scripts python infer.py --config ../config/inference.yamlunzip -q减少输出-d指定解压目录脚本建议接受--config参数而不是把路径硬编码同一个zip给多个项目用时只需替换配置文件。如果现场完全隔离不能在线pip install就在有网环境用pip download -r requirements.txt -d wheels/拉取依赖再把 wheels 目录一并打包。提示中文文件名在zip里出现乱码时优先检查解压工具的编码设置在Linux下用zip命令打包默认按UTF-8写入Windows老式资源管理器可能显示异常但解压后路径不影响程序读取。4.4 解压失败与运行报错的几个排查点现场最常见的几类错误按优先级排查第一invalid zip archive: could not find eocd提示压缩包尾部缺少结束记录通常是传输过程中文件被截断或磁盘写入中断。先执行unzip -t crack_system_v1.0.zip测试完整性如果报错重新从打包机拷贝。大文件存放时间较长再出现该报错时还要检查存储介质别只重传一次就完事。第二解压顺利但import cv2报错。这多半是现场装了opencv-python而脚本使用了cv2.ximgproc解决方式是改装opencv-contrib-python。如果项目还引用了cv2.dnn等模块也要注意两个包的版本差异。第三zip设了密码的情况下现场运维往往不知道把密码放在配置还是环境变量里且zip密码暴力破解成本低。敏感模型不建议依赖zip加密更稳妥的是在权重加载处增加授权校验让交付包即使被复制也无法直接使用。这样既绕开密码管理也让模型保护逻辑更可控。5. 路面裂缝检测识别系统的现场量化验证与迭代技巧5.1 按路段输出验证表现场验证不等于看几张可视化图要让脚本扫完测试集输出每张图的IoU、F1、预测裂缝长度和标注长度再按路段聚合。常见验证命令如下python eval.py \ --images test/images --masks test/labels \ --thr 0.4 --out result/result/下会生成metrics.csv每行包含图像编号、路段ID、F1、IoU、预测长度和标注长度。统计时要按路段而不是按图像取平均避免同一次采集序列的重复图像抬高指标。若某路段F1明显低于整体先确认它是否和训练集同一路段相邻帧如果是那就是光照或拍摄角度产生了新分布应该把这批数据追加进训练集而不是改测试集。5.2 逆光阴影下的预处理技巧树影和逆光造成的低对比度区域分割模型常出现碎块漏报。推理前对原图做一次CLAHE增强能明显改善裂缝与背景的对比度代价是噪声也会增强。预处理代码如下img cv2.cvtColor(bgr_img, cv2.COLOR_BGR2LAB) l, a, b cv2.split(img) clahe cv2.createCLAHE(clipLimit2.0, tileGridSize(8, 8)) l clahe.apply(l) img cv2.cvtColor(cv2.merge([l, a, b]), cv2.COLOR_LAB2BGR)clipLimit2.0在保持路面纹理的同时提升对比度雨天高噪声时可以降到1.5否则噪声颗粒会被放大成伪裂缝。如果模型训练时已经加入亮度扰动和随机gamma现场不一定需要这套预处理需要把有无CLAHE的指标对比后再决定是否保留。同一套模型跑不同路段时建议按路段分别存推理阈值阈值0.35适合细裂缝多、误报容忍度高的路段0.45适合标线清晰、要求减少人工复核量的路段。推理脚本读取图像所属路段ID后选用对应阈值而不是全局只用一个值。每次现场复测后把低于阈值的路段图像和对应的指标写回训练集清单在下一次迭代时按路段追加训练不要做全局随机切分。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价