简介:这份资源是面向计算机视觉方向学习者与毕业设计需求者的裂缝检测项目源码,基于深度学习技术实现,适合作为课程设计、期末大作业或毕设参考。压缩包共14个文件,以Python源码为主,辅以说明文档与数据集文件,整体约18KB,结构紧凑便于快速部署运行。项目采用孪生网络结构,包含训练、预测、数据加载与工具函数等模块,代码注释完整,新手也能理解整体流程。已有130人学习下载,说明其具备一定参考价值。读者可获得一套可直接运行的裂缝检测方案,涵盖模型搭建、数据读取、训练与推理全流程,同时能借鉴目录组织与模块划分思路,便于二次开发或迁移到其他图像检测任务中。
1. 裂缝检测项目为什么用孪生网络而不是普通 CNN
拿到这份「基于深度学习的裂缝检测技术Python源码.zip」的时候,我第一反应是翻 net 目录——看到siamese.py就明白了,这不是那种拿 ResNet 直接怼分类的常规玩法,而是走孪生网络(Siamese Network)做裂缝变化检测的路子。裂缝检测这个场景有个绕不开的痛点:同一段路面、同一面墙体,今天拍一张、下个月再拍一张,光照变了、角度偏了、表面还多了几道划痕,普通 CNN 直接对单张图做二分类,很容易把「光照差异」当成「新增裂缝」,误报率高得离谱。孪生网络把两张图分别送进共享权重的分支,在特征空间里比对差异,输出的不是「有没有裂缝」,而是「这两张图之间哪里变了」,这才是裂缝监测真正要的答案。
这份源码适合谁?做道路养护、桥梁健康监测、建筑结构巡检方向的同学,或者毕业设计选题卡在「裂缝识别」上、想找一个能跑通、有注释、结构清晰的基线项目的人。它不追求 SOTA 指标,但胜在工程完整:数据加载、训练、预测、工具函数分层清楚,config.py把超参集中管理,改起来不用满项目找变量。下面我按「先跑通、再拆解、最后避坑」的顺序,把这份源码从部署到调参完整过一遍。
2. 环境搭建与数据准备:从零把项目跑起来
2.1 依赖安装与目录结构确认
先把压缩包解压,进入CrackDetecion-main目录。注意目录名里Detecion是源码作者拼写时的笔误,不影响运行,但你在写脚本引用路径时要跟实际目录名一致,别自己改成Detection导致路径对不上。项目结构大致是这样:
CrackDetecion-main/ ├── main.py # 入口,训练/预测调度 ├── train.py # 训练主逻辑 ├── predict.py # 推理入口 ├── config.py # 超参与路径配置 ├── net/ │ └── siamese.py # 孪生网络定义 ├── utils/ │ ├── Dataset.py # 数据集封装 │ ├── Dataloader.py # 数据加载器 │ ├── train_utils.py # 训练辅助(损失、指标) │ ├── predict_utils.py # 推理辅助(后处理、可视化) │ └── tools.py # 通用工具函数 ├── DamDataset/ # 数据目录 └── README.md依赖方面,这类项目常见做法是 PyTorch + torchvision + numpy + opencv-python + Pillow + tqdm。我一般会先建一个干净的虚拟环境,避免和系统里的包打架:
python -m venv venv source venv/bin/activate # Windows 用 venv\Scripts\activate pip install torch torchvision numpy opencv-python Pillow tqdm matplotlib这里有个参数要留意:PyTorch 版本和 CUDA 版本必须匹配。如果你机器上没有 NVIDIA 显卡,直接装 CPU 版也能跑,只是训练慢,config.py里通常会有device字段,确认它写的是cuda还是cpu。我第一次跑的时候没注意,代码里硬编码了cuda,结果在无显卡的笔记本上直接报AssertionError: Torch not compiled with CUDA enabled,改成cpu就好了。
2.2 数据集组织与 config.py 关键参数
DamDataset目录是放数据的地方。孪生网络需要成对的图像,常见组织方式是每个样本包含before和after两张图,外加一张变化区域的标注 mask。具体子目录命名以你下载后的实际结构为准,如果只有单张图和对应 mask,那说明这份源码走的是「原图 + 标注」的配对增强路线,训练时在Dataset.py里动态构造正负样本对。
打开config.py,重点看这几类参数:
| 参数类别 | 典型字段 | 作用 | 调整建议 |
|---|---|---|---|
| 路径 | data_root、save_dir | 数据与权重存放位置 | 改成你本机绝对路径 |
| 训练 | batch_size、epochs、lr | 批大小、轮数、学习率 | 显存小就把 batch_size 降到 4 或 2 |
| 模型 | input_size、backbone | 输入分辨率、骨干网络 | 分辨率必须是 16 的倍数 |
| 设备 | device | 训练设备 | 无显卡改cpu |
input_size这个参数是血泪经验:孪生网络里两个分支共享权重,最后要做特征相减或拼接,如果输入尺寸不是下采样倍数的整数倍,中间特征图对不齐,会在 concat 那一步报维度错误。常见做法是设成 256 或 512,别设 250 这种数。
2.3 启动训练与日志观察
配置改好后,训练入口一般是main.py或train.py。先看main.py里是怎么调度的,很多项目用命令行参数控制模式:
python main.py --mode train --config config.py如果main.py没有参数解析,直接python train.py也行。启动后重点盯三样东西:loss 是否下降、显存占用是否稳定、每个 epoch 耗时是否合理。孪生网络的 loss 常见是对比损失(Contrastive Loss)或二值交叉熵,train_utils.py里能看到具体实现。如果 loss 在前几个 epoch 就卡住不动,八成是学习率太大或者数据对构造有问题——比如正负样本比例严重失衡,全是「无变化」对,模型学不到东西。
提示:第一次跑建议把
epochs改成 2,先验证整条链路能通,再放开完整训练。权重文件一般存在save_dir下,命名带 epoch 数,方便断点续训。
3. 孪生网络结构与训练流程拆解
3.1 siamese.py 里的共享权重机制
net/siamese.py是整个项目的核心。孪生网络的关键在于两个分支用同一套权重,代码里通常体现为一个self.backbone被调用两次,而不是定义两个独立的网络。我拆这类代码时会重点确认三件事:骨干网络是否只实例化一次、特征融合方式是什么、输出头怎么设计。
import torch import torch.nn as nn class SiameseNet(nn.Module): def __init__(self, backbone): super().__init__() # 关键:只实例化一次 backbone,两个分支共享 self.backbone = backbone # 融合后的输出头,输入通道是单分支特征的 2 倍(相减+拼接) self.head = nn.Sequential( nn.Conv2d(256 * 2, 128, 3, padding=1), nn.BatchNorm2d(128), nn.ReLU(inplace=True), nn.Conv2d(128, 1, 1) # 输出单通道变化图 ) def forward(self, x1, x2): f1 = self.backbone(x1) # 分支一 f2 = self.backbone(x2) # 分支二,复用同一 backbone diff = torch.abs(f1 - f2) # 绝对差,突出变化区域 feat = torch.cat([diff, f1], dim=1) # 拼接,保留原始特征 return self.head(feat)逻辑说明:self.backbone只定义一次,forward里对x1、x2分别调用,PyTorch 会自动共享梯度,这就是孪生的本质。torch.abs(f1 - f2)是变化检测里最常用的差异度量,比直接 concat 更能抑制共同背景。参数上,head第一层输入通道256 * 2取决于 backbone 输出通道数,如果你换了骨干网络,这个数必须跟着改,否则报通道不匹配。输出单通道配合 Sigmoid 就是变化概率图。
3.2 Dataset.py 与 Dataloader.py 的配对逻辑
utils/Dataset.py负责把原始数据变成模型要的成对样本。常见实现是继承torch.utils.data.Dataset,在__getitem__里返回(img1, img2, mask)三元组。这里有个容易翻车的地方:数据增强必须对两张图做完全相同的变换,否则几何变换不一致,模型学到的「差异」其实是增强引入的噪声。
class CrackPairDataset(Dataset): def __init__(self, root, transform=None): self.root = root self.transform = transform self.pairs = self._build_pairs() # 构造配对列表 def __getitem__(self, idx): img1_path, img2_path, mask_path = self.pairs[idx] img1 = Image.open(img1_path).convert('RGB') img2 = Image.open(img2_path).convert('RGB') mask = Image.open(mask_path).convert('L') if self.transform: # 用同一个随机种子,保证两图变换一致 seed = torch.initial_seed() torch.manual_seed(seed) img1 = self.transform(img1) torch.manual_seed(seed) img2 = self.transform(img2) torch.manual_seed(seed) mask = self.transform(mask) return img1, img2, mask逻辑说明:torch.manual_seed(seed)在三次 transform 前都设成同一个种子,确保随机裁剪、翻转对三张图同步。参数上,transform里如果用了RandomResizedCrop,mask 必须用最近邻插值,不能用双线性,否则边缘会糊掉,训练时 mask 的 0/1 边界变模糊,指标会虚高。Dataloader.py则负责包一层DataLoader,设置batch_size、shuffle、num_workers。num_workers在 Windows 上设大于 0 有时会卡死,设 0 最稳。
3.3 train_utils.py 的损失与指标
train_utils.py里一般放着损失函数和评估指标。裂缝变化检测的标签极度不平衡——变化区域往往只占整图的百分之几,所以直接用 BCE 会被背景淹没。常见做法是 BCE + Dice Loss 组合,或者用带pos_weight的 BCE。
def combined_loss(pred, target, bce_weight=0.5): bce = nn.BCEWithLogitsLoss(pos_weight=torch.tensor([5.0]).to(pred.device)) loss_bce = bce(pred, target) # Dice Loss 缓解正负样本不平衡 pred_sig = torch.sigmoid(pred) intersection = (pred_sig * target).sum() dice = 1 - (2 * intersection + 1e-6) / (pred_sig.sum() + target.sum() + 1e-6) return bce_weight * loss_bce + (1 - bce_weight) * dice逻辑说明:pos_weight=5.0是给正样本加权,具体值要看你的数据里变化像素占比,占比越低这个值越大。Dice Loss 直接优化预测和标签的重叠度,对小目标更友好。参数bce_weight控制两者比例,我一般从 0.5 起步,如果发现模型倾向于全预测为背景,就调低 bce_weight、加大 dice 比重。指标方面,train_utils.py里通常有 IoU 或 F1 的计算,训练时打印出来比只看 loss 更直观。
4. 推理与可视化:predict.py 怎么用、结果怎么看
4.1 加载权重做单对推理
训练完拿到权重文件后,predict.py负责推理。典型流程是:加载模型结构 → 载入权重 → 读入一对图像 → 前向 → 阈值化 → 保存变化图。
import torch from PIL import Image from net.siamese import SiameseNet from torchvision import transforms device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') model = SiameseNet(backbone).to(device) model.load_state_dict(torch.load('checkpoints/best.pth', map_location=device)) model.eval() transform = transforms.Compose([ transforms.Resize((256, 256)), transforms.ToTensor(), ]) img1 = transform(Image.open('a.jpg').convert('RGB')).unsqueeze(0).to(device) img2 = transform(Image.open('b.jpg').convert('RGB')).unsqueeze(0).to(device) with torch.no_grad(): logits = model(img1, img2) prob = torch.sigmoid(logits) mask = (prob > 0.5).float() # 阈值 0.5,可按需调整逻辑说明:model.eval()必须调用,否则 BatchNorm 会用当前 batch 的统计量,单张推理时结果会飘。map_location=device保证在有显卡训练的权重能在 CPU 机器上加载。阈值 0.5 是默认值,实际用的时候要根据漏检和误检的容忍度调——养护场景宁可误报也别漏报,可以把阈值降到 0.3。
4.2 predict_utils.py 的后处理技巧
predict_utils.py里通常有连通域过滤、形态学操作这些后处理。裂缝检测的输出图往往有零星噪点,直接阈值化会得到一堆小斑点。常见做法是用 OpenCV 做开运算去掉小噪点,再保留面积最大的几个连通域。
import cv2 import numpy as np def post_process(mask_np, min_area=50): mask_uint8 = (mask_np * 255).astype(np.uint8) kernel = np.ones((3, 3), np.uint8) opened = cv2.morphologyEx(mask_uint8, cv2.MORPH_OPEN, kernel) num, labels, stats, _ = cv2.connectedComponentsWithStats(opened) result = np.zeros_like(opened) for i in range(1, num): if stats[i, cv2.CC_STAT_AREA] >= min_area: result[labels == i] = 255 return result逻辑说明:min_area是最小保留面积,设太小去不掉噪点,设太大会把细裂缝也滤掉。裂缝通常是细长条,面积不大但长度可观,所以更稳妥的过滤条件是「面积 + 长宽比」组合,而不是只看面积。参数kernel大小也要注意,3x3 适合细裂缝,5x5 会把细线腐蚀断。
4.3 结果可视化与叠加显示
光看二值 mask 不直观,实际汇报时一般要把变化区域叠加到原图上。predict_utils.py里如果有可视化函数就直接用,没有的话自己加一段:把 mask 转成红色半透明层,叠在img2上,变化区域一目了然。这一步在毕业设计答辩时特别加分,导师一眼就能看懂模型检出了哪里。
注意:叠加显示时坐标要对齐。如果你在推理前做了 Resize,mask 要还原回原图尺寸再叠加,否则红色区域会错位。常见做法是记录原始尺寸,推理后
cv2.resize回去。
5. 避坑与常见问题排查
5.1 报错 CUDA out of memory
现象:训练刚开始或跑几个 batch 后报显存不足。原因:batch_size或input_size太大,孪生网络前向要存两份特征图,显存占用约是普通网络的 1.5 到 2 倍。解决:先把batch_size减半,还不行就把input_size从 512 降到 256,同时确认没有其他进程占着显卡。
5.2 loss 不下降或震荡剧烈
现象:训练几十个 epoch,loss 在某个值附近来回跳。原因:学习率过大,或者正负样本对构造比例失衡,也可能数据增强把两张图的对应关系打乱了。解决:学习率降到 1e-4 甚至 1e-5,检查Dataset.py里配对逻辑和增强种子,打印几个 batch 的样本对肉眼确认。
5.3 推理结果全黑或全白
现象:预测出来的 mask 要么全是背景,要么全是变化。原因:阈值设得不合理,或者模型根本没训好,输出 logits 集中在某一侧。解决:先把prob的分布打印出来看,如果集中在 0 附近说明模型没学到变化特征,回去检查训练数据和 loss;如果分布正常只是阈值不对,用prob.mean()附近的值试。
5.4 路径报错 FileNotFoundError
现象:启动就报找不到文件。原因:config.py里的路径是作者机器上的绝对路径,或者DamDataset目录结构和代码预期不一致。解决:把所有路径改成相对项目根目录的相对路径,用os.path.join拼接,别硬编码斜杠方向,Windows 和 Linux 混用容易翻车。
5.5 训练指标虚高但实际效果差
现象:验证集 IoU 很高,但拿新图预测一塌糊涂。原因:训练集和验证集来自同一段路面的相邻帧,两张图几乎一样,模型学到了「复制输入」而不是「检测变化」。解决:划分数据集时按路段或按时间划分,别随机打乱,保证验证集里的场景训练时没见过。
6. 进阶技巧:把这份源码改成你自己的裂缝监测工具
跑通只是第一步,真正要用起来得做几处改造。第一处是骨干网络替换,siamese.py里的 backbone 如果是最简单的几层卷积,可以换成torchvision里的预训练 ResNet 或 MobileNet,把head的输入通道数跟着改掉,小数据集上冻结前几层、只训后面,收敛快很多。第二处是输入尺寸自适应,实际巡检图像分辨率五花八门,我一般会在Dataset.py里做等比缩放加 padding,而不是暴力 Resize,这样裂缝的宽窄比例不会被拉变形。
第三处是推理批量化。predict.py默认一次处理一对图,实际监测往往有几百对要跑,改成DataLoader批量推理能快好几倍。下面这个批量推理的骨架可以直接抄:
def batch_predict(model, pair_list, transform, device, batch_size=8): model.eval() results = [] for i in range(0, len(pair_list), batch_size): batch = pair_list[i:i + batch_size] imgs1 = torch.stack([transform(Image.open(p[0]).convert('RGB')) for p in batch]).to(device) imgs2 = torch.stack([transform(Image.open(p[1]).convert('RGB')) for p in batch]).to(device) with torch.no_grad(): probs = torch.sigmoid(model(imgs1, imgs2)) results.extend(probs.cpu().numpy()) return results逻辑说明:torch.stack把多对图拼成 batch,一次前向出结果,比循环快得多。参数batch_size根据显存调,推理时没有梯度,显存占用比训练小,可以适当放大。注意transform里不能有随机操作,推理阶段必须确定性变换。
验证改造是否成功,我有个习惯:留一小段路面的前后对比图,人工标出真实变化区域,跑完算一下 IoU 和召回率,跟改造前对比。如果召回率上去了但误报也涨了,说明阈值或后处理要重新调。从那以后我每次改完模型结构或者换数据,都强制走一遍这个小验证集,不看到数字不动手写结论。这套流程帮我省过好几次「以为改好了其实退步了」的后悔药。
希望这份拆解能帮你把这份源码真正用起来,而不是停在「下载了、跑通了、然后呢」的阶段。
本文还有配套的精品资源,点击获取