《OpenCv视觉之眼》Python图像处理十四 :Opencv图像轮廓提取之Scharr算法和Canny算法

本专栏主要介绍如果通过OpenCv-Python进行图像处理,通过原理理解OpenCv-Python的函数处理原型,在具体情况中,针对不同的图像进行不同等级的、不同方法的处理,以达到对图像进行去噪、锐化等一系列的操作。同时,希望观看本专栏的小伙伴可以理解到OpenCv进行图像处理的强大哦,如有转载,请注明出处(原文链接和作者署名),感谢各位小伙伴啦!

前文参考:
《OpenCv视觉之眼》Python图像处理一 :Opencv-python的简介及Python环境搭建
《OpenCv视觉之眼》Python图像处理二 :Opencv图像读取、显示、保存基本函数原型及使用
《OpenCv视觉之眼》Python图像处理三 :Opencv图像属性、ROI区域获取及通道处理
《OpenCv视觉之眼》Python图像处理四 :Opencv图像灰度处理的四种方法及原理
《OpenCv视觉之眼》Python图像处理五 :Opencv图像去噪处理之均值滤波、方框滤波、中值滤波和高斯滤波
《OpenCv视觉之眼》Python图像处理六 :Opencv图像傅里叶变换和傅里叶逆变换原理及实现
《OpenCv视觉之眼》Python图像处理七 :Opencv图像处理之高通滤波和低通滤波原理及构造
《OpenCv视觉之眼》Python图像处理八 :Opencv图像处理之图像阈值化处理原理及函数
《OpenCv视觉之眼》Python图像处理九 :Opencv图像形态学处理之图像腐蚀与膨胀原理及方法
《OpenCv视觉之眼》Python图像处理十 :Opencv图像形态学处理之开运算、闭运算和梯度运算原理及方法
《OpenCv视觉之眼》Python图像处理十一 :Opencv图像形态学处理之顶帽运算与黑帽运算
《OpenCv视觉之眼》Python图像处理十二 :Opencv图像轮廓提取之基于一阶导数的Roberts算法、Prewitt算法及Sobel算法
《OpenCv视觉之眼》Python图像处理十三 :Opencv图像轮廓提取之基于二阶导数的Laplacian算法和LOG算法

上次博客,我们介绍了基于二阶导数的轮廓提取算法Laplacian算法和LOG算法,包括各自的优点个缺点,并且自行构造了对应算法的功能函数,但在现实的图像处理中,这两张算法如果能够用到,那么用得较多的就是LOG算法,因此掌握LOG二阶导算法原理就可。

本次博客,林君学长将介绍在进行图像处理中用得最多也是效果较好的两种算法Scharr算法和Canny算法。Scharr算法是配合Sobel算法运算而存在的,可以理解为Sobel算法的升级版本,也就是说,如果你决定用Sobel算法,那请一定用Scharr算法代替你的想法;而Canny算法则是一个多级边缘检测算法,是用的最多的,是一种被广泛应用于边缘检测的标准算法。

本次博客讲解完,我们的图像处理的基本处理步骤就讲解完成,该系列接下来就将进入到对图像处理的项目实战之中去了,小伙伴们要准备好呀,前面还没有了解完全的请参考林君学长前面的博客抓紧学习哦!

Python图像处理十四 :Opencv图像轮廓提取之Scharr算法和Canny算法

  • 一、Scharr算法
    • 1、Scharr算法原理
    • 2、Scharr算法功能函数构造
    • 3、OpenCV中Scharr算法库函数使用
  • 二、Canny算法
    • 1、Canny算法原理
    • 2、Canny算法功能函数构造
    • 3、OpenCV中Canny算法库函数使用

一、Scharr算法

1、Scharr算法原理

1)、Scharr算法简介
Scharr算子又称为Scharr滤波器,也是计算x或y方向上的图像差分,在OpenCV中主要是配合Sobel算子的运算而存在的;scharr算子实际上是sobel算子的优化,scharr算子在处理边缘时比sobel精度高一些。两种算子唯一的区别就是他们的卷积核不同,他们无论在计算时间还是复杂度都是一样的。
《OpenCv视觉之眼》Python图像处理十四 :Opencv图像轮廓提取之Scharr算法和Canny算法_第1张图片
由于Sobel算子在计算相对较小的核的时候,其近似计算导数的精度比较低,比如一个33的Sobel算子,当梯度角度接近水平或垂直方向时,其不精确性就越发明显。Scharr算子同Sobel算子的速度一样快,但是准确率更高,尤其是计算较小核的情景,所以利用33滤波器实现图像边缘提取更推荐使用Scharr算子。
2)、Scharr算法算法优缺点

  • 优点:Scharr算子同Sobel算子的速度一样快,但是准确率更高,尤其是计算较小核的情景、对灰度渐变和噪声较多的图像处理效果较好
  • 缺点:检测精度相比于Sobel算法有所提高,但不如Canny算法精度高

3)、Scharr算法计算原理
Scharr算法通过在3x3邻域内对x方向梯度和y方向梯度模板进行梯度差分求解得到结果,模板如下:
d x = [ − 3 0 3 − 10 0 10 − 3 0 3 ] , d y = [ − 3 − 10 − 3 0 0 0 3 10 3 ] dx=\begin{bmatrix} -3 & 0 & 3 \\ -10 & 0 & 10 \\-3 & 0 & 3 \end{bmatrix} , dy=\begin{bmatrix} -3 & -10 & -3 \\ 0 & 0 & 0 \\3 & 10 & 3 \end{bmatrix} dx=31030003103,dy=30310010303
通过对以上对于模板方向求解梯度差分,公式如下:
d x = ( 3 f [ i − 1 , j − 1 ] + 10 f [ i − 1 , j ] + 3 f [ i − 1 , j + 1 ] ) − ( 3 f [ i + 1 , j − 1 ] + 10 f [ i + 1 , j ] + 3 f [ i + 1 , j + 1 ] ) d y = ( 3 f [ i − 1 , j + 1 ] + 10 f [ i , j + 1 ] + 3 f [ i + 1 , j + 1 ] ) − ( 3 f [ i − 1 , j − 1 ] + 10 f [ i , j − 1 ] + 3 f [ i + 1 , j − 1 ] ) d x 、 d y 分 别 表 示 图 像 水 平 方 向 和 竖 直 方 向 的 计 算 出 的 像 素 值 图 像 轮 廓 S [ i , j ] = d x 2 + d y 2 d_x=(3f[i-1,j-1]+10f[i-1,j]+3f[i-1,j+1])-(3f[i+1,j-1]+10f[i+1,j]+3f[i+1,j+1])\\\\ d_y=(3f[i-1,j+1]+10f[i,j+1]+3f[i+1,j+1])-(3f[i-1,j-1]+10f[i,j-1]+3f[i+1,j-1])\\\\ d_x、d_y分别表示图像水平方向和竖直方向的计算出的像素值\\\\ 图像轮廓S[i,j]=\sqrt{d_x^2+d_y^2} dx=(3f[i1,j1]+10f[i1,j]+3f[i1j+1])(3f[i+1,j1]+10f[i+1j]+3f[i+1j+1])dy=(3f[i1,j+1]+10f[i,j+1]+3f[i+1j+1])(3f[i1,j1]+10f[i,j1]+3f[i+1j1])dxdyS[i,j]=dx2+dy2
那么通过以上的计算公式原理,我们就可以自行构造Scharr算法的功能函数啦,如下步骤

2、Scharr算法功能函数构造

1)、Scharr算法功能函数构造

'''
Scharr算法轮廓提取函数构造
'''
#通过原理,编写对应的Roberts图像轮廓提取算法
def Scharr(thresh1):
    #获取图像属性
    h,w=thresh1.shape[0:2]
    #定义空白图像,用于存放LOG算法提取出来的轮廓图
    Scharr=np.zeros((h,w),dtype=thresh1.dtype)
    #对阈值化图像进行遍历,进行Laplacian算法
    for i in range(1,h-1):
        for j in range(1,w-1):#得到模板卷积第一个位置
            dx=(3*int(thresh1[i-1,j-1])+10*int(thresh1[i-1,j])+3*int(thresh1[i-1,j+1]))-(3*int(thresh1[i+1,j-1])+10*int(thresh1[i+1,j])+3*int(thresh1[i+1,j+1]))
            dy=(3*int(thresh1[i-1,j+1])+10*int(thresh1[i,j+1])+3*int(thresh1[i+1,j+1]))-(3*int(thresh1[i-1,j-1])+10*int(thresh1[i,j-1])+3*int(thresh1[i+1,j-1]))
            Scharr[i,j]=np.sqrt(dx**2+dy**2)
    return Scharr

2)、读取图像并处理,然后调用Scharr算法进行轮廓提取,如下:

import cv2
import numpy as np
#np.set_printoptions(threshold=np.inf)  #打印数组中的全部内容
import matplotlib.pyplot as plt
plt.rcParams['font.sans-serif'] = ['SimHei'] #显示中文
'''
一般来说,对图像轮廓提取都会经过如下步骤,灰度-滤波去噪-阈值化处理-(形态学处理,前面如果达标,这步骤可以省略)-轮廓提取
'''
#读取图像
img=cv2.imread("my.jpg")
#图像灰度化
gray=cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
#图像高斯滤波去噪
blur=cv2.GaussianBlur(gray,(7,7),1,1)#核尺寸通过对图像的调节自行定义
#图像阈值化处理
ret,thresh1=cv2.threshold(blur,127,255,cv2.THRESH_BINARY)  #二进制阈值化
#调用Scharr算法函数进行图像轮廓提取
result=Scharr(thresh1)
#对原图进行格式转换,方便matplotlib图像显示
img=cv2.cvtColor(img,cv2.COLOR_BGR2RGB)
#对结果图进行格式转换,方便matplotlib图像显示
result=cv2.cvtColor(result,cv2.COLOR_BGR2RGB)
#图像显示
titles = ['原图', 'Scharr算法']  #标题
images = [img, result]   #图像对比显示
for i in range(2):
    plt.subplot(1,2,i+1),plt.imshow(images[i])  
    plt.title(titles[i])    
    plt.axis('off')#关闭坐标轴  设置为on则表示开启坐标轴
plt.show()#显示图像

《OpenCv视觉之眼》Python图像处理十四 :Opencv图像轮廓提取之Scharr算法和Canny算法_第2张图片

3、OpenCV中Scharr算法库函数使用

OpenCV官方给出了明确的Scharr算法的库函数,如下介绍
1)、函数原型:result=cv2.Scharr(img, ddepth, dx,dy)

  • img:需要轮廓提取的图像
  • ddepth:目标图深度
  • dx:dx方向上的差分阶数,取值1或 0
  • dy:y方向上的差分阶数,取值1或0

2)、Scharr算法库函数使用方法如下:

'''
Scharr轮廓提取算法-OpenCV库函数的使用方法
'''
import cv2
import numpy as np
#np.set_printoptions(threshold=np.inf)  #打印数组中的全部内容
import matplotlib.pyplot as plt
plt.rcParams['font.sans-serif'] = ['SimHei'] #显示中文
'''
一般来说,对图像轮廓提取都会经过如下步骤,灰度-滤波去噪-阈值化处理-(形态学处理,前面如果达标,这步骤可以省略)-轮廓提取
'''
#读取图像
img=cv2.imread("my.jpg")
#图像灰度化
gray=cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
#图像高斯滤波去噪
blur=cv2.GaussianBlur(gray,(7,7),1,1)#核尺寸通过对图像的调节自行定义
#图像阈值化处理
ret,thresh1=cv2.threshold(blur,127,255,cv2.THRESH_BINARY)  #二进制阈值化
#调用Scharr算法的OpenCV库函数进行图像轮廓提取
x = cv2.Scharr(thresh1, cv2.CV_32F, 1, 0) #X方向
y = cv2.Scharr(thresh1, cv2.CV_32F, 0, 1) #Y方向
absX = cv2.convertScaleAbs(x)       
absY = cv2.convertScaleAbs(y)
Scharr = cv2.addWeighted(absX, 0.5, absY, 0.5, 0)
#对原图进行格式转换,方便matplotlib图像显示
img=cv2.cvtColor(img,cv2.COLOR_BGR2RGB)
#对结果图进行格式转换,方便matplotlib图像显示
Scharr=cv2.cvtColor(Scharr,cv2.COLOR_BGR2RGB)
#图像显示
titles = ['原图', 'Scharr算法']  #标题
images = [img, Scharr]   #图像对比显示
for i in range(2):
    plt.subplot(1,2,i+1), plt.imshow(images[i])  
    plt.title(titles[i])    
    plt.axis('off')#关闭坐标轴  设置为on则表示开启坐标轴
plt.show()#显示图像

《OpenCv视觉之眼》Python图像处理十四 :Opencv图像轮廓提取之Scharr算法和Canny算法_第3张图片


二、Canny算法

1、Canny算法原理

1)、Canny算法简介
John F.Canny于1986年发明了一个多级边缘检测算法——Canny边缘检测算法,并创立了边缘检测计算理论(Computational theory of edge detection),该理论有效地解释了这项技术的工作理论。边缘检测通常是在保留原有图像属性的情况下,对图像数据规模进行缩减,提取图像边缘轮廓的处理方式。
Canny算法是一种被广泛应用于边缘检测的标准算法,其目标是找到一个最优的边缘检测解或找寻一幅图像中灰度强度变化最强的位置。最优边缘检测主要通过低错误率、高定位性和最小响应三个标准进行评价。
2)、Canny算法优缺点

  • 优点:不容易受噪声干扰,能够检测到真正的弱边缘;使用两种不同的阈值分别检测强边缘和弱边缘,并且当弱边缘和强边缘相连时,才将弱边缘包含在输出图像中;尽可能多地标识出图像中的实际边缘;标识出的边缘要与实际图像中的实际边缘尽可能接近。
  • 缺点:图像中的边缘只能标识一次,并且可能存在的图像噪声不应该标识为边缘。

3)、Canny算法构造原理
(1)、使用高斯平滑(如公式所示)去除噪声
K ( 5 , 5 ) = 1 / 273 [ 1 4 7 4 1 4 16 26 16 4 7 26 41 26 7 4 16 26 16 4 1 4 7 4 1 ] K(5,5)=1/273\begin{bmatrix} 1 & 4 & 7&4&1 \\ 4 & 16 & 26&16&4 \\7 & 26 & 41&26&7 \\4 & 16 & 26&16&4\\1 & 4 & 7&4&1\end{bmatrix} K(5,5)=1/2731474141626164726412674162616414741
《OpenCv视觉之眼》Python图像处理十四 :Opencv图像轮廓提取之Scharr算法和Canny算法_第4张图片

(2)、按照Sobel滤波器步骤计算梯度幅值和方向,寻找图像的强度梯度。先将卷积模板分别作用x和y方向,再计算梯度幅值和方向,其公式如下所示。梯度方向一般取0度、45度、90度和135度四个方向
d x = [ 1 0 − 1 2 0 − 2 1 0 − 1 ] , d y = [ − 1 − 2 − 1 0 0 0 1 2 1 ] 图 像 轮 廓 边 缘 强 度 : S [ i , j ] = d x 2 + d y 2 边 缘 角 度 : t a n = a r c t a n ( d y / d x ) dx=\begin{bmatrix} 1 & 0 & -1 \\ 2 & 0 & -2 \\1 & 0 & -1 \end{bmatrix} , dy=\begin{bmatrix} -1 & -2 & -1 \\ 0 & 0 & 0 \\1 & 2 & 1 \end{bmatrix} \\\\ 图像轮廓边缘强度:S[i,j]=\sqrt{d_x^2+d_y^2}\\\\ 边缘角度:tan=arctan(dy/dx) dx=121000121,dy=101202101S[i,j]=dx2+dy2 tan=arctan(dy/dx
《OpenCv视觉之眼》Python图像处理十四 :Opencv图像轮廓提取之Scharr算法和Canny算法_第5张图片

(3)、通过非极大值抑制(Non-maximum Suppression)过滤掉非边缘像素,将模糊的边界变得清晰。该过程保留了每个像素点上梯度强度的极大值,过滤掉其他的值

  • 将其梯度方向近似为以下值中的一个,包括0、45、90、135、即表示x方向,y方向和正负对角线方向。

《OpenCv视觉之眼》Python图像处理十四 :Opencv图像轮廓提取之Scharr算法和Canny算法_第6张图片

  • 比较该像素点和其梯度正负方向的像素点的梯度强度,如果该像素点梯度强度最大则保留,否则抑制(删除,即置为0)
    《OpenCv视觉之眼》Python图像处理十四 :Opencv图像轮廓提取之Scharr算法和Canny算法_第7张图片
    非极大值抑制算法:0°时取(x,y)、(x+1,y)、(x-1,y) 中的最大值与(x,y)比较,如(x,y)大,则保留像素值,如(x,y)小,则设置为0,其它角度类似

《OpenCv视觉之眼》Python图像处理十四 :Opencv图像轮廓提取之Scharr算法和Canny算法_第8张图片
(4)、利用双阈值方法来确定潜在的边界。经过非极大抑制后图像中仍然有很多噪声点,此时需要通过双阈值技术处理,即设定一个阈值上界和阈值下界。图像中的像素点如果大于阈值上界则认为必然是边界(称为强边界,strong edge),小于阈值下界则认为必然不是边界,两者之间的则认为是候选项(称为弱边界,weak edge)

  • 如果梯度幅值S[i,j]大于高阈值的话,令S[i,j]=255
  • 如果梯度幅值S[i,j]小于低阈值的话,令S[i,j]=0

《OpenCv视觉之眼》Python图像处理十四 :Opencv图像轮廓提取之Scharr算法和Canny算法_第9张图片
(5)、利用滞后技术来跟踪边界。若某一像素位置和强边界相连的弱边界认为是边界,其他的弱边界则被删除

  • 如果梯度幅值S[i,j]在高阈值和低阈值之间,并且周围8邻域内有比高阈值高的像素点存在,令S[i,j]=255,没有则令S[i,j]=0

那么对于Canny算法的功能函数构造也分为以上5个步骤,依次步骤进行处理,最后得到Canny算法边缘提取函数,如下步骤构造

2、Canny算法功能函数构造

1)、使用高斯平滑(如公式所示)去除噪声

	#获取图像属性
    h,w=gray.shape[0:2]
    # 定义存放高斯滤波处理后的图像
    gauss = np.zeros((h, w), dtype=gray.dtype)
    #定义高斯核卷积模板
    a=np.array([[1,4,7,4,1],[4,16,26,16,4],[7,26,41,26,7],[4,16,26,16,4],[1,4,7,4,1]])
    kernel=a*(1/273)
    '''
        使用高斯平滑(如公式所示)去除噪声
    '''
    #对图像遍历进行高斯滤波处理
    for i in range(2, h - 2):
        for j in range(2, w - 2):
            sum= np.sum(gray[i-2:i+2+1,j-2:j+2+1]*kernel)
            gauss[i, j]=sum

上面代码并没有进行黑边处理,正常情况下是需要处理的,如果小伙伴需要对图像进行黑边处理,请参考该系列博客教程前面讲解高斯滤波去噪的博客章节学习!
2)、按照Sobel滤波器步骤计算梯度幅值和方向

	'''
        按照Sobel滤波器步骤计算梯度幅值和方向,寻找图像的强度梯度
    '''
    Sobel=np.zeros((h,w),dtype=gray.dtype)
    #对阈值化图像进行遍历,进行Sobel梯度求解
    for i in range(h-1):
        for j in range(w-1):
            dx=(int(gauss[i-1,j-1])+2*int(gauss[i-1,j])+int(gauss[i-1,j+1]))-(int(gauss[i+1,j-1])+2*int(gauss[i+1,j])+int(gauss[i+1,j+1]))
            dy=(int(gauss[i-1,j+1])+2*int(gauss[i,j+1])+int(gauss[i+1,j+1]))-(int(gauss[i-1,j-1])+2*int(gauss[i,j-1])+int(gauss[i+1,j-1]))
            Sobel[i,j]=np.sqrt(dx**2+dy**2)

3)、通过非极大值抑制(Non-maximum Suppression)过滤掉非边缘像素,将模糊的边界变得清晰

	'''
        通过非极大值抑制(Non-maximum Suppression)过滤掉非边缘像素
    '''
    Suppression=np.zeros((h,w),dtype=gray.dtype)
    #对阈值化图像进行遍历,进行非极大值抑制
    for i in range(h-1):
        for j in range(w-1):
            dx=(int(gauss[i-1,j-1])+2*int(gauss[i-1,j])+int(gauss[i-1,j+1]))-(int(gauss[i+1,j-1])+2*int(gauss[i+1,j])+int(gauss[i+1,j+1]))
            dy=(int(gauss[i-1,j+1])+2*int(gauss[i,j+1])+int(gauss[i+1,j+1]))-(int(gauss[i-1,j-1])+2*int(gauss[i,j-1])+int(gauss[i+1,j-1]))
            #确保分母不是0
            dx = np.maximum(dx, 1e-10)
            seta=np.arctan(dy/dx)
            '''
                确定梯度角度
            '''
            if seta>-0.4142 and seta<0.4142:
                angle=0
            elif seta>0.4142 and seta<2.4142:
                angle=45
            elif abs(seta)>2.4142:
                angle=90
            elif seta>-2.4142 and seta<-0.4142:
                angle=135
            '''
                根据梯度角度方向,求得对应的非极大值抑制
            '''
            if angle==0:
                if max(Sobel[i,j],Sobel[i,j-1],Sobel[i,j+1])==Sobel[i,j]:#比较x方向梯度3个值中的最大值,如果Sobel[i,j]最大则保留,否则设置为0
                    Suppression[i,j]=Sobel[i,j]
                else:
                    Suppression[i,j]=0
            elif angle==45:
                if max(Sobel[i,j],Sobel[i-1,j+1],Sobel[i+1,j-1])==Sobel[i,j]:#比较正对角线方向梯度3个值中的最大值,如果Sobel[i,j]最大则保留,否则设置为0
                    Suppression[i,j]=Sobel[i,j]
                else:
                    Suppression[i,j]=0
            elif angle==90:
                if max(Sobel[i,j],Sobel[i-1,j],Sobel[i+1,j])==Sobel[i,j]:#比较y方向梯度3个值中的最大值,如果Sobel[i,j]最大则保留,否则设置为0
                    Suppression[i,j]=Sobel[i,j]
                else:
                    Suppression[i,j]=0
            elif angle==135:
                if max(Sobel[i,j],Sobel[i-1,j-1],Sobel[i+1,j+1])==Sobel[i,j]:#比较反对角线方向梯度3个值中的最大值,如果Sobel[i,j]最大则保留,否则设置为0
                    Suppression[i,j]=Sobel[i,j]
                else:
                    Suppression[i,j]=0

4)、利用双阈值方法来确定潜在的边界并利用滞后技术来跟踪边界

	'''
        利用双阈值方法来确定潜在的边界
    '''
    doubleThreshold=np.zeros((h,w),dtype=gray.dtype)
    #对阈值化图像进行遍历,进行双阈值处理
    for i in range(h):
        for j in range(w):
            if Suppression[i,j]>=maxT:#大于上阈值,则设置为255
                doubleThreshold[i,j]=255
            elif Suppression[i,j]<=minT:#小于下阈值,则设置为0
                doubleThreshold[i,j]=0
            else:
                '''
                    利用滞后技术来跟踪边界。若某一像素位置和强边界相连的弱边界认为是边界,其他的弱边界则被删除
                '''
                #周围8邻域内有比该像素值大的像素,则设置为255,否则设置为0
                if max(Suppression[i-1,j-1],Suppression[i-1,j],Suppression[i-1,j+1],Suppression[i,j-1],Suppression[i,j+1],Suppression[i+1,j-1],Suppression[i+1,j+1])>=Suppression[i,j]:
                    doubleThreshold[i,j]=255
                else:
                    doubleThreshold[i,j]=0

5)、Canny算法功能函数构造完整代码

'''
Canny算法轮廓提取算法构造
'''
def Canny(gray,minT,maxT):
    #获取图像属性
    h,w=gray.shape[0:2]
    #定义空白图像,用于存放LOG算法提取出来的轮廓图
    Canny=np.zeros((h,w),dtype=gray.dtype)
    # 定义存放高斯滤波处理后的图像
    gauss = np.zeros((h, w), dtype=gray.dtype)
    #定义高斯核卷积模板
    a=np.array([[1,4,7,4,1],[4,16,26,16,4],[7,26,41,26,7],[4,16,26,16,4],[1,4,7,4,1]])
    kernel=a*(1/273)
    '''
        使用高斯平滑(如公式所示)去除噪声
    '''
    #对图像遍历进行高斯滤波处理
    for i in range(2, h - 2):
        for j in range(2, w - 2):
            sum= np.sum(gray[i-2:i+2+1,j-2:j+2+1]*kernel)
            gauss[i, j]=sum
    '''
        按照Sobel滤波器步骤计算梯度幅值和方向,寻找图像的强度梯度
    '''
    Sobel=np.zeros((h,w),dtype=gray.dtype)
    #对阈值化图像进行遍历,进行Sobel求解梯度值
    for i in range(h-1):
        for j in range(w-1):
            dx=(int(gauss[i-1,j-1])+2*int(gauss[i-1,j])+int(gauss[i-1,j+1]))-(int(gauss[i+1,j-1])+2*int(gauss[i+1,j])+int(gauss[i+1,j+1]))
            dy=(int(gauss[i-1,j+1])+2*int(gauss[i,j+1])+int(gauss[i+1,j+1]))-(int(gauss[i-1,j-1])+2*int(gauss[i,j-1])+int(gauss[i+1,j-1]))
            Sobel[i,j]=np.sqrt(dx**2+dy**2)
    '''
        通过非极大值抑制(Non-maximum Suppression)过滤掉非边缘像素
    '''
    Suppression=np.zeros((h,w),dtype=gray.dtype)
    #对阈值化图像进行遍历,进行非极大值抑制
    for i in range(h-1):
        for j in range(w-1):
            dx=(int(gauss[i-1,j-1])+2*int(gauss[i-1,j])+int(gauss[i-1,j+1]))-(int(gauss[i+1,j-1])+2*int(gauss[i+1,j])+int(gauss[i+1,j+1]))
            dy=(int(gauss[i-1,j+1])+2*int(gauss[i,j+1])+int(gauss[i+1,j+1]))-(int(gauss[i-1,j-1])+2*int(gauss[i,j-1])+int(gauss[i+1,j-1]))
            #确保分母不是0
            dx = np.maximum(dx, 1e-10)
            seta=np.arctan(dy/dx)
            '''
                确定梯度角度
            '''
            if seta>-0.4142 and seta<0.4142:
                angle=0
            elif seta>0.4142 and seta<2.4142:
                angle=45
            elif abs(seta)>2.4142:
                angle=90
            elif seta>-2.4142 and seta<-0.4142:
                angle=135
            '''
                根据梯度角度方向,求得对应的非极大值抑制
            '''
            if angle==0:
                if max(Sobel[i,j],Sobel[i,j-1],Sobel[i,j+1])==Sobel[i,j]:#比较x方向梯度3个值中的最大值,如果Sobel[i,j]最大则保留,否则设置为0
                    Suppression[i,j]=Sobel[i,j]
                else:
                    Suppression[i,j]=0
            elif angle==45:
                if max(Sobel[i,j],Sobel[i-1,j+1],Sobel[i+1,j-1])==Sobel[i,j]:#比较正对角线方向梯度3个值中的最大值,如果Sobel[i,j]最大则保留,否则设置为0
                    Suppression[i,j]=Sobel[i,j]
                else:
                    Suppression[i,j]=0
            elif angle==90:
                if max(Sobel[i,j],Sobel[i-1,j],Sobel[i+1,j])==Sobel[i,j]:#比较y方向梯度3个值中的最大值,如果Sobel[i,j]最大则保留,否则设置为0
                    Suppression[i,j]=Sobel[i,j]
                else:
                    Suppression[i,j]=0
            elif angle==135:
                if max(Sobel[i,j],Sobel[i-1,j-1],Sobel[i+1,j+1])==Sobel[i,j]:#比较反对角线方向梯度3个值中的最大值,如果Sobel[i,j]最大则保留,否则设置为0
                    Suppression[i,j]=Sobel[i,j]
                else:
                    Suppression[i,j]=0
    '''
        利用双阈值方法来确定潜在的边界
    '''
    doubleThreshold=np.zeros((h,w),dtype=gray.dtype)
    #对阈值化图像进行遍历,进行双阈值处理
    for i in range(h):
        for j in range(w):
            if Suppression[i,j]>=maxT:#大于上阈值,则设置为255
                doubleThreshold[i,j]=255
            elif Suppression[i,j]<=minT:#小于下阈值,则设置为0
                doubleThreshold[i,j]=0
            else:
                '''
                    利用滞后技术来跟踪边界。若某一像素位置和强边界相连的弱边界认为是边界,其他的弱边界则被删除
                '''
                #周围8邻域内有比该像素值大的像素,则设置为255,否则设置为0
                if max(Suppression[i-1,j-1],Suppression[i-1,j],Suppression[i-1,j+1],Suppression[i,j-1],Suppression[i,j+1],Suppression[i+1,j-1],Suppression[i+1,j+1])>=Suppression[i,j]:
                    doubleThreshold[i,j]=255
                else:
                    doubleThreshold[i,j]=0
    return doubleThreshold

6)、读取图像,调用Canny算法函数进行边缘提取并显示处理结果

import cv2
import numpy as np
#np.set_printoptions(threshold=np.inf)  #打印数组中的全部内容
import matplotlib.pyplot as plt
plt.rcParams['font.sans-serif'] = ['SimHei'] #显示中文
#读取图像
img=cv2.imread("my.jpg")
#图像灰度化
gray=cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
#调用Canny算法函数
Canny= Canny(gray,50,150) #这里不需要阈值化处理,因为Canny算法自带阈值化处理
#对原图进行格式转换,方便matplotlib图像显示
img1=cv2.cvtColor(img,cv2.COLOR_BGR2RGB)
#对结果图进行格式转换,方便matplotlib图像显示
Canny=cv2.cvtColor(Canny,cv2.COLOR_BGR2RGB)
#图像显示
titles = ['原图', 'Canny算法']  #标题
images = [img1, Canny]   #图像对比显示
for i in range(2):
    plt.subplot(1,2,i+1), plt.imshow(images[i])  
    plt.title(titles[i])    
    plt.axis('off')#关闭坐标轴  设置为on则表示开启坐标轴
plt.show()#显示图像

《OpenCv视觉之眼》Python图像处理十四 :Opencv图像轮廓提取之Scharr算法和Canny算法_第10张图片

3、OpenCV中Canny算法库函数使用

在OpenCV中,Canny()库函数原型如下介绍
1)、函数原型:Canny = cv2.Canny(img, threshold1, threshold2,apertureSize)

  • img:需要轮廓图提取的图像
  • threshold1:第一个滞后性阈值
  • threshold2:第二个滞后性阈值
  • apertureSize:Sobel算法的孔径大小,其默认值为3

2)、Canny()库函数使用如下:

'''
Canny算法轮廓提取算法-OpenCV库函数的使用方法
'''
import cv2
import numpy as np
#np.set_printoptions(threshold=np.inf)  #打印数组中的全部内容
import matplotlib.pyplot as plt
plt.rcParams['font.sans-serif'] = ['SimHei'] #显示中文
'''
一般来说,对图像轮廓提取都会经过如下步骤,灰度-滤波去噪-阈值化处理-(形态学处理,前面如果达标,这步骤可以省略)-轮廓提取
'''
#读取图像
img=cv2.imread("my.jpg")
#图像灰度化
gray=cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
#图像高斯滤波去噪
blur=cv2.GaussianBlur(gray,(7,7),1,1)#核尺寸通过对图像的调节自行定义
Canny = cv2.Canny(blur, 50, 150) #这里不需要阈值化处理,因为Canny算法自带阈值化处理
#对原图进行格式转换,方便matplotlib图像显示
img=cv2.cvtColor(img,cv2.COLOR_BGR2RGB)
#对结果图进行格式转换,方便matplotlib图像显示
Canny=cv2.cvtColor(Canny,cv2.COLOR_BGR2RGB)
#图像显示
titles = ['原图', 'Canny算法-OpenCV']  #标题
images = [img, Canny]   #图像对比显示
for i in range(2):
    plt.subplot(1,2,i+1), plt.imshow(images[i])  
    plt.title(titles[i])    
    plt.axis('off')#关闭坐标轴  设置为on则表示开启坐标轴
plt.show()#显示图像

《OpenCv视觉之眼》Python图像处理十四 :Opencv图像轮廓提取之Scharr算法和Canny算法_第11张图片
从上面可以看出,Canny算法提取出的物体轮廓的效果是非常好的不仅明显,而且提取的边缘线比较细,这就是Canny算法被广泛应用于图像轮廓提取的原因,这是被作为边缘检测的标准算法的原因!

以上就是本次博客的全部内容,图像处理的基本步骤到这里也就完结了,接下来该系列的博客主要讲解通过前面对图像处理的基本步骤,进行项目实战、包括图像中某物体的检测、图像美化处理、硬币检测、人脸检测、直线检测、圆脸检测等等,本次博客内容到此结束,遇到问题的小伙伴记得留言评论,学长看到会为大家进行解答的,这个学长不太冷!

善待自己,不被别人左右,也不去左右别人,自信优雅。如果做一粒尘埃,就用飞舞诠释生命的内涵;如果是一滴雨,就倾尽温柔滋润大地。人生多磨难,要为自己鼓掌,别让犹豫阻滞了脚步,别让忧伤苍白了心灵。

陈一月的又一天编程岁月^ _ ^

你可能感兴趣的:(Opencv视觉之眼,python,Canny算法原理,Scharr算法原理,《OpenCV视觉之眼》,OpenCV图像轮廓提取)