opencv图像的梯度处理,边缘检测
1.图像的梯度处理1.1 图像梯度
如果你还记得高数中用一阶导数来求极值的话,就很轻易理解了:把图片想象成一连函数,因为边缘部门的像素值是与旁边像素明显有区别的,所以对图片局部求极值,就可以得到整幅图片的边缘信息了。不外图片是二维的离散函数,导数就变成了差分,这个差分就称为图像的梯度。
1.2 垂直边缘提取
滤波是应用卷积来实现的,卷积的关键就是卷积核,我们来观察下面这个卷积核:
k 1 = [ − 1 0 1 − 2 0 2 − 1 0 1 ] k1=\left[\begin{array}{c c c}{{-1}}&{{0}}&{{1}}\\ {{-2}}&{{0}}&{{2}}\\ {{-1}}&{{0}}&{{1}}\end{array}\right] k1= −1−2−1000121
这个核是用来提取图片中的垂直边缘的,怎么做到的呢?看下图:
https://i-blog.csdnimg.cn/direct/1425fb3e91dc41c697db74615b04e089.png
当前线左右两侧的元素举行差分,由于边缘的值明显小于(或大于)周边像素,所以边缘的差分结果会明显差异,这样就提取出了垂直边缘。
https://i-blog.csdnimg.cn/direct/9954cf65aa2c4be392eeea2df8495be2.png
同理,把上面那个矩阵转置一下,就是提取水平边缘。这种差分操作就称为图像的梯度计算:
k 2 = [ − 1 − 2 − 1 0 0 0 1 2 1 ] k2=\left[\begin{array}{c c c}{{-1}}&{{-2}}&{{-1}}\\ {{0}}&{{0}}&{{0}}\\ {{1}}&{{2}}&{{1}}\end{array}\right] k2= −101−202−101
https://i-blog.csdnimg.cn/direct/18df33f6155f42efb64618050d6c5b66.png
代码格式如下:
cv2.filter2D(src, ddepth, kernel)
filter2D函数是用于对图像举行二维卷积(滤波)操作。它允许自定义卷积核(kernel)来实现各种图像处理结果,如平滑、锐化、边缘检测等
[*]src: 输入图像,一样平常为numpy数组。
[*]ddepth: 输出图像的深度,可以是负值(表示与原图雷同)、正值或其他特定值(常用-1 表示输出与输入具有雷同的深度)。
[*]kernel: 卷积核,一个二维数组(通常为奇数大小的方形矩阵),用于计算每个像素附近邻域的加权和。
垂直边缘提取代码如下:
'''图像的梯度处理-垂直边缘提取'''
img=cv2.imread(r'../15day4.10/src/shudu.png')
kernel=np.array([[-1,0,1],
[-2,0,2],
[-1,0,1]],dtype=np.float32)
img2=cv2.filter2D(img,-1,kernel=kernel)
cv2.imshow("img",img)
cv2.imshow('img2',img2)
cv2.waitKey(0)
https://i-blog.csdnimg.cn/direct/37e6d059c6cf4b2a8af195586a647b07.png
水平边缘提取代码如下:
'''图像的梯度处理-水平边缘提取'''
img=cv2.imread(r'../15day4.10/src/shudu.png')
kernel=np.array([[-1,0,1],
[-2,0,2],
[-1,0,1]],dtype=np.float32)
img2=cv2.filter2D(img,-1,kernel=kernel.T)
cv2.imshow("img",img)
cv2.imshow('img2',img2)
cv2.waitKey(0)
https://i-blog.csdnimg.cn/direct/1c28086804ef426ab9d858e8bb68e6be.png
水平垂直边缘提取代码如下:
'''图像的梯度处理-水平垂直边缘提取'''
img=cv2.imread(r'../15day4.10/src/shudu.png')
kernel=np.array([[-1,0,1],
[-2,0,2],
[-1,0,1]],dtype=np.float32)
img2=cv2.filter2D(img,-1,kernel=kernel)
img3=cv2.filter2D(img,-1,kernel=kernel.T)
img4=cv2.add(img2,img3)
cv2.imshow("img",img)
cv2.imshow('img4',img4)
cv2.waitKey(0)
https://i-blog.csdnimg.cn/direct/898761819f45466fa6e7e4ed659e5158.png
1.3 Sobel算子
上面的两个卷积核都叫做Sobel算子,只是方向差异,它先在垂直方向计算梯度:
G x = k 1 × s r c G_{x}=k_{1}\times s r c Gx=k1×src
再在水平方向计算梯度:
G y = k 2 × s r c G_{y}=k_{2}\times s r c Gy=k2×src
最后求出总梯度:
G = G x 2 + G y 2 G={\sqrt{G x^{2}+G y^{2}}} G=Gx2+Gy2
在梯度处理方式这个组件中,当参数filter_method选择Sobel时,其他参数的含义如下所述:
https://i-blog.csdnimg.cn/direct/dc12a16278d440369669b838e155a756.png
代码格式如下:
cv2.Sobel(src,ddepth,dx,dy,ksize)
参数如下:
[*] src:这是输入图像,通常应该是一个灰度图像(单通道图像),因为 Sobel 算子是基于像素亮度梯度计算的。在彩色图像的情况下,通常须要先将其转换为灰度图像。
[*] ddepth:这个参数代表输出图像的深度,即输出图像的数据类型。在 OpenCV 中,-1 表示输出图像的深度与输入图像雷同。
[*] dx,dy:当组合为dx=1,dy=0时求x方向的一阶导数,在这里,设置为1意味着我们想要计算图像在水平方向(x轴)的梯度。当组合为 dx=0,dy=1时求y方向的一阶导数(如果同时为1,通常得不到想要的结果,想两个方向都处理的比力好 学习使用后面的算子)
[*] ksize:Sobel算子的大小,可选择3、5、7,默认为3。
代码如下:
'''Sobel算子'''
img=cv2.imread(r'../15day4.10/src/shudu.png')
img_gray=cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
img2=cv2.Sobel(img_gray,-1,dx=1,dy=0,ksize=3)
img2=cv2.Sobel(img_gray,-1,dx=0,dy=1,ksize=3)
img2=cv2.Sobel(img_gray,-1,dx=1,dy=1,ksize=3)
cv2.imshow("img",img)
cv2.imshow('img2',img2)
cv2.waitKey(0)
dx=1,dy=0的结果
https://i-blog.csdnimg.cn/direct/0b24648591e343fdbd925a11d41fa84c.png
dx=0,dy=1的结果
https://i-blog.csdnimg.cn/direct/8f7f1ee9a1474e17ac12cc4c45c3abf0.png
dx=1,dy=1的结果
https://i-blog.csdnimg.cn/direct/4797fe34afb444ef8b0b5697a9210b2b.png
Sobel算子就是不须要我们本身定义核的处理过程,Sobel算子的要处理的图要是灰度图
Sobel算子的卷积核这几个值是怎么来的呢?究竟上,并没有规定,你可以用你本身的。比如,最初只利用邻域间的原始差值来检测边缘的Prewitt算子:
k = [ − 1 0 1 − 1 0 1 − 1 0 1 ] k=\left[{\begin{array}{r r r}{-1}&{0}&{1}\\ {-1}&{0}&{1}\\ {-1}&{0}&{1}\end{array}}\right] k= −1−1−1000111
还有比Sobel更好用的Scharr算子,各人可以了解下:
k = [ − 3 0 3 − 10 0 10 − 3 0 3 ] k=\left[{\begin{array}{r r r}{-3}&{0}&{3}\\ {-10}&{0}&{10}\\ {-3}&{0}&{3}\end{array}}\right] k= −3−10−30003103
这些算法都是一阶边缘检测的代表,网上也有算子之间的对比资料。
1.4. Laplacian算子(cv2.Laplacian())
高数中用一阶导数求极值,在这些极值的地方,二阶导数为0,所以也可以通过求二阶导计算梯度:
d s t = ∂ 2 f ∂ x 2 + ∂ 2 f ∂ y 2 d s t={\frac{\partial^{2}f}{\partial x^{2}}}+{\frac{\partial^{2}f}{\partial y^{2}}} dst=∂x2∂2f+∂y2∂2f
一维的一阶和二阶差分公式分别为:
∂ f ∂ x = f ( x + 1 ) − f ( x ) {\frac{\partial f}{\partial x}}=f(x+1)-f(x) ∂x∂f=f(x+1)−f(x)
∂ 2 f ∂ x 2 = f ( x + 1 ) + f ( x − 1 ) − 2 f ( x ) {\frac{\partial^{2}f}{\partial x^{2}}}=f(x+1)+f(x-1)-2f(x) ∂x2∂2f=f(x+1)+f(x−1)−2f(x)
提取前面的系数,那么一维的Laplacian滤波核是:
k = [ 1 − 2 1 ] k= k=
而对于二维函数f(x,y),两个方向的二阶差分分别是:
∂ 2 f ∂ x 2 = f ( x + 1 , y ) + f ( x − 1 , y ) − 2 f ( x , y ) {\frac{\partial^{2}f}{\partial x^{2}}}=f(x+1,y)+f(x-1,y)-2f(x,y) ∂x2∂2f=f(x+1,y)+f(x−1,y)−2f(x,y)
∂ 2 f ∂ y 2 = f ( x , y + 1 ) + f ( x , y − 1 ) − 2 f ( x , y ) {\frac{\partial^{2}f}{\partial y^{2}}}=f(x,y+1)+f(x,y-1)-2f(x,y) ∂y2∂2f=f(x,y+1)+f(x,y−1)−2f(x,y)
合在一起就是:
V 2 f ( x , y ) = f ( x + 1 , y ) + f ( x − 1 , y ) + f ( x , y + 1 ) + f ( x , y − 1 ) − 4 f ( x , y ) V^{2}f(x,y)=f(x+1,y)+f(x-1,y)+f(x,y+1)+f(x,y-1)-4f(x,y) V2f(x,y)=f(x+1,y)+f(x−1,y)+f(x,y+1)+f(x,y−1)−4f(x,y)
同样提取前面的系数,那么二维的Laplacian滤波核就是:
k = [ 0 1 0 1 − 4 1 0 1 0 ] k=\left[\begin{array}{c c c}{0}&{1}&{0}\\ {1}&{-4}&{1}\\ {0}&{1}&{0}\end{array}\right] k= 0101−41010
这就是Laplacian算子的图像卷积模板,有些资料中在此底子上考虑斜对角情况,将卷积核拓展为:
k = [ 1 1 1 1 − 8 1 1 1 1 ] k=\left[\begin{array}{c c c}{1}&{1}&{1}\\ {1}&{-8}&{1}\\ {1}&{1}&{1}\end{array}\right] k= 1111−81111
代码格式如下:
cv2.Laplacian(src, ddepth,ksize)
参数说明:
[*] src:这是输入图像
[*] ddepth:这个参数代表输出图像的深度,即输出图像的数据类型。在 OpenCV 中,-1 表示输出图像的深度与输入图像雷同。
代码如下:
'''Laplacian算子'''
img=cv2.imread(r'../15day4.10/src/shudu.png')
img_gray=cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
img2=cv2.Laplacian(img_gray,-1,ksize=3)
cv2.imshow("img",img)
cv2.imshow('img2',img2)
cv2.waitKey(0)
https://i-blog.csdnimg.cn/direct/dd74ba6885d8473bb06c177198c468d0.png
2 图像边缘检测
Canny 边缘检测算法是一种非常流行的边缘检测算法,是 John F. Canny 于 1986年提出的,被认为是最优的边缘检测算法。
操作步骤按照如下举行:
[*]读取图像
[*]二值化图像。
[*]高斯滤波。
[*]计算图像的梯度和方向。
[*]非极大值抑制。
[*]双阈值筛选。
2.1. 高斯滤波
边缘检测本身属于锐化操作,对噪点比力敏感,所以须要举行平滑处理。这里使用的是一个5*5的高斯核对图像举行消除噪声。上一个实行中已经介绍了高斯滤波的具体过程,这里就不再过多叙述,只展示一下用到的5*5的高斯核:
k e r n e l = 1 256 [ 1 4 6 4 1 4 16 24 16 4 6 24 36 24 6 4 16 24 16 4 1 4 6 4 1 ] kernel=\frac{1}{256}\left[\begin{array}{c c c c c}{{1}}&{{4}}&{{6}}&{{4}}&{{1}}\\ {{4}}&{{16}}&{{24}}&{{16}}&{{4}}\\{{6}}&{{24}}&{{36}}&{{24}}&{{6}} \\{{4}}&{{16}}&{{24}}&{{16}}&{{4}}\\{{1}}&{{4}}&{{6}}&{{4}}&{{1}}\end{array}\right] kernel=2561 1464141624164624362464162416414641
2.2 计算图像的梯度与方向
这里使用了sobel算子来计算图像的梯度值,在上一章节中,我们了解到sobel算子其实就是一个核值固定的卷积核,如下所示:
s o b e l ( 水平方向 ) = [ − 1 0 1 − 2 0 2 − 1 0 1 ] sobel(水平方向)=\left[\begin{array}{c c c}{{-1}}&{{0}}&{{1}}\\ {{-2}}&{{0}}&{{2}}\\ {{-1}}&{{0}}&{{1}}\end{array}\right] sobel(水平方向)= −1−2−1000121
s o b e l ( 垂直方向 ) = [ − 1 − 2 − 1 0 0 0 1 2 1 ] sobel(垂直方向)=\left[\begin{array}{c c c}{{-1}}&{{-2}}&{{-1}}\\ {{0}}&{{0}}&{{0}}\\ {{1}}&{{2}}&{{1}}\end{array}\right] sobel(垂直方向)= −101−202−101
起首使用sobel算子计算中心像素点的两个方向上的梯度 G x G_{x} Gx和 G y G_{y} Gy,然后就能够得到其具体的梯度值:
G = G x 2 + G y 2 G={\sqrt{G_{x}{}^{2}+G_{y}{}^{2}}} G=Gx2+Gy2
也可以使用 G = ∣ G x ∣ + ∣ G y ∣ G=|G_{x}|+|G_{y}| G=∣Gx∣+∣Gy∣来取代。在OpenCV中,默认使用 G = ∣ G x ∣ + ∣ G y ∣ G=|G_{x}|+|G_{y}| G=∣Gx∣+∣Gy∣来计算梯度值。
然后我们根据如下公式可以得到一个角度值
θ = arctan ( G y G x ) \theta=\arctan\,({\frac{G_{\mathrm{y}}}{G_{x}}}) θ=arctan(GxGy)
这个角度值其实是当前边缘的梯度的方向。通过这个公式我们就可以计算出图片中全部的像素点的梯度值与梯度方向,然后根据梯度方向获取边缘的方向。
a). 并且如果梯度方向不是0°、45°、90°、135°这种特定角度,那么就要用到插值算法来计算当前像素点在其方向上举行插值的结果了,然后举行比力并判断是否保留该像素点。这里使用的是单线性插值,通过A1和A2两个像素点获得dTmp1与dTmp2处的插值,然后与中心点C举行比力(非极大值抑制)。具体的插值算法请参考图像旋转实行。
b). 得到 θ \theta θ的值之后,就可以对边缘方向举行分类,为了简化计算过程,一样平常将其归为四个方向:水平方向、垂直方向、45°方向、135°方向。并且:
当 θ \theta θ值为-22.5°~22.5°,或-157.5°~157.5°,则认为边缘为水平边缘;
当法线方向为22.5°~67.5°,或-112.5°~-157.5°,则认为边缘为45°边缘;
当法线方向为67.5°~112.5°,或-67.5°~-112.5°,则认为边缘为垂直边缘;
当法线方向为112.5°~157.5°,或-22.5°~-67.5°,则认为边缘为135°边缘;
https://i-blog.csdnimg.cn/direct/c09352532df542f98d4f1ac43c3d058a.png
2.3 非极大值抑制
得到每个边缘的方向之后,其实把它们连起来边缘检测就算完了,但是为什么还有这一步与下一步呢?是因为经过第二步得到的边缘不经过处理是没办法使用的,因为高斯滤波的缘故原由,边缘会变得模糊,导致经过第二步后得到的边缘像素点非常多,因此我们须要对其举行一些过滤操作,而非极大值抑制就是一个很好的方法,它会对得到的边缘像素举行一个清除,使边缘尽可能细一点。
在该步骤中
[*]我们须要查抄每个像素点的梯度方向上的相邻像素,并保留梯度值最大的像素,将其他像素抑制为零。
[*]假设当前像素点为(x,y),其梯度方向是0°,梯度值为G(x,y),那么我们就须要比力G(x,y)与两个相邻像素的梯度值:G(x-1,y)和G(x+1,y)。如果G(x,y)是三个值里面最大的,就保留该像素值,否则将其抑制为零。
总结:
非极大值抑制的目的是在已经计算出图像梯度幅度图像的底子上,进一步细化边缘位置,淘汰假响应并确保边缘轮廓的一致性和单像素宽度。
工作原理:
[*]在Canny算法中,起首通过高斯滤波和平滑图像,然后计算每个像素点的梯度幅值和方向。
[*]对于每一个像素点,假设已知其梯度幅值**(通常通过Sobel或其他导数算子计算得到)以及梯度的方向(通常是精确到某个离散的角度集合)。**
[*]非极大值抑制会沿着梯度方向查抄像素点的梯度幅值是否是其邻域内(包括梯度方向指向的临近像素点)的最大值。
[*]如果像素点的梯度幅值不是其梯度方向上局部极大值,则认为这个点不是边缘点,并将其梯度幅值置零或者忽略它。
[*]这样做可以去除那些位于边缘两侧但由于噪声或者其他缘故原由导致的不正确的梯度响应,从而保证终极得到的边缘只出现在梯度方向的极大值处,形成一连的、单像素宽的边缘。
2.4. 双阈值筛选
经过非极大值抑制之后,我们还须要设置阈值来举行筛选,当阈值设的太低,就会出现假边缘,而阈值设的太高,一些较弱的边缘就会被丢掉,因此使用了双阈值来举行筛选,保举高低阈值的比例为2:1到3:1之间,其原理如下图所示:
https://i-blog.csdnimg.cn/direct/51de130cb04942c3b6956b9bde5e0713.png
当某一像素位置的幅值超过最高阈值时,该像素必是边缘像素;当幅值低于最低像素时,该像素必不是边缘像素; 幅值处于最高像素与最低像素之间时,如果它能毗连到一个高于阈值的边缘时,则被认为是边缘像素,否则就不会被认为是边缘。也就是说,上图中的A和C是边缘,B不是边缘。因为C虽然不超过最高阈值,但其与A相连,所以C就是边缘。
总结:
双阈值检测的目的是基于非极大值抑制后的梯度幅值图像,通过设定高低两个阈值来区分强边和弱边,并有效毗连这些边缘点构成完备的边缘线。
工作原理:
[*]设定两个阈值,一样平常称为高阈值(highThreshold)和低阈值(lowThreshold)。
[*]如果一个像素点的梯度幅值大于即是高阈值,则标志为强边缘像素;
[*]若梯度幅值介于高阈值和低阈值之间,则标志为潜伏边缘像素;
[*]若梯度幅值低于低阈值,则认为黑白边缘像素,不予考虑。
[*]接下来,接纳某种形式的连通性分析,例如Hysteresis(滞后效应),只有当一个弱边缘像素与一个强边缘像素相邻时,才保留这个弱边缘像素作为终极的边缘点。否则,弱边缘像素会被抛弃。
2.5.API和使用
edges = cv2.Canny(image, threshold1, threshold2)
[*]image:输入的灰度/二值化图像数据。
[*]threshold1:低阈值,用于决定可能的边缘点。
[*]threshold2:高阈值,用于决定强边缘点。
代码如下:
'''边缘检测canny的api'''
img=cv2.imread(r'../15day4.10/src/face.png')
img_gray=cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
img2=cv2.Canny(img_gray,50,100)
cv2.imshow("img",img)
cv2.imshow('img_gray',img_gray)
cv2.imshow('img2',img2)
cv2.waitKey(0)
https://i-blog.csdnimg.cn/direct/6fcf9e92e79f4ca1bcb87006e3b7be27.png
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页:
[1]