简介这是一份以MATLAB和Python为工具面向路面裂缝检测系统设计的计算机视觉与深度学习实战教程。内容从公路养护中的真实需求切入剖析传统人工巡检效率低、主观影响大等问题并系统讲解如何借助图像预处理、灰度化、均值/中值滤波、对比度增强等手段提升裂缝特征可识别性。其中重点覆盖了图像灰度化的多种计算方式、频域变换、直方图均衡化、图像去噪与锐化等预处理手段以及CNN模型在裂缝检测中的部署思路。案例涵盖数据集准备、模型选择与训练、系统测试及优化等完整流程可直接用于课程设计或工程项目参考。资源为PDF文档共1个文件压缩包大小约1.46MB目前已有726人学习浏览适合交通工程、道路养护、图像处理及深度学习入门与进阶读者学习使用。1. 路面裂缝检测系统要解决什么问题为什么这项工程值钱又难做路面裂缝检测不是新鲜的科研题目但一直到今天很多养护单位还在靠人眼巡路、拿纸笔记录裂缝位置和长度。一套靠谱的计算机视觉裂缝检测系统要能在行车视频或路面照片里自动找出裂缝、量出大概的长度和宽度、标出严重程度。这就牵扯到图像分割、目标识别、深度学习模型训练和落地部署一整条链路。以MATLAB和Python为工具来搭这套系统是工程里非常务实的组合MATLAB负责原型验证和图像预处理Python承担深度学习的训练和推理两边各有长处配合得当能省大量时间。我见过不少做这个方向的初学者一上来就直接拿U-Net训练结果数据没整理好、标签画得乱、正负样本失衡模型怎么调都出不来效果。这个题目真正的难点并不在“用哪个网络”而在数据怎么标、patch怎么切、增强怎么做、模型输出怎么后处理。这篇文章就沿着“环境搭法→数据准备→模型训练→避坑→系统封装”这条线把一套能复现的完整方案讲清楚。2. MATLAB与Python双工具链怎么选预处理和训练各干各的少走弯路2.1 为什么不是二选一MATLAB做验证、Python做训练的分工逻辑很多人在搭环境时纠结一个问题既然深度学习基本都在Python里做那还要MATLAB干什么我的观点很直接——MATLAB在路面裂缝检测这个场景里最值钱的地方是图像预处理原型验证和快速看结果。裂缝图像有很强的特殊性光照不均、阴影干扰、路面纹理杂乱、裂缝对比度低。这些问题的处理方法比如顶帽变换、灰度校正、频域滤波MATLAB图像处理工具箱一套下来非常顺手代码短、可视化强适合把预处理链路先跑通。Python这边负责的是深度学习部分。PyTorch或者TensorFlow的生态优势不用多说尤其是数据增强、分布式训练、模型导出ONNX这些环节社区资源远比MATLAB丰富。而且说实话现在做深度学习模型训练绝大多数现成代码都是Python的直接用比搬运到MATLAB省太多事。我给的建议是预处理和可视化在MATLAB里做模型训练和推理放到Python里做。两者通过文件交换接起来——MATLAB导出处理后的图像Python读入训练。环节推荐工具理由图像读取、灰度化、顶帽变换MATLAB代码短、能边看边调imtophat一行搞定标注、切片、数据增强Python OpenCV增强库丰富能和训练流程无缝衔接模型定义、训练Python PyTorch生态好、现成U-Net代码多模型推理、裂缝长度宽度统计Python 为主批量处理方便也能调回MATLAB做复现展示2.2 MATLAB侧预处理代码灰度化、顶帽变换与裂缝对比度增强路面裂缝图像最让人头疼的问题就是背景太乱。沥青路面的石子纹理、阴影、车辙印都会干扰裂缝提取。常见做法是先做顶帽变换Top-hat它的作用是用形态学开运算估计背景再从原图里减去背景把比背景暗的细小结构裂缝单独凸显出来。% 读取路面图像 img imread(road_001.jpg); if size(img, 3) 3 gray rgb2gray(img); % 转灰度 else gray img; end % 顶帽变换提取暗裂缝结构 se strel(disk, 15); % 结构元素半径15按裂缝宽度调整 tophatImg imtophat(gray, se); % 对比度拉伸增强暗裂缝区域 enhanced imadjust(tophatImg); % 保存处理后图像供Python侧训练使用 imwrite(enhanced, road_001_enhanced.png);这段代码里strel(disk, 15)的半径是核心参数。半径太小会把路面颗粒噪声也当背景减掉噪声放大得厉害半径太大又会把裂缝当成背景的一部分削弱裂缝信号。以路面裂缝宽度3到8个像素为例结构元素直径设为裂缝宽度的2到4倍通常效果好。imadjust是自动对比度拉伸比手动设阈值更稳。2.3 Python侧环境配置从零到能跑U-Net的最小安装路线Python环境我建议用Anaconda管理省去一堆版本冲突的麻烦。vscode里的Python环境配置是常见问题conda创建虚拟环境之后在vscode里选解释器要选对不然容易用错环境。# 创建虚拟环境python版本3.9比较稳 conda create -n crack python3.9 -y conda activate crack # 安装深度学习依赖 pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118 pip install opencv-python numpy pandas matplotlib tqdm pip install albumentations scikit-learn # 验证环境 python -c import torch; print(torch.__version__, torch.cuda.is_available())这里有几个点要提醒。第一PyTorch的CUDA版本要和本机显卡驱动匹配装完一定要跑最后一行验证torch.cuda.is_available()返回False说明CUDA没配好后面训练会慢到怀疑人生。第二Windows系统下中文路径经常让OpenCV读图报错训练数据路径最好全部用英文。2.4 双工具链的数据交接约定MATLAB处理完图片Python怎么知道哪些图是训练集、哪些是验证集我的约定是输出目录里直接分好 train/valid/test 三个子文件夹图片命名带编号另存一份 index.csv 记录来源和标签。这个约定简单粗暴但非常有效两边不会因为路径、命名问题扯皮。# 目录结构示例 datasets/ ├── train/ │ ├── images/ # MATLAB增强后的路面图 │ └── masks/ # 标注的裂缝掩膜 ├── valid/ │ ├── images/ │ └── masks/ └── test/ ├── images/ └── masks/记得在交接时统一图片格式和位深。我遇到过MATLAB保存的PNG是16位Python端OpenCV读进来直接变成全黑的情况。统一用8位灰度PNG输出两边都不会出问题。3. 路面裂缝数据集从哪来、怎么处理标注规范、图像裁剪与数据增强的一套实操3.1 公开数据集怎么选CrackForest、Crack500这些名字背后的差异路面裂缝数据集在学术圈有不少公开资源常见的有CrackForestCFD、Crack500、DeepCrack、GAPs等。CFD主要面向城市道路沥青路面图片分辨率中等裂缝类型以横向、纵向和块状为主规模不算大大概百来张。Crack500是从路面图像里切出来的500张左右裂缝patch分辨率较高更贴近车载相机实拍场景。DeepCrack是像素级标注的裂缝数据集质量高但数据量也不大。选数据集的思路是先别贪多。拿CFD加一部分Crack500混合先跑通整个流程比一开始就找一个几百G的数据集要实际。数据集的标注格式也要重点看有的是单像素骨架标注有的是带状掩膜。骨架标注适合做裂缝检测找出位置带状掩膜适合做裂缝分割量宽度——这两种用途的模型训练目标不一样后期评估方式也不一样。3.2 自己标注用MATLAB Image Labeler还是LabelMe如果项目要求针对特定路段检测公开数据不够用就得自己标注。常见标注工具有MATLAB的Image Labeler、Python生态的LabelMe、以及深度学习专用的CVAT。我自己用下来小规模数据快速验证用MATLAB Image Labeler批量标注用LabelMe。# 将LabelMe导出的json掩膜批量转为与图同名的灰度标签图 import json import numpy as np import cv2 import os def json_to_mask(json_path, img_shape, out_path): with open(json_path, r) as f: data json.load(f) mask np.zeros((img_shape[0], img_shape[1]), dtypenp.uint8) for shape in data[shapes]: points np.array(shape[points], dtypenp.int32) # 裂缝标注区域填255其他类别可按需分配不同灰度值 cv2.fillPoly(mask, [points], color255) cv2.imwrite(out_path, mask)这段代码把LabelMe的json标注转成mask图。填图的顺序有讲究必须先填大的区域类别如背景杂物再填裂缝类别否则后续类别会覆盖前面的标注。mask里裂缝像素为255背景为0这个二值约定在后面写损失函数时很好用。3.3 图像裁剪成patch全图训练为何不现实大部分路面检测系统的输入是整张大图——几千乘几千像素很常见。直接把整图塞进U-Net训练显存会溢出而且一张图里裂缝占比极小模型很容易学到“全部输出背景”的偷懒解。常见做法是把大图裁剪成256×256或512×512的patch切片时要有重叠防止裂缝正好在切缝处被截断。# 用Python滑窗切patch带重叠 python cut_patches.py --input datasets/train/images --patch-size 512 --stride 256stride设为patch大小的一半是经验值。重叠率太低裂缝可能被切掉一半导致标签残缺重叠率太高数据量膨胀得太严重训练时间翻倍。50%重叠是性价比最高的设置。而且切patch的时候要把原图和mask一起切用相同的偏移量。常见错误是只切原图不切mask或者原图和mask的坐标对不上模型训练出来的结果直接错位。3.4 数据增强裂缝检测里哪些增强有效、哪些反而有害路面裂缝图像的一个特点是很死板——路面的朝向、光照角度相对固定。训练深度学习模型时数据增强做得合适能把模型泛化性拉高一大截。import albumentations as A # 适合裂缝图像的增强组合 train_transform A.Compose([ A.RandomRotate90(p0.5), # 裂缝方向不固定旋转必须有 A.HorizontalFlip(p0.5), A.VerticalFlip(p0.3), A.RandomBrightnessContrast( brightness_limit0.15, contrast_limit0.15, p0.8 ), # 模拟不同光照条件 A.GaussNoise(var_limit(10.0, 40.0), p0.3) # 模拟路面噪声 ])旋转和翻转本来是通用增强但在裂缝检测里格外重要——裂缝有横向、纵向、斜向、网状方向不增广模型就会偏向训练集里的主方向。亮度对比度的随机变化能模拟一天中不同时段的拍摄条件这对最终落地非常关键。相比之下弹性形变ElasticTransform要慎用——裂缝本身是细线结构过度形变会让裂缝扭曲失真模型学到的可能是变形的假裂缝。4. 训练一个可用的裂缝分割模型CNN选型、训练参数与IoU评估的完整步骤4.1 网络怎么选分割任务用U-Net还是DeepLabV3裂缝检测本质上是一个像素级二分类分割问题——每个像素要么是裂缝要么是背景。这不是目标检测能搞定的因为裂缝没有规整的边界框所以分割网络是标配。在这个领域里U-Net是首选的baseline结构简单、易于训练、小样本表现也不错DeepLabV3理论上感受野更大但对小目标和细线结构有时反而不如U-Net灵活。我用PyTorch复现一个轻量U-Net结构上做了一些针对裂缝的调整输入是单通道灰度图输出用sigmoid预测裂缝概率因此网络最终输出单通道的置信度图。import torch import torch.nn as nn class ConvBlock(nn.Module): def __init__(self, in_ch, out_ch): super().__init__() self.block 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.block(x) class LightUNet(nn.Module): def __init__(self, in_ch1, out_ch1, base32): super().__init__() # 下采样路径通道数逐级翻倍 self.enc1 ConvBlock(in_ch, base) self.enc2 ConvBlock(base, base * 2) self.enc3 ConvBlock(base * 2, base * 4) self.pool nn.MaxPool2d(2) # 中间瓶颈层 self.mid ConvBlock(base * 4, base * 8) # 上采样路径每次先转置卷积再跳连拼接 self.up3 nn.ConvTranspose2d(base * 8, base * 4, 2, stride2) self.dec3 ConvBlock(base * 8, base * 4) self.up2 nn.ConvTranspose2d(base * 4, base * 2, 2, stride2) self.dec2 ConvBlock(base * 4, base * 2) self.up1 nn.ConvTranspose2d(base * 2, base, 2, stride2) self.dec1 ConvBlock(base * 2, base) self.out nn.Conv2d(base, out_ch, 1) def forward(self, x): e1 self.enc1(x) e2 self.enc2(self.pool(e1)) e3 self.enc3(self.pool(e2)) m self.mid(self.pool(e3)) d3 self.up3(m) d3 torch.cat([d3, e3], dim1) # 拼接同尺寸跳连 d3 self.dec3(d3) d2 self.up2(d3) d2 torch.cat([d2, e2], dim1) d2 self.dec2(d2) d1 self.up1(d2) d1 torch.cat([d1, e1], dim1) d1 self.dec1(d1) return torch.sigmoid(self.out(d1))我把基础通道数base设成了32相比标准U-Net的64通道参数量小很多训练速度快在路面裂缝这个小样本场景下不容易过拟合。BatchNorm加在卷积和ReLU之间能明显加速收敛。最后输出接sigmoid预测值范围在0到1之间这样和BCE Loss配合是顺的。4.2 损失函数为什么用BCEDice而不是纯交叉熵裂缝分割最大的问题是正负样本极度失衡。一张512×512的patch里裂缝像素往往只占0.5%到2%。如果直接用二元交叉熵BCE模型会把所有像素预测为背景loss一样很低——因为background占98%以上这个“偷懒解”在数值上完全合理。解决方案是混合损失BCE负责像素级准确性Dice负责缓解类别不平衡。Dice系数衡量预测分割和真实标注的重叠程度它对小目标更敏感。公式是2倍交集除以并集的补集网络优化它等于强迫模型关注裂缝区域。import torch.nn.functional as F def bce_dice_loss(pred, target, smooth1.0): # pred: [B,1,H,W] 已经过sigmoid # target: [B,1,H,W] 取值0或1 bce F.binary_cross_entropy(pred, target) pred_flat pred.view(pred.size(0), -1) target_flat target.view(target.size(0), -1) intersection (pred_flat * target_flat).sum(dim1) dice 1 - (2.0 * intersection smooth) / ( pred_flat.sum(dim1) target_flat.sum(dim1) smooth ) return bce dice.mean()bce dice.mean()这个加法里有个细节——两个loss的量级要接近否则一个压过另一个。实测下来BCE大约在0.1到0.3之间Dice在0.5到0.9之间直接相加模型更倾向于优化Dice这反而是我们想要的。4.3 训练配置学习率、批大小、早停策略的一次完整说明训练配置有几个参数很关键batch_size、learning_rate、epochs、patience。以512×512的patch为例batch_size在8到16之间比较合理再大显存先撑不住。学习率初始1e-3或1e-4配合ReduceLROnPlateau动态下降比固定学习率好很多。from torch.utils.data import Dataset, DataLoader import torch.optim as optim # 数据集和训练循环示意 model LightUNet(in_ch1, out_ch1).cuda() optimizer optim.AdamW(model.parameters(), lr1e-3, weight_decay1e-4) scheduler optim.lr_scheduler.ReduceLROnPlateau( optimizer, modemin, factor0.5, patience5 ) best_iou 0.0 patience_counter 0 for epoch in range(100): model.train() train_loss 0.0 for imgs, masks in train_loader: imgs, masks imgs.cuda(), masks.cuda() preds model(imgs) loss bce_dice_loss(preds, masks) optimizer.zero_grad() loss.backward() optimizer.step() train_loss loss.item() # 每个epoch结束在验证集上评估 val_loss, val_iou evaluate(model, val_loader) print(fEpoch {epoch}: loss{train_loss:.4f}, val_iou{val_iou:.4f}) scheduler.step(val_loss) # 保存最优模型 if val_iou best_iou: best_iou val_iou torch.save(model.state_dict(), best_crack_unet.pth) patience_counter 0 else: patience_counter 1 if patience_counter 15: print(早停触发训练结束) break早停的patience设为15个epoch比较稳。路面裂缝数据量一般不大模型训练到50到80个epoch就收敛了一直跑满100个epoch是浪费时间。4.4 评估指标怎么算IoU、F1-Score、PA的差别要认清模型训练完之后光看loss下降是不够的必须用IoU交并比判断质量。IoU计算的是预测裂缝区域和真实裂缝区域的重叠程度。裂缝检测领域常用IoU、F1-Score和PAPixel Accuracy三个指标但它们的意义完全不同。指标计算方式在裂缝检测里的含义IoU交集 / 并集预测裂缝区域与真实裂缝区域的重叠度最严格F1-Score2×Precision×Recall / (PrecisionRecall)查准和查全的平衡适合评估误检率PA分类正确的像素 / 总像素容易被背景像素淹没裂缝检测里参考价值低def compute_iou(pred_mask, true_mask, threshold0.5): # pred_mask: 网络输出的概率图true_mask: 二值标签 pred_bin (pred_mask threshold).astype(np.uint8) true_bin (true_mask 0).astype(np.uint8) intersection np.logical_and(pred_bin, true_bin).sum() union np.logical_or(pred_bin, true_bin).sum() return intersection / (union 1e-6)threshold0.5是最常用的判定阈值但实际项目里我会在0.3到0.7之间扫一遍选验证集上F1最高那个阈值。裂缝检测的误判代价和漏检代价常常不一样用于养护决策根基是不要漏掉大裂缝这时候把阈值压到0.3到0.4反而更合理。5. 路面裂缝检测深度学习的避坑清单5个让模型翻车的工程细节5.1 标注的mask里裂缝粗细不一致宽度根本无法统计很多标注工程师凭感觉画裂缝有时候画2个像素宽有时候画10个像素宽。模型学到的裂缝宽度完全取决于标注者的手艺导致预测结果时粗时细后期统计裂缝宽度完全没有意义。解决方法是统一标注规范先用骨架提取算法或者人工描出裂缝中心线然后统一向外扩展固定像素作为带宽。我一般固定带宽为5个像素这样至少宽度统计在数据层面是自洽的。5.2 顶帽变换后的图直接丢给网络训练模型学到的是“增强痕迹”而不是裂缝这是我自己踩过的坑。MATLAB里做完顶帽变换图像会整体偏灰、对比度很强但直接拿这个处理图训练模型很容易学会识别“增强后图像的灰度模式”而不是裂缝本身。解决方法是把增强后的图像和原图组合成多通道输入或者更简单——在训练时同时做随机直方图均衡化让模型见到更多灰度分布。5.3 训练集和验证集没按路段划分评估分数虚高如果随机把所有图片按7:3切分可能会出现同一段路的图既出现在训练集也出现在验证集里。模型等于提前看过答案验证IoU能到0.85实际换一段新路直接跌到0.4。正确做法是按拍摄道路的路段划分——保证验证集里的任何一张图都不和训练集来自同一段路面这样做出来的评估才代表真实的泛化能力。5.4 MATLAB中文注释乱码代码在电脑之间反复拷最终变“乱码文件”MATLAB的老版本对UTF-8支持不佳中文注释在Windows中文系统上经常出现乱码尤其是用Git或者网盘同步项目文件时一旦编码被改动整个脚本注释全花代码几乎没法维护。我的做法是脚本里一律用英文注释中文只写在单独的说明文档里。这个习惯看起来很蠢但我见过好多团队在编码问题上耗掉大把时间。5.5 验证时IoU很高一放到实际车载视频里就翻车训练数据大多是正午、天气好的时候拍的换上阴天、晚上或者逆光的图模型就开始乱检。这属于典型的域偏移Domain Shift问题。解决思路是在数据增强里加随机灰度扰动和局部亮度遮挡模拟树荫、车影压过路面的效果。另外我在项目里还会留出10%的“脏数据”作为测试集——就是故意拍一些带油污、水渍、轮胎印的路面看模型的误检率能不能接受。6. 把模型变成裂缝检测系统批处理推理、后处理与量化评估的进阶做法6.1 先写一个靠谱的推理脚本再用它处理整条路的视频帧训练好的U-Net只是模型文件离“能用”还差一个推理管线。推理时要把大图切patch、逐patch预测、再拼回原图这个环节有个大坑patch与patch之间的重叠区域预测结果要取平均不然拼接缝处会出现明显的方块痕迹。我一般是在重叠区域用加权平均权重按离patch中心的距离自动衰减。import torch import numpy as np import cv2 def sliding_predict(model, img, patch_size512, stride256, devicecuda): 滑窗推理大图切patch逐块预测重叠区域加权平均拼接 model.eval() h, w img.shape[:2] score_map np.zeros((h, w), dtypenp.float32) weight_map np.zeros((h, w), dtypenp.float32) # 生成二维权重掩膜patch中心权重高边缘权重低 w_1d np.hanning(patch_size).astype(np.float32) patch_weight np.outer(w_1d, w_1d) with torch.no_grad(): for y in range(0, h - patch_size 1, stride): for x in range(0, w - patch_size 1, stride): patch img[y:ypatch_size, x:xpatch_size] patch_t torch.from_numpy(patch).float().unsqueeze(0).unsqueeze(0) patch_t patch_t.to(device) pred model(patch_t).squeeze().cpu().numpy() weight patch_weight score_map[y:ypatch_size, x:xpatch_size] pred * weight weight_map[y:ypatch_size, x:xpatch_size] weight # 归一化并转为0-255的mask score_map / (weight_map 1e-6) mask (score_map 0.5).astype(np.uint8) * 255 return masknp.hanning(patch_size)生成的窗口函数中间是1、两端靠近0这样重叠区域拼接时不会出现硬边界。这个细节决定了最终输出是“干净的一张裂缝图”还是“满是网格线的废图”。6.2 后处理比模型更影响落地效果形态学闭运算与连通域过滤模型输出的mask一般都有很多小噪点还有因为裂缝断开导致的碎片。后处理两步走先形态学闭运算把断裂的裂缝连起来再按连通域面积过滤掉小噪点。import cv2 def postprocess(mask, close_kernel9, min_area50): mask: 模型输出的二值图 close_kernel: 闭运算核大小 min_area: 小于该面积的连通域直接删除 # 闭运算先膨胀再腐蚀连接断裂的裂缝 kernel np.ones((close_kernel, close_kernel), np.uint8) closed cv2.morphologyEx(mask, cv2.MORPH_CLOSE, kernel) # 连通域过滤小噪声 num_labels, labels, stats, _ cv2.connectedComponentsWithStats(closed, connectivity8) result np.zeros_like(mask) for i in range(1, num_labels): if stats[i, cv2.CC_STAT_AREA] min_area: result[labels i] 255 return result闭运算的kernel大小要看训练时图片的分辨率。512×512 patch上9×9的kernel适中如果图像分辨率更高kernel可以加大到15×15。min_area的设定我一般取经验值50——也就是小于50个像素的连通域基本是噪点。这个值太小过滤不干净太大把细小裂缝删掉就得不偿失了。6.3 量化评估裂缝长度与宽度怎么从mask里算出来系统交付时用户不是要一张图而是要“这条路有多少裂缝、多长、多宽”。从二值mask里计算长度和宽度的标准做法是先骨架化提取裂缝中心线再统计中线上每个像素到最近边缘的距离乘以距离的两倍就是该点的裂缝宽度。from skimage.morphology import skeletonize from scipy.ndimage import distance_transform_edt def measure_crack(mask): 输入二值mask输出裂缝总长度(像素)和平均宽度(像素) mask_bin mask 0 # 骨架提取裂缝中心线 skeleton skeletonize(mask_bin) # 计算每个前景像素到背景的最近距离骨架上的距离×2就是宽度 distance distance_transform_edt(mask_bin) # 骨架像素总数就是裂缝长度像素单位 length_px int(np.sum(skeleton)) # 骨架上的宽度取平均 widths distance[skeleton] avg_width_px float(np.mean(widths)) * 2 if len(widths) 0 else 0 return length_px, avg_width_px这里的关键是长度是骨架像素数而不是mask的前景像素数——mask面积会受裂缝宽度影响而骨架表示裂缝的中心线其长度更接近实际裂缝长度。换算成物理尺寸时需要知道相机标定的像素/毫米比例这属于相机标定的范畴在这里不展开。6.4 ONNX导出把PyTorch训练好的模型迁移回MATLAB做复现展示最后说一个双工具链的进阶用法PyTorch训练好的模型导出为ONNX用MATLAB的importNetworkFromONNX可以直接导入并做推理展示。这对于编写项目结题报告、做信号级仿真的场合特别有用。import torch model LightUNet(in_ch1, out_ch1) model.load_state_dict(torch.load(best_crack_unet.pth)) model.cpu().eval() dummy_input torch.randn(1, 1, 512, 512) torch.onnx.export( model, dummy_input, crack_unet.onnx, input_names[input], output_names[output], dynamic_axes{input: {0: batch, 2: height, 3: width}, output: {0: batch, 2: height, 3: width}}, opset_version11 ) print(ONNX导出完成)dynamic_axes参数一定要加它允许推理时输入任意尺寸的图像否则ONNX模型会锁死512×512换个大图就报错。导出后在MATLAB里导入一条命令验证net importNetworkFromONNX(crack_unet.onnx)。整个系统走到这一步已经不再是“跑通一个深度学习模型”而是真正能处理一段视频、输出裂缝分布报告、接入养护决策流程的完整工具链。我在做这类系统时养成的习惯是——每次训练完模型顺手把最优阈值、预处理参数、后处理参数都记录在一个配置文件里因为三个月后再回来看项目这些细节早就忘了而它们每一个都能让结果差出好几个百分点。希望这篇文章里从双工具链分工到后处理量化评估的完整路径能帮你在做裂缝检测项目时少走几步弯路。本文还有配套的精品资源点击获取
