目录

OpenCV Chapter3 图像梯度与Canny边缘检测


OpenCV

图像梯度

Sobel算子

主要目的:找出图像中有梯度的地方

主要思路 $\rightarrow$ 计算左右/上下数据

最终落实在卷积核上面(注意此时权重仍然与位置有关,类似高斯滤波)(对应位置相加后求和) $$ Kernel_x=\begin{bmatrix} &-1\ 0\ +1\newline &-2\ 0\ +2\newline &-1\ 0\ +1\newline \end{bmatrix}\newline G_x = \sum_{1 \leq i \leq size, 1 \leq j \leq size} Kernel_x[i][j] \times image[i][j]\newline $$

$$ Kernel_y=\begin{bmatrix} &-1\ -2\ -1\newline &0\ 0\ 0\newline &+1\ +2\ +1\newline \end{bmatrix}\newline G_y = \sum_{1 \leq i \leq size, 1 \leq j \leq size} Kernel_y[i][j] \times image[i][j]\newline $$

1
cv2.Sobel(src, ddepth, dx, dy, ksize)
  • ddepth:图像的深度,通常为-1

  • dx和dy分别表示水平和竖直方向

  • ksize是Sobel算子的大小(一般为3x3或者5x5)

计算出来的数值如果越界会截断,只取范围内的最小或最大值

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
import cv2


def show(image):
    cv2.imshow('result', image)
    cv2.waitKey()
    cv2.destroyAllWindows()


img1 = cv2.imread("img path", 0)
show(img1)

img2 = cv2.Sobel(img1, cv2.CV_64F, 1, 1, ksize=3)  # 使用cv2.CV_64F,会取负值,如果是-1则做截断处理
show(img2)

将负数转换为绝对值(否则在显示的时候会按照截断处理),防止因为计算方向带来的误差

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
import cv2
import numpy as np


def show(image):
    cv2.imshow('result', image)
    cv2.waitKey()
    cv2.destroyAllWindows()


img1 = cv2.imread("img path", 0)
show(img1)

img2 = cv2.Sobel(img1, cv2.CV_64F, 1, 1, ksize=3)  # 使用cv2.CV_64F,会对负值取绝对值,如果是-1则做截断处理
show(img2)
print(img2)
'''
[[ 0.  0.  0. ...  0.  0.  0.]
 [ 0.  1.  0. ...  0. -2.  0.]
 [ 0. -1. -1. ...  0.  1.  0.]
 ...
 [ 0. -1.  2. ...  2. -2.  0.]
 [ 0.  0. -1. ... -1. -2.  0.]
 [ 0.  0.  0. ...  0.  0.  0.]]
'''
img3 = cv2.convertScaleAbs(img2)
show(img3)
print(img3)
'''
[[0 0 0 ... 0 0 0]
 [0 1 0 ... 0 2 0]
 [0 1 1 ... 0 1 0]
 ...
 [0 1 2 ... 2 2 0]
 [0 0 1 ... 1 2 0]
 [0 0 0 ... 0 0 0]]
'''

效果

原二值图

/img/Opencv/chapter3-1.png
Sobel(原图)

第一次使用Sobel算子

/img/Opencv/chapter3-2.png
Sobel(第一次使用Sobel算子)

取绝对值

/img/Opencv/chapter3-3.png
Sobel(取绝对值)

使用不同的方式求和

分别给0.5的权重

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import cv2


def show(image):
    cv2.imshow('result', image)
    cv2.waitKey()
    cv2.destroyAllWindows()


img1 = cv2.imread("img path", 0)
show(img1)

img2 = cv2.Sobel(img1, cv2.CV_64F, 1, 0, ksize=3)  # 使用cv2.CV_64F,会对负值取绝对值,如果是-1则做截断处理
show(img2)

img3 = cv2.convertScaleAbs(img2)
show(img3)

img4 = cv2.Sobel(img1, cv2.CV_64F, 0, 1, ksize=3)
show(img4)

img5 = cv2.convertScaleAbs(img4)
show(img5)

img6 = cv2.addWeighted(img3, 0.5, img5, 0.5, 0)  # 最后一个参数是初值 -> a1*x+b*y+c(最后一个参数)
show(img6)
print(img6)

为什么不同时设置$dx=1,dy=1$一起直接计算呢 $\rightarrow$ 直接计算效果不好(主要是融合效果不好),而且融合的时不如分开计算的自由度高

Scharr算子

权重与距离中心点的距离相关性更大,相比于Sobel算子来说更加准确敏感,可以看成是Sobel的改进 $$ Kernel_x=\begin{bmatrix} &-3\ 0\ +3\newline &-10\ 0\ +10\newline &-3\ 0\ +3\newline \end{bmatrix}\newline G_x = \sum_{1 \leq i \leq size, 1 \leq j \leq size} Kernel_x[i][j] \times image[i][j]\newline $$

$$ Kernel_y=\begin{bmatrix} &-3\ -10\ -3\newline &0\ 0\ 0\newline &+3\ +10\ +3\newline \end{bmatrix}\newline G_y = \sum_{1 \leq i \leq size, 1 \leq j \leq size} Kernel_y[i][j] \times image[i][j]\newline $$ 使用

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
import cv2


def show(image):
    cv2.imshow('result', image)
    cv2.waitKey()
    cv2.destroyAllWindows()


img1 = cv2.imread("img path", 0)
show(img1)

img2 = cv2.Scharr(img1, cv2.CV_64F, 1, 0)
show(img2)

Laplace算子

原理:二阶导数,对噪音点比较敏感,一般需要和其他方法配合使用 $$ Kernel=\begin{bmatrix} &0\ 1\ 0\newline &1\ -4\ 1\newline &0\ 1\ 0\newline \end{bmatrix}\newline G=\sum_{1 \leq i \leq size, 1 \leq j \leq size}Kernel_{i, j} \times image_{i, j}\newline $$

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
import cv2


def show(image):
    cv2.imshow('result', image)
    cv2.waitKey()
    cv2.destroyAllWindows()


img1 = cv2.imread("img path", 0)
show(img1)

img2 = cv2.Laplacian(img1, cv2.CV_64F)
show(cv2.convertScaleAbs(img2))

Canny边缘检测

流程

  1. 使用高斯滤波器,以平滑图像,滤除噪声
  2. 计算图像中每个像素点的梯度强度和方向
  3. 应用非极大值(Non-Maximum Suppression)抑制,以消除边缘检测带来的杂散响应
  4. 应用双阈值(Double-Threshold)检测来确定真实的和潜在的边缘
  5. 通过抑制孤立的弱边缘最终完成检测

高斯滤波器

$$ G(x, y)=\frac{1}{2\pi \sigma^2}e^{-\frac {x^2+y^2}{2\sigma^2}}\newline $$

$$ H=\begin{bmatrix} &0.0924\ 0.1192\ 0.0924\newline &0.1192\ 0.1538\ 0.1192\newline &0.0924\ 0.1192\ 0.0924\newline \end{bmatrix}\newline $$

$$ e=H * A = \begin{bmatrix} &h_{11}\ h_{12}\ h_{13}\newline &h_{21}\ h_{22}\ h_{23}\newline &h_{31}\ h_{32}\ h_{33}\newline \end{bmatrix} * \begin{bmatrix} &a\ b\ c\newline &d\ e\ f\newline &g\ h\ i\newline \end{bmatrix}=\sum_{1 \leq i \leq size, 1 \leq j \leq size}^{i, j} \begin{bmatrix} &a\times h_{11}\ b\times h_{12}\ c\times h_{13}\newline &d\times h_{21}\ e\times h_{22}\ f\times h_{23}\newline &g\times h_{31}\ h\times h_{32}\ i\times h_{33}\newline \end{bmatrix} $$

梯度和方向

使用Sobel算子

$$ Kernel_x=\begin{bmatrix} &-1\ 0\ +1\newline &-2\ 0\ +2\newline &-1\ 0\ +1\newline \end{bmatrix}\newline G_x = \sum_{1 \leq i \leq size, 1 \leq j \leq size} Kernel_x[i][j] \times image[i][j]\newline $$

$$ Kernel_y=\begin{bmatrix} &-1\ -2\ -1\newline &0\ 0\ 0\newline &+1\ +2\ +1\newline \end{bmatrix}\newline G_y = \sum_{1 \leq i \leq size, 1 \leq j \leq size} Kernel_y[i][j] \times image[i][j]\newline $$

$$ \begin{align} &G=\sqrt{G_x^2+G_y^2}\newline &\theta=arctan(\frac {G_y}{G_x})\newline \end{align}\newline $$

非极大值抑制

在所有框里面选出相关性最大的,同时还会比较该框和周围框的相关性

算法:遍历图像中的所有像素点,判断当前像素点是否是周围像素点中具有相同方向(此时会给$\theta$ 划分区间,同区间视为相同就行)梯度的最大值。如果是梯度最大的像素点,就保留,否则就抑制

这一步可以将模糊的边界变得清晰

双阈值检测

$$ \begin{align} \begin{cases} 梯度值>maxVal:处理为边界\newline minVal<梯度值<maxVal:如果该点可以通过线连到边界点(即大于阈值的点)则保留,否则舍弃\newline 梯度值<minVal:舍弃\newline \end{cases} \end{align} $$

minVal越小,条件越松,越多的边界会被检测到,反之则要求变高,检测的到边缘越少

maxVal越大,条件越高,反之越松

代码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
import cv2


def show(image):
    cv2.imshow('result', image)
    cv2.waitKey()
    cv2.destroyAllWindows()


img1 = cv2.imread("img path", 0)
show(img1)

img2 = cv2.Canny(img1, 80, 150) # -> src, minVal, maxVal
show(img2)

img3 = cv2.Canny(img1, 50, 100)
show(img3)

原图|(80, 150)|(50, 100)对比

/img/Opencv/chapter3-4.png
Canny(不同阈值下的效果(第一张是原图,第二张为(80, 150),第三张为(50, 100))