边缘提取

描述信号突变

对信号求导,xx方向上,有

f(x,y)x=limϵ0f(x+ϵ,y)f(x,y)ϵ\frac{\partial f(x,y)}{\partial x} = \lim_{\epsilon \to 0} \frac{f(x+\epsilon,y) - f(x,y)}{\epsilon}

可以用差分来近似替代

f(x,y)xf(x+1,y)f(x,y)1=f(x+1,y)f(x,y)\begin{aligned} \frac{\partial f(x,y)}{\partial x} &\approx \frac{f(x+1,y) - f(x,y)}{1}\\ &=f(x+1,y)-f(x,y) \end{aligned}

从而可以化为卷积的操作,卷积模板为:[11]\begin{bmatrix} -1 & 1 \end{bmatrix}

同理,yy方向上的模板为[11]\begin{bmatrix} -1\\ 1 \end{bmatrix}[11]\begin{bmatrix} 1 \\ -1 \end{bmatrix}

也可以定义为f(x+1,y)f(x1,y)f(x+1,y) - f(x-1,y),这样卷积模板就变成了[101]\begin{bmatrix} -1 & 0 & 1 \end{bmatrix}

图像的梯度f=[fx,fy]\nabla f = \big[ \frac{\partial f}{\partial x},\frac{\partial f}{\partial y} \big]

梯度方向,指向信号变化最大的方向。方向通常与边的方向垂直

f\Vert \nabla f \Vert称为梯度幅值(Gradient Magnitude)


几种典型算子

Prewitt算子

Mx=[101101101]My=[111000111]M_x = \begin{bmatrix} -1 & 0 & 1\\ -1 & 0 & 1\\ -1 & 0 & 1 \end{bmatrix} \qquad M_y = \begin{bmatrix} 1 & 1 & 1\\ 0 & 0 & 0\\ -1 & -1 & -1 \end{bmatrix}

xx方向来看,相比于[11]\begin{bmatrix} -1 & 1 \end{bmatrix},好处是将上面和下面的点一起加入比较,减少噪声造成的影响。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import cv2
import numpy as np

img = cv2.imread('img_set/miku.jpg')

# Prewitt算子
prewittx = np.array([[-1, 0, 1],
[-1, 0, 1],
[-1, 0, 1]])

prewitty = np.array([[1, 1, 1],
[0, 0, 0],
[-1, -1, -1]])

dst1 = cv2.filter2D(img, -1, prewittx)
dst2 = cv2.filter2D(img, -1, prewitty)

cv2.imshow('original', img)
cv2.imshow('convx', dst1)
cv2.imshow('convy', dst2)
cv2.waitKey(0)
cv2.destroyAllWindows()

Soble算子

Mx=[101202101]My=[121000121]M_x = \begin{bmatrix} -1 & 0 & 1\\ -2 & 0 & 2\\ -1 & 0 & 1 \end{bmatrix} \qquad M_y = \begin{bmatrix} 1 & 2 & 1\\ 0 & 0 & 0\\ -1 & -2 & -1 \end{bmatrix}

Sobel算子相当于在提取边缘之前,先对图像做了一次高斯滤波,然后再提取边缘。这样对噪声的敏感程度会更低一些。

Mx=[101202101]=[121][101]M_x = \begin{bmatrix} -1 & 0 & 1\\ -2 & 0 & 2\\ -1 & 0 & 1 \end{bmatrix} = \begin{bmatrix} 1 \\ 2 \\ 1 \end{bmatrix} \begin{bmatrix} -1 & 0 & 1 \end{bmatrix}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# Soble算子
soblex = np.array([[-1, 0, 1],
[-2, 0, 2],
[-1, 0, 1]])

sobley = np.array([[1, 2, 1],
[0, 0, 0],
[-1, -2, -1]])

dst3 = cv2.filter2D(img, -1, prewittx)
dst4 = cv2.filter2D(img, -1, prewitty)

cv2.imshow('soblex', dst3)
cv2.imshow('sobley', dst4)
cv2.waitKey(0)
cv2.destroyAllWindows()

Roberts算子

Mx=[0110]My=[1001]M_x = \begin{bmatrix} 0 & 1\\ -1 & 0\\ \end{bmatrix} \qquad M_y = \begin{bmatrix} 1 & 0\\ 0 & -1\\ \end{bmatrix}

衡量的是斜对角方向的差异值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# Roberts算子
robertsx = np.array([[0, 1],
[-1, 0]])

robertsy = np.array([[1, 0],
[0, -1]])

dst5 = cv2.filter2D(img, -1, robertsx)
dst6 = cv2.filter2D(img, -1, robertsy)

cv2.imshow('robertsx', dst5)
cv2.imshow('robertsy', dst6)
cv2.waitKey(0)
cv2.destroyAllWindows()


高斯偏导核

图像有噪声怎么办?

  1. 平滑,去躁
  2. 对平滑的结果进行求导

但是,卷积的操作是比较耗时的,先平滑再求导,相当于做了两次连续卷积。能不能一次卷积就可以得到结果呢?

由之前的知识可知,卷积具有交换律和结合律,因此

ddx(fg)=fddxg\frac{d}{dx} (f * g) = f * \frac{d}{dx}g

高斯核变成了高斯偏导核(Derivative of Gaussian filter)

高斯偏导模板是不可分离的,对高斯核求导就可以看出(高斯核里的xxyy是二次的,求导之后会变成一次的,无法分离出xxyy)。

高斯平滑核和高斯偏导核对比

高斯平滑核:移除“高频”信号;没有负数;加权求和后为1。

高斯偏导核:用来提取边缘信息;里面的值可以为负数;加权求和为0;值越高,代表越有可能是边缘。


Canny算子

论文下载:A Computational Approach to Edge Detection

非最大化抑制(Non-Maximum Suppression)

把很宽的边处理成窄边,真正的找到边的中点。

qq点在梯度方向上与邻居pprrpprrqq都只有一个像素)比较,保留梯度值大的那个点。通过q算出来的要与之比较的pprr有可能不在整数位置上,这时候可以用其周围的四个像素进行“双线性插值”求出像素点pprr的位置。

门限化

门限(阈值)如果设置的较高,会使一些真正的边“消失”;如果设置的较低,很多“假边”又会出来。

双门限化(Hysteresis Thresholding)

Canny算子做的工作是,先用高门限,把强边缘检测出来。然后降低门限,把响应值比较低的边也显现出来。假设“噪声边不会与强边缘有连接关系”,只有与强边缘有连接关系的边才是真正的边。

将强边缘与弱边缘叠加,并删除与强边缘没有连接关系的弱边缘。

算法流程:

  1. 用高斯偏导核对图像滤波
  2. 找到幅值最大的点和梯度方向
  3. 非最大化抑制
  4. 连接和门限化(hysteresis)
    • 定义高低两个门限
    • 用高门限去找到强边缘,用低门限把有连接关系的边找回来

该算法于1986年发表于人工智能领域顶级期刊 Pattern Analysis and Machine Intelligence(PAMI)上