CH08-图像边缘

1. 图像梯度

1.1 cv2.Sobel 函数

Sobel 算子具有高效的抗噪声能力,能够设定图像求导的方向(x 方向或 y 方向).

它还可以通过参数 ksize 设定使用的卷积核的大小,如果 ksize = -1, Sobel 算子会使用卷积核大小为 3×3 的 Scharr 滤波器,从而转变为 Scharr 算子.

dst = cv2.Sobel(src, ddepth, dx, dy, ksize = None)

一般不会令代码 dx 和 dy 同时为 1 或者同时为 0,而是选择分为两步来求得 x 和 y 对应的导数。

参数 说明
src 要进行处理的原图像
ddepth 图像深度
dx 如果为 1,表示求 x 方向上的一阶导数
dy 如果为 1, 表示求 y 方向上的一阶导数
ksize 卷积核大小
dst 输出的图像

1.2 Sobel算子和 Scharr 算子

# 获取图像
img = cv2.imread('img/CH08-1.png')

# 转换为灰度图
grey = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

# 求得x轴方向(横向方向)的导数,卷积核大小为 3
Sobelx = cv2.Sobel(grey,-1,1,0,ksize=3)

# 求得 y轴方向(纵向方向)的导数,卷积核大小为 3
Sobely = cv2.Sobel(grey, -1, 0, 1, ksize=3)

# 图像显示
cv2.namedWindow('x',cv2.WINDOW_NORMAL)
cv2.namedWindow('y',cv2.WINDOW_NORMAL)
cv2.imshow('x',Sobelx)
cv2.imshow('y',Sobely)
cv2.waitKey(0)
cv2.destroyAllWindows()
# 获取图像
img = cv2.imread('img/CH08-1.png')

# 转换为灰度图
grey = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

# 图像梯度
Sobelx = cv2.Sobel(grey, cv2.CV_32F, 1, 0, ksize=-1)  # 因为 ksize = -1, 所以改用 Scharr 算子
Sobely = cv2.Sobel(grey, cv2.CV_32F, 0, 1, ksize=-1)  # 因为 ksize = -1, 所以改用 Scharr 算子
grad = cv2.subtract(Sobelx,Sobely)
grad = cv2.convertScaleAbs(grad)

# 图像显示
cv2.namedWindow('x', cv2.WINDOW_NORMAL)
cv2.namedWindow('y', cv2.WINDOW_NORMAL)
cv2.namedWindow('grad', cv2.WINDOW_NORMAL)
cv2.imshow('x',Sobelx)
cv2.imshow('y',Sobely)
cv2.imshow('grad',grad)
cv2.waitKey(0)
cv2.destroyAllWindows()

1.3 Laplacian 算子

dst = cv2.Laplacian(src, ddepth, ksize = None)

参数 说明
src 需要进行梯度计算的图像
ddepth 图像深度
ksize 卷积核大小
dst 输出图像
# 获取图像
img = cv2.imread('img/CH08-1.png')

# 转换为灰度图
grey = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)

# 图像梯度
grad = cv2.Laplacian(grey, cv2.CV_64F)

# 图像显示
cv2.namedWindow('grad', cv2.WINDOW_NORMAL)
cv2.imshow('grad',grad)
cv2.waitKey(0)
cv2.destroyAllWindows()

2. Canny 边缘检测

2.1 Canny 边缘检测原理

Canny 边缘检测主要由 4 个步骤组成: 去除噪声, 计算图像梯度的阈值和方向, 非极大值抑制, 滞后阈值

  • 去除噪声
    • 一般采用高斯滤波器进行噪声的去除
  • 计算梯度的阈值和方向
    • 一般最后得到的梯度方向总是与轮廓处于垂直、水平或对角线的关系,即角度一般取 0°,45°,90°和 135°
  • 非极大值抑制
    • 对整张图像进行扫描,去除一些非轮廓上的点
    • 通常做法:
      • 对每一个像素点进行检查,看当前像素点的梯度是否是周围具有相同梯度方向的点中最大的一个,从而做到轮廓的收窄,缩小轮廓的厚度或者是宽度
  • 滞后阈值
    • 即确定图像中哪些轮廓才是真正的轮廓
    • 当图像边缘的灰度梯度高于 maxVal 时,将其视为真轮廓
    • 当低于 minVal时,就将对应的边缘舍弃
    • 如果介于 minVal和 maxVal之间时,看其是否与某个被确定为真正轮廓的点相连,如果存在就认为是轮廓,否则就舍弃

2.2 cv2.Canny 函数

edges = Canny(image, threshold1, threshold2, apertureSize = None, L2gradient)

minVal 和 maxVal一般按照 1:3或者 1:2 的比例来进行设置,如 50 与 150,100 与 200

参数 说明
image 用来进行边缘检测的图像
threshold1 下阈值,为 minVal
threshold2 上阈值,为 maxVal
apertureSize 控制图像梯度中使用的 Sobel 卷积核的大小,默认为 3
L2gradient 选择求梯度大小的方程,如果设置为 True,则为 L2范数,如果设置为 False,则为 L1 范数
edges 经过 Canny 边缘检测后得到的轮廓
# 读取图像
img = cv2.imread('img/CH08-1.png')
x = img.shape[0]
y = img.shape[1]

# 转换为灰度图
grey = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)

# 高斯模糊
gauss = cv2.GaussianBlur(grey, (3,3),0)

# Canny边缘检测
edges = cv2.Canny(gauss, 50, 150)

# 图像显示
cv2.namedWindow('edges', cv2.WINDOW_NORMAL)
cv2.resizeWindow('edges',int(y/2),int(x/2))
cv2.imshow('edges',edges)
cv2.waitKey(0)
cv2.destroyAllWindows()

3. 图像金字塔

图像金字塔本质是同一图像的不同分辨率的子图的集合

3.1 构建图像金字塔

  1. cv2.pyrDown 函数

cv2.pyrDown 可以从一个高分辨率的图像(金字塔底层)逐层向上构建金字塔,整个过程中图像尺寸逐渐变小,分辨率逐步降低

dst = cv2.pyrDown(src)

参数 说明
src 需要进行操作的输入图像
dst 获得的底分辨率的图像

src 最好满足以下两个条件:

  • 图像的长为 2的整数次幂
  • 图像的宽为 2 的整数次幂

  • cv2.pyrUp 函数

cv2.pyrUp 函数可以从一个低分辨率的图像逐层向下构建金字塔,整个过程中的图像尺寸逐渐变大,但分辨率保持不变,因此得到的底层图像越来越模糊.

dst = cv2.pyrUp(src)

参数 说明
src 需要进行操作的输入图像
dst 获得的与 src 相同分辨率的图像

3.2 高斯金字塔

高斯金字塔的顶部是底部图像进行一系列连续的行、列像素点去除操作而得到的图像,第 n 层图像中的每个像素值等于第 n+1层图像中 5 个像素点的高斯加权平均值.为此,对大小为 M×N 的第 n+1 层图像进行如上操作后,就会得到一个 M/2×N/2的图像,也就是高斯金字塔的第 n 层,很显然图像的面积为第 n+1 层图像面积的四分之一.这种操作为 Octave

# 读取图像
img = cv2.imread('img/CH08-2.jpg')
x = img.shape[0]
y = img.shape[1]

# 下采样
img_d1 = cv2.pyrDown(img)
img_d2 = cv2.pyrDown(img_d1)

# 上采样
img_u1 = cv2.pyrUp(img_d2)
img_u2 = cv2.pyrUp(img_u1)

# 图像显示
cv2.imshow('img',img)
cv2.imshow('img_d1',img_d1)
cv2.imshow('img_d2',img_d2)
cv2.imshow('img_u1',img_u1)
cv2.imshow('img_u2',img_u2)
cv2.waitKey(0)
cv2.destroyAllWindows()

3.3 拉普拉斯金字塔

拉普拉斯金字塔能够弥补下采样时造成的像素点丢失

def gause(image):
    # 设置金字塔的层数为 6
    level = 6
    temp = image.copy()
    gause_images = []
    for i in range(level):
        dst = cv2.pyrDown(temp)
        gause_images.append(dst)
        cv2.imshow('pyramid' + str(i), dst)
        temp = dst.copy()
    return gause_images

def laplacian(image):
    gause_images = gause(image)
    level = len(gause_images)
    for i in range(level-1, -1, -1):
        if (i-1) < 0:
            expand = cv2.pyrUp(gause_images[i],dstsize = image.shape[:2])
            lpls = cv2.subtract(image,expand)
            cv2.imshow('lapalian_down_' + str(i), lpls)
        else:
            expand = cv2.pyrUp(gause_images[i],dstsize=gause_images[i-1].shape[:2])
            lpls = cv2.subtract(gause_images[i-1], expand)
            cv2.imshow('lapalian_down_'+str(i),lpls)

src = cv2.imread('img/CH08-2.jpg')
cv2.namedWindow('out',cv2.WINDOW_NORMAL)
cv2.imshow('out',src)
laplacian(src)
cv2.waitKey(0)
cv2.destroyAllWindows()