opencv-python 边缘提取

发布时间 2023-07-31 23:01:17作者: 寒水浮云

边缘时像素值发生跃迁的位置,是图像的显著特征之一。在图像特征提取,对象检测,模式识别等方面有重要作用。

1 sobel(索贝尔)算子

sobel算子对图像求一阶导数。一阶导数越大,说明像素在该方向的变化越大,边缘信号越强。因为图像的灰度值都是离散的数字,sobel算子采用 离散差分算子 计算图像像素值的近似梯度。

图像是二维的,即沿着宽度/高度两个方向。可以使用两个卷积核(水平,垂直)进行处理 。

这样处理后,可以得到两个新的矩阵,分别反映了每一点像素在水平方向上和垂直方向上的亮度变化情况。综合这俩个方向的变化,可以用G = |G(x)| + |G(y)|来表示图像的边缘。

总的来说,sobel算子,其会分别计算x,y的梯度。(梯度算子)x方向计算边缘:左列像素-右列像素;y方向计算边缘:下行像素-上行像素。

import cv2
import numpy as np 

img = cv2.imread('./pie.png')

#dx = cv2.Sobel(img,-1,dx=1,dy=0,ksize=3)  #计算x轴方向梯度,消除水平方向线条,获得的是垂直方向的边缘(参数:img,返回图像结果位深,。。。)
                                        #位深为-1表示与原图相同,但是如果输出是负数会被直接截断成0.可以用cv2.CV_64F,保留负数,然后取绝对值,否则梯度为负数的边缘就没有了。
#dy = cv2.Sobel(img,-1,dx=0,dy=1,ksize=3)  #计算y轴方向梯度,消除垂直方向线条,获得的是水平方向的边缘

dx = cv2.Sobel(img,cv2.CV_64F,dx=1,dy=0,ksize=3)
dx = cv2.convertScaleAbs(dx)            #对计算出的梯度取绝对值,把梯度为负数的边缘保留下来 convert 转换 scale 缩放
dy = cv2.Sobel(img,cv2.CV_64F,dx=0,dy=1,ksize=3)
dy = cv2.convertScaleAbs(dy)

dst = cv2.add(dx,dy)  #sobel算子要把x,y方向结果合并在一起

#dst =cv2.cvtColor(dst,cv2.COLOR_BGR2GRAY)

cv2.imshow('img',img)
cv2.imshow('img_dx',dx)
cv2.imshow('img_dy',dy)
cv2.imshow('dst',dst)

cv2.waitKey(0)
cv2.destroyAllWindows()

2 scharr(沙尔)算子

沙尔算子是 sobel算子在3*3卷积核的升级,scharr算子只支持3*3的kernel,所以没有ksize参数;scharr只能求x方向或y方向的边缘;sobel算子的ksize设为-1就是scharr算子,scharr擅长寻找细小边缘。

import cv2
import numpy as np

img = cv2.imread('./lena.jpg')

# dx =cv2.Scharr(img,-1,dx=1,dy=0)
# dy =cv2.Scharr(img,-1,dx=0,dy=1)

dx = cv2.Scharr(img,cv2.CV_64F,dx=1,dy=0)
dx = cv2.convertScaleAbs(dx)            #对计算出的梯度取绝对值,把梯度为负数的边缘保留下来
dy = cv2.Scharr(img,cv2.CV_64F,dx=0,dy=1)
dy = cv2.convertScaleAbs(dy)

dst = cv2.add(dx,dy)  #把x,y方向结果合并在一起

cv2.imshow('img',img)
cv2.imshow('img_dx',dx)
cv2.imshow('img_dy',dy)
cv2.imshow('dst',dst)

cv2.waitKey(0)
cv2.destroyAllWindows()

3 Laplacian(拉普拉斯)算子

边缘处的二阶导数=0,可以利用这一特性去寻找图像的边缘,但是二阶导的位置也可能是无意义的位置,比如噪声(可以先去噪,再用拉普拉斯提取边缘)。

拉普拉斯算子推导过程:

可以同时对x方向和y方向进行计算,对噪声敏感,需要先降噪。 卷积核:[[0,1,0],[1,-4,1],[0,1,0]]   

opencv的拉普拉斯函数:Laplacian(scr,ddepth,dst[,ksize[,scale[,delta[,borderType[),用的时候很简单,如下:

import cv2

img = cv2.imread('./lena.jpg')
img = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)  #提取边缘跟图像颜色关系不大,可以先转换为灰度图节约时间
#img = cv2.imread('./pie.png')

dst = cv2.Laplacian(img,-1,ksize=3)


cv2.imshow('img',img)
cv2.imshow('dst',dst)

cv2.waitKey(0)
cv2.destroyAllWindows()

4 canny边缘检测

边缘检测最优的算法,边缘检测的三个评价标准:1 低错误率(标识出尽可能多的边缘,同时减少噪声的误判);2 高定位性(标识出的边缘要与原图的实际边缘尽可能接近);3 最小响应(图像中的边缘只能标识一次)。

canny检测的步骤:

1 去噪,一般用高斯滤波去噪。

2 计算梯度,对平滑后的图像采用sobel算子计算梯度,梯度方向分为水平,垂直和对角线四个)。

3 非极大值抑制(在获取了梯度和方向后,遍历图像,去除所有不是边界的点。实现方法:逐个遍历像素点,判断当前像素点是否是周围像素点中具有相同方向的梯度最大值)

比较形象的例子:

4 滞后阈值 (梯度值> maxVal 是边界保留;梯度值< minVal 不是边界 去掉;minVal < 梯度值 < maxVal 如果与边界相连(空间上连通),保留,不相连抛弃)

import cv2

img = cv2.imread('./lena.jpg')

dst1 = cv2.Canny(img,100,200)  #Canny(img,最小阈值,最大阈值) 
dst2 = cv2.Canny(img,50,120)   #小一点的阈值可以得到更精细的边缘,但是也会带入一些不想要的边缘
dst3 = cv2.Canny(img,100,300)

cv2.imshow('img',img)
cv2.imshow('dst1',dst1)
cv2.imshow('dst2',dst2)
cv2.imshow('dst3',dst3)
cv2.waitKey(0)
cv2.destroyAllWindows()