sobel算子
人眼怎么识别图像边缘?
比如有一幅图,图里面有一条线,左边很亮,右边很暗,那人眼就很容易识别这条线作为边缘.也就是像素的灰度值快速变化的地方.
对于f(t),其导数f'(t)反映了每一处的变化趋势.在变化最快的位置其导数最大. sobel算子的思路就是模拟求一阶导数.
sobel算子是一个离散差分算子.它计算图像像素点亮度值的近似梯度.
图像是二维的,即沿着宽度/高度两个方向.
我们使用两个卷积核对原图像进行处理:
- 水平方向
很好理解,原始像素灰度值-->(右边像素值-左边像素值),反映了水平方向的变化情况.
- 垂直方向
这样的话,我们就得到了两个新的矩阵,分别反映了每一点像素在水平方向上的亮度变化情况和在垂直方向上的亮度变换情况.
综合考虑这两个方向的变化,我们使用
反映某个像素的梯度变化情况.
有时候为了简单起见,也直接用绝对值相加替代.
opencv里可以使用了如下的卷积核,可以"放大像素的变化情况".
可以参考这个函数Scharr
opencv实现:
import cv2 as cv
def test():
src = cv.imread("/home/sc/disk/keepgoing/opencv_test/sidetest.jpeg")
src = cv.GaussianBlur(src, (3, 3), 0)
gray = cv.cvtColor(src, cv.COLOR_BGR2GRAY)
grad_x = cv.Sobel(gray, -1, 1, 0, ksize=3)
grad_y = cv.Sobel(gray, -1, 0, 1, ksize=3)
grad = cv.addWeighted(grad_x, 0.5, grad_y, 0.5, 0)
cv.imshow("origin",src)
cv.imshow("grad",grad)
cv.waitKey()
test()
首先是高斯模糊去噪.某种意义上说高斯模糊是和sobel相反的过程.高斯模糊平滑了某点像素与周边像素的差异.那为什么还要先高斯去噪呢?
(噪声就是像素的强度相对于真值有个突变。从时域上讲,通过高斯滤波能让一个像素的强度与周围的点相关,就减小了突变的影响;从频域上讲,突变引入了高频分量,而高斯滤波器可以滤除高频分量。)
高斯去噪是为了防止把噪点也检测为边缘.
然后计算grad_x,grad_y.即对原图做水平方向/垂直方向的sobel卷积核卷积
grad_x = cv.Sobel(gray, -1, 1, 0, ksize=3)
grad_y = cv.Sobel(gray, -1, 0, 1, ksize=3)
Sobel()
void cv::Sobel ( InputArray src,
OutputArray dst,
int ddepth,
int dx,
int dy,
int ksize = 3,
double scale = 1,
double delta = 0,
int borderType = BORDER_DEFAULT
)
Python:
dst = cv.Sobel( src, ddepth, dx, dy[, dst[, ksize[, scale[, delta[, borderType]]]]] )
Parameters
src | input image. |
dst | output image of the same size and the same number of channels as src . |
ddepth | output image depth, see combinations; in the case of 8-bit input images it will result in truncated derivatives. |
dx | order of the derivative x. |
dy | order of the derivative y. |
ksize | size of the extended Sobel kernel; it must be 1, 3, 5, or 7. |
scale | optional scale factor for the computed derivative values; by default, no scaling is applied (see getDerivKernels for details). |
delta | optional delta value that is added to the results prior to storing them in dst. |
borderType | pixel extrapolation method, see BorderTypes |
注意区分c++版本和python版本api. 在上述代码中,第二个参数-1代表我们希望输出的图像矩阵和原图有同样的depth,第3/4个参数分别代表在x/y方向做一阶差分.取值0或1.
ksize必须为奇数.
tips:通常我们使用( xorder = 1, yorder = 0, ksize = 3) or ( xorder = 0, yorder = 1, ksize = 3)来计算水平/垂直方向的一阶差分矩阵.ksize=3用的是标准sobel卷积核.如果ksize传入FILTER_SCHARR,则使用的是如下卷积核:
最后将两个矩阵叠加,综合考虑水平和垂直方向的像素灰度值变化强度.得到边缘.
grad = cv.addWeighted(grad_x, 0.5, grad_y, 0.5, 0)