Ⅰ. 形态学操作

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()


上面的腐蚀操作中,我们自行创建了一个5×5的全 1 卷积核但是遇到复杂的图像操作时,我们不可能每次都自行指定卷积核,因此OpenCV提供了获取卷积核的 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 礼帽与黑帽

礼帽:用来分离比领近点亮一些的斑块。当一幅图像具有大幅背景的时候,而微小的物品比较有规律的情况下,可以使用礼帽运算作为背景提取。

礼帽 = 原始输入 - 开运算结果
dst=tophat(src,element)=src−open (src,element)

黑帽:分离比领近点暗一些的斑块。

黑帽 = 闭运算结果 - 原始输入
dst=blackhat(src,element)=close(src,element)−src

cv2.morphologyEx(img,op,kernel)
cv2.MORPH_TOPHAT#礼帽运算
cv2.MORPH_BLACKHAT#黑帽运算

Ⅱ. 图像平滑

0x00图像噪声

概念:由于图像采集、处理、传输等过程不可避免的会受到噪声的污染,妨碍人们对图像理解及分析处理。常见的图像噪声有高斯噪声、椒盐噪声等。

1. 椒盐噪声
椒盐噪声也称为脉冲噪声,是图像中经常见到的一种噪声,它是一种随机出现的白点或者黑点,可能是亮的区域有黑色像素或是在暗的区域有白色像素(或是两者皆有)。

椒盐噪声的成因可能是影像讯号受到突如其来的强烈干扰而产生、类比数位转换器或位元传输错误等。

例如失效的感应器导致像素值为最小值,饱和的感应器导致像素值为最大值。

2. 高斯噪声

高斯噪声是指噪声密度函数服从高斯分布的一类噪声。

由于高斯噪声在空间和频域中数学上的易处理性,这种噪声(也称为正态噪声)模型经常被用于实践中。高斯随机变量z的概率密度函数由下式给出:

其中 Z表示灰度值, μ表示 Z的平均值或期望值, σ表示 Z的标准差。标准差的平方 σ2称为Z的方差。

高斯函数的曲线如图所示


0x01图像平滑
概念:图像平滑从信号处理的角度看就是去除其中的高频信息,保留低频信息。因此我们可以对图像实施低通滤波。低通滤波可以去除图像中的噪声,对图像进行平滑。

根据滤波器的不同可分为均值滤波,高斯滤波,中值滤波, 双边滤波。

均值滤波
采用均值滤波模板对图像噪声进行滤除。令 S\{xy\}表示中心在 (x,y)点,

尺寸为 m×n的矩形子图像窗口的坐标组。 均值滤波器可表示为:

由一个归一化卷积框完成的。它只是用卷积框覆盖区域所有像素的平均值来代替中心元素。

例如,3×3标准化的平均过滤器如下所示:

均值滤波的优点是算法简单,计算速度较快;

缺点是在去噪的同时去除了很多细节部分,将图像变得模糊。

API

cv.blur(src, ksize, anchor, borderType)

参数:

  • src:输入图像
  • ksize:卷积核的大小
  • anchor:默认值 (-1,-1) ,表示核中心
  • borderType:边界类型
    高斯滤波

二维高斯是构建高斯滤波器的基础,其概率分布函数如下所示:

G(x,y)的分布是一个突起的帽子的形状。这里的 σ可以看作两个值,一个是 x方向的标准差 σx,另一个是 y方向的标准差 σyσxσy取值越大,整个形状趋近于扁平;

σxσy取值越小,整个形状越突起。

正态分布是一种钟形曲线,越接近中心,取值越大,越远离中心,取值越小。

计算平滑结果时,只需要将”中心点”作为原点,其他点按照其在正态曲线上的位置,分配权重,就可以得到一个加权平均值。

高斯平滑在从图像中去除高斯噪声方面非常有效。

高斯平滑的流程:

  1. 首先确定权重矩阵

假定中心点的坐标是(0,0),那么距离它最近的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,表示与σx相同
  • 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:指求导的阶数,dx=1,dy=0沿着水平方向求导;dx=0,dy=1沿着垂直方向求导
  • ksize:是Sobel算子的大小,即卷积核的大小,必须为奇数,默认为3(若ksize= -1 ,就演变成3×3的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()

    算子比较: