首先要明白的是:
(a.) canny算子中非最大抑制(Non-maximum suppression)是回答这样一个问题: “当前的梯度值在梯度方向上是一个局部最大值吗?” 所以,要把当前位置的梯度值与梯度方向上两侧的梯度值进行比较.
(b.) 梯度方向垂直于边缘方向, 这一点不要误解.
-
Q1: 插值是何意?为啥取g1,g2为一组,g3,g4为一组计算插值?
A: 首先, 虽然插值是有必要的,但是没有说必须要插值. 例如, Wikipedia里[1]的canny算子一文中的所讲的非最大值抑制就只是在0\90\45\135度梯度方向上进行的. 每个像素点梯度方向按照相近程度用这四个方向来代替,这种情况下,非最大值抑制所比较的相邻两个像素就是:
0 : 左边 和 右边
90 : 上边 和 下边
45 : 右上 和 左下
135: 左上 和 右下
这样做的好处是简单, 但是, 这种简化的方法无法达到最好的效果, 因为, 自然图像中的边缘梯度方向不一定是沿着这四个方向的. 因此, 就有很大的必要进行插值, 目的是找出在一个像素点上最能吻合其所在梯度方向的两侧的像素值.
然而, 通过argtan(gx/gy)得到的方向可以是-90~90度内的任何值, 但是, 因为实际数字图像中的像素点是离散的二维矩阵, 所以, 处在真正中心位置C处的梯度方向两侧的点是不一定存在的, 或者说是一个亚像素(sub pixel)点, 而这个不存在的点, 以及这个点的梯度值就必须通过对其两侧的点进行插值来得到.
至此, 插值的目的我讲述了一遍.
举个直观的例子,就是上面贴的那张图.
这个图给出的情况是, gy > gx 且 gx*gy > 0(注意: gx*gy>0是因为图像坐标系中y轴是朝下的.)
蓝色的直线代表梯度方向. g1, g2, g3, g4四个点以及插值点dTemp1, dTemp2 的位置如图所示.
对应的代码是:
g1 = pnMag[nPos-imageWidth-1] ;
g3 = pnMag[nPos+imageWidth+1] ;
g2 = pnMag[nPos-imageWidth] ;
g4 = pnMag[nPos+imageWidth] ;
下面解释, 插值公式为什么是:
dTmp1 = weight*g1 + (1-weight)*g2 ;
dTmp2 = weight*g3 + (1-weight)*g4 ;
这里: weight = fabs(gx)/fabs(gy) = ctan(theta), theta为梯度方向.
上面那个公式变一下就可以变换成:
weight = |dTemp1-g2|/|g1-g2| = |dTemp1-g2|/|C-g2| = ctan(theta);
而从我画的图中可以直观的看出,
在由 <,C, g2, dTemp1>组成的三角形中, 正好符合这个公式.(这里我没有太care正负号,只是为了给你一个直观解释)
讲到这里,应该你的后两个问题都不成问题了吧.
Q2: 原理上是比较3*3矩阵内相邻像素点是不是最大值,他为何比较g1,g2,g3,g4就完事了
A: 最大值抑制中的比较最大值不是看是不是3*3矩阵内的最大值, 而是看是不是梯度方向上的局部最大值. 否则就成了一个3*3的最大值滤波了. 为什么比较g1~g4, Q1的回答中已经讲清楚了.
Q3: 为何要分X,Y方向导数谁大两种情况比较
A: 把X, Y方向导数谁大两种情况分开只是为了编程上的方便. 你也可以分四种情况分别进行最大值抑制.
转载自:https://bbs.csdn.net/topics/370004267 中visionfans的回答