目录
一、图像梯度处理
还记得高数中的一阶导数求极值吗?把图片想象成连续函数,因为边缘部分的像素值是与旁边像素明显有区别的,所以对图片局部求极值,就可以得到整幅图片的边缘信息了。不过图片是二维的离散函数,导数就变成了差分,这个差分就称为图像的梯度
1、垂直边缘提取
滤波是应用卷积来实现的,卷积的关键就是卷积核
这个核是用来提取图片中的垂直边缘的,当前列左右两侧的元素进行差分,由于边缘的值明显小于(或大于)周边像素,所以边缘的差分结果会明显不同,这样就提取出了垂直边缘
cv2.filter2D(src, ddepth, kernel)
filter2D函数是用于对图像进行二维卷积(滤波)操作。它允许自定义卷积核(kernel)来实现各种图像处理效果,如平滑、锐化、边缘检测等
src: 输入图像
ddepth:输出图像的深度,可以是负值(表示与原图相同)、正值或其他特定值(常用-1 表示输出与输入具有相同的深度)
kernel:卷积核,一个二维数组(通常为奇数大小的方形矩阵),用于计算每个像素周围邻域的加权和
同理,把上面那个矩阵转置一下,就是提取水平边缘。这种差分操作就称为图像的梯度计算
(将卷积核中的矩阵换成k2矩阵即可实现水平边缘提取,可以找张数独的图片效果对比更清晰)
2、Sobel算子
上面的 k1、k2 两个卷积核都叫做Sobel算子,只是方向不同
sobel_image = cv2.Sobel(src, ddepth, dx, dy, ksize)
src:通常应该是一个灰度图像,因为 Sobel 算子是基于像素亮度梯度计算的。在彩色图像的情况下,通常需要先将其转换为灰度图像
ddepth:输出图像的深度,即输出图像的数据类型(-1 表示输出图像的深度与输入图像相同 )
dx,dy:dx=1,dy=0时求x方向的一阶导数,意味着我们想要计算图像在水平方向(x轴)的梯度;
dx=0,dy=1时求y方向的一阶导数
ksize:Sobel算子的大小,可选择3、5、7,默认为3
3、Laplacian算子
Laplacian算子是一种二阶微分算子,通常用于图像处理中,用来检测图像中的边缘和细节。它对图像的每个像素应用一个“加权平均”,通过计算每个像素与其邻域像素的差异,来判断该点的变化程度。Laplacian算子用于捕捉图像中亮度变化剧烈的区域。常常用于边缘检测,因为边缘区域的亮度变化非常大,而平坦区域的亮度变化较小。它的优点是能够检测到图像的细节,包括边缘、角点等
cv2.Laplacian(src, ddepth)
src:输入图像
ddepth:代表输出图像的深度 ,-1 表示输出图像的深度与输入图像相同
二、图像边缘检测
(不是算子,接下来是一整套边缘检测的流程、方案)
1、高斯滤波
去除噪点,边缘检测本身属于锐化操作,对噪点比较敏感,所以需要进行平滑处理
2、计算图像的梯度、方向
首先使用sobel算子计算中心像素点的两个方向上的梯度G_{x}和G_{y},然后就能够得到 其具体的梯度值:
也可以使用来代替。在OpenCV中,默认使用
来计算 梯度值
然后我们根据公式可以得到一个角度值:
这个角度值其实是当前边缘的梯度的方向。通过这个公式我们就可以计算出图片中所有的像素点的梯度值与梯度方向,然后根据梯度方向获取边缘的方向
并且如果梯度方向不是0°、45°、90°、135°这种特定角度,那么就要用到插值算法来计算当前像素点在其方向上进行插值的结果了,然后进行比较并判断是否保留该像素点
(
当值为-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°边缘;
)
3、非极大值抑制
得到每个边缘的方向之后,其实把它们连起来边缘检测就算完了,但是为什么还有这一步与下一步呢?是因为经过第二步得到的边缘不经过处理是没办法使用的,因为高斯滤波的原因,边缘会变得模糊,导致经过第二步后得到的边缘像素点非常多,因此我们需要对其进行一些过滤操作,而非极大值抑制就是一个很好的方法,它会对得到的边缘像素进行一个排除,使边缘尽可能细一点。
在该步骤中,我们需要检查每个像素点的梯度方向上的相邻像素,并保留梯度值最大的像素,将其他像素抑制为零。假设当前像素点为(x,y),其梯度方向是0°,梯度值为G(x,y),那么我们就需要比较G(x,y)与两个相邻像素的梯度值:G(x-1,y)和G(x+1,y)。如果G(x,y)是三个值里面最大的,就保留该像素值,否则将其抑制为零
4、双阈值筛选
经过非极大值抑制之后,我们还需要设置阈值来进行筛选,当阈值设的太低,就会出现假边缘,而阈值设的太高,一些较弱的边缘就会被丢掉,因此使用了双阈值来进行筛选,推荐高低阈值的比例为2:1到3:1之间。
当某一像素位置的幅值超过最高阈值时,该像素必是边缘像素;当幅值低于最低像素时,该像素必不是边缘像素;幅值处于最高像素与最低像素之间时,如果它能连接到一个高于阈值的边缘时,则被认为是边缘像素,否则就不会被认为是边缘。也就是说,上图中的A和C是边缘,B不是边缘。因为C虽然不超过最高阈值,但其与A相连,所以C就是边缘
edges = cv2.Canny(image, threshold1, threshold2)
image:输入的灰度/二值化图像数据(即使读到的是彩色图也可以进行处理 )
threshold1:低阈值,用于决定可能的边缘点
threshold2:高阈值,用于决定强边缘点
三、绘制图像轮廓
1、概念
轮廓是一系列相连的点组成的曲线,代表了物体的基本外形。相对于边缘,轮廓是连续 的,边缘不一定连续,轮廓是一个闭合的、封闭的形状
(轮廓的作用:形状分析、目标识别、图像分割)
2、寻找轮廓
寻找轮廓需要将图像做一个二值化处理,并且根据图像的不同选择不同的二值化方法来 将图像中要绘制轮廓的部分置为白色,其余部分置为黑色。也就是说,我们需要对原始的 图像进行灰度化、二值化的处理,令目标区域显示为白色,其他区域显示为黑色。之后,对 图像中的像素进行遍历,当一个白色像素相邻(上下左右及两条对角线)位置有黑色像素存 在或者一个黑色像素相邻(上下左右及两条对角线)位置有白色像素存在时,那么该像素点 就会被认定为边界像素点,轮廓就是有无数个这样的边界点组成的
contours,hierarchy = cv2.findContours(image,mode,method)
contours:获取到的轮廓点的列表,检测到有多少个轮廓,该列表就有多少子列表,每一个子列表都代表了一个轮廓中所有点的坐标
hierarchy:表示轮廓之间的关系,前一条轮廓、后一条轮廓、子轮廓、父轮廓(该参数的使用情况会比较少 )
image:输入二值化图像
mode:轮廓的检索模式
RETR_EXTERNAL:只查找最外层的轮廓(只有前、后,没有父、子)
RETR_LIST:列出所有的轮廓(只有前、后,没有父、子)
RETR_CCOMP:列出所有的轮廓(分为两个层级:0 外部轮廓,1 内部轮廓)
RETR_TREE:列出所有的轮廓(轮廓按照树的方式显示:最外层树根,子轮廓树枝 )
method:轮廓的表示方法
CHAIN_APPROX_NONE:存储所有轮廓点
CHAIN_APPROX_SIMPLE:只存储有用的点(默认)
CHAIN_APPROX_TC89_L1:用 Teh-Chin 算法智能地简化轮廓,点数更少
(对于mode和method这两个参数来说,一般使用RETR_EXTERNAL和CHAIN_APPROX_SIMPLE这两个选项 )
3、绘制轮廓
轮廓找出来后,其实返回的是一个轮廓点坐标的列表,因此我们需要根据这些坐标将轮 廓画出来,因此就用到了绘制轮廓的方法
cv2.drawContours(image, contours, contourIdx, color, thickness)
image:原始图像,一般为单通道或三通道的 numpy 数组
contours:包含多个轮廓的列表,每个轮廓是一个由点坐标构成的二维数组(numpy数组)
contourldx:要绘制的轮廓索引。(如果设为 -1
,则会绘制所有轮廓)
color:绘制轮廓的颜色,可以是 BGR 值或者是灰度值(对于灰度图像)
thickness:轮廓线的宽度,如果是正数,则画实线;如果是负数,则填充轮廓内的区域