OpenCV-Python边缘检测:Sobel、Scharr、Laplacian算子和Canny算子

xiaohai 2023-07-15 16:59:41 687人围观 标签: Opencv 
简介图像梯度计算的是图像变化的速度。对于图像的边缘部分,其灰度值变化较大,梯度值也较大;相反,对于图像中比较平滑的部分,其灰度值变化较小,相应的梯度值也较小。图像梯度计算需要求导数,但是图像梯度一般通过计算像素值的差来得到梯度的近似值(近似导数值)。本节主要介绍Sobel算子、Scharr算子、Laplacian算子和Canny算子的使用.
一、Sobel算子

Sobel算子是一个主要用于边缘检测的离散微分算子(discrete differentiation operator)。它结合了高斯平滑和微分求导,用来计算图像灰度函数的近似梯度。在图像的任意一点使用此算子,都将会产生对应的梯度矢量或是其法矢量。

Sobel算子依然是一种过滤器,只是其是带有方向的。在OpenCV-Python中,使用Sobel的算子的函数原型如下:

 dst = cv.Sobel(src, ddepth, dx, dy[, dst[, ksize[, scale[, delta[, borderType]]]]])

前四个是必须的参数:

  • 第一个参数是需要处理的图像;
  • 第二个参数是图像的深度,-1表示采用的是与原图像相同的深度。目标图像的深度必须大于等于原图像的深度;通常为cv2.CV_64F。
  • dx和dy表示的是求导的阶数,0表示这个方向上没有求导,一般为0、1、2。

在实际操作中,计算梯度值可能会出现负数,图像通常是8位的,如果值为负数,那么会变成0,发生信息丢失。为了避免信息丢失,我们要用cv2.CV_64F,然后在取绝对值映射为8位图类型的。

方式1: 分别使用dx=1,dy=0和dx=0,dy=1计算图像在水平方向和垂直方向的边缘信息,然后将二者相加,构成两个方向的边缘信息。

方式2∶ 将参数dx和dy的值设为dx=1,dy=1,获取图像在两个方向的梯度。

首先,我们将图像的深度设为-1,示例如下:

import cv2

src_img = cv2.imread("3-1.jpg", cv2.IMREAD_UNCHANGED)
cv2.imshow("src_img", src_img)

sobel_x = cv2.Sobel(src_img, -1, 1, 0)
cv2.imshow("sobel_x", sobel_x)

cv2.waitKey(0)
cv2.destroyAllWindows()

图片alt
只能检测出右边边界,不能检测出左边边界。计算梯度值可能会出现负数,图像通常是8位的,如果值为负数,那么会变成0,发生信息丢失。为了避免信息丢失,我们要用cv2.CV_64F,然后在取绝对值“cv2.convertScaleAbs”映射为8位图类型的。示例如下:

import cv2

src_img = cv2.imread("3-1.jpg", cv2.IMREAD_UNCHANGED)
cv2.imshow("src_img", src_img)

sobel_x = cv2.Sobel(src_img, cv2.CV_64F, 1, 0)
sobel_x = cv2.convertScaleAbs(sobel_x)
cv2.imshow("sobel_x", sobel_x)

cv2.waitKey(0)
cv2.destroyAllWindows()

图片alt
现在就可以将左右边界都检测出来了。

下面我们将上下左右边界都检测出来:

import cv2

src_img = cv2.imread("3-1.jpg", cv2.IMREAD_UNCHANGED)
cv2.imshow("src_img", src_img)

sobel_x = cv2.Sobel(src_img, cv2.CV_64F, 1, 0)
sobel_x = cv2.convertScaleAbs(sobel_x)
cv2.imshow("sobel_x", sobel_x)

sobel_y = cv2.Sobel(src_img, cv2.CV_64F, 0, 1)
sobel_y = cv2.convertScaleAbs(sobel_y)
cv2.imshow("sobel_y", sobel_y)

cv2.waitKey(0)
cv2.destroyAllWindows()

图片alt

当上下边界都检测出来后,就可以将两张图片进行加法运算,这里使用函数“cv2.addWeighted”,示例如下:

import cv2

src_img = cv2.imread("3-1.jpg", cv2.IMREAD_UNCHANGED)
cv2.imshow("src_img", src_img)

sobel_x = cv2.Sobel(src_img, cv2.CV_64F, 1, 0)
sobel_x = cv2.convertScaleAbs(sobel_x)
cv2.imshow("sobel_x", sobel_x)

sobel_y = cv2.Sobel(src_img, cv2.CV_64F, 0, 1)
sobel_y = cv2.convertScaleAbs(sobel_y)
cv2.imshow("sobel_y", sobel_y)

sobel = cv2.addWeighted(sobel_x, 0.5, sobel_y, 0.5, 0)
cv2.imshow('sobel', sobel)

cv2.waitKey(0)
cv2.destroyAllWindows()

图片alt

将参数dx和dy的值设为dx=1,dy=1,获取图像在两个方向的梯度,示例如下:

import cv2

src_img = cv2.imread("3-1.jpg", cv2.IMREAD_UNCHANGED)
cv2.imshow("src_img", src_img)

sobel = cv2.Sobel(src_img, cv2.CV_64F, 1, 1)
sobel = cv2.convertScaleAbs(sobel)
cv2.imshow('sobel', sobel)

cv2.waitKey(0)
cv2.destroyAllWindows()

图片alt

将参数dx和dy的值设为dx=1,dy=1,这样不能检测出图形边界。

上面的图片都是很规则的,下面用一张普通的图片,示例如下:

import cv2

src_img = cv2.imread("1-gray.bmp", cv2.IMREAD_UNCHANGED)
cv2.imshow("src_img", src_img)

sobel_x = cv2.Sobel(src_img, cv2.CV_64F, 1, 0)
sobel_x = cv2.convertScaleAbs(sobel_x)
cv2.imshow("sobel_x", sobel_x)

sobel_y = cv2.Sobel(src_img, cv2.CV_64F, 0, 1)
sobel_y = cv2.convertScaleAbs(sobel_y)
cv2.imshow("sobel_y", sobel_y)

sobel = cv2.addWeighted(sobel_x, 0.5, sobel_y, 0.5, 0)
cv2.imshow('sobel', sobel)

cv2.waitKey(0)
cv2.destroyAllWindows()

图片alt

二、Scharr算子

Scharr 算子是对 Sobel 算子差异性的增强,两者之间的在检测图像边缘的原理和使用方式上相同。

dst = cv2.Scharr(src, ddepth, dx, dy, scale, delta, borderType)
  • src:原图像。
  • ddepth:输出图像的深度,可在-1、CV_8U、CV_16U、CV_16S和CV_32F中选择,一般默认为-1。
  • dx:水平方向导数的阶数,可以为0、1或2。
  • dy:竖直方向导数的阶数,可以为0、1或2。
  • scale:比例因子,一般为1。
  • delta:偏移量,一般为0。
  • borderType:边框模式,默认为cv2.BORDER_DEFAULT。

主要还是前面四个参数.Scharr使用根Sobel差不多,示例如下:

import cv2

src_img = cv2.imread("3-1.jpg", cv2.IMREAD_UNCHANGED)
cv2.imshow("src_img", src_img)

sobel_x = cv2.Scharr(src_img, cv2.CV_64F, 1, 0)
sobel_x = cv2.convertScaleAbs(sobel_x)
cv2.imshow("sobel_x", sobel_x)

sobel_y = cv2.Scharr(src_img, cv2.CV_64F, 0, 1)
sobel_y = cv2.convertScaleAbs(sobel_y)
cv2.imshow("sobel_y", sobel_y)

sobel = cv2.addWeighted(sobel_x, 0.5, sobel_y, 0.5, 0)
cv2.imshow('sobel', sobel)

cv2.waitKey(0)
cv2.destroyAllWindows()

图片alt

选一张其他图片示例:

import cv2

src_img = cv2.imread("1-gray.bmp", cv2.IMREAD_UNCHANGED)
cv2.imshow("src_img", src_img)

sobel_x = cv2.Scharr(src_img, cv2.CV_64F, 1, 0)
sobel_x = cv2.convertScaleAbs(sobel_x)
cv2.imshow("sobel_x", sobel_x)

sobel_y = cv2.Scharr(src_img, cv2.CV_64F, 0, 1)
sobel_y = cv2.convertScaleAbs(sobel_y)
cv2.imshow("sobel_y", sobel_y)

sobel = cv2.addWeighted(sobel_x, 0.5, sobel_y, 0.5, 0)
cv2.imshow('sobel', sobel)

cv2.waitKey(0)
cv2.destroyAllWindows()

图片alt
从上面两种算子比较,Sobel算子边界要粗糙些,Scharr算子边界要更细一些。

三、Laplacian算子

Laplacian(拉普拉斯)算子是一种二阶导数算子,其具有旋转不变性,可以满足不同方向的图像边缘锐化(边缘检测)的要求。通常情况下,其算子的系数之和需要为零。Laplacian算子类似二阶Sobel导数,需要计算两个方向的梯度值。计算结果的值可能为正数,也可能为负数。所以,需要对计算结果取绝对值,以保证后续运算和显示都是正确的。在OpenCV内使用函数cv2.Laplacian()实现Laplacian算子的计算,该函数的语法格式为:

dst=cv2.Laplacian (src,ddepth[,ksize[,scale[,delta[,borderType]]]])
  • ksize: 计算二阶导数的核尺寸大小,其值必须为正数的奇数
  • scale: 放缩比例因子
  • delta: 加到目标图像上的可选值
    当ksize的值大于1时,dst = 二阶偏导相加,
    当ksize的值为1时,Laplacian算子计算时采用上述3×3的核

示例:

import cv2

src_img = cv2.imread("3-1.jpg", cv2.IMREAD_UNCHANGED)
cv2.imshow("src_img", src_img)

res_img = cv2.Laplacian(src_img, cv2.CV_64F)
res_img = cv2.convertScaleAbs(res_img)
cv2.imshow("res_img", res_img)

cv2.waitKey(0)
cv2.destroyAllWindows()

图片alt

换一个图片:

import cv2

src_img = cv2.imread("1-gray.bmp", cv2.IMREAD_UNCHANGED)
cv2.imshow("src_img", src_img)

res_img = cv2.Laplacian(src_img, cv2.CV_64F)
res_img = cv2.convertScaleAbs(res_img)
cv2.imshow("res_img", res_img)

cv2.waitKey(0)
cv2.destroyAllWindows()

图片alt

四、Canny算子
edges = cv2.Canny(image, threshold1, threshold2, apertureSize, L2gradient)
  • image:输入图像,为二值图像。
  • threshold1:第一个阈值
  • threshold2:第二个阈值

当threshold1和threshold2值较小时,可以获得更多的边缘信息。

import cv2

src_img = cv2.imread("3-1.jpg", cv2.IMREAD_UNCHANGED)
cv2.imshow("src_img", src_img)

res_img = cv2.Canny(src_img,100,150)
cv2.imshow("res_img", res_img)

cv2.waitKey(0)
cv2.destroyAllWindows()

图片alt

换一张图片:

import cv2

src_img = cv2.imread("1-gray.bmp", cv2.IMREAD_UNCHANGED)
cv2.imshow("src_img", src_img)

res_img1 = cv2.Canny(src_img,100,200)
cv2.imshow("res_img1", res_img1)

res_img2 = cv2.Canny(src_img,30,50)
cv2.imshow("res_img2", res_img2)

cv2.waitKey(0)
cv2.destroyAllWindows()

图片alt
阈值越大,检测的边界越少;阈值越小,检测的边界越多。