学习记录三

边缘提取

  • 基础知识
  • Laplacian算子
  • Canny算子
    • 非极大值抑制
    • 双阈值算法检测(滞后阈值)

基础知识

梯度:是一个向量(矢量)。表示某一函数在该点处的方向导数沿着该方向取得最大值,即函数在该点处沿着该方向(此梯度的方向)变化最快,变化率最大(为该梯度的模)。
学习记录三_第1张图片
图像的边缘:指图象局部区域亮度变化显著的部分。通常边缘上的灰度变化平缓,边缘两侧的灰度变化较快。
(注:就像导数有正值也有负值一样,边缘也有正负之分:由暗到亮为正,由亮到暗为负)
边缘检测原理:在边缘部分,像素值出现”跳跃“或者较大的变化。因此在边缘部分求一阶导数,就会看到极值的出现,而在一阶导数为极值的地方,二阶导数为0(如下),利用这个事实就能进行边缘检测了。
学习记录三_第2张图片
学习记录三_第3张图片
边缘检测的步骤

  1. 滤波:边缘检测的算法主要是基于图像的一阶和二阶导数,但导数通常对噪声很敏感, 因此有必要采用滤波器来改善与噪声有关的边缘检测器的性能。常见的滤波方法主要有高斯滤波。
  2. 增强:增强边缘的基础是确定图像各点邻域强度的变化值。增强算法可以将图像灰度点邻域强度值有显著变化的点凸显出来。在具体编程实现时,可通过计算梯度幅值来确定。
  3. 检测:经过增强的图像,往往邻域中有很多点的梯度值比较大,而在特定的应用中,这些点并不是我们要找的边缘点,所以应该采用某种方法来对这些点进行取舍。实际工程中,常用 的方法是通过阈值化方法来检测。

边缘检测的常用算法有:

  • 一阶导数:sobel、Roberts、prewitt等算子;
  • 二阶导数:Laplacian、Canny算子。

其中一阶微分算子在这里已经简单介绍过,下面看看二阶微分算子。

Laplacian算子

根据图像处理的原理我们知道,二阶导数可以用来进行检测边缘 。 因为图像是 “二维”, 我们需要在两个方向进行求导。
Laplacian定义:
学习记录三_第4张图片

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是目前最优秀的边缘检测算法,其目标为找到一个最优的边缘,其最优边缘的定义为:

  • 好的检测:算法能够尽可能的标出图像中的实际边缘;
  • 好的定位:标识出的边缘要与实际图像中的边缘尽可能接近;
  • 最小响应:图像中的边缘只能标记一次

Canny算法的步骤如下:

  1. 对图像进行灰度化;
    方法1:Gray=(R+G+B)/3;
    方法2:Gray=0.299R+0.587G+0.114B。(这种参数考虑到了人眼的生理特点)
  2. 对图像进行高斯滤波。根据待滤波的像素点及其邻域点的灰度值按照一定的参数规则进行加权平均,这样可以有效滤去图像中的高频噪声。注:高斯卷积核大小的选择将影响Canny检测器的性能,尺寸越大,检测器对噪声的敏感度越低,边缘检测的定位误差将略有增加。一般5x5是一个比较不错的trade off。
    (但是通常来说,滤波和边缘检测是矛盾的。抑制图像噪声会使图像边缘变模糊,因为边缘实际上也是高频信号,如果过分抑制噪声会使图像边缘变模糊,从而增加边缘定位的不确定性,降低准确率。为什么选用高斯滤波?是因为经验表明,高斯核函数确定的核可以在抗噪声干扰和边缘检测精确定位之间提供一个较好的折中方案)
  3. 检测图像中的水平、垂直和对角边缘(如Prewitt,Sobel算子等);
  4. 对梯度幅值进行非极大值抑制;
  5. 用双阈值算法检测和连接边缘。

前三步都比较简单,就不做过多介绍了,下面看下第四五步。

非极大值抑制

边缘检测中的非极大值抑制主要是将当前像素的梯度强度与沿正负梯度方向上的两个像素进行比较。如果当前像素的梯度强度与另外两个像素相比最大,则该像素点保留为边缘点,否则该像素点将被抑制(灰度值置为0)。
学习记录三_第5张图片
上图中,点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()

结果:
学习记录三_第6张图片
可以看到这里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算子效果比其他的都要好,当然其实现过程也更复杂一些。

你可能感兴趣的:(计算机视觉,图像处理,图像识别)