下水道缺陷检测实战:从CCTV图像预处理到YOLOv8部署
2026/9/12 2:29:18 网站建设 项目流程

简介:这是一份面向计算机视觉学习者和工业检测从业者的下水道管道缺陷检测项目包,聚焦图像视觉在管道堵塞、裂缝、渗漏识别中的应用。项目以Python算法实现为核心,涵盖normalizeRGB、circularMask、arcDetect、Main等模块,涉及灰度转换、滤波去噪、边缘检测、特征提取等图像处理环节,并针对管道圆形区域掩膜、时间转换、轮廓高亮等关键任务提供可直接运行的脚本。压缩包内附两张示例图片和README说明文档,便于对照运行和理解流程。资源共9个文件,类型以6个py脚本、2张jpg样例图、1个markdown文档为主,总大小约910KB,轻量便捷,适合快速搭建检测流程或作为课程设计、算法练习的参考。已有107人学习浏览。项目价值在于提供一套从图像预处理到缺陷识别的完整代码骨架,用户可基于现有脚本扩展3D视觉、无人机巡检等场景,也可用于评估CNN等深度学习模型在管道缺陷分类中的基准效果,对城市基础设施智能化维护方向的入门与进阶均有助益。

1. 为什么下水道缺陷检测比想象中更吃技术

下水道管道堵塞和缺陷检测,听起来就是把摄像头探进管道拍一段视频,然后让算法标出哪里破了、哪里堵了。真实情况是:CCTV 巡检车扫完一条 2 公里的管线,产出上万帧画面,人工逐帧看,既要盯结构缺陷(裂缝、塌陷、变形),又要盯功能性堵塞(沉积、树根、异物),疲劳造成的漏检率一直压不下来。基于图像视觉的缺陷检测模型被引入这条产线,就是为了同时覆盖“管壁损伤”和“管腔堵塞”两类目标,并且保证在高帧率视频流下也能实时反馈。

我拿到这类需求时,习惯把项目拆成三个环节:缺陷标签体系 -> 检测模型训练与调参 -> 面向现场光照和帧率条件的推理优化。这个以项目分享包为名的 zip 文件,价值往往不只在于权重文件,更在于预处理脚本和阈值调节记录。适合往下读的是给排水信息化工程师、机器视觉工程岗,以及想用一个可复现案例完成毕设或 demo 的研究生。下面按最小可运行链路展开。

2. 下水道缺陷检测从哪开始:缺陷类别、标注与图像预处理

2.1 结构性缺陷与功能缺陷,用一张标签表定清楚

在下水道的工况里,缺陷不是一个标签能包住的“二分类”。检测报告一般区分为结构性缺陷与功能性缺陷,前者是管壁物理损伤,后者直接影响过水能力。把标签表先固定下来,后面标注、训练、验收才不会反复返工。常用标签体系如下表:

一级类别标签名视觉特征落地要点
结构性crack(裂缝)管壁黑色细线,常见分叉最难标,容易与阴影混淆
结构性deformation(变形)断面内凹或椭圆化需要参考管口正常形状
结构性collapse(塌陷)大块破损,管道阻断风险最高,优先保证召回
结构性corrosion(腐蚀)表面麻点、颜色剥落对比度低,依赖细节增强
功能性deposition(沉积/堵塞)管底泥砂堆积,过水断面变小样本量最大,常占一半以上
功能性root(树根侵入)黑色丝状物伸入管道目标小,建议切图训练
功能性scale(结垢)灰白色覆盖物容易与腐蚀混淆

标注时要避免只看单帧截图。我一般会把该帧前后各 3 帧一起发给标注员作为上下文,否则管道内车灯移动造成的光影变化会被大量标成裂缝或腐蚀。标签文件统一导成 COCO json,后续接 yolov8 还是转 halcon 缺陷检测流程都方便。标注工具优先用 CVAT,它支持按帧插值跟踪,而管道内缺陷在相邻帧之间位移很小,插值能省掉将近一半的人工框选量。

提示:如果最终要出结构性和功能性缺陷的分开报告,建议在标签名字上直接加前缀,例如 structure_crack、functional_root,不要在标注完成后靠后处理去猜类别归属。

2.2 管壁反光与暗角:CLAHE 与 gamma 预处理代码

管道内光照条件极差,摄像机自带光源造成画面中心过亮、边缘黑暗,水面还会出现条状反光。这种分带亮度问题会让模型学到“亮部等于缺陷”的假相关性,训练集来自不同井段时尤其明显。我在预处理阶段固定用下面这段代码,而不是把原图直接丢给网络:

import cv2 import numpy as np def preprocess_pipe_frame(image: np.ndarray) -> np.ndarray: # 1. 转 HSV,只对 V 通道做 CLAHE,避免颜色通道同步拉伸产生噪声 hsv = cv2.cvtColor(image, cv2.COLOR_BGR2HSV) v = hsv[:, :, 2] clahe = cv2.createCLAHE(clipLimit=3.0, tileGridSize=(8, 8)) v_equ = clahe.apply(v) hsv[:, :, 2] = v_equ img = cv2.cvtColor(hsv, cv2.COLOR_HSV2BGR) # 2. 暗部提升,gamma 大于 1 时暗部细节被拉开 gamma = 1.2 table = ((np.arange(256) / 255.0) ** (1.0 / gamma) * 255).astype(np.uint8) img = cv2.LUT(img, table) # 3. 裁掉画面上下 15% 和左右 10% 的无效黑色区域并缩放到统一尺寸 h, w = img.shape[:2] roi = img[int(h * 0.15):int(h * 0.85), int(w * 0.1):int(w * 0.9)] return cv2.resize(roi, (640, 640))

这段代码做了三件事:先分离亮度通道做 CLAHE,直接原因是管道图像的问题集中在亮度分布,而不在色相缺陷,对 V 通道做自适应直方图均衡可以在不放大颜色噪声的前提下拉平明暗;之后用 gamma 校正提升暗部纹理,参数 clipLimit 决定局部对比度强度,1080p 源图用 3.0 即可,如果源图是 720p,tileGridSize 要改成 (4,4),否则容易出现块状伪影;最后裁剪掉画面边缘的黑色无效区并 resize 到 640,这个尺寸与后续模型输入保持一致。水面反光区域在 CLAHE 下会被放大,clipLimit 超过 3.5 时反光边缘会形成一条明显的假边界,极其容易被识别成裂缝。

预处理做完后,最好抽 20 帧保存为 jpg 人工目检一遍,确认每帧缺陷和背景的对比度都有提升。如果某些井段逆光严重,gamma 可临时调到 1.4,但同一项目里不要多套 gamma 混用,否则模型会靠环境的亮度统计特征去分辨缺陷,而不是靠缺陷自身的纹理。

2.3 增强参数要按管网场景重新设:yaml 配置与注意事项

下水道视频缺陷检测的增强策略和通用目标检测不同。水平翻转可以用,但垂直翻转要慎重:管底沉积和管顶结垢在不同检测标准里对应不同判级维度,垂直翻转等于把这两类语义交换,模型会收到错误监督。我习惯把所有增强参数收敛到一份 yaml 里,方便训练时复现和修改:

# pipe_augment.yaml train: data/train/images val: data/val/images nc: 7 names: ['crack', 'deformation', 'collapse', 'corrosion', 'deposition', 'root', 'scale'] augment: hsv_h: 0.02 # 色相扰动幅度小,管道缺陷靠纹理不靠颜色 hsv_s: 0.5 hsv_v: 0.4 scale: 0.3 # 摄像头进深不同,尺度变化需要给足 fliplr: 0.5 # 水平翻转,管道左右对称 flipud: 0.0 # 禁掉垂直翻转,管顶/管底语义不可交换 mosaic: 0.8 # mosaic 对小目标密集场景提升明显 mixup: 0.0 # 管道图像纹理干净,mixup 容易产生脏标签

参数说明中几个关键值:hsv_h 只设 0.02,因为管壁材质和缺陷的判断依赖边缘灰度,而不是颜色;scale 给 0.3 是为了模拟摄像头在管道内不同工作距离造成的目标尺度差异,这比翻转更有价值;mosaic 保持 0.8,它能显著提升树根、细裂缝这类 64 像素以下小目标的特征泛化能力;mixup 建议关掉,管道里同类缺陷高度相似,混合两帧图像产生的中间样本既不属于源类型,又会把标签分布搅乱,实际增益为负。

3. 缺陷检测模型选型与训练参数:yolov8 为主,halcon 作补充

3.1 目标检测、实例分割、传统视觉三选一

下水道缺陷检测的本质是“找到异常区域并给出框”,目标检测已经覆盖大部分验收需求。实例分割对裂缝这类细长目标有像素级优势,但标注成本高,而且结构性和功能性缺陷的判级主要看长度和面积占比,分割结果还要进一步转成形态学指标,工程链路变长。Halcon 这类传统机器视觉方案,用阈值分割加形状特征筛选可以做锈蚀、结垢这种纹理稳定的缺陷,但遇到车灯角度变化导致的光照剧烈波动,算法参数就要跟着改,现场维护成本极高。对比关系如下:

方案单帧推理耗时小目标能力工程复杂度管道场景建议
Faster R-CNN 系列100ms 以上高,数据组织繁琐不推荐作为初始方案
YOLOv8s8-15ms中上首选,样本不足 5000 帧时最稳
YOLOv8m15-25ms样本超过 8000 帧时升级
RT-DETR30ms 左右实时性、精度双重要求时尝试
Halcon 传统视觉5ms 左右依赖规则库只做单一纹理类缺陷复核

部署时我会优先 YOLOv8s 起步,它在下水道这种背景相对单调、目标类型固定的场景里,训练收敛快,边缘设备也能跑得动。Halcon 的角色不是替代,而是在检测结果上做二次确认,例如对“腐蚀”类缺陷做灰度共生矩阵分析来过滤一部分规则性误报。

3.2 yolov8 训练命令与每组参数的依据

YOLOv8 的 CLI 可以直接完成训练,但要保证项目结果可复现,我习惯把关键增强参数和训练参数全部显式写进命令,而不是依赖默认配置。一个可实际跑通的启动命令如下:

yolo detect train \ data=pipe_augment.yaml \ model=yolov8s.pt \ imgsz=1280 \ batch=16 \ epochs=150 \ lr0=0.001 \ augment=true \ patience=30 \ project=runs/pipe \ name=train_v1

命令里最容易忽略的是 imgsz。管道视频原始帧通常是 1080p,imgsz 从默认 640 提到 1280,对小裂缝和树根的召回提升非常明显,代价是显存需求接近翻倍。8G 显存的 GPU 要把 batch 降到 8,否则会在前几个 epoch 就被 OOM 打断。lr0 设为 0.001 是因为 COCO 预训练权重和管道图像分布差异很大,学习率太大会把浅层通用特征直接冲坏。patience=30 表示验证集指标连续 30 个 epoch 不提升就提前停止,这是防止网络在数量少的缺陷类别上过拟合的最后一道保险。

训练日志看 runs/pipe/train_v1/ 下的 results.csv,重点盯 box_loss 的下降曲线。如果训练集 loss 一直降而验证集 loss 在某个 epoch 后反弹,说明模型开始背训练集里的光照特征,这时候回退到反弹前的权重,并检查增强配置里是否误开了垂直翻转。

3.3 数据不平衡与早停的使用边界

下水道数据里,沉积类样本往往占一半以上,裂缝可能只有 5%。直接拿去训练,模型会把稀缺类往背景上推。YOLOv8 没有内置 per-class loss 权重,常见的做法是重采样:把沉积类下采样到总样本的 30%,同时保留全部裂缝和塌陷样本。采样后要重新检查验证集分布,不能让某一类在 val 里一个样本都没有。

每个训练轮次结束后,还要单独打印每个类别的验证 AP。如果某类 AP 长期低于 0.5,不要急着改模型结构,先回查该类别的标注质量。管道缺陷标注最容易出问题的是“裂缝”和“阴影”,这两类混淆会让 AP 卡在 0.4 附近,并且无论怎么调超参都上不去。

4. 推理阶段的图像视觉调优:切图、阈值、去重与导出

4.1 小目标漏检,先做滑窗切图推理

模型上了工地就掉点,多半不是训练的问题,而是远端缺陷太小。CCTV 摄像头贴近管壁扫过时,画面深处的裂缝可能只有 20 到 40 像素高,整图推理时这些目标经过下采样后特征基本丢失。常见做法是切图推理,我一般用滑窗把原图切成 640x640 的块,并带 25% 重叠:

from ultralytics import YOLO model = YOLO("runs/pipe/train_v1/weights/best.pt") def detect_tiled(frame, tile_size=640, overlap=0.25, conf=0.25): h, w = frame.shape[:2] step = int(tile_size * (1 - overlap)) dets = [] x = 0 while x < w: y = 0 while y < h: tile = frame[y:y + tile_size, x:x + tile_size] res = model.predict(tile, imgsz=640, conf=conf, verbose=False)[0] for box in res.boxes: x1, y1, x2, y2 = box.xyxy[0].cpu().numpy() dets.append([x + x1, y + y1, x + x2, y + y2, float(box.conf), int(box.cls)]) y += step x += step return dets

切图参数说明:overlap 设为 0.25,也就是四个方向重叠 25%,避免目标恰好被 tile 边界切断;tile 尺寸和模型输入保持一致,避免二次 resize 引入形变。代价是单帧推理次数成倍增加,如果实时性不够,改成只对画面远端 1/3 区域切图,近端直接整图推理。重叠区域的目标会被检测多次,需要把框坐标换算回原图后做一次全局 NMS:

import torch from torchvision.ops import nms as tv_nms boxes = torch.tensor([d[:4] for d in dets], dtype=torch.float32) scores = torch.tensor([d[4] for d in dets], dtype=torch.float32) keep = tv_nms(boxes, scores, iou_threshold=0.5) final = [dets[i] for i in keep.tolist()]

4.2 置信度与 IoU 阈值控制在什么范围

现场推理时,置信度阈值不能直接沿用训练时的 0.25。甲方管道检测更怕漏报,因为漏掉一个塌陷意味着整段管道可能报废,而误报可以由人工复核兜底。落地时我会把 conf 压到 0.15 到 0.2,让模型多出候选框,再用规则过滤:面积小于 200 像素且连续出现少于 2 帧的框直接舍弃。IoU 阈值在 NMS 阶段保持 0.5,如果切图重叠区域出现大量重复框,可以提到 0.6,但不要超过 0.7,否则相邻的两个真实缺陷会被合并成一个。

阈值不是调一次就固定。准备 200 帧实拍片段,分别在 conf 0.1、0.2、0.3 下跑三遍,统计每档的框总数和人工确认数,然后画一条精确率-召回率曲线,再结合甲方的验收口径去选择落点。

4.3 跨帧去重,让堵塞缺陷的重复计次消失

视频检测最容易出现的问题是把同一个缺陷在相邻帧里重复计数。特别是“沉积/堵塞”这类连续大块目标,不去重的话,一段 5 米长的淤积会被计成几十个缺陷。处理方式是用 IoU 做跨帧关联:当前帧的框与上一帧任一框的 IoU 大于 0.3,就判定为同一缺陷,只记录首次出现帧号和类别,后续只更新最后出现帧号和最大置信度:

缺陷编号类别首次帧号末次帧号最大置信度
D001crack1281560.87
D002deposition2102400.92

输出这份清单后,再回到原始视频截取首次出现帧,交给人工复核。跨帧关联的 IoU 阈值 0.3 不能设太高,否则检测框抖动的帧会被误判为新缺陷;也不能设太低,否则紧挨着的两个独立缺陷会被合并成一个。

4.4 ONNX 导出与低配置工控机部署

项目验收阶段常要求部署到没有 GPU 的工控机。YOLOv8 导出 ONNX 后,用 onnxruntime CPU 推理即可:

yolo export model=runs/pipe/train_v1/weights/best.pt format=onnx opset=12 simplify=True

opset 用 12 是兼容性考虑,过高的版本在老设备上会报算子不支持;simplify 参数让 onnxsim 折叠常量,体积能缩小 10% 左右。CPU 推理时输入尺寸建议退回 640,否则单帧延迟可能从 20ms 拉到 100ms 以上。离线判读场景可以保留 1280 输入换取召回,实时预警场景则必须降到 640,并把 4.1 节的切图逻辑改成只对感兴趣区域执行。

5. 项目分享 zip 到手,验证三件套:校验、回归、置信度校准

5.1 先校验文件完整性,再对环境做差异化对比

从网上下载或同事转来的项目 zip,第一个坑是解压后路径里含中文,OpenCV 的 imread 在部分系统上会直接返回空对象。我拿到包的第一步是查目录结构,确认根目录下有没有 requirements.txt、环境 yaml、权重文件,以及数据集标注是否齐全。然后做完整性校验:

sha256sum defect_detection_pipe.zip unzip defect_detection_pipe.zip -d ./pipe_project cd pipe_project && python -c "import torch; print(torch.__version__, torch.cuda.is_available())"

对比发布者给出的 sha256 值只解决传输损坏问题,更关键的是环境差异。项目包的 requirements.txt 往往包含 opencv-python、ultralytics、torch 等版本,直接 pip install -r 可能把你本机已调好的 CUDA 环境破坏掉。我的做法是先pip freeze > current_env.txt,再与 requirements.txt 做差异比对,只安装缺失或版本不兼容的库。torch 的 cuda 版本建议单独安装,不放进 requirements。

5.2 用自备帧做回归,别直接重训

别人训好的权重不能直接拿来做验收,因为你不知道它的训练集长什么样。正确做法是先准备 50 到 100 帧来自目标管段的实拍截图,跑一遍推理脚本,统计框数随置信度变化的曲线。如果框集中在画面同一位置且与缺陷无关,基本可以判断模型过拟合了样本的管道环境;如果置信度分布整体偏低,说明源域和目标域差异较大,需要继续微调。

这一轮回归不依赖标注,先只看框的合理性。抽 10 帧人工核对,框住的区域里有没有真正的裂缝或沉积。如果人工认可率在 70% 以上,说明预训练权重还能用;低于 30% 就要重新标注一批本地数据来微调,而不是硬调阈值。

5.3 按验收口径校准检测置信度

最后一件事是校准置信度与验收指标的关系。如果模型检了 100 个框,人工确认其中 70 个是真实缺陷,那么当前阈值下的精确率就是 70%。甲方对功能性堵塞更在意漏报,也就是把有堵塞的管段判成通畅,这时要把精确率放宽到 50% 左右,换取更高的召回。反过来,如果甲方只是做内部普查,误报会给人工复核增加成本,可以收紧到精确率 80% 与召回率 60% 的交叉点。

实际操作中,我会把 conf 从 0.15 到 0.4 每隔 0.05 跑一遍回归集,画出精确率和召回的交叉曲线,再让甲方在曲线上选点。哪怕只是把 conf 从 0.25 改成 0.2,在低照度井段上漏报也能降低 8 到 10 个百分点;改完 conf 后,用 4.3 节的跨帧去重逻辑保证框数统计可信,这轮校准才算闭环。之后再去动训练代码里的增强参数和 imgsz,才是在有效基线之上的优化。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询