Python opencv图像处理基础总结(五) 图像金字塔 图像梯度 Canny算法边缘提取

2021-01-23 13:37:38 浏览数 (1)

鹏北海,凤朝阳,又携书剑路茫茫。

文章目录

一、图像金字塔

图像金字塔是图像多尺度表达的一种,是一种以多分辨率来解释图像的有效但概念简单的结构。一幅图像的金字塔是一系列以金字塔形状排列的分辨率逐步降低,且来源于同一张原始图的图像集合。其通过梯次向下采样获得,直到达到某个终止条件才停止采样。我们将一层一层的图像比喻成金字塔,层级越高,则图像越小,分辨率越低

高斯金字塔:用于下采样。高斯金字塔是最基本的图像塔。原理:首先将原图像作为最底层图像G0(高斯金字塔的第0层),利用高斯核(5*5)对其进行卷积,然后对卷积后的图像进行下采样(去除偶数行和列)得到上一层图像G1,将此图像作为输入,重复卷积和下采样操作得到更上一层图像,反复迭代多次,形成一个金字塔形的图像数据结构,即高斯金字塔。

拉普拉斯金字塔:用于重建图像,也就是预测残差,对图像进行最大程度的还原。比如一幅小图像重建为一幅大图,原理:用高斯金字塔的每一层图像减去其上一层图像上采样并高斯卷积之后的预测图像,得到一系列的差值图像即为 LP 分解图像。

  • reduce = 高斯模糊 降采样
  • expand = 扩大 卷积
  • PyrDown:降采样
  • PyrUp:还原
  • 高斯金字塔和拉普拉斯金字塔

代码如下:

代码语言:txt复制
import cv2 as cv


# 高斯金字塔
def pyramid_demo(image):
    level = 3  # 金字塔层数
    temp = image.copy()
    pyramid_images = []
    for i in range(level):
        dst = cv.pyrDown(temp)       # 降采样
        pyramid_images.append(dst)   # 降采样的结果添加进列表
        cv.imshow('pyramid_down'   str(i), dst)   # 金字塔第几层  imshow
        temp = dst.copy()            # 采样的图像又赋给temp  接着降采样
    return pyramid_images


# 拉普拉斯金字塔
# 由高斯金字塔可以构建拉普拉斯金字塔
def lapalian_demo(image):
    pyramid_images = pyramid_demo(image)
    level = len(pyramid_images)         # 求层数
    for i in range(level - 1, -1, -1):  # 每次递减
        if (i - 1) < 0:
            expand = cv.pyrUp(pyramid_images[i], dstsize=image.shape[:2])  # 升采样
            lpls = cv.subtract(image, expand)
            cv.imshow("lapalian_down"   str(i), lpls)
        else:
            expand = cv.pyrUp(pyramid_images[i], dstsize=pyramid_images[i - 1].shape[:2])
            lpls = cv.subtract(pyramid_images[i - 1], expand)
            cv.imshow("lapalian_down_"   str(i), lpls)


if __name__ == '__main__':
    img = cv.imread(r'./test/004.jpg')  # 图片须是2^n 格式大小  例如(512*512)的
    cv.imshow("input image", img)
    lapalian_demo(img)
    cv.waitKey(0)
    cv.destroyAllWindows()

运行效果如下:

二、图像梯度

图像梯度可以把图像看成二维离散函数,图像梯度其实就是这个二维离散函数的求导。

1. Sobel算子
  • Sobel算子用来计算图像灰度函数的近似梯度。Sobel算子根据像素点上下、左右邻点灰度加权差,在边缘处达到极值这一现象检测边缘。对噪声具有平滑作用,提供较为精确的边缘方向信息,边缘定位精度不够高。当对精度要求不是很高时,是一种较为常用的边缘检测方法。
  • Sobel具有平滑和微分的功效。即:Sobel算子先将图像横向或纵向平滑,然后再纵向或横向差分,得到的结果是平滑后的差分结果。
代码语言:txt复制
cv2.Sobel(src, ddepth, dx, dy, dst=None, ksize=None, scale=None, delta=None, borderType=None)
  • src:输入需要处理的图像
  • ddepth:输出图像深度
  • dx:x方向上的差分阶数,1或0
  • dy:y 方向上的差分阶数,1或0
  • ksize:Sobel算子的大小,必须为1、3、5、7
  • scale:缩放导数的比例常数,默认情况下没有伸缩系数
  • delta:一个可选的增量,将会加到最终的dst中,同样,默认情况下没有额外的值加到dst中
  • borderType:判断图像边界的模式,这个参数默认值为cv2.BORDER_DEFAULT
代码语言:txt复制
import cv2


def sobel_demo(image):
    grad_x = cv2.Sobel(image, cv2.CV_32F, 1, 0)   # x方向一阶导数
    grad_y = cv2.Sobel(image, cv2.CV_32F, 0, 1)   # y方向一阶导数
    # 一阶导数算出来可能有正负  最后全部转到8位的图像上去
    gradx = cv2.convertScaleAbs(grad_x)
    grady = cv2.convertScaleAbs(grad_y)
    cv2.imshow("gradient_x", gradx)
    cv2.imshow("gradient_y", grady)
    # 计算两个数组的加权和
    gradxy = cv2.addWeighted(gradx, 0.5, grady, 0.5, 0)
    cv2.imshow("gradient_xy", gradxy)


if __name__ == "__main__":
    src = cv2.imread(r"./test/018.jpg")
    src = cv2.resize(src, None, fx=0.5, fy=0.5)
    cv2.imshow("image", src)
    sobel_demo(src)
    cv2.waitKey(0)
    cv2.destroyAllWindows()

运行效果如下:

2. Scharr算子

原理跟Sobel算子类似,是Sobel算子的增强版本,当你用Sobel算子得不到很好的边缘效果时,用Scharr算子。

代码语言:txt复制
import cv2


def scharr_demo(image):
    grad_x = cv2.Scharr(image, cv2.CV_32F, 1, 0)   # x方向一阶导数
    grad_y = cv2.Scharr(image, cv2.CV_32F, 0, 1)   # y方向一阶导数
    # 一阶导数算出来可能有正负  最后全部转到8位的图像上去
    gradx = cv2.convertScaleAbs(grad_x)
    grady = cv2.convertScaleAbs(grad_y)
    cv2.imshow("gradient_x", gradx)
    cv2.imshow("gradient_y", grady)
    # 计算两个数组的加权和
    gradxy = cv2.addWeighted(gradx, 0.5, grady, 0.5, 0)
    cv2.imshow("gradient_xy", gradxy)


if __name__ == "__main__":
    src = cv2.imread(r"./test/018.jpg")
    src = cv2.resize(src, None, fx=0.5, fy=0.5)
    cv2.imshow("image", src)
    scharr_demo(src)
    cv2.waitKey(0)
    cv2.destroyAllWindows()

运行效果如下:

3. 拉普拉斯算子

拉普拉斯算子(Laplace Operator)是n维欧几里德空间中的一个二阶微分算子,定义为梯度(▽f)的散度(▽·f)。

代码语言:txt复制
cv2.Laplacian(src, ddepth, dst=None, ksize=None, scale=None, delta=None, borderType=None)
  • src:输入需要处理的图像
  • ddepth:输出图像深度
  • dst:参数表示输出与src相同大小和相同通道数的图像
  • ksize:用于计算二阶导数滤波器的孔径大小,大小必须是正数和奇数
  • scale:计算拉普拉斯算子值的比例因子,默认情况下没有伸缩系数
  • delta:一个可选的增量,将会加到最终的dst中,同样,默认情况下没有额外的值加到dst中
  • borderType:判断图像边界的模式,这个参数默认值为cv2.BORDER_DEFAULT
代码语言:txt复制
import cv2 as cv
import numpy as np


# 拉普拉斯算子
def Laplace_demo(image):
    dst = cv.Laplacian(image, cv.CV_32F)
    lpls_1 = cv.convertScaleAbs(dst)
    cv.imshow("Laplace_1", lpls_1)
    # 自定义拉普拉斯算子
    kernel = np.array([[1, 1, 1], [1, -8, 1], [1, 1, 1]])
    dst = cv.filter2D(image, cv.CV_32F, kernel)
    lpls_2 = cv.convertScaleAbs(dst)
    cv.imshow("Laplace_2", lpls_2)


if __name__ == "__main__":
    src = cv.imread(r"./test/018.jpg")
    src = cv.resize(src, None, fx=0.5, fy=0.5)
    cv.imshow("image", src)
    Laplace_demo(src)
    cv.waitKey(0)
    cv.destroyAllWindows()

运行效果如下:

ksize参数用的默认值,此时Laplacian()函数采用以下3x3的孔径:

三、Canny算法边缘提取

Canny 的目标是找到一个最优的边缘检测算法,最优边缘检测的含义是:

  • 最优检测:算法能够尽可能多地标识出图像中的实际边缘,漏检真实边缘的概率和误检非边缘的概率都尽可能小
  • 最优定位准则:检测到的边缘点的位置距离实际边缘点的位置最近,或者是由于噪声影响引起检测出的边缘偏离物体的真实边缘的程度最小
  • 检测点与边缘点一一对应:算子检测的边缘点与实际边缘点应该是一一对应

Canny边缘检测算法的步骤

  • 高斯模糊,降低噪声
  • 灰度转换
  • 计算梯度
  • 应用非最大抑制(non-maximum suppression)技术来消除边误检(本来不是但检测出来是)
  • 高低阈值输出二值图像
代码语言:txt复制
cv2.Canny(image, threshold1, threshold2, edges=None, apertureSize=None, L2gradient=None):
  • image:参表示8位输入图像
  • threshold1:设置的低阈值
  • threshold2:设置的高阈值
  • edges:输出边缘图像,单通道8位图像
  • apertureSize:Sobel算子的大小
  • L2gradient:一个布尔值,如果为真,则使用更精确的L2范数进行计算(即两个方向的倒数的平方和再开方),否则使用L1范数(直接将两个方向导数的绝对值相加)
代码语言:txt复制
import cv2 as cv


def edge_demo(image):
    # 高斯模糊  降低噪声
    blurred = cv.GaussianBlur(image, (3, 3), 0)
    # 转为灰度图像
    gray = cv.cvtColor(blurred, cv.COLOR_BGR2GRAY)
    # 计算x y 方向梯度
    grad_x = cv.Sobel(gray, cv.CV_16SC1, 1, 0)
    grad_y = cv.Sobel(gray, cv.CV_16SC1, 0, 1)
    edge_output = cv.Canny(grad_x, grad_y,  50, 100)
    cv.imshow('edge image_1', edge_output)
    # 彩色图像
    dist = cv.bitwise_and(image, image, mask=edge_output)
    cv.imshow('edge image_2', dist)


if __name__ == "__main__":
    src = cv.imread(r"./test/033.png")
    src = cv.resize(src, None, fx=0.5, fy=0.5)
    cv.imshow("image", src)
    edge_demo(src)
    cv.waitKey(0)
    cv.destroyAllWindows()

运行效果如下:

作者:叶庭云 微信公众号:修炼Python CSDN:https://yetingyun.blog.csdn.net/ 本文仅用于交流学习,未经作者允许,禁止转载,更勿做其他用途,违者必究。 觉得文章对你有帮助、让你有所收获的话,期待你的点赞呀,不足之处,也可以在评论区多多指正。

0 人点赞