图像的梯度就是描述图像中灰度的变化,微积分就是求函数的变化率,即导数(梯度)。
图像的梯度相当于2个相邻像素之间的差值。图像梯度可以把图像看成二维离散函数,图像梯度其实就是这个二维离散函数的求导:在x方向,选取某个像素,假设其像素值是100,沿x方向的相邻像素分别是90,90,90,则根据上面的计算其x方向梯度分别是10,0,0。这里只取变化率的绝对值,表明变化的大小即可。
灰度值100和90之间亮度相差10,并不是很明显,与一大群90的连续灰度值在一起,轮廓必然是模糊的。我们注意到,如果相邻像素灰度值有变化,那么梯度就有值,如果相邻像素灰度值没有变化,那么梯度就为0。如果我们把梯度值与对应的像素相加,那么灰度值没有变化的,像素值不变,而有梯度值的,灰度值变大了,那么图像的边缘更加明显。
2.2 OpenCV中的图像梯度应用(检测边缘):OpenCV提供三种类型的梯度滤波器或高通滤波器,Sobel,Scharr和Laplacian. 高通滤波器(英语:High-pass filter)是容许高频信号通过、但减弱(或减少)频率低于截止频率信号通过的滤波器。对于不同滤波器而言,每个频率的信号的减弱程度不同。它有时被称为低频剪切滤波器;Sober 基本数学原理:
Sober 算子是离散微分算子(discrete differentiation operator),用来计算图像灰度的近似梯度。
cv2.Sobel(src, ddepth, dx, dy, dst=None, ksize=None, scale=None, delta=None, borderType=None)
src.depth() 为原图像的深度,则一般对应的ddepth为:
sec.depth() | ddepth |
---|---|
CV_8U | -1/CV_16S/CV_32F/CV_64F |
CV_16U/CV_16S | -1/CV_32F/CV_64F |
CV_32F | -1/CV_32F/CV_64F |
CV_64F | -1/CV_64F |
import cv2
img = cv2.imread("first.jpg")
GRAYImg = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
SoberImg = cv2.Sobel(GRAYImg,-1,0,1)
cv2.imshow("img",img)
cv2.imshow("GRAYimg",GRAYImg)
cv2.imshow("Sober",SoberImg)
cv2.waitKey()
cv2.destroyAllWindows()
Sobel算子算法的优点是计算简单,速度快。但是由于只采用了2个方向的模板,只能检测水平和垂直方向的边缘,因此这种算法对于纹理较为复杂的图像,其边缘检测效果就不是很理想。该算法认为:凡灰度新值大于或等于阈值的像素点时都是边缘点。这种判断有点欠合理,可能会造成边缘点的误判,因为许多噪声点的灰度值也很大。
cv2.Scharr(src, ddepth, dx, dy, dst=None, scale=None, delta=None, borderType=None)
大小一样,故计算量一样。scharr算子临近像素的权重更大,故精确度更高。对比两种算子的处理效果。发现scharr算子能计算出更小的梯度变化 Scharr:
import cv2
img = cv2.imread("first.jpg")
GRAYImg = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
Scharr = cv2.Scharr(GRAYImg,-1,0,1)
cv2.imshow("img",img)
cv2.imshow("GRAYimg",GRAYImg)
cv2.imshow("Scharr",Scharr)
cv2.waitKey()
cv2.destroyAllWindows()
Laplacian算子属于二阶微分算子。 二阶微分的边缘定位能力更强,锐化效果更好
算子进行边缘检测并没有像的平滑过程,所以它会对噪声产生较大的响应,并且无法分别得到水平方向、垂直方向或者其他固定方向的的边缘。但是它只有一个卷积核,所以计算成本会更低。
从模板形式容易看出,如果在图像中一个较暗的区域中出现了一个亮点,那么用拉普拉斯运算就会使这个亮点变得更亮。因为图像中的边缘就是那些灰度发生跳变的区域,所以拉普拉斯锐化模板在边缘检测中很有用。一般增强技术对于陡峭的边缘和缓慢变化的边缘很难确定其边缘线的位置。但此算子却可用二次微分正峰和负峰之间的过零点来确定,对孤立点或端点更为敏感,因此特别适用于以突出图像中的孤立点、孤立线或线端点为目的的场合。同梯度算子一样,拉普拉斯算子(Laplacian)也会增强图像中的噪声,有时用拉普拉斯算子进行边缘检测时,可将图像先进行平滑处理。图像锐化处理的作用是使灰度反差增强,从而使模糊图像变得更加清晰。图像模糊的实质就是图像受到平均运算或积分运算 。因此可以对图像进行逆运算,如微分运算能够突出图像细节,使图像变得更为清晰。由于拉普拉斯是一种微分算子,它的应用可增强图像中灰度突变的区域,减弱灰度的缓慢变化区域。
cv2.Laplacian(src, ddepth, dst=None, ksize=None, scale=None, delta=None, borderType=None)
深度 | 取值范围 |
---|---|
CV_8U | 0–255 |
CV_8S | -128–127 |
CV_16U | 0–65535 |
CV_16S | -32768–32767 |
CV_32S | 0–65535 |
CV_32F(单精度浮点数) | 0.0–1.0 |
CV_64F(双精度浮点数 ) | 0.0–1.0 |
src.depth() | ddepth |
---|---|
CV_8U | -1/CV_16S/CV_32F/CV_64F |
CV_16S | -1/CV_32F/CV_64F |
CV_32F | -1/CV_32F/CV_64F |
CV_64F | -1/CV_64F |
转换深度后的图像可能需要归一化;
import cv2
img = cv2.imread("first.jpg",0) #灰度模式读取图像
LaplacianImg = cv2.Laplacian(img,-1)
cv2.imshow("img",img)
cv2.imshow("Laplacian",LaplacianImg)
cv2.waitKey()
cv2.destroyAllWindows()
2.3   边缘检测:
边缘检测是图像处理和计算机视觉中的基本问题,边缘检测的目的是标识数字图像中亮度变化明显的点。图像属性中的显著变化通常反映了属性的重要事件和变化。这些包括(i)深度上的不连续、(ii)表面方向不连续、(iii)物质属性变化和(iv)场景照明变化。边缘检测是图像处理和计算机视觉中,尤其是特征提取中的一个研究领域。图像边缘检测大幅度地减少了数据量,并且剔除了可以认为不相关的信息,保留了图像重要的结构属性。 有许多方法用于边缘检测,它们的绝大部分可以划分为两类:基于查找一类和基于零穿越的一类。基于查找的方法通过寻找图像一阶导数中的最大和最小值来检测边界,通常是将边界定位在梯度最大的方向。基于零穿越的方法通过寻找图像二阶导数零穿越来寻找边界,通常是Laplacian过零点或者非线性差分表示的过零点。边缘检测的一般步骤:
Canny 边缘检测 :
原理:
去噪:先经过高斯平滑
增强:寻找图像强度梯度
非极大抑制:这一步的目的是将模糊(blurred)的边界变得清晰(sharp)。通俗的讲,就是保留了每个像素点上梯度强度的极大值,而删掉其他的值。对于每个像素点,进行如下操作:将其梯度方向近似为以下值中的一个(0,45,90,135,180,225,270,315)(即上下左右和45度方向)
比较该像素点,和其梯度方向正负方向的像素点的梯度强度 如果该像素点梯度强度最大则保留,否则抑制(删除,即置为0)
经过非极大抑制后图像中仍然有很多噪声点。Canny算法中应用了一种叫双阈值的技术。即设定一个阈值上界和阈值下界(opencv中通常由人为指定的),图像中的像素点如果大于阈值上界则认为必然是边界(称为强边界,strong edge),小于阈值下界则认为必然不是边界,两者之间的则认为是候选项(称为弱边界,weak edge)。
思想:和强边界相连的弱边界认为是边界,其他的弱边界则被抑制
滞后阈值:现在需要确定哪些边界是真正的边界,需要两个阈值,minVal和maxVal。图像灰度梯度 高于maxVal被认为是真正的边界,低于minVal的舍弃。两者之间的值要判断是否与真正的边界相连,相连就保留,不相连舍弃。
Ⅰ.如果某一像素位置的幅值超过 高 阈值, 该像素被保留为边缘像素。
Ⅱ.如果某一像素位置的幅值小于 低 阈值, 该像素被排除。
Ⅲ.如果某一像素位置的幅值在两个阈值之间,该像素仅仅在连接到一个高于 高 阈值的像素时被保留。
cv2.Canny(image, threshold1, threshold2, edges=None, apertureSize=None, L2gradient=None)
import cv2
img = cv2.imread("first.jpg")
GRAYImg = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY) #转换为灰度图像
GauBurlGrayImg = cv2.GaussianBlur(GRAYImg,(3,3),1) #进行高斯滤波
imgCanny = cv2.Canny(GauBurlGrayImg,100,200) #边缘检测
cv2.imshow("img",img)
cv2.imshow("Canny",imgCanny)
cv2.waitKey()
cv2.destroyAllWindows()
扩展结束
项目代码
import numpy as np
import cv2
import matplotlib.pyplot as plt
import matplotlib.image as mpimg
import pickle
#读入图片
image = mpimg.imread('work/test_picture/0000000086.png')
#定义Sobel函数,因为做边缘检测时只考虑梯度的大小,所以对返回结果做绝对值处理
def sobel(img, orient = 'x', sobel_kernel=3, sobel_thresh = (30, 100)):
#转换为单通道灰度图,像素值做归一化
gray = cv2.cvtColor(img, cv2.COLOR_RGBA2GRAY)
#按方向进行梯度计算
if orient == 'x':
sobel = cv2.Sobel(gray,cv2.CV_64F, 1, 0,ksize=sobel_kernel)
if orient == 'y':
sobel = cv2.Sobel(gray,cv2.CV_64F, 0, 1,ksize=sobel_kernel)
abs_sobel = np.absolute(sobel)
#转换到 8-bit (0 - 255) 并转换为type = np.uint8
scaled_sobel = (abs_sobel/(np.max(abs_sobel)/255)).astype(np.uint8)
#根据梯度阈值,创建一个二进制掩膜
binary_output = np.zeros_like(scaled_sobel)
#将原图中高于阈值的像素点在掩膜中的对应位置置为1
binary_output[(scaled_sobel>=sobel_thresh[0]) & (scaled_sobel<=sobel_thresh[1])]=1
return binary_output
#定义像素点梯度总和函数
def mag_thresh(img, sobel_kernel=3, mag_thresh=(30, 100)):
#转换为单通道灰度图
gray = cv2.cvtColor(img, cv2.COLOR_RGBA2GRAY)
#分别计算x,y方向梯度
sobelx = cv2.Sobel(gray,cv2.CV_64F, 1, 0,ksize=sobel_kernel)
sobely = cv2.Sobel(gray,cv2.CV_64F, 0, 1,ksize=sobel_kernel)
#计算梯度和
gradmag = np.sqrt(np.absolute(sobelx) ** 2 + np.absolute(sobely) ** 2)
#计算梯度角
theta = np.arctan2(np.absolute(sobely),np.absolute((sobelx)))
#转换到 8-bit (0 - 255) 并转换为type = np.uint8
scaled_gradmag = (gradmag/(np.max(gradmag)/255)).astype(np.uint8)
#根据梯度阈值,创建一个二进制掩膜
binary_output = np.zeros_like(scaled_gradmag)
#将原图中高于阈值的像素点在掩膜中的对应位置置为1
binary_output[(scaled_gradmag>=mag_thresh[0]) & (scaled_gradmag<=mag_thresh[1])]=1
return binary_output
#对比输出
Sx_binary = sobel(img=image,orient='x',sobel_kernel=3, sobel_thresh = (30, 100))
Sx_binary_wider_thresh = sobel(img=image,orient='x',sobel_kernel=3, sobel_thresh = (30, 100))
Sx_binary_Larger_kernel = sobel(img=image,orient='x',sobel_kernel=9, sobel_thresh = (30, 100))
Sy_binary = sobel(img=image,orient='y',sobel_kernel=3, sobel_thresh = (150, 200))
Sy_binary_wider_thresh = sobel(img=image,orient='y',sobel_kernel=3, sobel_thresh = (15, 200))
mag_binary = mag_thresh(img=image, sobel_kernel=3, mag_thresh=(30, 100))
mag_binary_larger_kernel = mag_thresh(img=image, sobel_kernel=9, mag_thresh=(30, 100))
mag_binary_wider_thresh = mag_thresh(img=image, sobel_kernel=3, mag_thresh=(15, 200))
#可视化结果
f, (ax1, ax2, ax3) = plt.subplots(3, 3, figsize=(20, 10))
f.tight_layout()
ax1[0].imshow(image)
ax1[0].set_title('Original Image', fontsize=30)
ax2[0].imshow(mag_binary, cmap='gray')
ax2[0].set_title('Thresholded Magnitude', fontsize=30)
ax1[1].imshow(Sx_binary, cmap='gray')
ax1[1].set_title('Sobelx', fontsize=30)
ax3[0].imshow(Sx_binary_wider_thresh, cmap='gray')
ax3[0].set_title('Sx_wider_thresh', fontsize=30)
ax3[2].imshow(Sx_binary_Larger_kernel, cmap='gray')
ax3[2].set_title('Sx_Larger_kernel', fontsize=30)
ax1[2].imshow(Sy_binary, cmap='gray')
ax1[2].set_title('Sobely', fontsize=30)
ax3[1].imshow(Sy_binary_wider_thresh, cmap='gray')
ax3[1].set_title('Sy_wider_thresh', fontsize=30)
ax2[1].imshow(mag_binary_larger_kernel, cmap='gray')
ax2[1].set_title('mag_Larger_kernel', fontsize=30)
ax2[2].imshow(mag_binary_wider_thresh, cmap='gray')
ax2[2].set_title('mag_wider_thresh', fontsize=30)
plt.subplots_adjust(left=0., right=1., top=0.9, bottom=0.,wspace=0.2,hspace=0.1)
plt.show()
````
由以上图像处理结果发现,Sx的边缘检测效果比Sy要更明显,y方向需要更宽的threshold范围才能显示的比较明显;x,y方向的梯度和可以更清晰的检测出图像中的所有轮廓,所以可以根据实际应用的不同来决定使用x,y或者梯度和的方式进行边缘检测;调节参数kernel_size和梯度threshold,可发现更宽的threshold会检测出更多的目标点,同时也包含了较多噪点,而kernel_size加大会使图片保留更大尺度的边缘,降低对小尺度边缘的敏感性,一定程度抑制了噪点
part2 end...
作者:joker-wt
来AI Studio互粉吧~等你哦~( https://aistudio.baidu.com/aistudio/personalcenter/thirdview/276642)
编辑不易,欢迎三连!!!❤❤❤