机器视觉学习-day12-图像梯度处理及图像边缘检测
dx和dy是x和y的微分,趋向于无穷小,这是在连续的函数中求导的过程,可以认为这就是函数某一个点的梯度。上面的连续的函数,但是实际中图像是离散的。如果图像也能微分,可以理解为图像的像素点无穷小,图像的分辨率无穷大。
理想情况下图像的数据组成也应该可以进行微分的,因此存在x轴和y轴的梯度,需要同时考虑两个方向。
图像在计算机中是以离散的像素点分布的,因此使用的差分。
代码运行步骤:图片输入→梯度处理方式→图片输入
1.1 边缘计算 Filter2D
梯度计算的目的是为了寻找图像的边缘,因此把寻找图像边缘的计算称为边缘计算,梯度计算也是通过卷积核实现的。下面的是一个常用于边缘计算的卷积核:
上述数学式是由此化简得到的:1*30+(-1)*10+2*31+(-2)*11+1-32+(-1)*12
同理,改变卷积核形态,可以把图像的水平边缘提取,进行垂直梯度的计算:
import cv2
import numpy as npif __name__ == '__main__':# 1.图片输入:image_np = cv2.imread('1.jpg')# 2.梯度计算方式# 构建卷积核kernel = np.array([[-1, -2, -1],[0, 0, 0],[1, 2, 1]],dtype=np.float32)"""创建一个3×3的卷积核(滤波器)这是一个垂直方向的Sobel算子,用于检测图像中的垂直边缘dtype=np.float32指定数据类型为32位浮点数,避免计算时溢出""""""检测图像中的水平边缘# 构建卷积核kernel = np.array([[-1, -2, -1],[0, 0, 0],[1, 2, 1]],dtype=np.float32)kernel_transposed = kernel.T(矩阵转置为水平卷积核)"""print(kernel) # 打印卷积核的值,便于调试和验证dst_image = cv2.filter2D(image_np, # 原图-1, # 输出图像深度(-1表示与输入相同)kernel # 卷积核)"""使用filter2D函数对图像进行卷积操作卷积过程:将核在图像上滑动,计算每个位置的加权和这个过程会增强图像中的垂直边缘特征"""cv2.imshow('dst_image', dst_image)cv2.waitKey(0)cv2.imwrite('Filter2D.png', dst_image)
运行结果:Filter2D.png
1.2 Sobel算子
Sobel算子是一种特殊的Filter2D,也是最常用的一种边缘检测算法,使用特定的卷积核进行计算。使用k1,对原始图像src进行卷积操作,得到水平方向的梯度图Gx
使用k2,对原始图像src进行卷积操作,得到垂直方向的梯度图Gy
支持适应Gx和Gy求出总梯度,这种用法比较严格,需要同时具有水平和垂直梯度的像素才会显示比较明显。
k1和k2必须使用上述的参数,否则就不是Sobel算子。除了Sober算子外,也可以更改卷积核的权重比例形成其他的算子:
原始图像同上:1.jpg
import cv2if __name__ == '__main__':# 1. 图片输入image_np = cv2.imread('1.jpg')# 2. 梯度计算方式# Sobel算子必须基于灰度图image_np_gray = cv2.cvtColor(image_np, cv2.COLOR_BGR2GRAY)# Sobel变换dst_image = cv2.Sobel(image_np_gray, # 灰度图-1, # 位深度1, # dx1, # dyksize=5)# 3. 图片输出cv2.imshow('dst_image', dst_image)cv2.waitKey(0)cv2.imwrite('Sobel.png', dst_image)
运行后的图片效果:Sobel.png
【思考】上面的代码有什么问题?
存在梯度计算范围比无符号8位更广泛的问题,优化代码如下:
import cv2
import numpy as npif __name__ == '__main__':# 1. 图片输入image_np = cv2.imread('shudu.png')# 2. 梯度计算方式# Sobel算子必须基于灰度图image_np_gray = cv2.cvtColor(image_np, cv2.COLOR_BGR2GRAY)# Sobel变换dst_image = cv2.Sobel(image_np_gray, # 灰度图cv2.CV_16S, # 有符号16位深度,以便于存储梯度计算的范围1, # dx1, # dyksize=5)print(dst_image)# 给梯度取绝对值,绝对值本身可以反应梯度的变换率dst_image = np.abs(dst_image)print(dst_image)# 找到梯度(灰度)的最大值max_gray = np.max(dst_image)print(max_gray) # 1136# 归一化dst_image = (dst_image / max_gray) * 255print(dst_image)print(dst_image.dtype) # 还是小数# 转换为8位无符号整数dst_image = dst_image.astype(np.uint8)# 3. 图片输出cv2.imshow('dst_image', dst_image)cv2.waitKey(0)
1.3 Laplacian(拉普拉斯)算子
上面的算子都是一阶导数求极值,在这些极值的地方,二阶导数为0,因此梯度计算可以通过二阶导数。
原图像同上:1.jpg
import cv2if __name__ == '__main__':# 1. 图片输入image_np = cv2.imread('1.jpg')# 2. 梯度计算方式# 拉普拉斯dst_image = cv2.Laplacian(image_np, # 原图-1 # 位深度)# 3. 图片输出cv2.imshow('dst_image', dst_image)cv2.waitKey(0)cv2.imwrite('Laplacin.png',dst_image)
运行效果后图片:Laplacin.png
上面仅仅进行梯度计算,如果要进行边缘检测需要继续优化流程。
2 图像边缘检测
代码实验步骤:图片输入→灰度化→二值化→高斯滤波→计算梯度与方向→非极大值抑制→双阈值筛选→图片输出
本实验使用的算法为Canny算法,此算法进行边缘检测被誉为最优方法。
Canny算法的输入图像应该为二值化图像,包括以下步骤:
1. 高斯滤波
2. 计算图像的梯度和方向(Sobel)
3. 非极大值抑制
4. 双阈值筛选
2.1 高斯滤波
边缘检测属于对噪点比较敏感的算法,因此需要降噪,对图像进行平滑处理,这里直接使用一个5*5的高斯核对图像降噪:
经过高斯滤波后,图会变得更加模糊,边缘会变粗。
2.2 计算图像梯度与方向
内部使用的是Sobel算子来计算图像的梯度值,分为水平和垂直方向的核: