OpenCV-Python边缘检测: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()
只能检测出右边边界,不能检测出左边边界。计算梯度值可能会出现负数,图像通常是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()
现在就可以将左右边界都检测出来了。
下面我们将上下左右边界都检测出来:
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()
当上下边界都检测出来后,就可以将两张图片进行加法运算,这里使用函数“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()
将参数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()
将参数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()
二、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()
选一张其他图片示例:
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()
从上面两种算子比较,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()
换一个图片:
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()
四、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()
换一张图片:
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()
阈值越大,检测的边界越少;阈值越小,检测的边界越多。