图像恢复重建--去噪算法

在这里插入图片描述

图像恢复重建

一、问题简介

图像是一种非常常见的信息载体,但是在图像的获取、传输、存储的过程中可能由于各种原因使得图像受到噪声的影响。
如何去除噪声的影响,恢复图像原本的信息是计算机视觉中的重要研究问题。
以下通过人为的加上一定的噪声,然后通过不同的去噪算法进行去噪,观察比较不同算法的实现效果。

代码建议在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

最终效果:
在这里插入图片描述
在这里插入图片描述

四、总结

最终发现这里使用中值滤波的效果并没有均值的效果好(也可能是自己写的代码的问题)。目前存在的一个疑惑就是误差越大,图片的效果看起来越好?
后面有机会的话再尝试其他的滤波方法,大家觉得有用的话就给个关注吧!

猜你喜欢

转载自blog.csdn.net/m0_46366547/article/details/128265790