ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

HED边缘检测实战:从网络结构到训练推理的完整指南

HED边缘检测实战:从网络结构到训练推理的完整指南 简介这份资源面向计算机视觉初学者与深度学习实践者聚焦基于HEDHypercolumns for Edge Detection的边缘检测算法实现与验证。HED利用卷积神经网络多层特征捕获不同尺度边缘相比Canny、Sobel等传统算子能端到端预测边缘地图并适应复杂场景资源可帮助读者理解预训练与微调两阶段流程并动手测试模型效果。压缩包共3个文件约2KB包含Python脚本、Caffe部署配置prototxt及预训练模型下载shell脚本分别对应推理代码、网络结构定义与权重获取环节结构精简便于快速上手。目前已有1239人学习下载。通过该资源读者可加载预训练模型直接对新图像做边缘检测也可调整参数适配特定场景同时结合脚本理解多尺度分支加权融合与损失优化思路为后续研究或工程落地提供可复用的代码起点。1. HED 边缘检测到底解决了传统算子哪些翻车现场如果你用过 Sobel、Canny 或者 Prewitt 做边缘检测大概率遇到过这种场景光照一变阈值就得重调纹理一复杂满屏都是碎边物体轮廓明明肉眼可见算子却给你断成几截。HEDHolistically-Nested Edge Detection就是冲着这些痛点来的。它不是又一个手工设计的卷积核而是用深度学习的方式让网络自己学会「哪里该有边、哪里是噪声」。HED 的核心思路是整体嵌套一个主干网络多个侧输出分支每个分支在不同尺度上预测边缘最后融合成一张精细的边缘图。它适合做图像预处理、工业质检、医学图像分割前端、自动驾驶感知里的轮廓提取。如果你正在找「边缘检测 深度学习」的落地入口HED 是绕不开的经典基线也是理解 PiDiNet 等后续轻量方案的基础。2. HED 的网络结构拆解为什么多尺度侧输出比单层卷积靠谱2.1 从 VGG16 主干到五个侧输出分支HED 的主干通常用 VGG16 的卷积层部分去掉全连接层。输入一张 RGB 图经过五个 stage 的卷积和下采样得到五个不同分辨率的特征图。每个 stage 后面接一个侧输出分支先是一个 1×1 卷积把通道数压到 1再上采样到原图尺寸最后用 sigmoid 归一化到 [0,1] 表示每个像素是边缘的概率。五个侧输出分别对应不同感受野浅层特征分辨率高能捕捉细边缘深层特征语义强能抑制纹理噪声。融合层把五个侧输出按通道拼接再用一个 1×1 卷积加权求和得到最终边缘图。这种设计的好处是单层卷积只能看到局部梯度遇到模糊边缘或颜色渐变就失效多尺度侧输出相当于让网络同时用「放大镜」和「广角镜」看图像细边和粗轮廓都不丢。我一般会保留五个侧输出用于中间监督训练时每个分支都算损失这样梯度能直接传到浅层避免深层主导导致细边丢失。2.2 损失函数与类别不平衡处理边缘检测有个天然问题边缘像素只占全图 1% 到 5%正负样本极度不平衡。如果直接用交叉熵网络会倾向于全预测为非边缘准确率看着高但边缘全丢。HED 原论文用了加权交叉熵对正样本给更高权重通常设正样本权重为 0.95负样本 0.05或者用正负样本比例动态调整。更稳的做法是加一个 Dice loss 或 IoU loss 作为辅助直接优化边缘重叠度。下面是一个 PyTorch 版本的损失函数示例我一般会这样组合import torch import torch.nn as nn import torch.nn.functional as F class HEDLoss(nn.Module): def __init__(self, pos_weight0.95, use_diceTrue): super().__init__() self.pos_weight pos_weight self.use_dice use_dice def forward(self, preds, target): # preds: list of tensors, each [B,1,H,W] # target: [B,1,H,W], values in {0,1} total_loss 0.0 for pred in preds: # 加权交叉熵正样本权重大缓解类别不平衡 weight torch.where(target 0.5, torch.tensor(self.pos_weight, devicepred.device), torch.tensor(1 - self.pos_weight, devicepred.device)) bce F.binary_cross_entropy(pred, target, weightweight) loss bce if self.use_dice: # Dice loss 直接优化重叠度对细边缘更敏感 intersection (pred * target).sum(dim(2,3)) union pred.sum(dim(2,3)) target.sum(dim(2,3)) dice 1 - (2 * intersection 1e-6) / (union 1e-6) loss loss dice.mean() total_loss loss return total_loss / len(preds)参数说明pos_weight控制正样本权重0.9 到 0.95 之间比较稳use_dice建议开启尤其当边缘很细时。注意每个侧输出都参与损失计算融合输出也单独算一次这样中间监督才有效。如果显存吃紧可以只对融合输出和最后两个侧输出算损失但细边质量会下降。2.3 训练数据准备与标注格式HED 训练需要边缘标注图通常是二值图边缘为 1背景为 0。常用数据集有 BSDS500、NYUDv2工业场景可以自己标。标注时注意边缘宽度控制在 2 到 3 像素太细网络学不到太粗会导致边缘模糊。如果只有原图没有标注可以用 Canny 生成伪标签但阈值要调好否则噪声会被当成边缘学进去。数据增强对 HED 很重要。我一般会做随机裁剪、水平翻转、颜色抖动但不做垂直翻转因为自然图像中上下边缘分布不同。裁剪尺寸建议 256×256 或 320×320太小会丢上下文太大显存扛不住。下面是一个简单的 Dataset 写法from torch.utils.data import Dataset import cv2 import numpy as np class EdgeDataset(Dataset): def __init__(self, img_paths, edge_paths, size256): self.img_paths img_paths self.edge_paths edge_paths self.size size def __len__(self): return len(self.img_paths) def __getitem__(self, idx): img cv2.imread(self.img_paths[idx]) img cv2.cvtColor(img, cv2.COLOR_BGR2RGB) edge cv2.imread(self.edge_paths[idx], 0) # 随机裁剪 h, w img.shape[:2] if h self.size and w self.size: top np.random.randint(0, h - self.size) left np.random.randint(0, w - self.size) img img[top:topself.size, left:leftself.size] edge edge[top:topself.size, left:leftself.size] else: img cv2.resize(img, (self.size, self.size)) edge cv2.resize(edge, (self.size, self.size)) # 归一化 img img.astype(np.float32) / 255.0 img (img - [0.485, 0.456, 0.406]) / [0.229, 0.224, 0.225] edge (edge 127).astype(np.float32) img torch.from_numpy(img).permute(2,0,1) edge torch.from_numpy(edge).unsqueeze(0) return img, edge这里用了 ImageNet 的均值和方差做归一化因为主干是 VGG16 预训练权重。边缘图阈值 127 转成 0/1。注意裁剪时图像和边缘图要同步否则监督信号错位训练直接崩。3. 从零跑通 HED环境、训练、推理的最小闭环3.1 环境配置与依赖安装HED 对环境要求不高PyTorch 1.7 以上都能跑。我一般用 Miniconda 建独立环境避免和系统 Python 打架。下面是一套 CPU 和 GPU 都能用的配置命令conda create -n hed python3.8 -y conda activate hed # 安装 PyTorch根据 CUDA 版本选对应命令 pip install torch1.10.0 torchvision0.11.0 # 其他依赖 pip install opencv-python numpy matplotlib tqdm如果只有 CPU把 torch 换成 CPU 版本即可训练会慢但推理能跑。显存建议 6GB 以上batch size 设 4 到 8。注意不要混用 conda 和 pip 装 torch容易出玄学问题我一般统一用 pip。3.2 模型定义与侧输出融合下面是一个精简版 HED 模型定义主干用 torchvision 的 VGG16 特征层import torch import torch.nn as nn import torchvision.models as models class HED(nn.Module): def __init__(self): super().__init__() vgg models.vgg16(pretrainedTrue).features # VGG16 五个 stage 的切分点 self.stage1 vgg[:4] # conv1_1, conv1_2 self.stage2 vgg[4:9] # conv2_1, conv2_2 self.stage3 vgg[9:16] # conv3_1, conv3_2, conv3_3 self.stage4 vgg[16:23] # conv4_1, conv4_2, conv4_3 self.stage5 vgg[23:30] # conv5_1, conv5_2, conv5_3 # 侧输出分支1x1 卷积压到 1 通道 self.side1 nn.Conv2d(128, 1, 1) self.side2 nn.Conv2d(256, 1, 1) self.side3 nn.Conv2d(512, 1, 1) self.side4 nn.Conv2d(512, 1, 1) self.side5 nn.Conv2d(512, 1, 1) # 融合层 self.fuse nn.Conv2d(5, 1, 1) def forward(self, x): h, w x.shape[2:] c1 self.stage1(x) c2 self.stage2(c1) c3 self.stage3(c2) c4 self.stage4(c3) c5 self.stage5(c4) # 每个侧输出上采样到原图尺寸 s1 F.interpolate(self.side1(c1), size(h,w), modebilinear, align_cornersFalse) s2 F.interpolate(self.side2(c2), size(h,w), modebilinear, align_cornersFalse) s3 F.interpolate(self.side3(c3), size(h,w), modebilinear, align_cornersFalse) s4 F.interpolate(self.side4(c4), size(h,w), modebilinear, align_cornersFalse) s5 F.interpolate(self.side5(c5), size(h,w), modebilinear, align_cornersFalse) # 融合 fused self.fuse(torch.cat([s1,s2,s3,s4,s5], dim1)) return [torch.sigmoid(s) for s in [s1,s2,s3,s4,s5, fused]]注意F.interpolate的align_cornersFalse这是 PyTorch 新版推荐设置避免上采样偏移。侧输出分支没有加 bias因为后面接 sigmoidbias 影响不大。融合层用 1×1 卷积学习五个分支的权重初始可以设成均值 0.2但让网络自己学也行。3.3 训练循环与学习率策略训练时我一般用 Adam 优化器学习率 1e-4每 10 个 epoch 降一半。batch size 根据显存调4 到 8 都行。下面是一个最小训练循环model HED().cuda() criterion HEDLoss(pos_weight0.95, use_diceTrue) optimizer torch.optim.Adam(model.parameters(), lr1e-4) scheduler torch.optim.lr_scheduler.StepLR(optimizer, step_size10, gamma0.5) for epoch in range(50): model.train() total_loss 0 for img, edge in dataloader: img, edge img.cuda(), edge.cuda() preds model(img) loss criterion(preds, edge) optimizer.zero_grad() loss.backward() optimizer.step() total_loss loss.item() scheduler.step() print(fEpoch {epoch}, Loss: {total_loss/len(dataloader):.4f})关键点preds是六个输出损失函数内部会遍历。如果显存不够可以把torch.cuda.amp混合精度加上速度能快 30% 左右。训练 50 个 epoch 在 BSDS500 上差不多收敛工业数据可能要 100 个 epoch。注意保存验证集上指标最好的模型不要只看训练 loss。3.4 推理与后处理从概率图到二值边缘推理时只用融合输出取 sigmoid 后的概率图阈值一般设 0.5但实际可以调。我一般会做非极大值抑制NMS细化边缘再用形态学闭运算连接断边。下面是一个推理脚本model.eval() with torch.no_grad(): img_tensor preprocess(img).unsqueeze(0).cuda() preds model(img_tensor) edge_prob preds[-1].squeeze().cpu().numpy() # 阈值化 edge_bin (edge_prob 0.5).astype(np.uint8) * 255 # 可选NMS 细化 # 这里用简单形态学闭运算连接断边 kernel np.ones((3,3), np.uint8) edge_bin cv2.morphologyEx(edge_bin, cv2.MORPH_CLOSE, kernel) cv2.imwrite(edge_result.png, edge_bin)阈值 0.5 是起点如果边缘太碎就降到 0.3如果噪声多就升到 0.7。NMS 可以用 OpenCV 的cv2.Canny对概率图做一次但会引入新参数我一般先用形态学。注意推理时要把图像 resize 到训练时的尺寸或者用全卷积方式跑原图但显存要够。4. 避坑与排查HED 训练和推理中常见的五个翻车点4.1 损失不下降边缘全黑或全白现象训练几个 epoch 后loss 卡在 0.6 左右输出图要么全黑要么全白。原因正负样本权重设反了或者学习率太大导致梯度爆炸。解决检查pos_weight是否大于 0.5学习率降到 1e-5 再试。如果全白说明网络把所有像素预测为边缘把负样本权重调高全黑则相反。我一般会先跑一个 batch 过拟合看 loss 能不能降到 0.1 以下不能就是代码有 bug。4.2 边缘断裂、不连续现象推理结果边缘断成虚线尤其是弱边缘。原因侧输出融合时浅层权重太低或者 Dice loss 权重不够。解决把融合层初始权重偏向浅层或者单独给浅层侧输出加更大损失权重。另一个办法是后处理用形态学闭运算但治标不治本。我一般会在损失里给 side1 和 side2 乘 1.5 倍权重强迫网络关注细边。4.3 显存溢出OOM现象训练到一半报 CUDA out of memory。原因batch size 太大或者上采样时特征图没释放。解决减小 batch size 到 2 或 1用torch.cuda.empty_cache()清理缓存。如果还不行把 VGG16 的 stage5 去掉只用到 stage4显存能省 30%。推理时用torch.no_grad()并分块处理大图。4.4 验证集指标高但视觉效果差现象ODS、OIS 指标看着不错但实际边缘图很脏。原因BSDS500 的标注本身有模糊边缘指标高不代表细边好。解决自己标一批工业图做验证用肉眼判断。我一般会同时看 ODS 和固定阈值下的 F1后者更接近实际部署效果。如果指标高但视觉差多半是阈值没调好或者训练数据标注太粗。4.5 推理速度慢达不到实时现象单张 512×512 图推理要 200ms 以上。原因VGG16 主干太重上采样用双线性插值也耗时。解决换轻量主干如 MobileNetV2或者用 PiDiNet 这类专门优化的边缘检测网络。如果必须用 HED可以把输入 resize 到 320×320推理后再放大速度能快一倍。FPGA 部署的话HED 不太合适参数量太大建议看 PiDiNet 或更轻的模型。5. 进阶技巧用 HED 做预训练 backbone 和跨域迁移HED 训练完之后主干 VGG16 其实学到了很好的边缘特征可以直接拿来当其他任务的预训练权重。我做过一个实验把 HED 的 stage1 到 stage4 冻结后面接一个小的分割头在医学图像上做细粒度分割比直接用 ImageNet 预训练收敛快 20% 左右。原因是 HED 的主干对边缘和纹理更敏感而医学图像恰好依赖边界信息。具体做法是加载 HED 模型取stage1到stage4的权重迁移到新网络里学习率设成其他层的 0.1 倍。如果目标域和自然图像差异大比如超声或 X 光可以先在目标域上跑一遍 HED 推理生成伪边缘标签再微调 HED 本身。这个过程叫自训练能显著提升跨域效果。另一个技巧是侧输出融合权重的可视化。训练完后把融合层的 1×1 卷积权重打印出来通常浅层权重在 0.3 到 0.4深层在 0.1 到 0.2。如果某个分支权重接近 0说明那个尺度没学到东西可以砍掉。我一般会保留权重最高的三个分支推理速度能快 40%精度掉不到 1%。最后说一个我踩过的坑HED 对输入图像的对比度很敏感。如果测试图和训练图亮度差异大边缘会丢很多。解决办法是在预处理里加自适应直方图均衡化CLAHE但训练时也要加同样的增强否则分布不一致。我现在的习惯是任何边缘检测模型上线前先跑一遍 CLAHE 对比实验确认不会掉点再部署。希望帮到你。本文还有配套的精品资源点击获取
返回列表