Ⅰ. 形态学操作
0x00 腐蚀和膨胀
腐蚀和膨胀是最基本的形态学操作,腐蚀和膨胀都是针对白色部分(高亮部分)而言的。
膨胀就是使图像中的高亮部分扩张,效果图拥有比原图更大的高亮区域;腐蚀是原图中的高亮区域被蚕食,效果图拥有比原图更小的高亮区域。膨胀是求局部最大值的操作,腐蚀是求局部最小值的操作。
腐蚀
操作:
用一个结构元素扫描图像中的每一个像素,用结构元素中的每一个像素与覆盖的像素做“与”操作,结果都为 1 ,则该像素为 1 ,否则为 0 。
即用卷积核扫描图像, 只不过腐蚀操作的卷积和一般都是1, 如果卷积核内所有像素点都是白色, 那么锚点即为白色。
作用:
消除物体边界点,使目标缩小,可以消除小于结构元素的噪声点
💬API
cv2.erode(img,kernel,iterations)
参数:
- img:要处理的图像
- kernel:核结构
- iterations:腐蚀的次数,默认为1
膨胀
操作:用一个结构元素扫描图像中的每一个像素,用结构元素中的每一个像素与其覆盖的像素做“与”操作,如果都为 0 ,则该像素为 0 ,否则为 1 。
💬API
cv2.dilate(img,kernel,iterations)
参数:
- img:要处理的图像
- kernel:核结构
- iterations:腐蚀的次数,默认为1
💎示例:
# 1.读取图像
img = cv2.imread('sun.jpg')
# 2.创建核结构
kernel = np.ones((10, 10), np.uint8)
# 3.图像的腐蚀与膨胀
erosion_1 = cv2.erode(img, kernel, iterations=1) # 腐蚀
erosion_2 = cv2.erode(img, kernel, iterations=2)
erosion_3 = cv2.erode(img, kernel, iterations=3)
dilate_1 = cv2.dilate(img, kernel, iterations=1)# 膨胀
dilate_2 = cv2.dilate(img, kernel, iterations=2)
dilate_3 = cv2.dilate(img, kernel, iterations=3)
# 4.图像显示
res_1 =np.hstack((erosion_1, erosion_2, erosion_3))
res_2 =np.hstack((dilate_1, dilate_2, dilate_3))
cv2.imshow('erosion', res_1)
cv2.imshow('dilate', res_2)
cv2.waitKey(0)
cv2.destroyAllWindows()
上面的腐蚀操作中,我们自行创建了一个的全 1 卷积核但是遇到复杂的图像操作时,我们不可能每次都自行指定卷积核,因此提供了获取卷积核的 APL.不需要我们手工创建卷积核。
getStructuringElement(shape, ksize[, anchor])
shape是指卷积核的形状, 注意不是指长宽, 是指卷积核中1形成的形状.
- MORPH_RECT 卷积核中的1是矩形, 常用.
- MORPH_ELLIPSE 椭圆
- MORPH_CROSS 十字
0x01 开运算与闭运算
开运算
先腐蚀,再膨胀
作用:分离物体,消除小区域。消除噪点,去除小的干扰块,而不影响原来的图像
闭运算
先膨胀,再腐蚀
作用:消除“闭合”物体里面的孔洞,可以填充闭合区域
💬API
cv2.morphologyEx(img,op,kernel)
参数:
- img: 要处理的图像
- op:处理方式,若为开运算,则设为cv2.MORPH_OPEN;若为闭运算,则设为cv2.MORPH_CLOSE
- kernel:核结构
0x02 礼帽与黑帽
礼帽:用来分离比领近点亮一些的斑块。当一幅图像具有大幅背景的时候,而微小的物品比较有规律的情况下,可以使用礼帽运算作为背景提取。
礼帽 = 原始输入 - 开运算结果
黑帽:分离比领近点暗一些的斑块。
黑帽 = 闭运算结果 - 原始输入
cv2.morphologyEx(img,op,kernel)
cv2.MORPH_TOPHAT#礼帽运算
cv2.MORPH_BLACKHAT#黑帽运算
Ⅱ. 图像平滑
0x00图像噪声
概念:由于图像采集、处理、传输等过程不可避免的会受到噪声的污染,妨碍人们对图像理解及分析处理。常见的图像噪声有高斯噪声、椒盐噪声等。
1. 椒盐噪声
椒盐噪声也称为脉冲噪声,是图像中经常见到的一种噪声,它是一种随机出现的白点或者黑点,可能是亮的区域有黑色像素或是在暗的区域有白色像素(或是两者皆有)。
椒盐噪声的成因可能是影像讯号受到突如其来的强烈干扰而产生、类比数位转换器或位元传输错误等。
例如失效的感应器导致像素值为最小值,饱和的感应器导致像素值为最大值。
2. 高斯噪声
高斯噪声是指噪声密度函数服从高斯分布的一类噪声。
由于高斯噪声在空间和频域中数学上的易处理性,这种噪声(也称为正态噪声)模型经常被用于实践中。高斯随机变量z的概率密度函数由下式给出:
其中 表示灰度值, 表示 的平均值或期望值, 表示 的标准差。标准差的平方 称为 的方差。
高斯函数的曲线如图所示
0x01图像平滑
概念:图像平滑从信号处理的角度看就是去除其中的高频信息,保留低频信息。因此我们可以对图像实施低通滤波。低通滤波可以去除图像中的噪声,对图像进行平滑。
根据滤波器的不同可分为均值滤波,高斯滤波,中值滤波, 双边滤波。
均值滤波
采用均值滤波模板对图像噪声进行滤除。令 表示中心在 点,
尺寸为 的矩形子图像窗口的坐标组。 均值滤波器可表示为:
由一个归一化卷积框完成的。它只是用卷积框覆盖区域所有像素的平均值来代替中心元素。
例如, 标准化的平均过滤器如下所示:
均值滤波的优点是算法简单,计算速度较快;
缺点是在去噪的同时去除了很多细节部分,将图像变得模糊。
💬API
cv.blur(src, ksize, anchor, borderType)
参数:
- src:输入图像
- ksize:卷积核的大小
- anchor:默认值 (-1,-1) ,表示核中心
- borderType:边界类型
高斯滤波
二维高斯是构建高斯滤波器的基础,其概率分布函数如下所示:
的分布是一个突起的帽子的形状。这里的 可以看作两个值,一个是 方向的标准差 ,另一个是 方向的标准差
当 和 取值越大,整个形状趋近于扁平;
当 和取值越小,整个形状越突起。
正态分布是一种钟形曲线,越接近中心,取值越大,越远离中心,取值越小。
计算平滑结果时,只需要将"中心点"作为原点,其他点按照其在正态曲线上的位置,分配权重,就可以得到一个加权平均值。
高斯平滑在从图像中去除高斯噪声方面非常有效。
高斯平滑的流程:
- 首先确定权重矩阵
假定中心点的坐标是,那么距离它最近的8个点的坐标如下:
更远的点以此类推。
为了计算权重矩阵,需要设定 的值。假定 =1.5,则模糊半径为1的权重矩阵如下:
这9个点的权重总和等于0.4787147,如果只计算这9个点的加权平均,还必须让它们的权重之和等于1,因此上面9个值还要分别除以0.4787147,得到最终的权重矩阵。
2. 计算高斯模糊
有了权重矩阵,就可以计算高斯模糊的值了。
假设现有9个像素点,灰度值(0-255)如下:
每个点乘以对应的权重值:
得到
将这9个值加起来,就是中心点的高斯模糊的值。
对所有点重复这个过程,就得到了高斯模糊后的图像。如果原图是彩色图片,可以对RGB三个通道分别做高斯平滑。
💬API
cv2.GaussianBlur(src,ksize,sigmaX,sigmay,borderType)
参数:
- src: 输入图像
- ksize:高斯卷积核的大小,注意 : 卷积核的宽度和高度都应为奇数,且可以不同
- sigmaX: 水平方向的标准差
- sigmaY: 垂直方向的标准差,默认值为0,表示与相同
- borderType:填充边界类型
中值滤波
中值滤波是一种典型的非线性滤波技术,基本思想是用像素点邻域灰度值的中值来代替该像素点的灰度值。
中值滤波对椒盐噪声(salt-and-pepper noise)来说尤其有用,因为它不依赖于邻域内那些与典型值差别很大的值。
💬API
cv.medianBlur(src, ksize )
参数:
- src:输入图像
- ksize:卷积核的大小
总结
图像噪声
- 椒盐噪声:图像中随机出现的白点或者黑点
- 高斯噪声:噪声的概率密度分布是正态分布
图像平滑
- 均值滤波:算法简单,计算速度快,在去噪的同时去除了很多细节部分,将图像变得模糊cv2.blur()
- 高斯滤波: 去除高斯噪声 cv2.GaussianBlur()
- 中值滤波: 去除椒盐噪 cv2.medianBlur()
Ⅲ. 边缘检测
边缘检测是图像处理和计算机视觉中的基本问题,其目的是为了识别数字图像中亮度变化明显的点。
图片边缘检测大幅度地减少了数据量,并且剔除了可以认为不相关的信息,保留了图像重要的结构属性。
有许多方法用于边缘检测,它们的绝大部分可以划分为两类:基于搜索和基于零穿越
基于搜索:通过寻找图像一阶导数中的最大值来检测边界,然后利用计算结果估计边缘的局部方向,通常采用梯度的方向,并利用此方向找到局部梯度模的最大值。
代表的算法是Sobel算子和Scharr算子。
基于零穿越:通过寻找图像的二阶导数零穿越来寻找边界。
代表的算法是Laplacian算子。
0x00 Sober检测算子
- 水平变化:将图像I与奇数大小的模版进行卷积,结果为Gx.比如,当模板大小为3时, Gx为:
- 垂直变化:将图像I与奇数大小的模板进行卷积,结果为Gy。比如,当模板大小为3时, Gy为:
在图像的每一- 点,结合以上两个结果求出:
统计极大值所在的位置,就是图像的边缘。
Tip:当内核大小为3时,以上Sobel内核可能产生比较明显的误差,为解决这一问题,我们使用Scharr函数,但该函数仅作用于大小为3的内核。该函数的运算与Sobel函数一样快,但结果却更加精确,其计算方法为:
即:右减左,下减上
参数:
- src:传入的图像
- ddepth:图像的深度
- dx和dy:指求导的阶数,,沿着水平方向求导;,沿着垂直方向求导
- ksize:是Sobel算子的大小,即卷积核的大小,必须为奇数,默认为3(若ksize= -1 ,就演变成的Scharr算子)
- scale:缩放导数的比例常数,默认情况为没有伸缩系数
- borderType:图像边界的模式,默认值为cv2.BORDER_DEFAULT
Sobel函数求完导数后会有负值,还有会大于255的值。而原图像是uint8,即8位无符号数,所以Sobel建立的图像位数不够,会有截断。
因此要使用16位有符号的数据类型,即cv2.CV_16S。
处理完图像后,再使用cv2.convertScaleAbs( )函数将其转回原来的uint8格式,否则图像无法显示。
Sobel算子是在两个方向计算的,最后还需要用cv2.addWeighted( )函数将其组合起来
💬API
Scale_abs = cv2.convertScaleAbs(src)#格式转换函数
result = cv2.addWeighted(src1, alpha, src2, beta)#图像混合
💎实例:
1.若只处理x轴:
img = cv2.imread('sun.jpg')
sobel = cv2.Sobel(img, cv2.CV_64F, 1, 0, ksize=3)
cv2.imshow('sobel', sobel)
cv2.waitKey(0) # 等待时间,毫秒级,0表示按任意键终止
cv2.destroyAllWindows()
2.处理x轴与y轴
img = cv2.imread('sun.jpg')
gray_image = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
sobelx = cv2.Sobel(gray_image,cv2.CV_64F, 1, 0, ksize=3)
sobely = cv2.Sobel(gray_image, cv2.CV_64F, 0, 1, ksize=3)
sobelx = cv2.convertScaleAbs(sobelx)#convertScaleAbs 图像增强函数
sobely = cv2.convertScaleAbs(sobely)
sobelxy = cv2.addWeighted(sobelx, 0.5, sobely, 0.5, 0)
0x01 Laplacian算子
Laplacian是利用二阶导数来检测边缘。因为图像是 "2维",我们需要在两个方向求导,如下式所示:
那不连续的函数的二阶导数是:
那使用的卷积核是:
即中间点和周围比较
💬API
laplacian = cv2.Laplacian(src, ddepth[, dst[, ksize[, scale[, delta[, borderType]]]]])
💎示例:
三种算子的处理对比:
img = cv2.imread('sun.jpg')
gray_image = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
sobelx = cv2.Sobel(gray_image,cv2.CV_64F, 1, 0, ksize=3)
sobely = cv2.Sobel(gray_image, cv2.CV_64F, 0, 1, ksize=3)
sobelx = cv2.convertScaleAbs(sobelx)#convertScaleAbs 图像增强函数
sobely = cv2.convertScaleAbs(sobely)
sobelxy = cv2.addWeighted(sobelx, 0.5, sobely, 0.5, 0)
scharrx = cv2.Scharr(gray_image, cv2.CV_64F, 1, 0)
scharry = cv2.Scharr(gray_image, cv2.CV_64F, 0, 1)
scharrx = cv2.convertScaleAbs(scharrx)
scharry = cv2.convertScaleAbs(scharry)
scharrxy = cv2.addWeighted(scharrx, 0.5, scharry, 0.5, 0)
laplacian = cv2.Laplacian(gray_image, cv2.CV_64F)
laplacian = cv2.convertScaleAbs(laplacian)
res = np.hstack((sobelxy, scharrxy,laplacian))
cv2.imshow('res', res)
cv2.waitKey(0) # 等待时间,毫秒级,0表示按任意键终止
cv2.destroyAllWindows()
从左到右分别为sobel算子处理,scharr算子处理,laplacian算子处理
总结:
边缘检测的原理
- 基于搜索:利用一阶导数的最大值获取边界
- 基于零穿越:利用二阶导数为0获取边界
Sobel算子
- 基于搜索的方法获取边界
cv.sobel()
cv.convertScaleAbs()
cv.addweights()
Laplacian算子
- 基于零穿越获取边界
cv.Laplacian()
算子比较: