图像的边缘信息通俗来讲变化较大。基于此特征和数字图像的离散信号,我们可以计算图片的差分或梯度。
图像处理中有多种边缘检测的算电子,包括普通一阶差分,Sobel算子,Scharr算子等等,是基于寻找梯度强度。而普通二阶差分中,Laplacian算子其思想是基于过零点检测。
对每一个像素点px,用有右减左侧的特征值。因为对于边缘特征来说,px的值较大。那么值越大的说明其越有可能是边缘特征。
如下图所示,表示计算图像的水平特征。右侧减去左侧数值。
from cv2 import cv2 as cv
import numpy as np
img = cv.imread("2021-12-01--image/2021-12-01--image/sobel.bmp",0)
cv.imshow("img",img)
sobel_x = cv.Sobel(img,-1,1,0) #-1处理结果代表与原图一致,1代表x,0代表y
cv.imshow("sobel_x",sobel_x)
cv.waitKey(0)
cv.destroyAllWindows()
结果如下,可以看到图像边缘x轴方向,采集只有右侧,因为sobel算法是,右侧减去左侧。那么左侧的点就被算为负值,直接被赋值为0,所以要对处理结果,取绝对值。让负值变为正值。
那么修改代码为:
from cv2 import cv2 as cv
import numpy as np
img = cv.imread("2021-12-01--image/2021-12-01--image/sobel.bmp",0)
cv.imshow("img",img)
sobel_x = cv.Sobel(img,-1,1,0)
sobel_x = cv.convertScaleAbs(sobel_x)
cv.imshow("sobel_x",sobel_x)
cv.waitKey(0)
cv.destroyAllWindows()
代码运行结果如下:
可以看到还是如此,为什么呢?因为Sobel函数默认采用np.uint8数据类型,也就是说没有负值。都自动取0了。所以我们一般不用-1,而用cv.CV_64F
from cv2 import cv2 as cv
import numpy as np
img = cv.imread("2021-12-01--image/2021-12-01--image/sobel.bmp",0)
cv.imshow("img",img)
sobel_x = cv.Sobel(img,cv.CV_64F,0,1)
sobel_x = cv.convertScaleAbs(sobel_x)
sobel_y = cv.Sobel(img,cv.CV_64F,1,0)
sobel_y = cv.convertScaleAbs(sobel_y)
sobel_add_xy = cv.addWeighted(sobel_x,0.5,sobel_y,0.5,0)
cv.imshow("sobel_xy",sobel_xy)
cv.waitKey(0)
cv.destroyAllWindows()
Scharr算子和Sobel算子类似,不同的是,Scharr算子,同一列或同一行的数值差异更大。
from cv2 import cv2 as cv
import numpy as np
img = cv.imread("CV-Pictures/010.jpg",0)
cv.imshow("img",img)
Scharr_x = cv.Scharr(img,cv.CV_64F,0,1)
Scharr_x = cv.convertScaleAbs(Scharr_x)
Scharr_y = cv.Scharr(img,cv.CV_64F,1,0)
Scharr_y = cv.convertScaleAbs(Scharr_y)
Scharr_add_xy = cv.addWeighted(Scharr_x,0.5,Scharr_y,0.5,0)
cv.imshow("Scharr_x",Scharr_x)
cv.imshow("Scharr_y",Scharr_y)
cv.imshow("Scharr_xy",Scharr_add_xy)
cv.waitKey(0)
cv.destroyAllWindows()
可以对比一下Sobel检测,如下如所示
我们可以看到,简单的算子是要分别计算x方向和y方向,为了防止数值溢出,采用加权和。而拉普拉斯算子实现了同时计算两个方向的算子。
from cv2 import cv2 as cv
import numpy as np
img = cv.imread("CV-Pictures/015.jpg",0)
laplacian_img = cv.Laplacian(img,cv.CV_64F)
laplacian_img = cv.convertScaleAbs(laplacian_img)
cv.imshow("img",img)
cv.imshow("laplacian",laplacian_img)
cv.waitKey(0)
cv.destroyAllWindows()
这个方法是传统的边缘检测方法中最为有效,效果最好的方法。其主要包括四个步骤,分别为:
边缘检测已收到噪声的影响。因此,在进行边缘检测前,通常需要先进行去噪。去噪的方法一般是采用高斯滤波器去噪
且一个像素点的临近像素具有更高的重要度,对周围的像素计算加权平均值。其中的邻近的像素具有更大的权重
对平滑后的图像采用Sobal算子计算梯度和方向。
其中梯度的方向一般总是与边界垂直。梯度的方向分为四类,垂直、水平、两个对角线方向。
获得梯度的大小和方向后,逐个遍历像素点,判断当前像素点是否是周围像素点中具有相同梯度的最大值。
假设A,B,C三点具有相同的方向,梯度方向垂直于边缘。判断是否极大值,是的话保留,不是就抑制。
我们提供一个最小阈值和最大阈值,也就是说所有检测出来的边缘值,都要存在于我的阈值之间,在阈值范围内舍弃,超出阈值的舍弃。只保留边界在阈值的边界之间的检测线。
from cv2 import cv2 as cv
import numpy as np
img = cv.imread("CV-Pictures/lena2.jpg")
cv.imshow("img",img)
dst1 = cv.Canny(img,100,200)
dst2 = cv.Canny(img,64,128)
cv.imshow("canny",dst1)
cv.imshow("canny1",dst2)
cv.waitKey(0)
cv.destroyAllWindows()
代码运行结果如下:
特征检测检测出的主要是图像的轮廓。那么轮廓相比于不连续的边缘是一个整体。
图像的轮廓处理要注意以下问题:
在OpenCV中,我们使用findContours()和drawContours()分别来查找图像的轮廓与绘制图像的轮廓。
from cv2 import cv2 as cv
import numpy as np
img = cv.imread("2021-12-01--image/2021-12-01--image/contours.bmp")
gray = cv.cvtColor(img,cv.COLOR_BGR2GRAY)
#img = cv.imread("2021-12-01--image/2021-12-01--image/contours.bmp",0) #直接获取灰度图像
ret,binary = cv.threshold(gray,127,255,cv.THRESH_BINARY) #获得二值图
counters,hierarchy = cv.findContours(binary,cv.RETR_TREE,cv.CHAIN_APPROX_SIMPLE) #参数分别为图像轮廓检测模式,图像的轮廓近似方法。
co = img.copy()
r = cv.drawContours(co,counters,3,(0,0,255),3) #counters 表示需要绘制的边缘数组,(-1代表全部,0开始依次从外到内,从右到左),颜色,线条宽度
print(img.shape)
cv.imshow("img",img)
cv.imshow("draw",r)
cv.waitKey(0)
cv.destroyAllWindows()
小程序员将代码文件和相关素材整理到了百度网盘里,因为文件大小基本不大,大家也不用担心限速问题。后期小程序员有能力的话,将在gitee或者github上上传相关素材。
链接:https://pan.baidu.com/s/1Ce14ZQYEYWJxhpNEP1ERhg?pwd=7mvf
提取码:7mvf