图像恢复重建
一、问题简介
图像是一种非常常见的信息载体,但是在图像的获取、传输、存储的过程中可能由于各种原因使得图像受到噪声的影响。
如何去除噪声的影响,恢复图像原本的信息是计算机视觉中的重要研究问题。
以下通过人为的加上一定的噪声,然后通过不同的去噪算法进行去噪,观察比较不同算法的实现效果。
代码建议在notebook中运行,直接在exe中运行可能会出错
二、定义相关函数
1.导包
from matplotlib import pyplot as plt # 展示图片
import numpy as np # 数值处理
import cv2 # opencv库
from sklearn.linear_model import LinearRegression, Ridge, Lasso # 回归分析
import random
from skimage.measure import compare_ssim as ssim
from scipy import spatial
from PIL import Image
2.定义相关函数
read_image函数;读取图片
plot_image函数:展示图片
save_image函数:保存图片
normalization函数:将图片数据归一化
get_noise_mask函数:获取噪声图像
compute_error函数:评估恢复后的图像与原始图像之间的误差;越小越好
calc_ssim函数:计算Cosine 相似度;越大越好
calc_csim函数:计算SSIM 相似度;越大越好
def read_image(img_path):
"""
读取图片,图片是以 np.array 类型存储
:param img_path: 图片的路径以及名称
:return: img np.array 类型存储
"""
# 读取图片
img = cv2.imread(img_path)
# 彩色图像使用 OpenCV 加载时是 BGR 模式,后面使用matplotlib显示图像时是采用RGB模式,所以这里需要将 BGR模式转换为 RGB 模式【cv2.cvtColor】
if len(img.shape) == 3:
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
return img
def plot_image(image, image_title, is_axis=False):
"""
展示图像
:param image: 展示的图像,一般是 np.array 类型
:param image_title: 展示图像的名称
:param is_axis: 是否需要关闭坐标轴,默认展示坐标轴
:return:
"""
# 展示图片
plt.imshow(image)
# 关闭坐标轴,默认关闭
if not is_axis:
plt.axis('off')
# 展示受损图片的名称
plt.title(image_title)
# 展示图片
plt.show()
def save_image(filename, image):
"""
将np.ndarray 图像矩阵保存为一张 png 或 jpg 等格式的图片
:param filename: 图片保存路径及图片名称和格式
:param image: 图像矩阵,一般为np.array
:return:
"""
# np.copy() 函数创建一个副本。
# 对副本数据进行修改,不会影响到原始数据,它们物理内存不在同一位置。
img = np.copy(image)
# 从给定数组的形状中删除一维的条目
img = img.squeeze()
# 将图片数据存储类型改为 np.uint8
if img.dtype == np.double:
# 若img数据存储类型是 np.double ,则转化为 np.uint8 形式
img = img * np.iinfo(np.uint8).max
# 转换图片数组数据类型
img = img.astype(np.uint8)
# 将 RGB 方式转换为 BGR 方式
img = cv2.cvtColor(img, cv2.COLOR_RGB2BGR)
# 生成图片
cv2.imwrite(filename, img)
def normalization(image):
"""
将数据线性归一化
:param image: 图片矩阵,一般是np.array 类型
:return: 将归一化后的数据,在(0,1)之间
"""
# 获取图片数据类型对象的最大值和最小值
info = np.iinfo(image.dtype)
# 图像数组数据放缩在 0-1 之间
return image.astype(np.double) / info.max
def get_noise_mask(noise_img):
"""
获取噪声图像,一般为 np.array
:param noise_img: 带有噪声的图片
:return: 噪声图像矩阵
"""
# 将图片数据矩阵只包含 0和1,如果不能等于 0 则就是 1。
return np.array(noise_img != 0, dtype='double')
def compute_error(res_img, img):
"""
计算恢复图像 res_img 与原始图像 img 的 2-范数
:param res_img:恢复图像
:param img:原始图像
:return: 恢复图像 res_img 与原始图像 img 的2-范数
"""
# 初始化
error = 0.0
# 将图像矩阵转换成为np.narray
res_img = np.array(res_img)
img = np.array(img)
# 如果2个图像的形状不一致,则打印出错误结果,返回值为 None
if res_img.shape != img.shape:
print("shape error res_img.shape and img.shape %s != %s" % (res_img.shape, img.shape))
return None
# 计算图像矩阵之间的评估误差
error = np.sqrt(np.sum(np.power(res_img - img, 2)))
return round(error,3)
def calc_ssim(img, img_noise):
"""
计算图片的结构相似度
:param img: 原始图片, 数据类型为 ndarray, shape 为[长, 宽, 3]
:param img_noise: 噪声图片或恢复后的图片,
数据类型为 ndarray, shape 为[长, 宽, 3]
:return:
"""
return ssim(img, img_noise,
multichannel=True,
data_range=img_noise.max() - img_noise.min())
def calc_csim(img, img_noise):
"""
计算图片的 cos 相似度
:param img: 原始图片, 数据类型为 ndarray, shape 为[长, 宽, 3]
:param img_noise: 噪声图片或恢复后的图片,
数据类型为 ndarray, shape 为[长, 宽, 3]
:return:
"""
img = img.reshape(-1)
img_noise = img_noise.reshape(-1)
return 1 - spatial.distance.cosine(img, img_noise)
3.加上噪声
思路:先生成一个噪声遮罩mask,和原图的矩阵数据相乘,即可到受损图像。
由于原始图像数据的维度是(h,w,channel),可以先转换成(channel,h,w)。h,w分别表示图像的高和宽,channel指通道维。可以使用np.trsnspose函数。
转换后img1的矩阵[channel,h,w]就可以理解为有3个[h,w]的矩阵,依次对应着RGB3个通道。
def noise_mask_image(img, noise_ratio=[0.8,0.4,0.6]):
"""
根据题目要求生成受损图片
:param img: cv2 读取图片,而且通道数顺序为 RGB
:param noise_ratio: 噪声比率,类型是 List,,内容:[r 上的噪声比率,g 上的噪声比率,b 上的噪声比率]
默认值分别是 [0.8,0.4,0.6]
:return: noise_img 受损图片, 图像矩阵值 0-1 之间,数据类型为 np.array,
数据类型对象 (dtype): np.double, 图像形状:(height,width,channel),通道(channel) 顺序为RGB
"""
# 受损图片初始化
noise_img = None
# -------------实现受损图像答题区域-----------------
#mask为噪声遮罩;noise_img为受损图片
img1=np.transpose(img.copy(),(2,0,1))
mask=np.transpose(img,(2,0,1)) #对img的矩阵作变换,将channel通道放在第一个维度
for i in range(mask.shape[0]):
for j in range(mask.shape[1]):
rate=int(noise_ratio[i]*mask.shape[2])
mask[i][j][:rate]=0
mask[i][j][rate:]=1
np.random.shuffle(mask[i][j])
noise_img=img1*mask
noise_img=np.transpose(noise_img,(1,2,0))
return noise_img
生成的加噪后图片:
三、滤波处理方法
1.均值滤波
(1)如果对所有像素点一起处理,效果并不好:
def restore_image(noise_img, size=4):
"""
使用 你最擅长的算法模型 进行图像恢复。
:param noise_img: 一个受损的图像
:param size: 输入区域半径,长宽是以 size*size 方形区域获取区域, 默认是 4
:return: res_img 恢复后的图片,图像矩阵值 0-1 之间,数据类型为 np.array,
数据类型对象 (dtype): np.double, 图像形状:(height,width,channel), 通道(channel) 顺序为RGB
"""
# 恢复图片初始化,首先 copy 受损图片,然后预测噪声点的坐标后作为返回值。
res_img = np.copy(noise_img)
# 获取噪声图像
noise_mask = get_noise_mask(noise_img)
# -------------实现图像恢复代码答题区域----------------------------
noise_img = np.transpose(noise_img, (2, 0, 1))
size = 3 # 核的尺寸
num = int((size - 1) / 2) # 输入图像需要填充的尺寸
list = []
list1 = []
for x in range(3):
img = cv2.copyMakeBorder(noise_img[x], num, num, num, num, cv2.BORDER_REPLICATE)
h, w = img.shape[0:2] # 获取输入图像的长宽和高
for i in range(num, h - num): # 遍历出原图像
for j in range(num, w - num):
list.extend(
[img[i, j], img[i - 1, j - 1], img[i - 1, j], img[i - 1, j + 1], img[i, j - 1], img[i, j + 1],
img[i + 1, j], img[i + 1, j - 1], img[i + 1, j + 1]])
# list.sort() #中值滤波
list[4] = sum(list) / 9 # 均值滤波
list1.append(list[4])
list = []
res_img = np.array(list1).reshape(noise_img.shape)
res_img = np.transpose(res_img, (1, 2, 0))
# ---------------------------------------------------------------
return res_img
最终效果:
(2)只针对有效像素点进行处理:
def restore_image(noise_img, size=4):
"""
使用 你最擅长的算法模型 进行图像恢复。
:param noise_img: 一个受损的图像
:param size: 输入区域半径,长宽是以 size*size 方形区域获取区域, 默认是 4
:return: res_img 恢复后的图片,图像矩阵值 0-1 之间,数据类型为 np.array,
数据类型对象 (dtype): np.double, 图像形状:(height,width,channel), 通道(channel) 顺序为RGB
"""
# 恢复图片初始化,首先 copy 受损图片,然后预测噪声点的坐标后作为返回值。
res_img = np.copy(noise_img)
# 获取噪声图像
noise_mask = get_noise_mask(noise_img)
# -------------实现图像恢复代码答题区域----------------------------
#只针对有效像素点进行均值处理
init_img = np.transpose(res_img, (2, 0, 1))
init_mask= np.transpose(noise_mask, (2, 0, 1))
res_img = np.copy(init_img)
gao, kuang = res_img.shape[1], res_img.shape[2]
size = 2 # 核的尺寸;可以自定义大小,size越大,恢复效果越模糊
valid_pixel = 0 #定义表示有效像素点变量【valid_pixel】
for channel in range(3):
for i in range(gao):
for j in range(kuang):
valid_pixel=init_mask[channel][i:i + size, j:j + size].sum() #获取每个size区域内有效像素(不为0的像素)的个数
if valid_pixel != 0:
res_img[channel][i][j] = (init_img[channel][i:i + size, j:j + size] * init_mask[channel][i:i + size,j:j + size]).sum() / valid_pixel
valid_pixel = 0
else:
size+=1 #如果size区域内有效像素点个数为0,则扩大处理区域
for channel in range(3):
for i in range(gao):
for j in range(kuang):
valid_pixel = init_mask[channel][i:i + size, j:j + size].sum()
if valid_pixel != 0:
res_img[channel][i][j] = (init_img[channel][i:i + size, j:j + size] * init_mask[
channel][
i:i + size,
j:j + size]).sum() / valid_pixel
res_img=np.transpose(res_img,(1,2,0))
# ---------------------------------------------------------------
return res_img
最终效果:
2.中值滤波
思路和上面均值相同,只是对滤波器窗口内所有有效像素点的值取它们的中值。
由于想用到np.median方法,所有先转化成列表后删除无效像素值,在转化成矩阵使用该方法。
def restore_image(noise_img, size=4):
"""
使用 你最擅长的算法模型 进行图像恢复。
:param noise_img: 一个受损的图像
:param size: 输入区域半径,长宽是以 size*size 方形区域获取区域, 默认是 4
:return: res_img 恢复后的图片,图像矩阵值 0-1 之间,数据类型为 np.array,
数据类型对象 (dtype): np.double, 图像形状:(height,width,channel), 通道(channel) 顺序为RGB
"""
# 恢复图片初始化,首先 copy 受损图片,然后预测噪声点的坐标后作为返回值。
res_img = np.copy(noise_img)
# 获取噪声图像
noise_mask = get_noise_mask(noise_img)
# -------------实现图像恢复代码答题区域----------------------------
#只针对有效像素点进行中值滤波
init_img = np.transpose(res_img, (2, 0, 1))
init_mask= np.transpose(noise_mask, (2, 0, 1))
res_img = np.copy(init_img)
gao, kuang = res_img.shape[1], res_img.shape[2] #获取图像的高和宽
size = 2 # 核的尺寸
valid_pixel = 0 #定义表示有效像素点变量【valid_pixel】
for channel in range(3):
for i in range(gao):
for j in range(kuang):
valid_pixel = init_mask[channel][i:i + size, j:j + size].sum() # 计算有效像素点个数
if valid_pixel != 0:
r1 = (init_img[channel][i:i + size, j:j + size] * init_mask[channel][i:i + size,
j:j + size]).flatten() #将矩阵展平成一维数组
r2 = np.matrix.tolist(r1) # r1转换为列表
r3 = list(set(r2)) # 去掉r2中的'0'元素,但是转换成集合后还是会留下一个'0'
if '0' in r3:
r3.remove(0) # 删除唯一的0元素
r4 = np.median(np.array(r3)) # r3再转换为矩阵并求中值
res_img[channel][i][j] = r4
else:
size+=1 #如果size区域内有效像素点个数为0,则扩大处理区域
for channel in range(3):
for i in range(gao):
for j in range(kuang):
valid_pixel = init_mask[channel][i:i + size, j:j + size].sum()
if valid_pixel != 0:
r1 = (init_img[channel][i:i + size, j:j + size] * init_mask[channel][i:i + size,
j:j + size]).flatten()
r2 = np.matrix.tolist(r1)
r3 = list(set(r2))
if '0' in r3:
r3.remove(0)
r4 = np.median(np.array(r3))
res_img[channel][i][j] = r4
res_img=np.transpose(res_img,(1,2,0))
# ---------------------------------------------------------------
return res_img
最终效果:
四、总结
最终发现这里使用中值滤波的效果并没有均值的效果好(也可能是自己写的代码的问题)。目前存在的一个疑惑就是误差越大,图片的效果看起来越好?
后面有机会的话再尝试其他的滤波方法,大家觉得有用的话就给个关注吧!