如何高效的进行图像边缘检测分割

​ 

图像边缘检测任务在实际应用场景中非常多见,一般对于检测出的边缘可以进行分割提取,计算边缘、轮廓内的面积、周长等。图像分割技术大体可以分为两类:以传统机器学习为主的图像梯度计算方法、以及以深度学习为主的端到端的图像分割学习方法,两者的计算成本和最终效果也是大相径庭。

简而言之,传统的检测分割方法计算成本要低,但是效果较差,大部分情况下都达不到我们的要求,而深度学习为主的端到端的检测分割方法计算成本高,一般都是需要GPU的计算,但是效果非常的好,目前实际落地应用中,基本全都是以深度学习为主的端到端的分割算法实现的。今天我们主要讨论以传统机器学习为主的图像梯度及算法方法,以深度学习为主的端到端的图像分割方法我们在后期线上、线下课程中会有详细的讲解,同时,我们也会有相关的介绍资料,关注 Wei Xin gong zhong hao:深度人工智能学院,我们长期分享更新干货案例和实用技术,也可以咨询相关技术。


 

 

 

思路分析

以传统机器学习为主的图像边缘检测算法主要是以图像本身的像素梯度来计算,如果相邻的像素之间像素值的梯度相差过大,我们认为即是轮廓或者边缘部分。但是很显然这只是基于简单的梯度计算,到底梯度达到多少才是我们需要的边缘,这里就需要一个阈值来确定,另一方面,计算梯度,我们可以使用求导的方法计算,但是如果只是求取一阶导数,得到的只是图像的梯度变化较大的一些轮廓部分,也就是说但凡当梯度达到阈值的部分,都会被提取出来,很容易造成提取的轮廓线段过多。

边缘检测一般是提取轮廓部分梯度变化最大的地方,既然是提取变化最大的地方,那么直接求取二阶导数不就可以了嘛,顺着这个思路继续思考,如果直接提取图像像素值的二阶导,确实是可以得到图像边缘部分的,也就是说求取图像像素值的二阶导会避免只求取一阶导而导致图像轮廓线段杂乱的现象。

但是如果只是求取图像像素值的二阶导数获取图像边缘的话,其实得到的结果是不太理想的,最主要问题就是图像的边缘部分可能是断断续续的,或者边缘连接部分不够平滑,这主要是由于图像的噪声、以及图像当中的漏洞造成的。所以在对图像进行梯度计算之前,应该先进行图像形态学操作,使用开操作和闭操作分别进行去噪和填补漏洞,让所有的边缘尽量都连接成完整闭合的线段,这样提取图像边缘的时候才不会出现断断续续、或者杂乱的线段。

到此,好像能够影响图像边缘检测的因素都被排除了,但是还有一种情况要说明,就是当被检测的图像边缘部分的像素变化不够明显的时候,是否还能够正常的检测分割出图像的边缘呢?以文章后面的人脸CT图片为例,要检测出人脸的轮廓部分是有很大难度的。

一般这种情况如果直接按照上面的流程进行检测,实际上效果是非常不理想的。由于图像本身应该被分割的边缘部分由于像素差异过小,从而导致计算出来的梯度也非常的小,这种情况求取像素值的二阶导会忽略掉这部分,二阶导数求取是图像像素梯度变化最大的地方,很明显,在这一点上,我们所要求应该被分割的“边缘”是不符合算法的要求的。

接着这个问题继续探讨,我们可以从两方面去思考解决方案,第一种解决方案就是直接抛弃图像梯度计算的这种方法,使用一种完全按照我们对图像边缘要求分割的方法;第二种方案就是继续按照图像像素梯度的计算方法改进当前的算法。

如果按照第一种方案的话,就不需要计算图像像素的梯度了,目前传统算法对于图像边缘分割都是基于像素梯度的计算,而不需要图像梯度就能实现图像分割的就只有深度学习的算法了,深度学习算法是一种基于端到端的学习方法,对于算法的设计难度较低,只需要指定输入和输出,以及对于标签的设定就可以了,深度学习方法除了对于设备的要求较高之外,训练技巧也较多,是需要一定的经验和技术积累的。

第二种方案是我们要探讨的重点,如何按照图像像素梯度计算的方法对弱边缘图像进行分割呢?其实答案的线索就藏在这个问题里面,既然图像的边缘较弱,那么意味着这部分图像的像素梯度也较弱,如果有一种方法能够增加这部分图像的像素梯度,那么问题就迎刃而解了。

实际上,确实是这样的解决思路,那么具体是如何操作的呢?操作方法就是放大像素值之间的差距,让这部分图像的像素值的梯度变得更大,当然如果放大得过多,图像的像素梯度反而会变小,因为我们知道像素值最大是255,当像素之间的差距被放大的时候,较大的像素只会逼近255,所以,在放大像素梯度的时候,要注意放大的方法和强度。

在实际的程序代码操作中,通常可以通过乘法或者平方的方法放大像素梯度,由于平方等幂次方的操作过于激进,很容易将图像的大部分像素放大到255,所以一般使用乘法放大图像像素梯度的方法更常用。

至此,使用传统机器学习方法进行图像边缘检测分割的思路已分析完毕,那么具体的程序代码是如何实现的呢?很幸运我们所处的这个时代,连造车都不需要从轮子开始,只需要会组装就可以了。所有实现的代码都是现成的,当然现成的代码也是需要我们去调整相关参数的,这很符合我们炼丹师的风格,无限实验,错了再调整参数就可以,总有一天,会调整好的。


代码实现

接下来进入正题,来看看代码当中的实现过程吧。代码案例主要以成熟好用的opnecv为例展开讲解。假设我们要提取的是下面这张图像的轮廓和字体,那么接下来的操作步骤如下:

在opnecv中,计算图像像素值的梯度之前,先要对图像进行灰度化处理,这是因为图像本身是RGB三个通道的,而图像不同通道的相同位置的梯度是不一样的,为了方便统一计算,这里先将图像的三个通道转成一个通道,然后再进行图像像素梯度的计算。

图像的梯度计算,一般使用Sobel算子来实现,Sobel算子是通过像素图的横轴和纵轴分别计算各自的梯度,然后再统一计算出当前每个像素点的梯度,除了Sobel算子,还有对Sobel算子升级后的Scharr 算子和 Laplacian算子。

Sobel算子

Scharr 算子

每个像素值的梯度值等于横轴和纵轴梯度的平方求和再开方

Scharr的卷积核更大,当把sobel算子的卷积核从3*3扩大到5*5,再求取各个X轴和Y轴上二阶导数之后,实际效果是和Scharr 算子一样的,Scharr算子的目的就是为例获取更多的轮廓细节部分。

Laplacian算子的效果相当于对sobel算子进行二阶导数的结果,卷积核越小,边缘线越细。

以下代码就是实现过程,附带详细注释说明:

import cv2import matplotlib.pyplot as pltimg=cv2.imread("img.png",0)#1.索贝尔算子,#5*5的核与二阶导数组合相当于沙尔算子,1*1的核与二阶导数的组合相当于拉普拉斯算子sobelX=cv2.Sobel(img,-1,dx=1,dy=0,ksize=3)sobelY=cv2.Sobel(img,-1,dx=0,dy=1,ksize=3)#增强梯度sobelXABS=cv2.convertScaleAbs(sobelX,1,1)#src*alpha+beta,计算绝对值,将结果转成8位sobelYABS=cv2.convertScaleAbs(sobelY,1,1)#src*alpha+beta,计算绝对值,将结果转成8位#x轴和y轴的梯度权重各占一半soble=cv2.addWeighted(sobelXABS,0.5,sobelYABS,0.5,0)#2.沙尔算子scharrX=cv2.Scharr(img,-1,dx=1,dy=0)scharrY=cv2.Scharr(img,-1,dx=0,dy=1)scharrXABS=cv2.convertScaleAbs(scharrX)scharrYABS=cv2.convertScaleAbs(scharrY)scharr=cv2.addWeighted(scharrXABS,0.5,scharrYABS,0.5,0)#3.拉普拉斯算子,是索贝尔算子求二阶段的结果Laplacian=cv2.Laplacian(img,-1)title=["IMG","sobelXABS","sobelYABS","soble","scharrXABS","scharrYABS","scharr","Laplacian"]images=[img,sobelXABS,sobelYABS,soble,scharrXABS,scharrYABS,scharr,Laplacian]for i in range(8):    plt.subplot(2,4,i+1)    plt.imshow(images[i],cmap="gray")    plt.title(title[i])    plt.xticks(),plt.yticks()plt.show()

以上源代码输出的结果:

把sobel算子的导数改成二阶导,卷积核改成5*5之后输出的结果:

sobelX=cv2.Sobel(img,-1,dx=2,dy=0,ksize=5)sobelY=cv2.Sobel(img,-1,dx=0,dy=2,ksize=5)

改完之后发现:把sobel算子各轴的导数改成二阶导,卷积核从3*3扩大到5*5之后,实际效果是和Scharr 算子一样的:

把sobel算子的导数改成二阶导,卷积核改成1*1之后输出的结果:

sobelX=cv2.Sobel(img,-1,dx=2,dy=0,ksize=1)sobelY=cv2.Sobel(img,-1,dx=0,dy=2,ksize=1)

改完之后发现:把sobel算子各轴的导数改成二阶导,卷积核从3*3减小到1*1之后,实际效果是和Laplacian算子一样的:

上面这个案例只是一个比较简单的图像轮廓提取案例,而当图像自身的边界轮廓不够明显的时候该如何提取呢?下面我们就这个案例来展开继续:

上图是一个人体头部的CT图片,假设我们现在要提取图像的人脸轮廓部分,那么我们能否直接使用上面的方法呢?

从输出的结果上来看,显然不是我们想要的结果,那么如果我们根据前面的推论进行改进呢?理论是可以的,但是在实际操作当中,直接使用sobel算子改进效果还是不够理想,所以在此基础上发展除了一个canny算子,canny算子和sobel算子的最大区别就是canny算子使用了轮廓线的双阈值处理,将杂乱多余的线段消除了,而sobel算子则没有这个步骤,所以按照上面分析的思路,直接使用sobel算子,会出现很多杂乱的线段。

双阈值处理的原理非常简单,以下图为例:先设定两个阈值,minVal和maxVal。大于maxVal的边缘肯定是边缘(保留),低于minVal的边缘是非边缘(舍去)。对于介于两者之间的值,判断是否与真正的边界(强边界)相连,相连就保留,否则丢弃。

以下是代码实现过程,分别对比了图像梯度(对比度)增强前后分别使用canny算子的效果,以及使用sobel算子二阶导数的效果:

import cv2import matplotlib.pyplot as plt#canny算子'''步骤:1、彩色图像转换为灰度图像统一梯度 2、高斯滤波,滤除噪声点 3、高亮处理,增强图像梯度对比4、形态学操作,填补图像漏洞,连接断线5、canny算子提取边缘5.1、通过sobel算子计算图像梯度,根据梯度计算边缘幅值与角度 5.2、非极大值抑制,相当于求取二阶导 5.3、双阈值边缘连接处理 6、二值化图像输出结果'''#1.原图转灰度图img=cv2.imread(r"img.jpg",0)#2.高斯模糊处理GaussianBlur=cv2.GaussianBlur(img,(9,9),3)# 3.高亮处理,增强差异度/梯度ScaleAbs=cv2.convertScaleAbs(GaussianBlur,alpha=8,beta=0)#4.形态学闭操作填补漏洞kerner=cv2.getStructuringElement(cv2.MORPH_RECT,(9,9))MORPH_CLOSE=cv2.morphologyEx(ScaleAbs,cv2.MORPH_CLOSE,kerner)#5.canny算子提取边缘# 通过sobel算子计算图像梯度,根据梯度计算边缘幅值与角度# 非极大值抑制# 双阈值边缘连接处理Canny1=cv2.Canny(img,100,150)Canny2=cv2.Canny(MORPH_CLOSE,100,150)#sobel算子sobelX=cv2.Sobel(MORPH_CLOSE,-1,dx=2,dy=0,ksize=5)sobelY=cv2.Sobel(MORPH_CLOSE,-1,dx=0,dy=2,ksize=5)#增强梯度sobelXABS=cv2.convertScaleAbs(sobelX,1,1)#src*alpha+beta,计算绝对值,将结果转成8位sobelYABS=cv2.convertScaleAbs(sobelY,1,1)#src*alpha+beta,计算绝对值,将结果转成8位#x轴和y轴的梯度权重各占一半soble=cv2.addWeighted(sobelXABS,0.5,sobelYABS,0.5,0)# 6.边缘二值化处理ret1,thresh1=cv2.threshold(Canny1,0,255,cv2.THRESH_BINARY|cv2.THRESH_OTSU)ret2,thresh2=cv2.threshold(Canny2,0,255,cv2.THRESH_BINARY|cv2.THRESH_OTSU)#展示最终结果plt.subplot(231),plt.imshow(img,cmap="gray"),plt.title("img"),plt.xticks([]),plt.yticks([])plt.subplot(232),plt.imshow(ScaleAbs,cmap="gray"),plt.title("ScaleAbs"),plt.xticks([]),plt.yticks([])plt.subplot(233),plt.imshow(thresh1,cmap="gray"),plt.title("thresh1"),plt.xticks([]),plt.yticks([])plt.subplot(234),plt.imshow(thresh2,cmap="gray"),plt.title("thresh2"),plt.xticks([]),plt.yticks([])plt.subplot(235),plt.imshow(soble,cmap="gray"),plt.title("soble"),plt.xticks([]),plt.yticks([])plt.show()

以上源代码输出结果:

从结果可以得出结论canny算子就是sobel算子求二阶导数之后进行双阈值处理后的结果,只是canny算子在求取二阶导数的时候没有直接像sobel算子那样直接计算二阶导,而是采用了非极大值抑制的方法(NMS),也称作局部最大值搜索,从而获取更加精准的局部边缘,达到和求二阶导数一样的效果。

后记:

 

其实在opencv中,除了sobel算子和canny算子能够对图像进行边缘轮廓的提取,分水岭算法也能够根据灰度值的大小对图像进行分割,实际上也是基于梯度的算法。这些算法都有一个共同点就是都需要对图像进行灰度化处理,从而根据灰度值进行梯度处理。

当然,我们发现以上案例都是对颜色分别较为单一的目标进行边缘检测和分割的,如果被检查的目标是多种颜色集合的目标,那么使用基于图像像素梯度的传统机器学习边缘检测方法是达不到我们想要的效果的,其实想一想就能明白,既然是像素梯度算法,那么只要颜色变化较大,那么对于的像素梯度也会变大,所以当被检测分割的目标是多种颜色,比如以人体分割为例,由于人的衣服可能是彩色花纹组成,那么此时使用基于像素梯度的分割方法很难获取一个完整的分割目标的。

那么这种多种颜色组合的目标的分割一般是使用深度学习算法来分割的,由于深度学习算法只和所设定的学习目标相关,只要被学习的目标设定合适了,通过反向传播算法更新相关参数矩阵就可以获得正确的学习结果,然后使用这些参数的模型就可以直接进行新目标的分割。

至于基于图像像素梯度的传统机器学习分割方法,更适合颜色较为单一、背景简单的检测目标,比如一般是被灰度化后的图像,或者是图像前景和背景颜色较为明显的单一颜色的图像。

此外再说明一下,无论是传统的机器学习边缘检测分割方法,还是深度学习为主的图像分割方法,二者都各有优势,如果被分割的目标颜色复杂度较低,那么使用传统的机器学习方法进行分割也是可以的,如果被分割的图像颜色复杂度高,那么最好使用深度学习的分割方法效果会更好。

关注微信公众号:深度人工智能学院,获取更多人工智能方面的知识!

                官方公众号                                    官方微信号

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值