双边滤波的实验原理和在python上的具体代码实现
图像去噪是用于解决图像由于噪声干扰而导致其质量下降的问题,通过去噪技术可以有效地提高图像质量,增大信噪比,更好的体现原来图像所携带的信息。在我们的图像中常见的噪声主要有以下4种:加性噪声、乘性噪声、量化噪声、椒盐噪声。根据不同的噪声特点,我们可以采用不同的去噪算法,按照数学运算主要分为两大类,一类是通过滤波(相当于积分的过程),又可以在空域(和频率域(傅立叶变换和小波变换)中分别采用此操作,比如说空域中值滤波对于椒盐噪声有很好的处理效果,另一类是通过偏微分方程,具有各向异性的特点,具有平滑图像和将边缘尖锐化的能力,在低噪声密度的图像处理中取得了较好的效果,但是在处理高噪声密度图像时去噪效果不好。
双边滤波不同于以往的平滑滤波,是一种常用于图像边缘保持的空间域非线性滤波方法,主要是利用邻域内像素点的空间邻近度和像素值相似度来构建高斯权重滤波器。由Tomasi等人提出该算法,在图像处理趋于有着广泛的应用,比如去噪,去马赛克,流光估计等等。
双边滤波之所以能够既作平滑处理又保留边界,是因为它综合了高斯滤波器和α-截尾均值滤波器的特点,同时考虑了空间域与值域,即其核是由空间域核和值域核相乘得到。
① opencv库,是专门解决计算机视觉的库函数,在本次实验中用于打开、存储图片,RGB图片转灰度图,调用其双边滤波函数等图像操作
② math库,提供了许多数学运算函数,本次实验中用到了其中的幂次函数来计算两个滤波模板中涉及到的系数。
③ numpy库,主要用于处理任意维度数组和矩阵。本次实验用此库了构造n*n大小的模板矩阵。
④ matplotlib库,一个常用的画图库,本次实验中用于将多幅图放在一起作对比分析。
模板不同参数对不同图像处理效果的分析和探究
共有3个参数可以设置,对于一幅256*256像素大小的RGB图像,作3组实验,比较其运行时间及图像模糊程度。
① 模板大小kernel_size改变,两个标准差为10时的运行时间如下表1所示。
双边滤波与其他去噪算法的图像处理效果对比与分析
① 为图像人工加入随机噪声,接着用高斯滤波,均值滤波,中值
滤波,双边滤波进行去噪处理,比较其去噪效果。
② 对于一幅真实人像,接着用高斯滤波,均值滤波,中值滤波,
双边滤波进行去噪处理,比较其去噪效果。
python 3.9
import cv2
import math
import numpy as np
import matplotlib.pyplot as plt
import random
#构造距离高斯模板,即模板的中心权重系数高,离中心距离越远,权重系数越小
def kernel_distance_gaussian(kernel_size, gsigma):
#定中心,找模板边界[-r,r]
row = kernel_size // 2
column = kernel_size // 2
#方差
gsigma2 = gsigma * gsigma
# 高斯系数 1 / 根号2π * σ^2
coefficient = 1 / (math.sqrt(2 * math.pi) * gsigma2)
#构造二维矩阵0
gkernel = np.zeros((kernel_size, kernel_size))
#(i,j)为模板中心像素坐标,按照高斯公式
for i in range(-row, row + 1):
for j in range(-column, column + 1):
gkernel[i + row][j + column] = coefficient * np.exp(-(i * i + j * j) / (2 * gsigma2))
return gkernel
#构造像素值高斯模板,即模板的中心权重系数高,与中心像素值差异越大的像素权重系数越小
def kernel_pixel_gaussian(image, kernel_size, psigma, centroid_x, centroid_y, channel):
#定中心,找模板边界[-r,r]
row = kernel_size // 2
column = kernel_size // 2
#对于边界上的像素点需要打补丁,便于后面用模板进行卷积
# image1 = np.pad(image, ((row, column), (row, column)), constant_values=0)
# image = image1
#方差
psigma2 = 2 * psigma * psigma
pkernel = np.zeros((kernel_size, kernel_size))
# image[centroid_x][centroid_y]为模板中心像素值,
for m in range(-row, row + 1):
for n in range(-column, column + 1):
for k in range(0, channel):
pkernel[m + row][n + column] = np.exp(
-pow((image[centroid_x][centroid_y][k] - image[centroid_x + m][centroid_y + n][k]), 2) / (2 * psigma2))
return pkernel
#构造双边滤波模板,为上述两个模板相乘
def kernel_bilateral(image, kernel_size, psigma, gsigma, centroid_x, centroid_y, channel):
kernel = np.zeros((kernel_size, kernel_size))
#得到距离高斯模板
gkernel = kernel_distance_gaussian(kernel_size, gsigma)
pkernel = kernel_pixel_gaussian(image, kernel_size, psigma, centroid_x, centroid_y, channel)
for i in range(0, kernel_size):
for j in range(0, kernel_size):
kernel[i][j] = gkernel[i][j] * pkernel[i][j]
sum_kernel = sum(sum(kernel))
kernel = kernel / sum_kernel
return kernel
#开始双边滤波
def start_bilateral(image, kernel_size, psigma, gsigma):
r = kernel_size // 2
c = kernel_size // 2
# # 对于边界上的像素点需要打补丁,便于后面用模板进行卷积
# image1 = np.pad(image, ((r, c), (r, c)), constant_values=0)
# image = image1
#读取图像的行数和列数
row = image.shape[0]
col = image.shape[1]
#RGB为3通道图或灰度图
if len(image.shape) == 2:
channel = 1
else:
channel = image.shape[2]
#bilater_image = np.zeros((row, col))
image = image.reshape(row, col, channel)
#用0打补丁,方便卷积
image1 = np.pad(image, ((r, c), (r, c), (0,0)), constant_values=0)
bilater_image = image.copy()
for i in range(r, row-r):
for j in range(c, col-c):
for k in range(channel):
kernel_all = kernel_bilateral(image1, kernel_size, psigma, gsigma, i, j, k)
# img_padding = np.pad(img_1channel, ((r, c), (r, c)), constant_values=0)
# bilater_image[i][j][k] = np.sum(np.multiply(image[i-r:i+r+1,j-c:j+c+1],kernel_all))
# 用每一个小模板与下面的像素值相乘求和做卷积
weight =0
for m in range(-r, r + 1):
for n in range(-c, c + 1):
weight += image[i + m][j + n][k] * kernel_all[m + r][n + c]
bilater_image[i][j][k] = weight
return bilater_image
def pepper_and_salt(img, percentage):
num = int(percentage * img.shape[0] * img.shape[1]) # 椒盐噪声点数量
random.randint(0, img.shape[0])
img2 = img.copy()
for i in range(num):
X = random.randint(0, img2.shape[0] - 1) # 从0到图像长度之间的一个随机整数,因为是闭区间所以-1
Y = random.randint(0, img2.shape[1] - 1)
if random.randint(0, 1) == 0: # 黑白色概率55开
img2[X, Y] = (255, 255, 255) # 白色
else:
img2[X, Y] = (0, 0, 0) # 黑色
return img2
if __name__ == '__main__':
path = 'face2.png'
#image = cv2.imread(path, cv2.IMREAD_GRAYSCALE)
image = cv2.imread(path, 1)
#cans
# if kernel_size % 2 == 0:
# print("ERROR: the value of kernel_size should be odd!")
gsigma = 5
psigma = 5
#对于边界上的像素点需要打补丁,便于后面用模板进行卷积
# image_bilateral = start_bilateral(image, kernel_size=1 , psigma , gsigma)
# image_bilateral = start_bilateral(image, kernel_size=3, psigma, gsigma)
image_bilateral = start_bilateral(image, kernel_size=55, psigma = 5, gsigma = 5)
cv2.imwrite("face2_kernel5psigma20gsigma20.png",image_bilateral)
plt.figure()
plt.subplot(121)
plt.imshow(cv2.cvtColor(image, cv2.COLOR_BGR2RGB)), plt.axis('off')
plt.subplot(122)
plt.imshow(cv2.cvtColor(image_bilateral, cv2.COLOR_BGR2RGB)), plt.axis('off')
plt.show()