梯度:是一个向量(矢量)。表示某一函数在该点处的方向导数沿着该方向取得最大值,即函数在该点处沿着该方向(此梯度的方向)变化最快,变化率最大(为该梯度的模)。
图像的边缘:指图象局部区域亮度变化显著的部分。通常边缘上的灰度变化平缓,边缘两侧的灰度变化较快。
(注:就像导数有正值也有负值一样,边缘也有正负之分:由暗到亮为正,由亮到暗为负)
边缘检测原理:在边缘部分,像素值出现”跳跃“或者较大的变化。因此在边缘部分求一阶导数,就会看到极值的出现,而在一阶导数为极值的地方,二阶导数为0(如下),利用这个事实就能进行边缘检测了。
边缘检测的步骤:
边缘检测的常用算法有:
其中一阶微分算子在这里已经简单介绍过,下面看看二阶微分算子。
根据图像处理的原理我们知道,二阶导数可以用来进行检测边缘 。 因为图像是 “二维”, 我们需要在两个方向进行求导。
Laplacian定义:
import cv2
import numpy as np
img=cv2.imread("path/lenna.png",0)
#为了让结果更清晰,这里的ksize设为3,
gray_lap=cv2.Laplacian(img,cv2.CV_16S,ksize=3)#拉式算子
dst=cv2.convertScaleAbs(gray_lap)
cv2.imshow('original',img)
cv2.imshow('laplacian',dst)
cv2.waitKey(0)
cv2.destroyAllWindows()
结果:
可以看到,Laplacian更关注对图像前景的边缘检测。
Canny是目前最优秀的边缘检测算法,其目标为找到一个最优的边缘,其最优边缘的定义为:
Canny算法的步骤如下:
前三步都比较简单,就不做过多介绍了,下面看下第四五步。
边缘检测中的非极大值抑制主要是将当前像素的梯度强度与沿正负梯度方向上的两个像素进行比较。如果当前像素的梯度强度与另外两个像素相比最大,则该像素点保留为边缘点,否则该像素点将被抑制(灰度值置为0)。
上图中,点c为当前像素点,在它的邻域附近有八个点,分别为它上下左右四个点+正方形四个顶点。蓝线为c点的梯度方向,可以看到梯度方向上的两个点dtmp1和dtmp2不在八个邻域点上,对于这种情况就要用到插值来求出两个点处的像素值,进一步与c点的像素值进行比较。
完成非极大值抑制后,会得到一个二值图像,非边缘的点灰度值均为0,可能为边缘的局部灰度极大值点可设置其灰度为128(也可以为256或其他值)。但这样的检测结果还是包含了很多由噪声及其他原因造成的假边缘,因此还需要进一步的处理。
双阈值检测:(阈值的选择取决于给定输入图像的内容)
经过双阈值检测后,被划分为强边缘的像素点已经被确定为边缘,因为它们是从图像中的真实边缘中提取出来的。但对于弱边缘像素有一些争论,因为这些像素可以从真实边缘提取也可以是因噪声或颜色变化引起的。
为了抑制由后者引起的弱边缘,我们抑制孤立低阈值点,具体如下:通常,由真实边缘引起的弱边缘像素将连接到强边缘像素,而噪声引起的弱边缘则不会。为了跟踪边缘连接,通过查看弱边缘像素及其8个邻域像素,只要其中一个为强边缘像素, 则该弱边缘点就可以保留为真实的边缘。
import cv2
import numpy as np
'''
cv2.Canny(image, threshold1, threshold2[, edges[, apertureSize[, L2gradient ]]])
必要参数:
第一个参数是需要处理的原图像,该图像必须为单通道的灰度图;
第二个参数是滞后阈值1;
第三个参数是滞后阈值2。
'''
img = cv2.imread("path/lenna.png", 1)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
cv2.imshow("canny", cv2.Canny(gray, 200, 300))
cv2.waitKey()
cv2.destroyAllWindows()
结果:
可以看到这里Canny检测到的边缘较少,那是因为我们把阈值设置的较高,为了更有助于对比学习,下面给出了动态选择阈值的代码,感兴趣的自行运行查看结果吧。
'''
Canny边缘检测:优化的程序
'''
import cv2
import numpy as np
def CannyThreshold(lowThreshold):
detected_edges = cv2.GaussianBlur(gray,(3,3),0) #高斯滤波
detected_edges = cv2.Canny(detected_edges,
lowThreshold,
lowThreshold*ratio,
apertureSize = kernel_size) #边缘检测
# just add some colours to edges from original image.
dst = cv2.bitwise_and(img,img,mask = detected_edges) #用原始颜色添加到检测的边缘上
cv2.imshow('canny demo',dst)
lowThreshold = 0
max_lowThreshold = 100
ratio = 3
kernel_size = 3
img = cv2.imread('path/lenna.png')
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY) #转换彩色图像为灰度图
cv2.namedWindow('canny demo')
#设置调节杠,
'''
下面是第二个函数,cv2.createTrackbar()
共有5个参数,其实这五个参数看变量名就大概能知道是什么意思了
第一个参数,是这个trackbar对象的名字
第二个参数,是这个trackbar对象所在面板的名字
第三个参数,是这个trackbar的默认值,也是调节的对象
第四个参数,是这个trackbar上调节的范围(0~count)
第五个参数,是调节trackbar时调用的回调函数名
'''
cv2.createTrackbar('Min threshold','canny demo',lowThreshold, max_lowThreshold, CannyThreshold)
CannyThreshold(0) # initialization
if cv2.waitKey(0) == 27: #wait for ESC key to exit cv2
cv2.destroyAllWindows()
到这儿,边缘检测的几种算法就结束啦,我们最终对比下sobel、laplace以及canny:
import cv2
import numpy as np
from matplotlib import pyplot as plt
img = cv2.imread("path/lenna.png",1)
img_gray = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY)
img_sobel_x = cv2.Sobel(img_gray, cv2.CV_64F, 1, 0, ksize=3) # 对x求导
img_sobel_y = cv2.Sobel(img_gray, cv2.CV_64F, 0, 1, ksize=3) # 对y求导
# Laplace 算子
img_laplace = cv2.Laplacian(img_gray, cv2.CV_64F, ksize=3)
#img_laplace = cv2.convertScaleAbs(img_laplace)
# Canny 算子
img_canny = cv2.Canny(img_gray, 100 , 150)
plt.subplot(231), plt.imshow(img_gray, "gray"), plt.title("Original")
plt.subplot(232), plt.imshow(img_sobel_x, "gray"), plt.title("Sobel_x")
plt.subplot(233), plt.imshow(img_sobel_y, "gray"), plt.title("Sobel_y")
plt.subplot(234), plt.imshow(img_laplace, "gray"), plt.title("Laplace")
plt.subplot(235), plt.imshow(img_canny, "gray"), plt.title("Canny")
plt.show()
结果:
可以看到Canny算子效果比其他的都要好,当然其实现过程也更复杂一些。