【第五章 (卷积全家桶!)OpenCv卷积介绍、 图像噪声+模糊、边缘检测、形态学操作(通俗了解与联系图像卷积相关操作!)】

1. 前言

1.1 卷积介绍

(1)卷积核: 无论是在神经网络中的CNN的卷积层、OpenCv中的滤波操作还是对二值化图像(多通道也可以但是不常用)进行形态学处理,都用到了卷积相关知识,都是利用卷积核在图像进行滑动运算。(神经网络中的卷积与滤波中的卷积类似但是不完全相同)。

(2)CNN中的卷积: 卷积常用在图像处理,计算机视觉,语音识别,自然语言处理,文本分类,模式识别,等领域。在图像领域,它可以将图像中的特征提取出来,用于更好地理解图像的内容。卷积通常用来检测图像中的边缘纹理、物体等特征。它通过滑动移动一个小的滤波器(称为卷积核)来提取图像中的特征,并将提取出来的特征组合在一起,以获得更好的图像分析结果。

(3)滤波中的卷积核: 图像滤波是一种改善图像质量的技术,它可以消除图像中的噪声模糊其他不需要的特征。它还可以用来增加图像的对比度突出图像中的边缘或特征

(4)形态学操作的卷积: 图像经过预处理->背景与目标分割之后,常常会出现各种噪点、以及空洞、以及稍微相连,因此常常用形态学操作进行操作(类似产品二次加工)。其中卷积核(结构元素)在形态学操作可自主定义,分为矩形、十字形、椭圆形(后面形态学操作章节中会分享一个基于画图功能的结构元素定义方法)等;形态学操作主要包括腐蚀、膨胀、开运算、闭运算、顶帽、黑帽、形态学梯度。熟悉了解形态学的相关理论知识,有助于我们对图像的二次加工,得到想要的目标轮廓,进行物体框选。

(5)导语: 因此在学习深度学习、图像模糊、图像边缘提取、形态学操作之前,有必要学习卷积基础知识。

1.2 模糊操作

(1)作用: 模糊操作是图像预处理中最常用的基础操作,能减少噪声、改善图像质量,降低后续算法的复杂度。

(2)分类: 图像模糊分为 均值、中值、双边、高斯、非锐化模糊

(3)导语: 如何灵活使用这些模糊操作,因此有必要了解各种模糊的底层原理,能根据图像不同特征选择恰当的模糊操作对图像进行预处理。

1.3 边缘检测

(1)作用: 可用来提取边界、轮廓、纹理。

(2)分类: 第三章中提到图像分割的分类,阈值分割属于背景与目标分割中的第一类,然后边缘检测则为第二类分割。边缘检测可分为:基于微分算子、梯度算子、还有效果最优的Canny边缘检测。

(3)导语: 在车道线检测、文档矫正中透视变换等案例中,也常用到边缘检测。因此有必要了解边缘检测算法,能根据不同的场景,灵活选择背景与目标分割算法。

1.4 形态学操作

(1)作用: 形态学操作在图像处理中起着非常重要的作用,其原理是基于模板的形状(卷积核的形状),它可以使用腐蚀、膨胀、开运算、闭运算、等操作,以达到提取图像特征或分割图像的目的。
(2)分类: 腐蚀(缩小目标)、膨胀(扩大目标)、开运算(先腐蚀再膨胀)、闭运算(先膨胀再腐蚀)、顶帽运算(噪声=原图-开运算(先腐后膨))、黑帽运算(空洞=闭运算(先膨后腐)-原图)、形态学梯度(物体边缘=原图-腐蚀)、根据实际需求膨胀和腐蚀多种混合运算等。

(3)导语: 当我们将背景与目标分割出来之后,会出现一些空洞、噪声、或者物体相连。因此有必要对其进行形态学去噪,填空洞,或者分割物体。因此有必要熟悉了解形态学操作。

2.卷积介绍

2.1 卷积操作

(1)卷积:

  • 滑动卷积核窗口kernel在输入图像Img上进行滑动,重合区域进行相乘,最后相加,先向,再向遍历,输出结果就是我们所说的特征图。因为卷积就是提取特征。

  • 这里Size(Img)为5x5,Size(Kernel)为3x3。从图可看出非0像素组成数字1。这里选择了常用来图像锐化的卷积核进行演示。

  • 卷积核一般都是设置奇数如:1x1,3x3,5x5,与偶数卷积核相比,在卷积计算中,可以节省一定计算量;在滤波中,可以保证滤波器的对成型;同时,奇数卷积核更容易找到中心点,方便定位,如果偶数,会出现定位模糊。

图2.1.1 输入图像、卷积核

(2)卷积过程:

   如下图所示,卷积参数为:步长stride为1,填充padding为0。这时候,可以注意到,输入图像为5x5,输出的特征图为3x3的大小。当深度学习网络卷积层数很大,每次卷积后输出的特征图都会缩小,从而到最后会缩小成1x1的大小。导致无法后续的卷积计算。因此需要进行填充操作。

图2.1.2 卷积过程

2.2 填充(扩大输出特征图大小)

(1)参数: 输入:5x5; 卷积核:3x3; 步幅:2; 填充:1(上下左右各填充1行/列);输出:5x5。

(2)特点: 根据输出特征图与输入特征图相比,组成数字1的像素值明显变大,与其相连的其他像素值明显变小,两者之间的差值越大。这是卷积核选择了常用的锐化滤波核,sum(卷积核)=1(>1则为亮度增强,<1降低亮度),在后续的模糊锐化中大家就会见到。

(3)注意点: 在解释卷积操作的同时,通过这个例子,阐述了图像由像素值组成,像素值越大,亮度越高;轮廓像素值与相邻像素差值越大,对比度越强,也是属于图像锐化;


图2.2.1 卷积过程(stride=1,padding=1)

  • 因为整体截图范围广,因此下面每行截图一张图片,方便大家查看。


图2.2.2 卷积运算图

2.3 步幅(可缩小输出特征图大小,减少下一次卷积计算量)

(1)步幅: 卷积核移动步伐, 如果步幅不为1的情况,假设为2(每次滑动两个单元),该例子的输入特征图大小则会变成3x3


图2.3.3 卷积过程(stride=2,padding=1)

(2)总结: 填充>1的时候,输出特征图大小增大,可维持与原图大小;步幅>1的时,输出特征图大小减小,用来降低后续卷积计算量。

2.4 计算公式

*(1)备注: 输入大小(IH,IW);滤波器大小(FH.FW);输出大小(OH,OW);填充P,步幅S。
O H = I H + 2 P − F H S + 1 ( 1 ) OH = \frac{IH+2P-FH}{S}+1 \qquad (1) OH=SIH+2PFH+1(1)
O W = I W + 2 P − F W S + 1 ( 2 ) OW = \frac{IW+2P-FW}{S}+1 \qquad (2) OW=SIW+2PFW+1(2)

2.5 验证代码(Pytorch进行实现)

(1)代码:

import torch
import torch.nn.functional  as F
# 输入
input =torch.tensor([[0,0,1,0,0],
                    [0,2,3,0,0],
                    [0,0,4,0,0],
                    [0,0,5,0,0],
                    [0,6,7,8,0]])
# 卷积核
kernel = torch.tensor([[0,-1,0],
                      [-1,5,-1],
                      [0,-1,0]])

# nn卷积操作,数据格式是(batch_size,channel,h,w)
input = torch.reshape(input,(1,1,5,5))
kernel = torch.reshape(kernel,(1,1,3,3))
print(input)
print(kernel)

# 进行卷积操作,input:输入图像;kernel:卷积核;stride:步伐;,padding:填充,默认填充数据为0
output = F.conv2d(input,kernel,stride=2,padding=1)
print(output)

(2)卷积结果: 卷积是个数学运算,我们用的什么语言,或者python库都能实现,但是目前实现最方面的是pytorch框架,大势所趋。

tensor([[[[0, 0, 1, 0, 0],
          [0, 2, 3, 0, 0],
          [0, 0, 4, 0, 0],
          [0, 0, 5, 0, 0],
          [0, 6, 7, 8, 0]]]])
tensor([[[[ 0, -1,  0],
          [-1,  5, -1],
          [ 0, -1,  0]]]])
tensor([[[[ 0,  2,  0],
          [ 0, 12,  0],
          [-6, 16, -8]]]])

2.6 推荐阅读链接

初衷:
1)放推荐链接,一方面是本文章有部分内容参考了相关链接;
2)同时觉得这些文章写得相当不错,已经有很不错的轮子了,没有必要重新造一模一样的轮子;
3)大家根据理解程度,选择阅读,根据多方面文章,理解,再综合,这样对一个点的理解才能够透彻(个人觉得有很多文章,都有自己的定位以及亮点,很难单读一篇文章就全对该知识点理解得很透彻,但是看多了相关文章,学习每一篇文章的亮点之后,便会有自己的独特见解)
4)方便大家找到该知识点相关的优质文章,提高效率。

1.小土堆,卷积讲解,通俗易懂
2.最容易理解的对卷积(convolution)的解释
备注:信号处理系统中的卷积和OpenCV的卷积基本一致,但它们还是有一些区别。信号处理系统中的卷积旨在操作信号,而OpenCV的卷积用于图像处理。此外,OpenCV的卷积运算提供了更多的可调参数,如内核大小、步幅等,而信号处理系统中的卷积则更加简单,只有一个参数,即信号的形状.
3.各种卷积方式的最全讲解


2.图像模糊

导语: 模糊操作主要是对图像进行去噪声,因此在学习模糊操作之前,有必要对噪声类型进行了解,结合几种模糊处理的特点,能根据图片特点,选择合适的模糊算法,对图像进行模糊操作,提升图像质量,降低图像处理复杂度。

2.1 常见噪声介绍

2.1.1 椒盐噪声

(1)椒盐噪声: 图像中的椒盐点,它们是像素值的极大(白色)或极小(黑色),它们的存在会影响图像的质量;

(2)记忆方法:粉(色),色),当图像有黑白点杂点,便可以采用中值滤波进行去噪。同时大家可以通过在图片随机添加亮暗杂点进行模拟。其中九十年代的老式黑白电视机,雪花屏其实就是椒盐噪声。彩色电视机出现的彩色噪点是高斯噪声。


图2.1.1.1 雪花屏(椒盐噪声)

(3)代码:(给图片添加椒盐噪声)

# 作者:OpenCv机器视觉
# 时间:2023/1/21
# 功能:给图片添加椒盐噪声
import cv2 as cv
import numpy as np



# 添加椒盐噪声
def make_salt_pepper_noise(img):
    # 进行添加噪声
    for i in range(1000):
        # 椒(黑色)噪点,随机坐标
        xb = np.random.randint(0, w)
        yb = np.random.randint(0, h)
        # 盐(白色)噪点,随机坐标
        xw = np.random.randint(0, w)
        yw = np.random.randint(0, h)

        # 根据坐标进行添加早点
        img[yb, xb, :] = 255  # h,w,c,第一个参数是高度、第二个参数是宽度、第三个参数是通道数
        img[yw, xw, :] = 0  # h,w,c,第一个参数是高度、第二个参数是宽度、第三个参数是通道数

    return img

# 读取图片
img = cv.imread(".\\img\\lena.jpg")

# 获取图片,h,w,c参数
h,w,c=img.shape
# 添加椒盐噪声
img_msp = make_salt_pepper_noise(img.copy())
# 图像拼接
stack = np.hstack([img, img_msp])


# 显示图像
cv.namedWindow("img", 0)
cv.imshow("img", stack)
cv.waitKey(0)


(4)效果: 可以看出,椒盐噪点像素差值大,杂点比较明显,亮暗点对比度强,也就显得比较刺眼。

图2.1.1.2 原图(左)、加椒盐噪声(右)


2.1.2 随机噪声

(1)随机噪声: 又称背景噪声,没有规律性,对图像的对比度以及亮度会有影响、用均值滤波的效果最好。椒盐噪声,黑白点;高斯噪声彩色点,但是符合高斯分布;随机噪声个人理解,就是彩色噪声,同时不符合规律分布的就是随机噪声。(大家可以参考阅读文章1,个人理解还不够全面,又由于随机噪声与椒盐噪声很类似,我就以前面椒盐噪声方式理解,但是随机噪声不具备分布规律,大家有好的理解方法可以评论区共同探讨。)

(2)代码:

# 作者:OpenCv机器视觉
# 时间:2023/1/24
# 功能:给图片添加随机噪声
import cv2 as cv
import numpy as np

# 生成随机噪声
def make_random_noise(img_noise, num=100):

    # 获取图片h,w,c参数
    h,w,c = img_noise.shape

    # 加噪声
    for i in range(num):
        b = np.random.randint(0,255)    # 每个通道数值随机生成
        g = np.random.randint(0,255)
        r = np.random.randint(0,255)

        x = np.random.randint(0, w)  # 随机生成指定范围的整数
        y = np.random.randint(0, h)
        img_noise[y, x] = (b,g,r)   # opencv读取图像后,是BGR格式

    return img_noise



img = cv.imread(".\\img\\lena.jpg")


# 生成随机噪声,噪声点数量没加,就按照函数默认参数100来
img_rn = make_random_noise(img.copy(),5000)
# 合并图像
stack = np.hstack([img,img_rn])

# 显示图像
cv.namedWindow("img", 0)
cv.imshow("img", stack)
cv.waitKey(0)

(3)效果: 噪声分布没有规律,彩色(彩色图)/黑白(灰度图),有点像椒盐噪声(黑白)的分布,因为都是随机生成;也有些像高斯噪声(彩色),但是没有分布规律。大家大概知道是怎么样的就可以,不必强行记住,反正就是没有规律的噪声。


图2.1.2.1 原图(左)随机噪声(右)

2.1.3 高斯噪声

(1)高斯噪声: 高斯噪声是指图像中的随机噪音,它会降低图像的质量。老式彩色电视机下雨天容易出现很多有颜色的噪点,这就是高斯噪声(灰度图也有高斯噪声,只是为灰色噪点)。


图2.1.3.1 彩色电视机噪声(高斯噪声)

(2)代码(给彩色图片添加高斯噪声):

# 作者:OpenCv机器视觉
# 时间:2023/1/21
# 功能:给彩色图片添加高斯噪声

import numpy as np
import cv2 as cv



def make_gauss_noise(img, mean=0, sigma=25):
    img = np.array(img / 255, dtype=float)  # 将原始图像的像素值进行归一化,因为高斯分布是在0-1区间
    # 创建一个均值为mean,方差为sigma,呈高斯分布的图像矩阵
    noise = np.random.normal(mean, sigma / 255.0, img.shape)
    add = img + noise  # 将噪声和原始图像进行相加得到加噪后的图像
    img_mgn= np.clip(add, 0.0, 1.0)
    img_mgn = np.uint8(img_mgn * 255.0)

    return img_mgn

img = cv.imread(".\\img\\lena.jpg")
img_mgn = make_gauss_noise(img,0,np.random.randint(25,50))

# 图像拼接
stack = np.hstack([img, img_mgn])


# 显示图像
cv.namedWindow("img", 0)
cv.imshow("img", stack)
cv.waitKey(0)

(3)添加效果:

图2.1.3.2 原图(左)、添加高斯噪声(右)

2.1.3 推荐阅读文章

1.数字图像处理:噪声模型(椒盐噪声、随机噪声、高斯噪声)和滤波方法
2.图像模糊(均值、高斯、中值、双边滤波)
3.图像运动模糊
4.图像加高斯噪声


2.2 运动模糊

(1)运动模糊: 它是由于相机或检测目标的相对移动速度过快导致的(运动模糊往往是朝一个方向模糊);因此我们在相机选型时,往往需要考虑物体的运动速度以及帧率,如果运动速度较快,帧率较低,就容易造成运动模糊。解决运动模糊就是采用较高的帧率的相机。

(2)代码: 给图片添加运动模糊。

# 作者:OpenCv机器视觉
# 时间:2023/1/21
# 功能:给图片添加运动模糊效果
import cv2 as cv
import numpy as np

# 制作运动模糊
def make_motion_blur_noise(img, degree=180*1, angle=90):
    
    # 这里生成任意角度的运动模糊kernel的矩阵, degree越大,模糊程度越高
    M = cv.getRotationMatrix2D((degree/2, degree/2), angle, 1)
    print(M)

    motion_blur_kernel = np.diag(np.ones(degree))
    print(motion_blur_kernel)

    motion_blur_kernel = cv.warpAffine(motion_blur_kernel, M, (degree, degree))
    print(motion_blur_kernel)

    motion_blur_kernel = motion_blur_kernel / degree
    print(motion_blur_kernel)

    blurred = cv.filter2D(img, -1, motion_blur_kernel)
    
    # convert to uint8
    cv.normalize(blurred, blurred, 0, 255, cv.NORM_MINMAX)
    blurred = np.array(blurred, dtype=np.uint8)
    return blurred

# 清晰原图
img = cv.imread(".\\img\\bxg_clear.jpg")
# 由于相机移动导致的运动模糊
img_original_motion_blur = cv.imread(".\\img\\bxg_original_motion_blur.jpg")

# 制作运动模糊
img_motion_blur = make_motion_blur_noise(img)
# 图像水平拼接,np.vstack为垂直拼接
stack = np.hstack([img,img_original_motion_blur,img_motion_blur])


# 显示图像
cv.namedWindow("img",0)
cv.imshow("img",stack)
cv.waitKey(0)

(3)运动模糊效果: 左图:清晰原图;中间:相机移动拍摄导致的运动模糊;右图;利用代码制作运动模糊效果。

图2.1.3 运动模糊对比


2. 3均值模糊

(1)原理: 对图像中每个像素点周围的像素值(滤波核覆盖范围)进行求和平均,然后用这个平均值来替换原来像素点的值。

(2)类别: 是一种线性(具有线性关系,输入与输入是通过像素加减乘除线性运算所得)滤波,其中线性滤波还包括:中均值滤波、高斯滤波、双边滤波。常见非线性滤波则有:形态学操作中的膨胀滤波、侵蚀滤波。
3)应用: 常用来去除随机噪声(应用), 因为计算的范围是卷积核覆盖的范围,所以卷积核越大,当前像素点值为周围更广范围像素的均值,则计算量越大,去噪效果更好,图像会越模糊越失真,滤波一张图像越耗时,会模糊掉一些细节。因为需要根据实际情况进行选择恰当的滤波核大小。

(4)原理图解: 滤波核多为奇数,这里生成一个矩阵为7x7大小,均值滤波核为3x3

  • 均值模糊卷积核:
    K e r n e l = 1 K w ∗ K h ( 1 1 ⋯ 1 1 1 ⋯ 1 ⋮ ⋮ ⋱ ⋮ 1 1 ⋯ 1 ) (3) Kernel = \frac 1{K_w*K_h} \begin{pmatrix} 1& 1 & \cdots &1 & \\ 1& 1 & \cdots &1 &\\ \vdots & \vdots & \ddots & \vdots \\ 1& 1&\cdots & 1 \end{pmatrix} \tag{3} Kernel=KwKh1 111111111 (3)

  • 3X3的卷积核则如下:

图2.3.1 3x3均值滤波核

  • 滤波过程:滤波核与图像所覆盖像素点进行相乘&求和。最后将该值赋给深红色像素点。卷积核遍历滑动整张图片时,则滤波完成。
图2.3.2 滤波过程

  • 程序运算结果(结果为58,做了记号标出)
均值滤波前:
[[255  10  10  10  10  10  10]
 [ 10  10  10 200  10  10  10]
 [ 10  10 200 200  10  10  10]
 [ 10 255  10 200  10  10  10]
 [ 10  10  10 200  10  10  10]
 [ 10  10 200 200 200  10  10]
 [ 10  10  10  10  10  10   0]]
均值滤波后:[[ 37  37  52  52  52  10  10]
 [ 375873  73  52  10  10]
 [ 64  58 122  94  73  10  10]
 [ 64  58 122  94  73  10  10]
 [ 64  58 122 116  94  31  10]
 [ 10  31  73  94  73  30   9]
 [ 10  52  94 137  94  51   9]]

5.疑问?: 因为滤波核中心像素值,是滤波核覆盖范围像素均值,那么边缘还有四个角滤波核会超出图像之外,那是怎么计算的?(如下图)

图2.3.3 边界情况

  • 那么提出问题:(1)如果滤波核一部分超出图像,那么滤波核值都是1/9还是1/(滤波核与图像重叠的像素个数)【通过边缘来验证】;(2)左上角值与附件那些像素有关?(3)有关的像素点,是否对左上角像素值贡献度一样?因此个人通过代码进行了验证:

  • 经过验证,(1):都不是;(2)(3)与左上角四个像素有关,每个像素贡献度不同,但是具有对称性(img[0][1]与img[1][0]其一改变相同值,img[0][0]值不变。img[1][1]改变相同值,但是会变)。边缘或者四个角的像素点,其实都过扩充,的形式,但是其填充数值以及计算方式都不清楚(可能是个人了解有限,也可能与opencv的API内部实现算法计算方式也有关),大家直接理解周围像素均值来代替就可以了,如果是边缘直接填充0进行辅助计算即可,比较图像边缘不是我们的重点,刚好有这个疑问,所以就找了相关文章,进行验证,但是基本都不正确。(如有兴趣,大家可调整代码中矩阵值进行验证测试,有好的文章或者四路大家可以评论区共同探讨,后续了解到会更新)

  • 验证代码: (大家可以修改矩阵值,进行验证)这里是创建一张图片(方便打印查阅图像值),进行模糊,看其效果(也是进一步让大家了解图像其底层原理)

# 作者:OpenCv机器视觉
# 时间:2023/1/22
# 功能:利用numpy创建图片(从像素解释模糊),添加椒盐噪声+均值模糊

import cv2 as cv
import numpy as np

# 创建图片
def test():

    # 创建一个全为0,大小为7x7的单通道矩阵
    img = np.array([[255,10,10,10,10,10,10],
                    [10,10,10,200,10,10,10],
                    [10,10,200,200,10,10,10],
                    [10,255,10,200,10,10,10],
                    [10,10,10,200,10,10,10],
                    [10,10,200,200,200,10,10],
                    [10,10,10,10,10,10,0]
                    ],dtype=np.uint8)
    print("均值滤波前::")
    print(img)



    # 利用3x3的均值滤波核,对图像进行模糊
    blurred = cv.blur(img, (3, 3))
    print("均值滤波后::")
    print(blurred)

    cv.namedWindow("test", 0)
    cv.imshow("test", np.hstack([img,blurred]))
	cv.waitKey(0)


img = cv.imread(".\\img\\lena.jpg")

# 获取图片,h,w,c参数
h,w,c=img.shape
test()


  • 验证效果:
    代码模拟三个椒盐噪声(两白一黑),可以看出均值滤波之后,图像去噪了,但也变模糊了,同时数字1的细节(对椒盐噪声去除效果不是那么理想)。
图2.3.4 创建图片并均值滤波效果

(6)随机噪声+椒盐噪声+高斯噪声对比 and 不同滤波核大小对图像的模糊降噪模糊效果 and 均值滤波对随机噪声、椒盐噪声、高斯噪声的去噪效果

  • 代码(部分代码会重复,主要是为了方便使用,直接全部复制,读取图片就可以看效果,不能自己过多操作)
# 作者:OpenCv机器视觉
# 时间:2023/1/25
# 功能:随机噪声+椒盐噪声+高斯噪声对比 and 不同滤波核大小对图像的模糊降噪模糊效果 and 均值滤波对随机噪声、椒盐噪声、高斯噪声的去噪效果

import cv2 as cv
import numpy as np


# 添加高斯噪声
def make_gauss_noise(image, mean=0, sigma=25):
    image = np.array(image / 255, dtype=float)  # 将原始图像的像素值进行归一化,因为高斯分布是在0-1区间
    # 创建一个均值为mean,方差为sigma,呈高斯分布的图像矩阵
    noise = np.random.normal(mean, sigma / 255.0, image.shape)
    add = image + noise  # 将噪声和原始图像进行相加得到加噪后的图像
    img_noise= np.clip(add, 0.0, 1.0)
    img_noise = np.uint8(img_noise * 255.0)


    return img_noise
# 添加随机噪声
def make_random_noise(img_noise, num=100):

    # 获取图片h,w,c参数
    h,w,c = img_noise.shape

    # 加噪声
    for i in range(num):
        b = np.random.randint(0,255)    # 每个通道数值随机生成
        g = np.random.randint(0,255)
        r = np.random.randint(0,255)

        x = np.random.randint(0, w)  # 随机生成指定范围的整数
        y = np.random.randint(0, h)
        img_noise[y, x] = (b,g,r)   # opencv读取图像后,是BGR格式


    return img_noise
# 添加椒盐噪声
def make_salt_pepper_noise(img_noise):
    # 进行添加噪声
    for i in range(1000):
        # 椒(黑色)噪点,随机坐标
        xb = np.random.randint(0, w)
        yb = np.random.randint(0, h)
        # 盐(白色)噪点,随机坐标
        xw = np.random.randint(0, w)
        yw = np.random.randint(0, h)

        # 根据坐标进行添加早点
        img_noise[yb, xb, :] = 255  # h,w,c,第一个参数是高度、第二个参数是宽度、第三个参数是通道数
        img_noise[yw, xw, :] = 0  # h,w,c,第一个参数是高度、第二个参数是宽度、第三个参数是通道数

    return img_noise



# 读取图片
img = cv.imread(".\\img\\lena.jpg")

# 获取图片,h,w,c参数
h,w,c=img.shape

# 添加随机噪声、椒盐噪声、高斯噪声
img_rn= make_random_noise(img.copy(),2000)
img_spn = make_salt_pepper_noise(img.copy())
img_gn = make_gauss_noise(img.copy())
img_noise_stack = np.hstack([img,img_rn,img_spn,img_gn])


# 不同大小的滤波核的滤波效果
blurred_3 = cv.blur(img_rn, (3, 3))
blurred_5 = cv.blur(img_rn, (5, 5)) # 对本张图片,5x5的滤波效果较好
blurred_7 = cv.blur(img_rn, (7, 7))
img_diffsize_stack = np.hstack([img,blurred_3,blurred_5,blurred_7])

# 对高斯噪声图进行中值模糊
blurred_rn5  = cv.blur(img_rn,(5,5))
blurred_spn5 = cv.blur(img_spn, (5, 5))
blurred_gn5 = cv.blur(img_gn, (5, 5))
img_diffnoise_stack = np.hstack([img,blurred_rn5,blurred_spn5,blurred_gn5])



# 垂直拼凑图片
stack = np.vstack([img_noise_stack,img_diffsize_stack,img_diffnoise_stack])    #

# 显示图像
cv.namedWindow("img", 0)
cv.imshow("img", stack)
cv.waitKey(0)
  • 效果:

1)噪声对比:图1-4,分别为原图、随机噪声、椒盐噪声、高斯噪声图像

2)滤波核大小影响:从 图5-8可看出,不同卷积核对随机噪声去除效果,其中5x5去噪效果较为良好。卷积核越大、噪声去除效果更好、图像轮廓细节更模糊、图像更失真。

3)均值滤波对不同噪声的抑制效果: 图9-12可看出均值滤波对不同噪声的去噪效果对比,可看出均值滤波对随机噪声的抑制效果较为良好。
4)总结:卷积核大小一般根据实际需求权衡利弊之后来选取。

  • 效果图
图2.3.5 图像模糊效果

2.4 中值模糊

(1)原理: 中值滤波是一种非线性滤波,其基本思想是在图像上选定一个窗口,用窗口中所有像素值的中值来代替窗口中心点的像素值。由于中值滤波器使用的是窗口中像素值的中值,而不是像素值的平均值,所以它可以有效抑制噪声,同时又能保留较高的空间分辨率。

(2)应用: 有效去除椒盐噪声。

(3)原理图解:

  • 滤波核遍历整张图片后,便完成了去噪。
图2.4.1.中值模糊原理

图2.4.2.中值模糊过程

(4)创建图片,查看中值滤波后变换: 对创建的图片进行中值模糊(方便打印查阅图像值)

  • 代码
# 作者:OpenCv机器视觉
# 时间:2023/1/25
# 功能:创建一张图片进行中值滤波,并查看前后的像素值

import cv2 as cv
import numpy as np

# 函数说明,ctrl点cv.medianBlur就可以进入,里面有说明函数如何使用以及参数含义
"""
    medianBlur(src, ksize[, dst]) -> dst
    .   The function smoothes an image using the median filter 
    .   @param src input 1-, 3-, or 4-channel image; when ksize is 3 or 5, the image depth should be
    .   CV_8U, CV_16U, or CV_32F, for larger aperture sizes, it can only be CV_8U.
    .   @param dst destination array of the same size and type as src.
    .   @param ksize aperture linear size; it must be odd and greater than 1, for example: 3, 5, 7 ...

    """

# 创建图片进行中模糊
def test():

    # 创建一个全为0,大小为7x7的单通道矩阵
    img = np.array([[255,10,10,10,10,10,10],
                    [10,10,10,200,10,10,10],
                    [10,10,200,200,10,10,10],
                    [10,255,10,200,10,10,10],
                    [10,10,10,200,10,10,10],
                    [10,10,200,200,200,10,10],
                    [10,10,10,10,10,10,0]
                    ],dtype=np.uint8)
    print("中值滤波前::")
    print(img)



    # 利用3x3的均值滤波核,对图像进行模糊
    blurred = cv.medianBlur(img, 3)
    print("中值滤波后::")
    print(blurred)

    cv.namedWindow("test", 0)
    cv.imshow("test", np.hstack([img,blurred]))



test()
cv.waitKey(0)
  • 输出结果: 滤波前后像素值:可以看出,在边缘或者对角像素点中值滤波时,填充的不是0,也不是10,具体扩充内容是opencv中值滤波算法觉决定,因此同均值滤波,这里我们不做细纠,前面GIF填充的是10,大家了解过程即可,因为边缘信息不是那么重要,重要信息一般显示在图像中央。
均值滤波前:[[255  10  10  10  10  10  10]
 [ 10  10  10 200  10  10  10]
 [ 10  10 200 200  10  10  10]
 [ 10 255  10 200  10  10  10]
 [ 10  10  10 200  10  10  10]
 [ 10  10 200 200 200  10  10]
 [ 10  10  10  10  10  10   0]]
均值滤波后:[[ 10  10  10  10  10  10  10]
 [ 10  10  10  10  10  10  10]
 [ 10  10 200  10  10  10  10]
 [ 10  10 200  10  10  10  10]
 [ 10  10 200 200  10  10  10]
 [ 10  10  10  10  10  10  10]
 [ 10  10  10  10  10  10  10]]

  • 滤波效果: 可以能较好去除椒盐噪声、同时图像整体亮度不变,去椒盐噪声效果良好。
图2.4.3.中值模糊结果

(5)中值滤波对不同噪声的去除效果 (不同大小卷积核的模糊效果均值滤波已经验证,后面不再重复)

  • 代码
# 作者:OpenCv机器视觉
# 时间:2023/1/25
# 功能:随机噪声+椒盐噪声+高斯噪声对比 and 不同滤波核大小对图像的模糊降噪模糊效果 and 中值滤波对随机噪声、椒盐噪声、高斯噪声的去噪效果


import cv2 as cv
import numpy as np

# 函数说明,ctrl点cv.medianBlur就可以进入,里面有说明函数如何使用以及参数含义
"""
    medianBlur(src, ksize[, dst]) -> dst
    .   The function smoothes an image using the median filter 
    .   @param src input 1-, 3-, or 4-channel image; when ksize is 3 or 5, the image depth should be
    .   CV_8U, CV_16U, or CV_32F, for larger aperture sizes, it can only be CV_8U.
    .   @param dst destination array of the same size and type as src.
    .   @param ksize aperture linear size; it must be odd and greater than 1, for example: 3, 5, 7 ...

    """

# 添加高斯噪声
def make_gauss_noise(image, mean=0, sigma=25):
    image = np.array(image / 255, dtype=float)  # 将原始图像的像素值进行归一化,因为高斯分布是在0-1区间
    # 创建一个均值为mean,方差为sigma,呈高斯分布的图像矩阵
    noise = np.random.normal(mean, sigma / 255.0, image.shape)
    add = image + noise  # 将噪声和原始图像进行相加得到加噪后的图像
    img_noise= np.clip(add, 0.0, 1.0)
    img_noise = np.uint8(img_noise * 255.0)


    return img_noise
# 添加随机噪声
def make_random_noise(img_noise, num=100):

    # 获取图片h,w,c参数
    h,w,c = img_noise.shape

    # 加噪声
    for i in range(num):
        b = np.random.randint(0,255)    # 每个通道数值随机生成
        g = np.random.randint(0,255)
        r = np.random.randint(0,255)

        x = np.random.randint(0, w)  # 随机生成指定范围的整数
        y = np.random.randint(0, h)
        img_noise[y, x] = (b,g,r)   # opencv读取图像后,是BGR格式


    return img_noise
# 添加椒盐噪声
def make_salt_pepper_noise(img_noise):
    # 进行添加噪声
    for i in range(1000):
        # 椒(黑色)噪点,随机坐标
        xb = np.random.randint(0, w)
        yb = np.random.randint(0, h)
        # 盐(白色)噪点,随机坐标
        xw = np.random.randint(0, w)
        yw = np.random.randint(0, h)

        # 根据坐标进行添加早点
        img_noise[yb, xb, :] = 255  # h,w,c,第一个参数是高度、第二个参数是宽度、第三个参数是通道数
        img_noise[yw, xw, :] = 0  # h,w,c,第一个参数是高度、第二个参数是宽度、第三个参数是通道数

    return img_noise



# 读取图片
img = cv.imread(".\\img\\lena.jpg")

# 获取图片,h,w,c参数
h,w,c=img.shape


# 添加随机噪声、椒盐噪声、高斯噪声
img_rn= make_random_noise(img.copy(),2000)
img_spn = make_salt_pepper_noise(img.copy())
img_gn = make_gauss_noise(img.copy())
img_noise_stack = np.hstack([img,img_rn,img_spn,img_gn])


# 不同大小的滤波核的滤波效果
blurred_3 = cv.medianBlur(img_spn, 3)
blurred_5 = cv.medianBlur(img_spn,5) # 对本张图片,5x5的滤波效果较好
blurred_7 = cv.medianBlur(img_spn, 7)
img_diffsize_stack = np.hstack([img,blurred_3,blurred_5,blurred_7])

# # 对随机噪声、椒盐噪声、高斯噪声噪声图进行中值模糊
blurred_rn5  = cv.medianBlur(img_rn,5)
blurred_spn5 = cv.medianBlur(img_spn, 5)
blurred_gn5 = cv.medianBlur(img_gn, 5)
img_diffnoise_stack = np.hstack([img,blurred_rn5,blurred_spn5,blurred_gn5])



# 垂直拼凑图片
stack = np.vstack([img_noise_stack,img_diffsize_stack,img_diffnoise_stack])    #

# 显示图像
cv.namedWindow("img", 0)
cv.imshow("img", stack)
cv.waitKey(0)
  • 效果:中值滤波对椒盐噪声去除效果杠杠的(对高斯滤波的效果勉强还算可以,效果也跟高斯噪声参数设置有关,如噪声类型、质量)。不同卷积核大小去除效果也挺好的,图片亮度不变,且保留图片原有的细节特征。
图2.4.4.中值滤波对不同噪声的去除对比

2.5 高斯模糊

2.5.1 高斯模糊

(1)原理: 有别于均值滤波(用窗口中所有像素值的均值来代替窗口中心点的像素值)各像素的权重相同,而高斯分布的各权重则按照高斯分布来,离窗口中心点越近,权值越大,对中心像素的影响越大。
(2)应用: 平滑图像、消除高斯噪声。
(3)高斯分布概率密度分布函数: (具体原理见推荐阅读文章,里面讲得比较仔细,这里只是大致讲解一下)

  • 一维:y=f(x), μ \mu μ(均值)决定的是对称中心、 σ \sigma σ(标准差), σ 2 \sigma^2 σ2(方差)决定的是分布形状——越大,越矮胖(方差越大、偏离均值越大、图像越宽,面积为1,故高度越低,所以约矮胖),同理,越小,越瘦高。

图2.5.1.1 一维高斯分布概率密度公式

  • 一维正态分布概率分布图像:

图2.5.1.2 一维高斯分布图像

  • 二维:z = f(x,y),

图2.5.1.3 二维高斯分布概率密度公式

  • 二维高斯分布:

图2.5.1.4 二维(图像)高斯分布图像

  • 当每个方向的方差 σ \sigma σ都相等时,可以化简表达图下:

图2.5.1.5 二维(图像)高斯分布概率密度公式

  • 通过高斯分布生成的高斯核如下:越靠近滤波核中心,权重占比越大,约远离则权重占比越小,单从X/Y维度,可以看出来,非常像高斯分布的图形。

图2.5.1.6 二维(图像)高斯分布概率密度公式

  • 高斯核生成代码链接: 参考了此链接(参考链接)同时也给大家加上备注:
# 作者:OpenCv机器视觉
# 时间:2023/1/26
# 功能:生成高斯核
import cv2 as cv
import numpy as np
from matplotlib import pyplot as plt



def dnorm(x, mu, sd):
    """
    :param x:输入距离
    :param mu: 均值
    :param sd: 标准差
    """
    # 利用一维高斯分布函数进行计算
    return 1 / (np.sqrt(2 * np.pi) * sd) * np.e ** (-np.power((x - mu) / sd, 2) / 2)


def gaussian_kernel(size, sigma=1, verbose=False):
    # 高斯核其他像素点到中心点的距离,距离值之所以是这么计算,大家可以自行了解欧式距离等距离概念。
    kernel_1D = np.linspace(-(size // 2), size // 2, size)  # [-1.  0.  1.]

    # 根据距离,生成高斯分布的权重
    for i in range(size):                   #  [0.21296534 0.26596152 0.21296534]
        kernel_1D[i] = dnorm(kernel_1D[i], 0, sigma)


    # 滤波核是二维,且是实(数)对称矩阵,所以两个1维函数转置(对于1维结果还是1维,猜这里增加了转置可能是为了保证多维度计算的的对应)
    kernel_2D = np.outer(kernel_1D.T, kernel_1D.T)
    """
       Given two vectors, ``a = [a0, a1, ..., aM]`` and
    ``b = [b0, b1, ..., bN]``,
    the outer product [1]_ is::

      [[a0*b0  a0*b1 ... a0*bN ]
       [a1*b0    .
       [ ...          .
       [aM*b0            aM*bN ]]

    """

    # 进行归一化,卷积神经网络CNN中常对数据进行归一化,一方面是为了减少计算量,同时减少样本差异,归一化的方式也有多种,大家后续会学到
    kernel_2D *= 1.0 / kernel_2D.max()



    # 可以将像素以不同颜色进行显示,全总越大,颜色越深
    if verbose:
        plt.imshow(kernel_2D, interpolation='none', cmap='gray')   # 设置颜色
        plt.title("Image")
        plt.show()

    return kernel_2D

img_kernel = gaussian_kernel(3,1.5,1)
# 显示图像
cv.namedWindow("img", 0)
cv.imshow("img", img_kernel)
cv.waitKey(0)

(4)原理图解:(后来想想,还是进行一次模拟运算)

  • 1.步骤:
    1)生成高斯卷积核:高斯分布生成权重->归一化
    2)创建图片->卷积

  • 2.类似均值模糊,在滤波中,思路都大同小异,大家要串联起来理解。


图2.5.1.6 高斯滤波原理

  • 3.原理验证代码:(利用pytorch框架进行卷积运算,如果大家还没按照环境,可以根据第一章进行安装)
# 作者:OpenCv机器视觉
# 时间:2023/1/26
# 功能:高斯原理演示

import cv2 as cv
import numpy as np
import torch
import torch.nn.functional  as F

# 高斯分布概率密度计算
def dnorm(x, mu, sd):
    """
    :param x:输入距离
    :param mu: 均值
    :param sd: 标准差
    """
    # 利用一维高斯分布函数进行计算
    return 1 / (np.sqrt(2 * np.pi) * sd) * np.e ** (-np.power((x - mu) / sd, 2) / 2)
# 获取高斯核
def gaussian_kernel(size, sigma=1, verbose=False):
    # 高斯核其他像素点到中心点的距离,距离值之所以是这么计算,大家可以自行了解欧式距离等距离概念。
    kernel_1D = np.linspace(-(size // 2), size // 2, size)  # [-1.  0.  1.]

    # 根据距离,生成高斯分布的权重
    for i in range(size):                   #  [0.21296534 0.26596152 0.21296534]
        kernel_1D[i] = dnorm(kernel_1D[i], 0, sigma)


    # 滤波核是二维,且是实(数)对称矩阵,所以两个1维函数转置(对于1维结果还是1维,猜这里增加了转置可能是为了保证多维度计算的的对应)
    kernel_2D = np.outer(kernel_1D.T, kernel_1D.T)
    """
       Given two vectors, ``a = [a0, a1, ..., aM]`` and
    ``b = [b0, b1, ..., bN]``,
    the outer product [1]_ is::

      [[a0*b0  a0*b1 ... a0*bN ]
       [a1*b0    .
       [ ...          .
       [aM*b0            aM*bN ]]

    """

    # 进行归一化,卷积神经网络CNN中常对数据进行归一化,一方面是为了减少计算量,同时减少样本差异,归一化的方式也有多种(除sum或者最大值,大家后续会学到
    # kernel_2D *= 1.0 / kernel_2D.max()    # x = x*x/max归一化
    print("归一化前:\n",kernel_2D)
    kernel_2D = kernel_2D / kernel_2D.sum()    # x = x*sum(x)归一化
    print("sum(kernel):" ,kernel_2D.sum())      # 归一化后,sum()=1,也就是图片总体亮度不变
    print("归一化后:\n",kernel_2D)


    return kernel_2D



# 获取高斯核
kernel = gaussian_kernel(3,1.5,1)


# 定义tensor二维数组,因为后面要卷积运算
input =torch.tensor([[255, 10, 10, 10, 10, 10, 10],
                    [10, 10, 10, 200, 10, 10, 10],
                    [10, 10, 200, 200, 10, 10, 10],
                    [10, 255, 10, 200, 10, 10, 10],
                    [10, 10, 10, 200, 10, 10, 10],
                    [10, 10, 200, 200, 200, 10, 10],
                    [10, 10, 10, 10, 10, 10, 0]
                    ],dtype=torch.float64)
# 转为图片,需要(h,w,c)格式,同时uint8
input_img = torch.reshape(input,(7,7))
input_img = np.array(input_img,np.uint8)


# 卷积前需要转为tensor格式
kernel = torch.tensor(kernel)   # np转tensor

# 转为卷积计算格式,batch_size,chancle,height,weight
input = torch.reshape(input,(1,1,7,7))
kernel = torch.reshape(kernel,(1,1,3,3))
print("input:\n",input)
print("kernel:\n",kernel)

# 卷积,为了保证输出大小不变,进行填充
output = F.conv2d(input,kernel,stride=1,padding=1)
output = torch.reshape(output,(7,7))    # 将维度转为7x7
output= np.array(output,np.uint8)      # 转为np格式,以及无符号Int类型,用于图片显示

blurred = cv.GaussianBlur(input_img, (3, 3), 0)
stack1 = np.hstack([input_img,output,blurred])
print("output:\n",output)
cv.namedWindow("img", 0)
cv.imshow("img", stack1)
cv.waitKey(0)

  • 4.运行结果:

归一化前:
 [[0.04535423 0.05664058 0.04535423]
 [0.05664058 0.07073553 0.05664058]
 [0.04535423 0.05664058 0.04535423]]
sum(kernel): 1.0000000000000002
归一化后:
 [[0.09474166 0.11831801 0.09474166]
 [0.11831801 0.14776132 0.11831801]
 [0.09474166 0.11831801 0.09474166]]
input:
 tensor([[[[255.,  10.,  10.,  10.,  10.,  10.,  10.],
          [ 10.,  10.,  10., 200.,  10.,  10.,  10.],
          [ 10.,  10., 200., 200.,  10.,  10.,  10.],
          [ 10., 255.,  10., 200.,  10.,  10.,  10.],
          [ 10.,  10.,  10., 200.,  10.,  10.,  10.],
          [ 10.,  10., 200., 200., 200.,  10.,  10.],
          [ 10.,  10.,  10.,  10.,  10.,  10.,   0.]]]], dtype=torch.float64)
kernel:
 tensor([[[[0.0947, 0.1183, 0.0947],
          [0.1183, 0.1478, 0.1183],
          [0.0947, 0.1183, 0.0947]]]], dtype=torch.float64)
output:
 [[ 40  35  24  29  24   6   4]
 [ 35  51  72  78  50  10   6]
 [ 30  61 119 105  68  10   6]
 [ 35  64 119 101  68  10   6]
 [ 30  56 114 119  90  28   6]
 [  6  32  78 105  78  31   5]
 [  4  24  47  65  47  23   3]]

  • 5.运行效果: 模糊效果基本一致,但跟opencv的高斯模糊API有小小区别,可能是方差参数设置不同导致`,也有可能是归一化函数不同,但是总体一样。
图2.5.1.7 高斯模拟对比

6.效果分析: 可以直观看出,左上角以及最后一行像素值,原本黑色像素因为周围有较亮像素,经过高斯滤波之后,像素值就变高。图像征整体对比度下降,变得更加平滑模糊。

图2.5.1.8 创建图片并进行高斯滤波效果

(4)高斯模糊实验代码: 几种滤波代码总体都一样,变的是滤波函数,为了先copy先用,还是全部贴上。

# 作者:OpenCv机器视觉
# 时间:2023/1/25
# 功能:随机噪声+椒盐噪声+高斯噪声对比 and 不同滤波核大小对图像的模糊降噪模糊效果 and 高斯滤波对随机噪声、椒盐噪声、高斯噪声的去噪效果


import cv2 as cv
import numpy as np

# 函数说明,ctrl点cv.medianBlur就可以进入,里面有说明函数如何使用以及参数含义
"""
    GaussianBlur(src, ksize, sigmaX, dst=None, sigmaY=None, borderType=None)
    .   The function convolves the source image with the specified Gaussian kernel.
    .   @param src input image; the image can have any number of channels, which are processed
    .   independently, but the depth should be CV_8U, CV_16U, CV_16S, CV_32F or CV_64F.
    .   @param dst output image of the same size and type as src.
    .   @param ksize Gaussian kernel size. 
    .   @param sigmaX Gaussian kernel standard deviation in X direction.
    .   @param sigmaY Gaussian kernel standard deviation in Y direction; if sigmaY is zero, it is set to be
    .   equal to sigmaX, if both sigmas are zeros, they are computed from ksize.width and ksize.height,
    .   @param borderType pixel extrapolation method, see #BorderTypes.
    .   @sa  sepFilter2D, filter2D, blur, boxFilter, bilateralFilter, medianBlur
"""

# 添加高斯噪声
def make_gauss_noise(image, mean=0, sigma=15):
    image = np.array(image / 255, dtype=float)  # 将原始图像的像素值进行归一化,因为高斯分布是在0-1区间
    # 创建一个均值为mean,方差为sigma,呈高斯分布的图像矩阵
    noise = np.random.normal(mean, sigma / 255.0, image.shape)
    add = image + noise  # 将噪声和原始图像进行相加得到加噪后的图像
    img_noise= np.clip(add, 0.0, 1.0)
    img_noise = np.uint8(img_noise * 255.0)


    return img_noise
# 添加随机噪声
def make_random_noise(img_noise, num=100):

    # 获取图片h,w,c参数
    h,w,c = img_noise.shape

    # 加噪声
    for i in range(num):
        b = np.random.randint(0,255)    # 每个通道数值随机生成
        g = np.random.randint(0,255)
        r = np.random.randint(0,255)

        x = np.random.randint(0, w)  # 随机生成指定范围的整数
        y = np.random.randint(0, h)
        img_noise[y, x] = (b,g,r)   # opencv读取图像后,是BGR格式


    return img_noise
# 添加椒盐噪声
def make_salt_pepper_noise(img_noise):
    # 进行添加噪声
    for i in range(1000):
        # 椒(黑色)噪点,随机坐标
        xb = np.random.randint(0, w)
        yb = np.random.randint(0, h)
        # 盐(白色)噪点,随机坐标
        xw = np.random.randint(0, w)
        yw = np.random.randint(0, h)

        # 根据坐标进行添加早点
        img_noise[yb, xb, :] = 255  # h,w,c,第一个参数是高度、第二个参数是宽度、第三个参数是通道数
        img_noise[yw, xw, :] = 0  # h,w,c,第一个参数是高度、第二个参数是宽度、第三个参数是通道数

    return img_noise



# 读取图片
img = cv.imread(".\\img\\lena.jpg")
# 获取图片,h,w,c参数
h,w,c=img.shape



# 添加随机噪声、椒盐噪声、高斯噪声
img_rn= make_random_noise(img.copy(),2000)
img_spn = make_salt_pepper_noise(img.copy())
img_gn = make_gauss_noise(img.copy())
img_noise_stack = np.hstack([img,img_rn,img_spn,img_gn])


# 不同大小的滤波核的滤波效果
blurred_3 = cv.GaussianBlur(img_gn, (3,3),0)
blurred_5 = cv.GaussianBlur(img_gn,(5,5),0) # 对本张图片,5x5的滤波效果较好
blurred_7 = cv.GaussianBlur(img_gn, (7,7),0)
img_diffsize_stack = np.hstack([img,blurred_3,blurred_5,blurred_7])

# # 对随机噪声、椒盐噪声、高斯噪声噪声图进行高斯模糊
blurred_rn5  = cv.GaussianBlur(img_rn,(5,5),0)
blurred_spn5 = cv.GaussianBlur(img_spn, (5,5),0)
blurred_gn5 = cv.GaussianBlur(img_gn, (5,5),0)
img_diffnoise_stack = np.hstack([img,blurred_rn5,blurred_spn5,blurred_gn5])



# 垂直拼凑图片
stack = np.vstack([img_noise_stack,img_diffsize_stack,img_diffnoise_stack])    #

# 显示图像
cv.namedWindow("img", 0)
cv.imshow("img", stack)
cv.waitKey(0)

(5)效果:

  • 从图12看出,高斯滤波去除高斯噪声效果很不错。
图2.5.1.9 高斯滤波对不同噪声的去除对比

2.5.2 Stackblur模糊(高斯模糊的近似)

(1)应用: 对于超分辨图像,如果卷积核过大,则会造成计算非常慢,因此stackblur就是针对大卷积核运算慢提出的一种优化算法。
(2)算法介绍: Stackblur算法介绍传送门

2.6 双边模糊

(1)原理: 双边滤波是一种改善图像质量的图像处理技术,它可以有效的保留图像的细节,同时又能抑制噪声。它基于图像的灰度值,通过计算图像中每个像素其邻域像素之间的高斯加权距离,来确定像素值,从而使得图像边缘更加清晰,噪声更加抑制。简单来说,就是高斯模糊没有考虑到相邻像素的相似值,图像会总体比较模糊,双边滤波就是为了解决边缘模糊问题。

(2)应用: 相对于前面的模糊操作,都会对模糊了轮廓边缘,双边模糊既模糊了图像,又很好地保留了轮廓的边缘。可用来美颜磨皮、轮廓清晰,内部去噪。

(3)特点: 优:减少不想要的噪声同时边缘轮廓细节;缺:计算慢

(4)代码:

# 作者:OpenCv机器视觉
# 时间:2023/1/27
# 功能:随机噪声+椒盐噪声+高斯噪声对比 and 不同Sigma大小对图像的模糊降噪模糊效果 and 双边滤波对随机噪声、椒盐噪声、高斯噪声的去噪效果


import cv2 as cv
import numpy as np


# 函数说明,ctrl点cv.bilateralFilter就可以进入,里面有说明函数如何使用以及参数含义,

"""

    bilateralFilter(src, d, sigmaColor, sigmaSpace, dst=None, borderType=None)
     @brief Applies the bilateral filter to an image.
     
    .   The function 
    .   bilateralFilter can reduce unwanted noise very well while keeping edges fairly sharp. 
    .   However, it is very slow compared to most filters.
    .   
    .   src: 输入图像,可以是Mat类型,图像必须是8位或浮点型单通道、三通道的图像。 
    .  sigmaColor: 颜色空间过滤器的sigma值,这个参数的值越大,表明该像素邻域内有越宽广的颜色会被混合到一起,产生较大的半相等颜色区域。 
    .   sigmaSpace: 坐标空间中滤波器的sigma值,如果该值较大,则意味着越远的像素将相互影响,从而使更大的区域中足够相似的颜色获取相同的颜色。

    .   _Filter size_: Large filters (d \> 5) are very slow, so it is recommended to use d=5 for real-time
    .   applications, and perhaps d=9 for offline applications that need heavy noise filtering.
    
    .   _Sigma values_: For simplicity, you can set the 2 sigma values to be the same. If they are small (\<
    .   10), the filter will not have much effect, whereas if they are large (\> 150), they will have a very
    .   strong effect, making the image look "cartoonish".

"""


# 添加高斯噪声
def make_gauss_noise(image, mean=0, sigma=15):
    image = np.array(image / 255, dtype=float)  # 将原始图像的像素值进行归一化,因为高斯分布是在0-1区间
    # 创建一个均值为mean,方差为sigma,呈高斯分布的图像矩阵
    noise = np.random.normal(mean, sigma / 255.0, image.shape)
    add = image + noise  # 将噪声和原始图像进行相加得到加噪后的图像
    img_noise= np.clip(add, 0.0, 1.0)
    img_noise = np.uint8(img_noise * 255.0)


    return img_noise
# 添加随机噪声
def make_random_noise(img_noise, num=100):

    # 获取图片h,w,c参数
    h,w,c = img_noise.shape

    # 加噪声
    for i in range(num):
        b = np.random.randint(0,255)    # 每个通道数值随机生成
        g = np.random.randint(0,255)
        r = np.random.randint(0,255)

        x = np.random.randint(0, w)  # 随机生成指定范围的整数
        y = np.random.randint(0, h)
        img_noise[y, x] = (b,g,r)   # opencv读取图像后,是BGR格式


    return img_noise
# 添加椒盐噪声
def make_salt_pepper_noise(img_noise):
    # 进行添加噪声
    for i in range(1000):
        # 椒(黑色)噪点,随机坐标
        xb = np.random.randint(0, w)
        yb = np.random.randint(0, h)
        # 盐(白色)噪点,随机坐标
        xw = np.random.randint(0, w)
        yw = np.random.randint(0, h)

        # 根据坐标进行添加早点
        img_noise[yb, xb, :] = 255  # h,w,c,第一个参数是高度、第二个参数是宽度、第三个参数是通道数
        img_noise[yw, xw, :] = 0  # h,w,c,第一个参数是高度、第二个参数是宽度、第三个参数是通道数

    return img_noise



# 读取图片
img = cv.imread(".\\img\\lena.jpg")
# 获取图片,h,w,c参数
h,w,c=img.shape


# 添加随机噪声、椒盐噪声、高斯噪声
img_rn= make_random_noise(img.copy(),2000)
img_spn = make_salt_pepper_noise(img.copy())
img_gn = make_gauss_noise(img.copy())
img_noise_stack = np.hstack([img,img_rn,img_spn,img_gn])


# sigmaColor不同情况下的滤波效果
blurred_50 = cv.bilateralFilter(img, d=0, sigmaColor=50, sigmaSpace=15)# 双边保留滤波,经可能模糊其他背景,但是边缘保留下来。d 像素的领域直径,由颜色空间标准差sigmaColor(越大越好),坐标空间的标准差sigmaSpace(越小越好),决定
blurred_100 = cv.bilateralFilter(img, d=0, sigmaColor=100, sigmaSpace=15)
blurred_150 = cv.bilateralFilter(img, d=0, sigmaColor=150, sigmaSpace=15)
img_diffsize_stack = np.hstack([img,blurred_50,blurred_100,blurred_150])

# # 对随机噪声、椒盐噪声、高斯噪声噪声图进行双边滤波
blurred_rn5  = cv.bilateralFilter(img_rn, d=0, sigmaColor=50, sigmaSpace=15)
blurred_spn5 = cv.bilateralFilter(img_spn, d=0, sigmaColor=50, sigmaSpace=15)
blurred_gn5 = cv.bilateralFilter(img_gn, d=0, sigmaColor=50, sigmaSpace=15)
img_diffnoise_stack = np.hstack([img,blurred_rn5,blurred_spn5,blurred_gn5])



# 垂直拼凑图片
stack = np.vstack([img_diffsize_stack,img_diffnoise_stack])    #

# 显示图像
cv.namedWindow("img", 0)
cv.imshow("img", stack)
cv.waitKey(0)

(5)效果: sc参数设置越大,考虑到的颜色范围越广,图像越模糊;同时对高斯噪声抑制效果良(具备高斯模糊分配权重的模糊特点,还具备保留边缘优点),同时还保留了边缘。

图2.5.1.10 双边滤波效果

(6) 推荐阅读文章:

1.双边滤波器
1.bilateral filter双边滤波器的通俗理解
3.双边滤波以及代码实现

2.7 总结

均值滤波:随机噪声
中值滤波:椒盐噪声
高斯滤波:高斯噪声
双边滤波:高斯噪声、在人物美艳磨皮中比较有效果。
自定义滤波:图像锐化、图像增强

2.8 推荐阅读文章

1.运动模糊/拖影的原因分析
2.深入浅出分析:cv2.getRotationMatrix2D

3.高斯模糊的原理
4.python+OpenCv笔记(九):均值滤波
5.中值滤波(Median filtering)
6.真正搞懂均值模糊、中值模糊、高斯模糊、双边模糊)
7.高斯模糊)
8.高斯滤波)
9.多维高斯分布
10.CV2逐步学习-2:cv2.GaussianBlur()详解
11.OpenCv

备注: 制作运动模糊中用到了cv.getRotationMatrix2D旋转矩阵相关知识,该知识点也常用在透视变换深度学习数据增强中的旋转变换当中。改知识点目前大家先不用去纠结。大概找到运动模糊是怎么样的就可以。等基础上来之后,根据项目应用,再学习cv.getRotationMatrix2D相关知识,我也会更新,并贴上原理解析文章,从线代开始证明。 一文解决透视变换理论+实战应用(后面更新)

3. 边缘检测

(1)引语:前面的去噪是图像预处理中的重要基础环节,在边缘检测方面,主要就是开始对预处理后的图像进行分割。边缘检测的应用非常广泛,有在车牌检车、道路分割、以及文档矫正都会经常用到边缘检测算法。

图3.1 车道线检测

图3.2 文档检测

(2)实现方式: 边缘检测与CNN卷积、以及模糊各种滤波的卷积大同小异,都是卷积模板在图像进行运算。只是模板不一样,在边缘检测当中,可分为好多算子。下面一一介绍其算子类型以及实现效果。(算子的概念,在 第三章有提到。)

图3.3 算子解释

(3)本文先对各种边缘检测算子逐个说明,最后再进一步汇总对比各算子之间的优缺点。同时,opencv很多现有的传统算法鲁棒性都不是很强,大部分需要进行改进应用(可以搜索一些算法改进论文),但是学习这些算法是基础前提,只有懂得了基础,站在巨人的肩膀上,才能写出更好的算法。

3.1 Robert (罗伯茨)算子

(1)原理: 利用局部差分算子寻找边缘的算子(局部区域内,相邻两个像素点值得变化,其导数可以用来衡量像素变化速度,如果变换较大,则大概率是边缘处)。可分为两个模板,一个x方向,一个y方向。

(2)特点: 抑制噪声能力较弱,检测水平垂直线较圆弧好(与Sobel有些相反,见下面实验),边缘不是很平滑,所以定位不是很准确。

(3)算子介绍:

图3.1.1 算子坐标

  • Gx算子

G x =   ( 1 0 0 − 1 ) (4) G_x = \ \begin{pmatrix} 1 & 0 \\ 0 & -1 \\ \end{pmatrix} \tag{4} Gx= (1001)(4)

  • Gy算子
    G x =   ( 0 1 − 1 0 ) (5) G_x = \ \begin{pmatrix} 0 & 1 \\ -1 & 0 \\ \end{pmatrix} \tag{5} Gx= (0110)(5)

  • G算子
    G = G x 2 + G y 2 (6) G= \sqrt{G_x^2+G_y^2} \tag{6} G=Gx2+Gy2 (6)

  • 论文解释:

图3.1.2 Gx、Gy、G论文说明

(4)检测效果:

  • 1.原图
    图3.1.3 实验原图


    2.顺便为观察sobert算子对噪声的抑制效果,给图片添加三种不同噪声:原图(左上),随机噪声(右上),椒盐噪声(左下),高斯噪声(右下)
    图3.1.4 添加噪声图

  • 3.除噪前后对比:Robert算子抑制噪声较弱,如果图像有较多噪声,则会大大降低边缘检测效果。因此边缘检测前需要对其去噪。

图3.1.4 添加噪声图

    1. G x G_x Gx算子检测、 G x G_x Gx算子检测、 G x G_x Gx算子检测对比:
      蓝色框:算子中0组成对角线方向线条检测良好;
      绿色框:同时分别有135°、45°的斜线检测不到(可以参考下面原理代码,就明白了);
      黄色框:x算子或者y算子对圆弧的检测线条不够连续,但是两个算子加权起来之后,检测还是比较完整;
      总体来说: 对于一些形状比较规则的检测(同时没有噪声干扰的情况下,如果有需要去噪),其实robert算子的边缘检测效果还是可以的,但是对一些复杂的图来说,例如较多斜线、弧线,则会漏检测。

图3.1.5 Gx、Gy、G算子边缘检测效果

  • 5.检测代码:
"""
作者:OpenCv机器视觉
时间:2023/1/31
功能:1.利用roberts算子进行边缘检测,看GX、Gy、G算子检测不同角度直线+圆弧下的效果;
    2.添加噪声以及除噪后的实验对比

"""
import cv2 as cv
import numpy as np


# 添加高斯噪声
def make_gauss_noise(image, mean=0, sigma=15):
    image = np.array(image / 255, dtype=float)  # 将原始图像的像素值进行归一化,因为高斯分布是在0-1区间
    # 创建一个均值为mean,方差为sigma,呈高斯分布的图像矩阵
    noise = np.random.normal(mean, sigma / 255.0, image.shape)
    add = image + noise  # 将噪声和原始图像进行相加得到加噪后的图像
    img_noise= np.clip(add, 0.0, 1.0)
    img_noise = np.uint8(img_noise * 255.0)


    return img_noise
# 添加随机噪声
def make_random_noise(img_noise, num=300):

    # 获取图片h,w,c参数
    h,w,c = img_noise.shape

    # 加噪声
    for i in range(num):
        b = np.random.randint(0,255)    # 每个通道数值随机生成
        g = np.random.randint(0,255)
        r = np.random.randint(0,255)

        x = np.random.randint(0, w)  # 随机生成指定范围的整数
        y = np.random.randint(0, h)
        img_noise[y, x] = (b,g,r)   # opencv读取图像后,是BGR格式


    return img_noise
# 添加椒盐噪声
def make_salt_pepper_noise(img_noise):
    h,w = img_noise.shape[:2]
    print(h,w)
    # 进行添加噪声
    for i in range(500):
        # 椒(黑色)噪点,随机坐标
        xb = np.random.randint(0, w)
        yb = np.random.randint(0, h)
        # 盐(白色)噪点,随机坐标
        xw = np.random.randint(0, w)
        yw = np.random.randint(0, h)

        # 根据坐标进行添加早点
        img_noise[yb, xb, :] = 255  # h,w,c,第一个参数是高度、第二个参数是宽度、第三个参数是通道数
        img_noise[yw, xw, :] = 0  # h,w,c,第一个参数是高度、第二个参数是宽度、第三个参数是通道数

    return img_noise









# 左上角原图、右上角随机噪声、左下角椒盐噪声、右下角高斯噪声
img= cv.imread(".\\img\\1.jpg")
h,w,c = img.shape
mh,mw = h//2,w//2
img_top_r = img[0:mh,mw:w:]
img_under_l=img[mh:h,0:mw,:]
img_under_r = img[mh:h,mw:w:,:]
# 加噪声
# img_top_r = make_random_noise(img_top_r)
# img_under_l = make_salt_pepper_noise(img_under_l)
# img_under_r = make_gauss_noise(img_under_r)
# # 去噪
# img_top_r = cv.blur(img_top_r, (5, 5))
# img_under_l=cv.medianBlur(img_under_l, 3)
# img_under_r = cv.GaussianBlur(img_under_r,(5,5),0)

img[0:mh,mw:w:] = img_top_r
img[mh:h,0:mw,:] = img_under_l
img[mh:h,mw:w:,:] = img_under_r



gray= cv.cvtColor(img,cv.COLOR_BGR2GRAY)        # 转灰度
ret,binary = cv.threshold(gray,10,255,cv.THRESH_BINARY)   # 二值化

# robert算子定义
kernel_x = np.array([[1,0],[0,-1]],np.int8)
kernel_y = np.array([[0,1],[-1,0]],np.int8)

# robert算子检测
x=cv.filter2D(binary,cv.CV_16S,kernel_x)     # 可以看成卷积运算,原理通卷积但是又有些区别
y=cv.filter2D(binary,cv.CV_16S,kernel_y)    
absX = cv.convertScaleAbs(x)                # 因为算子中有-1,所以卷积后有部分是负数,需要取绝对值
absY = cv.convertScaleAbs(y)
Roberts = cv.addWeighted(absX,0.5,absY,0.5,0)   #  GX、Gy合成G
print(x[199])
print(absX[199])


stack = np.hstack([absX,absY,Roberts])

# 显示图片
cv.namedWindow("img",0)
cv.imshow("img",img)
cv.namedWindow("stack",0)
cv.imshow("stack",stack)
cv.namedWindow("Gx",0)
cv.imshow("Gx",absX)
cv.namedWindow("Gy",0)
cv.imshow("Gy",absY)
cv.namedWindow("G",0)
cv.imshow("G",Roberts)
cv.waitKey(0)

(5)边缘检测算子卷积原理演示:

  • 1.其实说白就是卷积原理,只是算子(等同卷积核、滤波核,不同应用叫法区别)叫Robert算子。

图3.1.6 代码实现效果

    1. 模拟效果:
      图3.1.6 代码实现效果

  • 3.实现代码:
"""
作者:OpenCv机器视觉
时间:2023/1/31
功能:模拟Robert原理

"""

import cv2 as cv
import numpy as np


# 底层原理,输出结果与cv.fliter2D的结果会发生做偏移1个像素
def robert_x_y(img,kernel):
    # 复制一张原图
    result = img.copy()

    # 对图像进行扩充1行1列,用于边缘的计算
    h, w = img.shape
    add_r = np.zeros((1,w))
    add_c = np.zeros((h+1,1))    # 后增加的需要多加多一个角点
    img = np.row_stack((img,add_r))
    img = np.column_stack((img,add_c))
    h, w = img.shape

     # 平滑图像进行卷积运算
    for i in range(h):
        for j in range(w):
            if (i+2 < h) and (j+2 < w):
                roi = img[i:i + 2, j:j + 2]
                list_robert = kernel * roi
                result[i, j] = abs(list_robert.sum())  # 求和加绝对值
                print('{' ':>9}'.format(abs(list_robert.sum())),end='') # 输出右对齐
        print()
    return  result




input = np.array([[0, 0, 0, 0, 0, 0, 0],
                    [0, 0, 0, 0, 0, 0, 0],
                    [0, 0, 255, 255, 255, 0, 0],
                    [0, 0, 255, 255, 255, 0, 0],
                    [0, 0, 255, 255, 255, 0, 0],
                    [0, 0, 0, 0, 0, 0, 0],
                    [0, 0, 0, 0, 0, 0, 0]
                    ],dtype=np.uint8)





# robert算子定义
kernel_x = np.array([[1,0],[0,-1]],np.int8)
kernel_y = np.array([[0,1],[-1,0]],np.int8)

# 自己实现的函数
robertx= robert_x_y(input,kernel_x)
roberty= robert_x_y(input,kernel_y)
robertxy = cv.addWeighted(robertx,0.5,roberty,0.5,0)

#调用Opencv的函数
x=cv.filter2D(input,cv.CV_16S,kernel_x)
y=cv.filter2D(input,cv.CV_16S,kernel_y)
absX = cv.convertScaleAbs(x)
absY = cv.convertScaleAbs(y)
Roberts = cv.addWeighted(absX,0.5,absY,0.5,0)

# 图片拼接
stack1 = np.hstack([robertx,roberty,robertxy])
stack2 = np.hstack([absX,absY,Roberts])
stack = np.vstack([stack1,stack2])


cv.namedWindow("input",0)
cv.imshow("input",input)

cv.namedWindow("stack",0)
cv.imshow("stack",stack)

cv.waitKey(0)

(6)推荐阅读文章:

1.图像分割 Robert算子
2.边缘检测之Robert算子
链接: link

3.2 Sobel 算子

(1)原理: 根据图像的梯度信息来检测边缘。
(2)特点: 耗时短、具有一定的抗噪性,边缘检测效果较好。

(3)算子介绍:

  • 1.跟Robert相似,只是算子模板不一样,分为水平方向模板(可以理解为卷积核)以及垂直方向模板(两个互为转置,A[i][j]=B[j][i])

图3.1.6 代码实现效果

(4)检测效果:
1.测试原图:同上
2.为了测量Sobel算子抗噪声能力,给图片添加三种噪声,同上。
3.除噪前后对比:Sobel算子具有 一 定 的 抗 噪 性。

(5)边缘检测算子卷积原理演示:
(6)推荐阅读文章:
【OpenCV 例程200篇】64. 图像锐化——Sobel 算子
https://blog.csdn.net/youcans/article/details/122152278
python+OpenCV图像处理(八)边缘检测
https://blog.csdn.net/qq_40962368/article/details/81416954
OpenCV——Sobel边缘检测
https://blog.csdn.net/qq_36686437/article/details/120814041
Sobel边缘检测
https://www.cnblogs.com/yibeimingyue/p/10878514.html

3.3Scharr 算子

(1)原理:
(2)特点:
(3)算子介绍:
(4)检测效果:
(5)边缘检测算子卷积原理演示:
(6)推荐阅读文章:

3.4 Prewitt 算子

(1)原理:
(2)特点:
(3)算子介绍:
(4)检测效果:
(5)边缘检测算子卷积原理演示:
(6)推荐阅读文章:

3.5 Kirsh 算子

(1)原理:
(2)特点:
(3)算子介绍:
(4)检测效果:
(5)边缘检测算子卷积原理演示:
(6)推荐阅读文章:

3.6 Robinson算子

(1)原理:
(2)特点:
(3)算子介绍:
(4)检测效果:
(5)边缘检测算子卷积原理演示:
(6)推荐阅读文章:

3.7 Laplacian 算子

(1)原理:
(2)特点:
(3)算子介绍:
(4)检测效果:
(5)边缘检测算子卷积原理演示:
(6)推荐阅读文章:

3.8 Canny算子

(1)原理:
(2)特点:
(3)算子介绍:
(4)检测效果:
(5)边缘检测算子卷积原理演示:
(6)推荐阅读文章:

3.9 深度学习的边缘检测

(1)优点:
(2)推荐了解模型:
边缘检测的各种微分算子比较(Sobel,Robert,Prewitt,Laplacian,Canny)
https://www.cnblogs.com/molakejin/p/5683372.html

不同算子边缘检测代码+效果对比

推荐阅读文章

  • 在边缘检测当中,知道其应用,已经有哪些方法可以实现边缘检测,每种边缘检测方法效果是怎么样的?哪一个边缘检测算法较好。然后具体原理,大家可以参考下面文章。我将一些写得不错得文章,放在这里方便大家阅读。

推荐阅读文章

1.数字图像处理:边缘检测(Edge detection)
Python图像锐化及边缘检测(Roberts、Prewitt、Sobel、Lapllacian、Canny、LOG)>
真实的产品案例:实现文档边缘检测
传统边缘检测算子
直线检测算法汇总直线检测算法博文中缺失的几个源码(Hough_line、LSD、FLD、EDlines、LSWMS、CannyLines、MCMLSD、LSM)

4.形态学操作

4.1 腐蚀

4.2 膨胀

4.3 开操作

4.4 闭操作

4.5 顶帽

4.6 黑帽:

4.7 形态学梯度:

猜你喜欢

转载自blog.csdn.net/DGWY161744/article/details/128582667