ARTICLE DETAIL

资讯详情

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

一种边缘胞腔编码的简易方法

一种边缘胞腔编码的简易方法 目录1 基础边缘标记算法 LabelCells2 二值化梯度边缘算法的改进方案3 彩色图像带符号色彩差值函数 ColorDifSign4 可视化调试工具说明1 基础边缘标记算法 LabelCells本算法引入复合图像CombIm存储边缘对应的胞腔复形其分辨率与待处理原图存在固定映射关系复合图像宽度 、高度 。尺寸表达式中额外加 1用于为复合图像最右侧、最底部边界上的顶点分配独立存储空间。这类边界顶点在部分边缘后处理流程中具备关键作用不可省略。LabelCells逐行读取经过极值滤波器预处理后的图像遍历每一个像素分别计算该像素与其左侧邻像素、上方邻像素之间的色彩差值绝对值并与预设边缘阈值对比。阈值由用户自主配置。阈值越低检出的边缘裂缝数量越多同时边缘发生膨胀、线条变粗的风险也随之上升阈值最优取值一般需要通过多组对照实验调试确定。若像素与的色彩差值绝对值大于阈值则将两像素中间的垂直裂缝垂直边缘元胞标记为 1若像素与的色彩差值绝对值大于阈值则将两像素中间的水平裂缝水平边缘元胞标记为 1。裂缝两端相连的顶点计数同步自增。单个顶点最多可交汇 4 条边缘裂缝顶点标签最终的计数值等价于交汇于该点的裂缝总数该顶点连接信息可供后续边缘解析、边缘拓扑重建使用。算法源码C#版/// summary /// 基础版边缘胞腔标记算法 /// 基于像素色彩差阈值检测水平、垂直边缘裂缝并统计顶点连接度数 /// /summary /// param nameth边缘检测阈值/param /// param nameImage3经过极值滤波预处理后的输入图像/param public void LabelCells(int th, CImage Image3) { int difH, difV, nbyte, NXB Image3.width, x, y; const byte EdgeLabel 1; // 判断图像通道数24‑bit彩色图像占3字节灰度图像单字节 nbyte Image3.N_Bits 24 ? 3 : 1; // 清空胞腔网格缓冲区 for (x 0; x width * height; x) Grid[x] 0; byte[] Colorh new byte[3]; // 上方邻像素颜色缓存 byte[] Colorp new byte[3]; // 当前像素颜色缓存 byte[] Colorv new byte[3]; // 左侧邻像素颜色缓存 // 初始化所有顶点网格置零隔点映射原图坐标 for (y 0; y height; y 2) { for (x 0; x width; x 2) { Grid[x width * y] 0; } } // 遍历裂缝所在网格位置执行边缘检测 for (y 1; y height; y 2) { for (x 1; x width; x 2) { // 垂直裂缝检测 if (x 3) { // 读取左右一对相邻原图像素的色彩 for (int c 0; c nbyte; c) { Colorv[c] Image3.Grid[c nbyte * ((x - 2) / 2) nbyte * NXB * (y / 2)]; Colorp[c] Image3.Grid[c nbyte * (x / 2) nbyte * NXB * (y / 2)]; } // 计算色彩差值的绝对值 if (nbyte 3) difV ColorDifAbs(Colorp, Colorv); else difV Math.Abs(Colorp[0] - Colorv[0]); if (difV 0) difV -difV; // 差值超限则标记垂直边缘裂缝并更新上下端点计数 if (difV th) { Grid[x - 1 width * y] EdgeLabel; Grid[x - 1 width * (y - 1)]; Grid[x - 1 width * (y 1)]; } } // 水平裂缝检测 if (y 3) { for (int c 0; c nbyte; c) { Colorh[c] Image3.Grid[c nbyte * (x / 2) nbyte * NXB * ((y - 2) / 2)]; Colorp[c] Image3.Grid[c nbyte * (x / 2) nbyte * NXB * (y / 2)]; } if (nbyte 3) difH ColorDifAbs(Colorp, Colorh); else difH Math.Abs(Colorp[0] - Colorh[0]); if (difH th) { Grid[x width * (y - 1)] EdgeLabel; Grid[x - 1 width * (y - 1)]; Grid[x 1 width * (y - 1)]; } } } } }2 二值化梯度边缘算法的改进方案基础二值梯度边缘检测方案存在明显缺陷当灰度或色彩差异阈值设置偏低时输出边缘线条往往过粗。 该问题源于图像局部模糊区域所形成的亮度渐变斜坡。若渐变斜坡的横向宽度超过 4 像素常规 5×5 窗口极值滤波器前文代码窗口参数hWind2无法完全滤除斜坡带来的渐变效应最终输出边缘会出现模糊、膨胀变宽的现象。为解决边缘过粗问题本文在LabelCells算法基础之上提出改进版本LabelCellsSign。该算法设计目标与 Canny (1986) 提出的非极大值抑制思想相近但底层实现方案完全独立。2.1 数字图像梯度的离散化定义数字图像不属于连续欧氏空间因此无法直接套用连续域梯度定义梯度为亮度函数分别对坐标 x、y 求偏导得到的二维向量。 工程上必须采用有限差分近似替代微分运算 ‑ X 方向差分‑ Y 方向差分像素亮度差值并不隶属于任意单个像素而是定义于一对相邻像素中间、被称作裂缝的一维胞腔之上梯度信息则存储在交汇两条裂缝的顶点零维胞腔当中。胞腔复形ACC的数据架构恰好适配该存储模型。灰度图像的边缘检测思路逐行逐列计算图像灰度差分标记差分幅值大于边缘阈值的候选边缘区间在沿梯度走向连通的候选区间内定位差分幅值的最大值位置该极值点即为最终输出的单像素边缘。对于亮度渐变斜坡差分最大值所在位置就是真实边缘所在位置。因此无需沿着梯度方向搜索完整连通域 ‑垂直边缘逐行遍历相邻像素筛选出差分值超过阈值的连续区间取区间差分最大值作为唯一垂直边缘 ‑水平边缘逐列遍历相邻像素筛选出差分值超过阈值的连续区间取区间差分最大值作为唯一水平边缘。⚠️ 关键约束不可仅使用差分绝对值 亮度斜坡分为上升沿与下降沿。深色条带上方存在一条亮度递减斜坡、下方存在一条亮度递增斜坡两处斜坡对应两条相互独立的边缘。若仅依靠差分绝对值求取极值两处峰值将被合并进而造成一条边缘漏检。 差分运算必须保留正负符号正向斜坡搜索正差值峰值负向斜坡搜索负差值谷值。2.2 LabelCellsSign 算法原理灰度‑彩色图像通用本算法采用有限状态机实现斜坡区间检测状态变量State用于记录当前斜坡检测阶段初始状态置 0。调用接口CombIm.LabelCellsSign(threshold, InputImage)复合图像CombIm尺寸依旧为。算法分为两轮独立扫描流程第一轮逐行扫描检出所有垂直边缘裂缝第二轮逐列扫描检出所有水平边缘裂缝。垂直裂缝扫描逻辑外层循环遍历所有奇数行 y内层循环起始坐标设置为x3以此保证索引x‑2不会触发图像越界计算相邻原图像素之间带符号色彩差值 difV不取绝对值。‑Inp标记差分与阈值的关系Inp 1差值 正向阈值 thInp 0‑th ≤差值 ≤thInp -1差值 负向阈值‑th ‑ 复合控制变量Contr State * 3 Inp驱动状态机分支跳转 ‑xStartP正向亮度斜坡区间起始坐标xStartM负向亮度斜坡区间起始坐标xopt保存差分极值对应的最优边缘坐标。状态机运行流程垂直斜坡扫描行起始时刻State 0当Inp1时Contr1算法进入正向斜坡检测状态记录斜坡起始坐标、当前差分最大值更新State 1斜坡持续区间State1, Inp1→Contr4沿扫描方向迭代更新正向差分最大值与最优边缘坐标斜坡结束State1, Inp0→Contr3在最优坐标位置标记垂直边缘裂缝同步更新裂缝上下两端顶点计数检测状态重置为 0。水平裂缝检测逻辑与垂直裂缝完全对称遍历维度由行改为列变量映射关系difV→difHxopt→yoptxStartP→yStartPxStartM→yStartM。算法源码C#版/// summary /// 带符号梯度改进型单像素边缘检测算法 /// 实现类非极大值抑制效果解决渐变斜坡造成边缘膨胀过粗的问题 /// /summary /// param nameth边缘检测阈值/param /// param nameExtrm经过极值滤波预处理后的输入图像/param /// returns标记完成返回1/returns public int LabelCellsSign(int th, CImage Extrm) { int difH, difV, c, maxDif, minDif, nByte, NXB Extrm.width, x, y, xopt, yopt; int Inp, State, Contr, xStartP, xStartM, yStartP, yStartM; nByte Extrm.N_Bits 24 ? 3 : 1; // 清空胞腔网格 for (x 0; x width * height; x) Grid[x] 0; byte[] Colorp new byte[3]; byte[] Colorh new byte[3]; byte[] Colorv new byte[3]; maxDif 0; minDif 0; // 第一阶段垂直裂缝检测 for (y 1; y height; y 2) { State 0; xopt -1; xStartP xStartM -1; for (x 3; x width; x 2) { // 读取左右一对相邻像素色彩 for (c 0; c nByte; c) { Colorv[c] Extrm.Grid[c nByte * ((x - 2) / 2) nByte * NXB * (y / 2)]; Colorp[c] Extrm.Grid[c nByte * (x / 2) nByte * NXB * (y / 2)]; } // 计算带符号色彩差值 if (nByte 3) difV ColorDifSign(Colorp, Colorv); else difV Colorp[0] - Colorv[0]; // 判定当前差分所处区间 if (difV th) Inp 1; else if (difV -th) Inp 0; else Inp -1; Contr State * 3 Inp; switch (Contr) { case 4: if (x xStartP difV maxDif) { maxDif difV; xopt x; } break; case 3: Grid[xopt - 1 width * y] 1; Grid[xopt - 1 width * (y - 1)]; Grid[xopt - 1 width * (y 1)]; State 0; break; case 2: Grid[xopt - 1 width * y] 1; Grid[xopt - 1 width * (y - 1)]; Grid[xopt - 1 width * (y 1)]; minDif difV; xopt x; xStartM x; State -1; break; case 1: maxDif difV; xopt x; xStartP x; State 1; break; case 0: break; case -1: minDif difV; xopt x; xStartM x; State -1; break; case -2: Grid[xopt - 1 width * y] 1; Grid[xopt - 1 width * (y - 1)]; Grid[xopt - 1 width * (y 1)]; maxDif difV; xopt x; xStartP x; State 1; break; case -3: Grid[xopt - 1 width * y] 1; Grid[xopt - 1 width * (y - 1)]; Grid[xopt - 1 width * (y 1)]; State 0; break; case -4: if (x xStartM difV minDif) { minDif difV; xopt x; } break; } } } // 第二阶段水平裂缝检测 for (x 1; x width; x 2) { State 0; minDif 0; yopt -1; yStartP yStartM 0; for (y 3; y height; y 2) { // 读取上下一对相邻像素色彩 for (c 0; c nByte; c) { Colorh[c] Extrm.Grid[c nByte * (x / 2) nByte * NXB * ((y - 2) / 2)]; Colorp[c] Extrm.Grid[c nByte * (x / 2) nByte * NXB * (y / 2)]; } if (nByte 3) difH ColorDifSign(Colorp, Colorh); else difH Colorp[0] - Colorh[0]; if (difH th) Inp 1; else if (difH -th) Inp 0; else Inp -1; Contr State * 3 Inp; switch (Contr) { case 4: if (y yStartP difH maxDif) { maxDif difH; yopt y; } break; case 3: Grid[x width * (yopt - 1)] 1; Grid[x - 1 width * (yopt - 1)]; Grid[x 1 width * (yopt - 1)]; State 0; break; case 2: Grid[x width * (yopt - 1)] 1; Grid[x - 1 width * (yopt - 1)]; Grid[x 1 width * (yopt - 1)]; yopt y; State -1; break; case 1: maxDif difH; yopt y; yStartP y; State 1; break; case 0: break; case -1: minDif difH; yopt y; yStartM y; State -1; break; case -2: Grid[x width * (yopt - 1)] 1; Grid[x - 1 width * (yopt - 1)]; Grid[x 1 width * (yopt - 1)]; yopt y; State 1; break; case -3: Grid[x width * (yopt - 1)] 1; Grid[x - 1 width * (yopt - 1)]; Grid[x 1 width * (yopt - 1)]; State 0; break; case -4: if (y yStartM difH minDif) { minDif difH; yopt y; } break; } } } return 1; }3 彩色图像带符号色彩差值函数 ColorDifSign彩色图像包含红、绿、蓝三个独立通道每个通道均可生成一组梯度。如果直接对三通道带符号差值求和各通道差值正负号相反时将会发生幅值抵消最终导致真实边缘丢失。为规避通道抵消问题本文采用幅值‑符号分离策略色彩差的幅值取 RGB 三通道差值绝对值之和差值的正负符号由两幅像素的亮度大小关系决定。/// summary /// 带符号彩色差分计算函数 /// 幅值为三通道差绝对值之和符号由像素亮度差值决定 /// /summary /// param nameColp当前像素RGB颜色数组/param /// param nameColh相邻像素RGB颜色数组/param /// returns带符号色彩差分值/returns int ColorDifSign(byte[] Colp, byte[] Colh) { int Dif 0; // 累加RGB三通道差值的绝对值 for (int c 0; c 3; c) { Dif Math.Abs(Colp[c] - Colh[c]); } // 根据像素亮度判定差值符号 int Sign; double lumP MaxC(Colp[2], Colp[1], Colp[0]); double lumH MaxC(Colh[2], Colh[1], Colh[0]); Sign (lumP - lumH 0) ? 1 : -1; // 幅值缩放后返回带符号差值 return (Sign * Dif) / 3; } /// summary /// MaxC函数计算像素亮度定义见文档第3章 /// /summary /// param namer红色通道/param /// param nameg绿色通道/param /// param nameb蓝色通道/param /// returns像素亮度值/returns private double MaxC(byte r, byte g, byte b) { // 亮度计算逻辑由原工程实现 return 0; }4 可视化调试工具说明项目WFdetectEdges1为LabelCellsSign算法配套调试平台用于直观观测斜坡‑边缘检出效果工具功能清单如下提取极值滤波图像单行亮度变化曲线绘制色彩差分幅值与边缘阈值的对比曲线可视化渲染算法输出的垂直边缘裂缝位置支持鼠标交互点击图像点位即可局部放大选中扫描行的边缘检测结果。可视化曲线释义下方曲线展示图像单行亮度分布上方竖线标记差分幅值超过阈值的候选点位红色竖线对应正向亮度斜坡绿色竖线对应负向亮度斜坡红、绿标记点位即为算法最终输出的精细化单像素边缘。然而采用本文所提的ExtremLightUni算法并设置一个滑动窗口尺寸大于渐变斜坡宽度时该算法大多只能检测出窄边缘表现为一条位于斜坡内部的细线。因此前文介绍的简易算法LabelCells便可取得良好的检测效果。为验证本文边缘检测算法的有效性我们选取维基百科《坎尼边缘检测器》一文提供的图像作为测试样本并将本算法作用于其中的彩色图像。图 7‑7 依次展示了该图像局部片段 (a)、本文算法检出的边缘结果 (b)以及坎尼边缘检测器得到的边缘结果 (c)。
返回列表