目录
一、卷积概念
1.定义
在图像处理领域,卷积是一种极其重要且基础的数学运算。它的核心思想是利用一个小矩阵,即卷积核(也常被称为滤波器),在输入图像上进行滑动操作。在滑动过程中,对于图像上的每个位置,卷积核会与该位置周围邻域内的像素进行相互作用。具体来说,就是将卷积核的每个元素与对应位置邻域内的像素值相乘,然后将这些乘积进行求和,得到的结果作为该位置新的像素值。通过这样的方式,对图像上的所有位置都进行处理后,最终生成经过卷积操作处理后的新图像。卷积操作就像是一个“特征提取器”,能够根据卷积核的不同设计,提取出图像中不同的特征信息。
2.边缘卷积计算
处理图像边缘像素的卷积计算是一个重要问题,因为卷积核在滑动过程中可能会超出图像边界,介绍一个最多使用的:
(1)镜像反射填充
通过镜像对称的方式扩展原始图像的边界
假设原图像为:
[ 1 2 3 4 5 6]
[ 7 8 9 10 11 12]
[13 14 15 16 17 18]
[19 20 21 22 23 24]
[25 26 27 28 29 30]
[31 32 33 34 35 36]
卷积核:
[1 1 1]
[1 1 1]
[1 1 1]
①例如要计算[0,0]处的值,采用镜像反射填充如下图所示,按这两个轴镜像反射,得到边缘
再进行卷积操作:
1×8+1×7+1×8 + 1×2+1×2+1×2 + 1×8+1×7+1×8=51
所以卷积操作后的[0,0]处的值为51
②再例如要计算[0,2]处的值
再进行卷积操作:
1×7+1×8+1×9 + 1×1+1×2+1×3 + 1×7+1×8+1×9=54
所以卷积操作后的[0,0]处的值为54
给出代码验证:
import cv2
import numpy as np
kernel=np.array([[1,1,1],[1,1,1],[1,1,1]],dtype=np.float32) #定义核
image = np.arange(1, 37).reshape(6, 6).astype(np.float32) #定义图像
for i in range(6): #打印原始图像
print('')
for j in range(6):
print(image[i][j],end=' ')
print('\n')
new=cv2.filter2D(image,-1,kernel) #进行卷积操作
for i in range(6): #打印卷积后的图像
print('')
for j in range(6):
print(new[i][j],end=' ')
(2)零填充(Zero Padding)
在图像边缘填充 0 值像素。例如,使用3×3 的卷积核,对一个 3×3 的图像进行 1 像素的零填充后,尺寸变为 5×5:再进行卷积操作,正好覆盖每一个像素
原始图像: 填充后:
[1 2 3] [0 0 0 0 0]
[4 5 6] → [0 1 2 3 0]
[7 8 9] [0 4 5 6 0]
[0 7 8 9 0]
[0 0 0 0 0]
3.数学原理
设输入图像为,它可以看作是一个二维的像素矩阵。卷积核为
,同样是一个二维矩阵。卷积操作输出图像
中每个像素值
由以下公式计算得出:
其中,k和l分别是卷积核在x和y方向上的半径。这个公式的含义是,对于输出图像
中坐标为
的像素,需要在输入图像
中以
为中心,取与卷积核大小(2k+1)×(2l+1)相同的邻域。然后,将该邻域内的像素值与卷积核
对应位置的元素值相乘,并将所有乘积相加,得到的和就是输出图像
中
位置的像素值。通过这个公式,卷积操作能够系统地对图像的每个像素进行处理,从而实现对图像的各种变换。
- g :输出图像,g(x,y) 表示卷积后坐标 (x,y) 处的像素值
- f:输入图像(二维像素矩阵),f(x,y) 表示坐标 (x,y) 处的像素值
- h:卷积核(二维矩阵,如 3×3 边缘检测核),h(u,v) 表示卷积核第 u 行、第 v 列的权重
- u,v为卷积核内的相对坐标,k和l分别是卷积核在x和y方向上的半径,通常卷积核大小为 (2k+1)×(2l+1)
4.实例计算
计算输出 g(2,2)
假设输入图像是一个 5×5 的矩阵,卷积核是一个 3×3 的矩阵。
输入图像矩阵:
卷积核矩阵:
这里,卷积核的尺寸为 3×3,因此 k=1,l=1。我们需要计算输出图像中的 g (2,2)。
根据卷积公式:
我们需要计算所有 u 和 v 的组合(-1,0,1)对应的乘积并求和:
代入具体数值:
所以,g (2,2) 的值为 -8。
5.作用
卷积操作在图像处理中具有广泛的应用,它能够改变图像的特征,以满足不同的处理需求。在图像滤波任务中,卷积可以去除图像中的噪声,使图像更加平滑;在边缘检测中,通过特定的卷积核设计,能够突出图像中像素值变化较大的区域,即图像的边缘;在特征提取方面,卷积能够提取出图像中的各种特征,如纹理、形状等,为后续的图像识别、目标检测等任务提供重要的信息。可以说,卷积操作是许多高级图像处理算法的基础。
二、针对图像噪声的滤波技术——均值滤波
图像噪声 是图像处理中常见的问题,它是由于各种原因引入的不希望的随机变化或干扰,导致图像质量下降。噪声可以出现在图像的亮度、颜色和纹理等方面,对图像分析、计算机视觉和图像处理任务造成困难。为了减少或消除图像中的噪声,常常使用不同类型的滤波技术。
高斯噪声:这是一种典型的连续随机噪声,其分布类似于高斯分布。它通常由电子器件或传感器的随机波动引起,会导致图像中像素值的微小随机变化。
椒盐噪声:这是一种离散的随机噪声,通常表现为图像中的亮点和暗点,类似于椒盐的颗粒。这种噪声可能是由于传感器损坏或数据传输错误引起的。
均匀噪声:均匀噪声是一种均匀分布的随机噪声,它在图像中引入一种均匀的背景噪声,会降低图像对比度。
波纹噪声:这种噪声表现为图像中的周期性亮度和暗度变化,通常是由于光照不均匀或传感器故障引起的。
1.均值滤波概念
均值滤波概定义:均值滤波是一种线性平滑滤波方法,它通过将每个像素周围的像素值的平均值替换当前像素值来减小噪声。在图像处理中常用于降噪(如消除随机噪声点),但会以模糊图像细节为代价
(1)均值滤波作用
均值滤波是一种简单而有效的滤波技术,用于去除图像中的噪声。均值滤波对于轻度高斯噪声去除效果良好,因为噪声通常表现为图像中随机出现的、与周围像素值差异较大的像素点,通过均值滤波的平均操作,可以使这些噪声点的影响被周围像素值所“稀释”,但在去除噪声的同时可能会导致图像细节的模糊。
(2)计算方式
均值滤波基于一个小的滑动窗口,将窗口中像素的平均值分配给窗口中心的像素。这个操作在整个图像上以滑动窗口的方式进行。它所使用的均值卷积核具有这样的特点:核内所有元素值相同,并且这些元素值的总和为 1。在进行均值滤波时,卷积核在图像上滑动,对于每个位置的邻域像素,将它们的像素值进行简单的平均计算,得到的平均值作为该位置新的像素值。
(3)均值滤波的缺点
由于它对邻域内所有像素一视同仁地进行平均,在平滑噪声的同时,也可能会使图像的边缘变得模糊。因为图像的边缘通常是像素值变化较为剧烈的区域,均值滤波的平均操作会削弱这种变化,导致边缘的清晰度下降。
2.均值滤波计算
均值滤波是一种常见的线性滤波操作,核心是用卷积核覆盖区域内像素的平均值来替换中心像素值,以下用实例计算g (2,2):
3.均值滤波 核心函数
(1)cv2.filter2D()
——使用 自定义卷积核
# 定义均值卷积核
kernel = np.ones((5, 5), np.float32) / 25
# 进行卷积操作(核心步骤)
dst = cv2.filter2D(image, -1, kernel)
① kernel = np.ones((5, 5), np.float32) / 25 解释:
- 创建一个 5×5 的矩阵,所有元素初始化为
1(ones)
- 每个元素除以
25
(即1/25 = 0.04
) - 这样的卷积核表示:取周围 25 个像素的平均值
- 卷积核越大会产生更强的平滑效果,但也会使图像边缘和细节更模糊
② dst = cv2.filter2D(image, -1, kernel) 解释:
cv2.filter2D()
是 OpenCV 的通用卷积函数,参数如下:image
:输入图像- ddepth=
-1
:输出图像的深度。-1表示输出图像(dst
)的数据类型与输入图像(image
)相同,例如输入是float32,输出也为float32- 常用的
ddepth
参数值:参数值 含义 -1
默认值,输出图像深度与输入图像一致(如输入是 uint8
,输出也为uint8
)。cv2.CV_8U
8 位无符号整数(0~255),用于表示灰度图或 RGB 图像。 cv2.CV_16U
16 位无符号整数(0~65535),适用于需要更高动态范围的场景(如高分辨率深度图)。 cv2.CV_16S
16 位有符号整数(-32768~32767),较少直接使用。 cv2.CV_32F
32 位浮点数(单精度),用于需要精确计算的场景(如梯度计算、滤波过程中避免溢出)。 cv2.CV_64F
64 位浮点数(双精度),用于高精度计算(如科学计算)。
- 常用的
kernel
:使用的卷积核- 第四个参数可以修改边缘处理方法,默认是镜像反射填充
- 它会将卷积核在图像上滑动,对每个像素执行以下操作:
- 取出当前像素周围 5×5 区域 的像素值
- 将这些值与卷积核对应位置相乘(这里相当于直接求和)
- 将结果除以 25(即求平均值)
- 用这个平均值替换当前像素的值
完整:dst = filter2D(src, ddepth, kernel[, dst[, anchor[, delta[, borderType]]]])
其中,参数:
- src 表示输入图像;
- dst 表示输出的边缘图,其大小和通道数与输入图像相同;
- ddepth 表示目标图像所需的深度;
- kernel 表示卷积核,一个单通道浮点型矩阵;
- anchor 表示内核的基准点,其默认值为 (-1,-1),位于中心位置;
- delta 表示在储存目标图像前可选的添加到像素的值,默认值为0;
- borderType 表示边框模式。
③效果:
- 每个像素的值被替换为其 5×5 邻域的平均值
- 噪声点(异常值)会被周围像素 “平均化”,从而达到 平滑图像、减少噪声 的效果
(2)cv.blur()
——均值滤波专用函数
blur = cv.blur(img, (5, 5))
cv.blur()
是 OpenCV 提供的均值滤波函数,它会用(5,5)
大小的卷积核(5×5 窗口),对img
(输入图像)逐像素计算邻域均值,结果存入blur
,实现均值滤波的核心逻辑 。
总代码:
import cv2
import numpy as np
import matplotlib.pyplot as plt
# 读取图像
image = cv2.imread('example.jpg', cv2.IMREAD_GRAYSCALE)
if image is None:
print("无法读取图像,请检查路径。")
else:
# 定义均值卷积核
kernel = np.ones((5, 5), np.float32) / 25
# 进行卷积操作
dst = cv2.filter2D(image, -1, kernel)
#上面这两行可以换成 blur = cv.blur(img, (5, 5))
# 显示原始图像和卷积后的图像
plt.subplot(121), plt.imshow(image, cmap='gray')
plt.title('Original Image'), plt.xticks([]), plt.yticks([])
plt.subplot(122), plt.imshow(dst, cmap='gray')
plt.title('Convolved Image'), plt.xticks([]), plt.yticks([])
plt.show()
代码解释
- 读取图像:cv2.imread('example.jpg', cv2.IMREAD_GRAYSCALE) 使用 OpenCV 的 imread 函数以灰度模式读取图像文件 example.jpg。灰度模式下,图像被表示为单通道矩阵,更便于进行滤波操作。如果图像读取失败,即返回值为 None,则打印提示信息,告知用户检查图像路径是否正确。
- 定义均值卷积核:kernel = np.ones((5, 5), np.float32) / 25 使用 NumPy 库创建一个大小为 5×5 的矩阵,其中所有元素初始值都为 1。然后将每个元素除以 25,使得卷积核内所有元素值的总和为 1,符合均值卷积核的要求。
- 进行卷积操作:dst = cv2.filter2D(image, -1, kernel) 使用 OpenCV 的 filter2D 函数对输入图像 image 进行二维卷积操作。
- 参数1:输入图像
- 参数 2:输出图像的深度(数据类型),决定了像素值的范围。
ddepth = -1
:表示输出图像的深度与输入图像相同(例如输入是uint8
,输出也为uint8
)。 - 参数 3:kernel 则是前面定义的均值卷积核。
- 函数返回经过卷积处理后的图像 dst。
- 显示原始图像和卷积后的图像:使用 matplotlib 库的 subplot 函数创建一个包含两个子图的绘图布局。
plt.subplot(121)
,这里的1
表示整个绘图区域被分成 1 行,2
表示分成 2 列,最后的1
代表当前操作的是第 1 个子图,表示第一个子图位于 1 行 2 列的第 1 个位置。plt.imshow(image, cmap='gray') 显示原始灰度图像,设置颜色映射为 gray 以正确显示灰度图像。plt.title('Original Image') 设置子图标题,plt.xticks([]) 和 plt.yticks([]) 则隐藏坐标轴刻度,使图像显示更加简洁。同理,plt.subplot(122) 显示卷积后的图像 dst,并进行相应的设置。最后 plt.show() 显示整个绘图。 - 使用
plt.subplot(nrows, ncols, index)
创建子图时,最后创建的子图会成为当前活跃子图。plt.xticks([])
和plt.yticks([])
等函数默认作用于当前活跃的子图(即最后一个通过plt.subplot()
创建的子图)。若连续调用plt.subplot()
创建多个子图后,未明确切换子图,则后续的plt.xticks([])
会应用到最后创建的子图。
均值滤波结果:
三.中值滤波
1.定义
中值滤波:中值滤波是一种非线性滤波方法,它用窗口中像素值的中值来替代当前像素的值。中值滤波的原理如下:
- 将一个小的滑动窗口(通常是正方形的)放在图像的每个像素上。
- 将窗口中的所有像素值排序,找到中值(即中间位置的像素值)。
- 使用中值来替代当前像素的值。
中值滤波对椒盐噪声和脉冲噪声的去除效果非常好,因为它不受极端值的影响。它可以保留图像的边缘和细节,但可能对高斯噪声的去除效果不如高斯滤波。
2.选择滤波方法
选择适当的滤波方法取决于噪声的类型和图像的特性。通常,均值滤波对于轻度高斯噪声有效,而高斯滤波对于更强的高斯噪声适用。中值滤波通常在椒盐噪声和脉冲噪声的情况下表现良好,但不适用于高斯噪声。
此外,滤波器的窗口大小也是一个重要的参数。较小的窗口适用于去除细小的噪声,但可能会丧失图像细节。较大的窗口可以更好地去除大面积的噪声,但可能会模糊图像。综上所述,选择合适的滤波方法需要对噪声和图像进行仔细分析,以平衡噪声去除和图像细节保留之间的权衡。
3.中值滤波原理
中值滤波会取当前像素点及其周围临近像素点(一共有奇数个像素点)的像素值,将这些像素值排序,然后将位于中间位置的像素值作为当前像素点的像素值。 对如下矩阵:
将其邻域设置为3×3大小,对其3×3邻域内像素点的像素值进行排序(升序降序均可),按升序排序后得到序列值为:[66,78,90,91,93,94,95,97,101]。在该序列中,处于中心位置(也叫中心点或中值点)的值是“93”,因此用该值替换原来的像素值78,作为当前点的新像素值。中值滤波效果如下:
4.核心函数
在OpenCV中,实现中值滤波的函数是cv2.medianBlur(),其语法格式如下:
dst=cv2.medianBlur(src,ksize)
- dst是返回值,表示进行中值滤波后得到的处理结果。
- src 是需要处理的图像,即源图像。它能够有任意数量的通道,并能对各个通道独立处理。图像深度应该是CV_8U、CV_16U、CV_16S、CV_32F 或者 CV_64F中的一种。
- ksize 是滤波核的大小。滤波核大小是指在滤波处理过程中其邻域图像的高度和宽度。需要注意,核大小必须是比1大的奇数,比如3、5、7等。 例如:
ksize=3
表示使用 3×3 的邻域窗口。
5.总代码
import cv2 as cv
def cv_show(name, img):
cv.imshow(name, img)
cv.waitKey(0)
cv.destroyAllWindows()
#添加椒盐噪声函数
def add_peppersalt_noise(image, n=10000):
result = image.copy()
# 测量图片的长和宽
w, h =image.shape[:2]
# 生成n个椒盐噪声
for i in range(n):
x = np.random.randint(1, w)
y= np.random.randint(1, h)
if np.random.randint(0, 2) == 0 :
result[x, y] = 0
else:
result[x,y] = 255
return result
#读图像
img = cv.imread('D:\\dlam.jpg')
if img is None:
print('Failed to read the image')
#添加椒盐噪声
img1 = add_peppersalt_noise(img)
cv_show('after', img1)
# 中值滤波,可对灰色图像和彩色图像使用
img2 = cv.medianBlur(img1, 3)
cv_show('after1', img2)
# ksize变大图像变模糊
img3 = cv.medianBlur(img1, 9)
cv_show('after2', img3)
详细解释 添加椒盐噪声函数:
#添加椒盐噪声函数
def add_peppersalt_noise(image, n=10000):
result = image.copy()
# 测量图片的长和宽
w, h =image.shape[:2]
# 生成n个椒盐噪声
for i in range(n):
x = np.random.randint(1, w)
y= np.random.randint(1, h)
if np.random.randint(0, 2) == 0 :
result[x, y] = 0
else:
result[x,y] = 255
return result
- add_peppersalt_noise(image, n=10000)参数
image
:输入图像,是一个 NumPy 数组,数据类型通常为uint8
。n
:要添加的噪声点数量,默认值为 10000。
np.random.randint(0, w)
:生成一个范围在[0, w-1)
之间的随机整数,这里的w
是图像的宽度。np.random.randint(0, h)
:生成一个范围在[0, h-1)
之间的随机整数,h
是图像的高度。
np.random.randint(0, 2)
:生成随机整数 0 或者 1,且生成这两个数的概率是相等的。- 噪声类型判断:
- 当结果为 0 时,把当前像素值设为 0,也就是黑色,这就是 “椒噪声”。
- 当结果为 1 时,将当前像素值设为 255,即白色,这就是 “盐噪声”。
最后还是用subplot将图片并排比较看着明显:
import cv2
import numpy as np
import matplotlib.pyplot as plt
def add_peppersalt_noise(img,n=10000):
result=img.copy()
h,w=img.shape[:2]
for i in range(n):
x=np.random.randint(1,w)
y=np.random.randint(1,h)
if np.random.randint(0,2)==0:
result[x,y]=0
else:
result[x,y]=255
return result
img=cv2.imread('Y:\\pycharm\\lion.png',cv2.IMREAD_GRAYSCALE)
if img is None:
print('无法读取')
else:
img1=add_peppersalt_noise(img)
img2=cv2.medianBlur(img1,3)
img3=cv2.medianBlur(img1,5)
plt.subplot(131),plt.imshow(img1,cmap='gray')
plt.title('img1'),plt.xticks([]),plt.yticks([])
plt.subplot(132), plt.imshow(img2, cmap='gray')
plt.title('img2'), plt.xticks([]), plt.yticks([])
plt.subplot(133), plt.imshow(img3, cmap='gray')
plt.title('img3'), plt.xticks([]), plt.yticks([])
plt.show()