a) 噪声定义文件 noise.py

from abc import ABC
from typing import Tuple
import numpy as np
from functools import singledispatchmethod
import cv2
# from IPython import embed

# from numpy.typing import ArrayLike

__all__ = [
    "NoiseGenerator",
    "UniformNoiseGenerator",
    "NormalNoiseGenerator",
    "GaussianNoiseGenerator",
    "SaltPepperNoiseGenerator",
]


class NoiseGenerator:
    """The base distribution (without uniform noise)."""
    __slots__ = []

    def __init__(self):
        pass

    @singledispatchmethod
    def generate_noise(self):
        """Generating noise which should be implemented"""
        raise NotImplementedError

    def add_noise(self, image: np.ndarray) -> np.ndarray:
        """Adding noise to image"""
        image_shape = image.shape
        return image + self.generate_noise(image_shape=image_shape)
  1. __all__定义将定义的所有类,从而更好的供后续使用,定义噪声基类与虚函数。
  • 这里本来想用 python 3.8 提供的 @singledispatchmethod 装饰器,在抽象基类里定义两个虚函数,然后在继承的时候重载函数及调用方式,但是发现如果是继承的类好像不能直接继承(?)因为会报没有这个方法的错,只能把 @singledispatchmethod 写在类内部?不知道能否有人解答我的疑惑。
class UniformNoiseGenerator(NoiseGenerator, ABC):
    """The base distribution with uniform noise."""
    __slots__ = ['min_val', 'max_val']

    def __init__(self, min_val: float = 0, max_val: float = 1):
        super(UniformNoiseGenerator, self).__init__()
        self.min_val = min_val
        self.max_val = max_val

    def generate_uniform_noise(self,
                               min_val: float = 0,
                               max_val: float = 1,
                               image_shape: Tuple[int, int] = None) -> np.ndarray:
        """Generating noise
        :param:     image_shape:        Image shape of generated noise
        :return:    output_noise:       ArrayLike noise or single noise
        """
        if min_val is None and max_val is None:
            min_val = self.min_val
            max_val = self.max_val
        assert (min_val < max_val)  # assertion
        distribution_range = max_val - min_val  # getting range of Uniform(0, b - a)
        moving_factor = distribution_range - max_val  # getting move of Uniform(a, b) from Uniform(0, b - a)

        if image_shape is None:  # just generate one noise
            return np.random.rand() * distribution_range - moving_factor
        return np.random.rand(*image_shape) * distribution_range - moving_factor  # ArrayLike output

    def add_noise(self, image: np.ndarray) -> np.ndarray:
        """Adding noise to image"""
        image_shape = image.shape
        return image + self.generate_uniform_noise(image_shape=image_shape)
  1. 定义均匀噪声类,使用numpy.random.rand生成0,1之间的均匀噪声,同时利用平移和放缩的到任意范围的均匀噪声。

    • 由于前面定义的的是抽象类,因此后面继承的时候需要同时继承 ABC 抽象类。

    • 感觉这里写的有些问题,可能应该写成

      class Noise(abc.ABCMeta):
          @abc.abstractmethod  
          def read(self):
              pass
      

      不太确定。

    • 用__slots__减少类初始化和定义时的时间消耗。

    • numpy.random.rand(*image_shape) 时利用了拆包,类似于 *args 的用法

class NormalNoiseGenerator(UniformNoiseGenerator, ABC):
    """The base distribution with additive i.i.d. uniform noise."""

    def __init__(self):
        super(NormalNoiseGenerator, self).__init__()

    def generate_normal_noise(self, image_shape: Tuple[int, int]) -> np.ndarray:
        """Generating Normal noise"""
        uniform_noise_1 = self.generate_uniform_noise(0, 1, image_shape)
        uniform_noise_2 = self.generate_uniform_noise(0, 1, image_shape)
        normal_noise = np.cos(2.0 * np.pi * uniform_noise_1) * np.sqrt(-2.0 * np.log(uniform_noise_2))
        return normal_noise

    def add_noise(self, image: np.ndarray) -> np.ndarray:
        """Adding noise to image"""
        image_shape = image.shape
        return image + self.generate_normal_noise(image_shape=image_shape)
  1. 继承均匀噪声类,使用 Box-Muller 变换得到标准正态分布的噪声。
    • Box-Muller 的核心就是通过极坐标变换将正态分布的指数变换为极坐标的辐角的均匀分布。
class GaussianNoiseGenerator(NormalNoiseGenerator, ABC):
    """Gaussian distribution with reparameterization trick."""

    def __init__(self):
        super(GaussianNoiseGenerator, self).__init__()

    def generate_gaussian_noise(self, image_shape: Tuple[int, int], mean: float = 0, var: float = 1) -> np.ndarray:
        """
        Generating Normal noise
        :param      image_shape:        Shape of input image
        :param      mean:               Mean of gaussian
        :param      var:                Variance of gaussian
        :return:    gaussian_noise:     Output noise with same size
        """
        normal_noise = self.generate_normal_noise(image_shape)
        gaussian_noise = normal_noise * np.sqrt(var) + mean
        return gaussian_noise

    def add_all_channel_noise(self, image: np.ndarray, mean: float = 0, var: float = 1) -> np.ndarray:
        """Adding gaussian noise to image with SAME noise in each channel
        :param      image:              input image
        :param      mean:               Mean of gaussian
        :param      var:                Variance of gaussian
        :return:    output_img:         Output image with same size adding gaussian noise
        """
        image_shape = image.shape
        input_img = image / 255
        gaussian_noise = np.expand_dims(self.generate_gaussian_noise(image_shape[:2], mean, var), -1)
        gaussian_noise = gaussian_noise.repeat(3, axis=-1)
        output_img = input_img + gaussian_noise
        output_img[input_img > 1] = 1
        output_img[input_img < 0] = 0
        return output_img * 255

    def add_channel_wise_noise(self, image: np.ndarray, mean: float = 0, var: float = 1) -> np.ndarray:
        """Adding gaussian noise to image with DIFFERENT noise in each channel
        :param      image:              input image
        :param      mean:               Mean of gaussian
        :param      var:                Variance of gaussian
        :return:    output_img:         Output image with same size adding channel-wise gaussian noise
        """
        image_shape = image.shape
        input_img = image / 255
        gaussian_noise = self.generate_gaussian_noise(image_shape, mean, var)
        output_img = input_img + gaussian_noise
        output_img[input_img > 1] = 1
        output_img[input_img < 0] = 0
        return output_img * 255
  1. 通过重参数化技巧(re-parameterization trick),从标准正态分布噪声生成带有均值和方差的高斯噪声分布。同时设置两种加噪声方式:分通道添加或整体添加。

    • 注意,这里如果想直接使用 cv2 或其他方式直接可视化,有两种方式,

      • 一种是除 255 变换到 [0, 1] 之间,

      • 第二种是通过 uint8 的方式转化为整值。

        如果直接可视化的话,会产生过亮的情况,因为会自动判定为 0-1 从而被 clip 成高亮图像。

class SaltPepperNoiseGenerator(UniformNoiseGenerator, ABC):
    """Gaussian distribution with additive i.i.d. uniform noise."""

    def __init__(self):
        super(SaltPepperNoiseGenerator, self).__init__()

    def generate_saltpepper_noise(self, image_shape: Tuple[int, int]) -> np.ndarray:
        """
        :param:     prob_1:             Probability of "Salt" noise.
        :param:     prob_2:             Probability of "Pepper" noise.
        :param:     image_shape:        Shape of input image.
        :return:    saltpepper_noise:   ArrayLike output salt pepper noise.
        """
        uniform_noise = self.generate_uniform_noise(0, 1, image_shape)
        saltpepper_noise = uniform_noise.copy()
        return saltpepper_noise

    def add_saltpepper_noise(self, prob_1: float, prob_2: float, image: np.ndarray) -> np.ndarray:
        """Adding noise to image"""
        output_img = image.copy()
        image_shape = image.shape
        saltpepper_noise = self.generate_saltpepper_noise(image_shape[:2])
        output_img[saltpepper_noise > 1 - prob_1] = 255
        output_img[saltpepper_noise < prob_2] = 0
        return output_img
  1. 继承均匀噪声类,生成均匀噪声,根据均匀噪声的值确定为原始图、胡椒噪声或原始噪声。
if __name__ == '__main__':
    uniform_noise_generator = UniformNoiseGenerator()
    print(uniform_noise_generator.generate_uniform_noise(2, 4, (2, 3)),
          uniform_noise_generator.generate_uniform_noise())
    normal_noise_generator = NormalNoiseGenerator()
    print(normal_noise_generator.generate_normal_noise((2, 3)))
    gaussian_noise_generator = GaussianNoiseGenerator()
    print(gaussian_noise_generator.generate_gaussian_noise((2, 3)))
    input_image = cv2.imread('./test_image/test3.jpg', 1)
    print(type(input_image))
    cv2.namedWindow('input_image', cv2.WINDOW_AUTOSIZE)
    cv2.imshow('input_image', input_image)
    # saltpepper_noise_generator = SaltPepperNoiseGenerator()
    # tar = saltpepper_noise_generator.add_saltpepper_noise(0.12, 0.1, input_image)
    # cv2.imshow('saltpepper_noise', tar)
    gaussian_noise_generator = GaussianNoiseGenerator()
    tar2 = gaussian_noise_generator.add_all_channel_noise(input_image, 0, 0.05)
    tar2_channel_wise = gaussian_noise_generator.add_channel_wise_noise(input_image, 0, 0.05)
    cv2.imshow('gaussian_noise', tar2)
    cv2.imshow('gaussian_noise_channel', tar2_channel_wise)
    cv2.waitKey(0)

b) 空间滤波器定义文件 spatial_filter.py

from typing import Tuple, List, Optional
import numpy as np
import logging
import sys
import functools
import cv2
from IPython import embed

level = logging.DEBUG
fmt = "[%(levelname)s] %(asctime)s - %(message)s"
logging.basicConfig(level=level, format=fmt)


def get_median(noised_image: np.ndarray, x_min: int, x_max: int, y_min: int, y_max: int, c: int) -> float:
    median_area = noised_image[x_min:x_max + 1, y_min:y_max + 1, c].flatten()
    sorted_area = sorted(median_area)
    n = len(median_area)
    median = sorted_area[n // 2 + 1] if n % 2 else (sorted_area[n // 2] + sorted_area[n // 2 + 1]) / 2
    return median


def get_adaptive_median(noised_image: np.ndarray, x: int, y: int, h: int, w: int, c: int,
                        max_size: int = None) -> float:
    median_area = noised_image[max(0, x - h):x + h + 1, max(0, y - w):y + w + 1, c].flatten()
    max_val, min_val = np.max(median_area), np.min(median_area)
    sorted_area = sorted(median_area)
    n = len(median_area)
    median = sorted_area[n // 2 + 1] if n % 2 else (int(sorted_area[n // 2]) + int(sorted_area[n // 2 + 1])) // 2
    if min_val < median < max_val:
        return noised_image[x][y][c] if min_val < noised_image[x][y][c] < max_val else median
    elif h < max_size and w < max_size:
        return get_adaptive_median(noised_image, x, y, h + 1, w + 1, c, max_size)
    else:
        return noised_image[x][y][c] if min_val < noised_image[x][y][c] < max_val else median


def get_mean(noised_image: np.ndarray, x_min: int, x_max: int, y_min: int, y_max: int) -> np.ndarray:
    return np.mean(noised_image[x_min:x_max + 1, y_min:y_max + 1], axis=(0, 1))
  1. 定义静态函数,完成计算规定kernel内的均值,中值与自适应中值。
    • 我有一个坏习惯,经常使用 print debug(虽然我现在还是这样),但我尝试使用 log 来输出。
class SpatialFilter:
    """
    This is description which will be showed
    The Class of Padding Adder
    """
    instance = None                                 # There should be ONLY ONE Spatial Filter once
    __slots__ = ['noised_image', 'kernel_size']

    def __new__(cls, *args):
        if cls.instance is None:  # Once no adder exists
            cls.instance = super().__new__(cls)
        return cls.instance

    def __init__(self, noised_image: np.ndarray, kernel_size: int = 3):
        self.noised_image = noised_image
        self.kernel_size = kernel_size

    def reverse_image(self, image: np.ndarray, reflect_size: int = None, axis: int = 0) -> np.ndarray:
        """
        reverse image in one axis for reflect padding
        :param:     image:          Input image for reflection
        :param:     reflect_size:   The size of reflect area edge + 1
        :param:     axis:           The axis that will be reflected
        :return:    reversed_img:   Reversed image at axis
        """
        if reflect_size is None:
            reflect_size = self.kernel_size
            logging.info(f"No kernel size input, automatically use size = {self.kernel_size}")
        if axis == 0:
            reflect_area_left = image[reflect_size - 1::-1]
            reflect_area_right = image[-1 * reflect_size:][::-1]
        else:
            reflect_area_left = image[:, reflect_size - 1::-1]
            reflect_area_right = image[:, -1 * reflect_size:][:, ::-1]
        reversed_img = np.concatenate([reflect_area_left, image, reflect_area_right], axis=axis)
        return reversed_img

    def add_reflect_padding(self, image: np.ndarray, kernel_size: int = None) -> np.ndarray:
        """
        Adding reflect padding
        :param:     image:          Input image for reflection
        :param:     kernel_size:    The size of reflect kernel
        :return:    reversed_img:   Padded image.
        """
        if kernel_size is None:
            kernel_size = self.kernel_size
            logging.info(f"No kernel size input, automatically use size = {self.kernel_size}")
        horizontal_flip = self.reverse_image(image, kernel_size, 0)
        reversed_img = self.reverse_image(horizontal_flip, kernel_size, 1)
        return reversed_img

    def add_zero_padding(self, image: np.ndarray, kernel_size: int = None):
        """
        Adding zero padding
        :param:     image:          Input image for padding
        :param:     kernel_size:    The size of padding kernel
        :return:    output_img:     Padded image.
        """
        if kernel_size is None:
            kernel_size = self.kernel_size
            logging.info(f"No kernel size input, automatically use size = {self.kernel_size}")
        if len(image.shape) > 2:
            h, w, c = image.shape
            output_img = np.zeros((h + 2 * kernel_size, w + 2 * kernel_size, c))
        else:
            h, w = image.shape
            output_img = np.zeros((h + 2 * kernel_size, w + 2 * kernel_size))
        output_img[kernel_size:h + kernel_size, kernel_size:w + kernel_size] = image
        return output_img
  1. 定义Spatial Filter类,同时,由于防止复用时覆盖掉特性,以及在要求的三个函数中不必每次初始化类,因此重载__new__函数,保证只需要使用同一个Spatial Filter,同时实现将图像翻转的功能,供后续对称padding使用。

  2. 定义两种padding方式,对称填充和零填充。

  3. 对输入参数做预处理,填充未给出的值。

    • (主要是 pycharm 提示,duplicate too long)所以我就写进一个函数了。
    def _preprocess_args(self,
                         noised_image: np.ndarray = None,
                         kernel_size: int = None,
                         padding_func: Optional = None,
                         padding_type: str = None,
                         max_size: int = None):
        assert (padding_type is None or padding_func is None)
        assert (kernel_size % 2)
        if noised_image is None:
            noised_image = self.noised_image
        if kernel_size is None:
            kernel_size = self.kernel_size
        if max_size is None:
            max_size = self.kernel_size + 4
        if padding_func is not None:
            padded_image = padding_func(noised_image, (kernel_size - 1) // 2)
        elif padding_type is not None:
            padding_func = getattr(sys.modules[__name__].SpatialFilter, "add_%s_padding" % padding_type)
            padded_image = padding_func(self, noised_image, (kernel_size - 1) // 2)
        else:
            padded_image = self.add_zero_padding(noised_image, (kernel_size - 1) // 2)
        depth = (kernel_size - 1) // 2
        c_img = 1
        if len(padded_image.shape) > 2:
            h_img, w_img, c_img = noised_image.shape
        else:
            h_img, w_img = noised_image.shape
        output_img = padded_image.copy()
        return noised_image, padded_image, kernel_size, padding_func, max_size, depth, h_img, w_img, c_img, output_img
  1. 实现中值滤波与均值滤波。
   def mean_filter(self,
                    noised_image: np.ndarray = None,
                    kernel_size: int = None,
                    padding_func: Optional = None,
                    padding_type: str = None):
        """
        Mean filter
        Support two type of padding:
            1. define it yourself and put it with padding_func
            2. use the predefined type
        :param:     noised_image:   Input image for padding
        :param:     kernel_size:    The size of padding kernel
        :param:     padding_func:   Padding function defined by yourself
        :param:     padding_type:   Type of padding, use in ["reflect", "zero"]
        :return:    output_img:     Output image after filter
        """
        noised_image, padded_image, kernel_size, padding_func,  _, depth, h_img, w_img, c_img, output_img = \
            self._preprocess_args(noised_image, kernel_size, padding_func, padding_type)
        for h in range(depth, h_img + depth):
            for w in range(depth, w_img + depth):
                output_img[h, w, :] = get_mean(padded_image, h - depth, h + depth, w - depth, w + depth)
        output_img = output_img[depth:h_img + depth, depth:w_img + depth]
        return np.uint8(output_img)

    def median_filter(self,
                      noised_image: np.ndarray = None,
                      kernel_size: int = None,
                      padding_func: Optional = None,
                      padding_type: str = None):
        noised_image, padded_image, kernel_size, padding_func, _, depth, h_img, w_img, c_img, output_img = \
            self._preprocess_args(noised_image, kernel_size, padding_func, padding_type)
        for h in range(depth, h_img + depth):
            for w in range(depth, w_img + depth):
                for c in range(c_img):
                    output_img[h, w, c] = get_median(padded_image, h - depth, h + depth, w - depth, w + depth, c)
        output_img = output_img[depth:h_img + depth, depth:w_img + depth]
        return np.uint8(output_img)

    def adaptive_median_filter(self,
                               noised_image: np.ndarray = None,
                               kernel_size: int = None,
                               max_size: int = None,
                               padding_func: Optional = None,
                               padding_type: str = None):
        noised_image, padded_image, kernel_size, padding_func, _, depth, h_img, w_img, c_img, output_img = \
            self._preprocess_args(noised_image, kernel_size, padding_func, padding_type)
        for h in range(depth, h_img + depth):
            for w in range(depth, w_img + depth):
                for c in range(c_img):
                    output_img[h, w, c] = get_adaptive_median(padded_image, h, w, depth, depth, c, max_size)
        output_img = output_img[depth:h_img + depth, depth:w_img + depth]
        return np.uint8(output_img)
  1. 实现自适应中值滤波。

c) 测量指标文件 metric.py

  1. 由于并非任务要求,因此这里使用了库函数。
    • 本来想用 sys.modules 调用变量,后来才知道类和函数是存在 sys.modules 里的,变量是存在 locals() 或者 globals() 里。
# The implementation of digital image processing assignment 2.
# Random Noise and Spatial Filter

# Metrics
# author: leafy
# 2022-12-5
# last_modified: 2022-12-5

from itertools import product
import math
import numpy as np
from skimage.metrics import structural_similarity
import cv2


def mean_squared_error(img1: np.ndarray, img2: np.ndarray) -> float:
    mse = np.mean((img1 / 1.0 - img2 / 1.0) ** 2)
    return float(mse)


def peak_signal_noise_ratio(mse: float) -> float:
    if mse < 1.0e-10:
        return 100
    return 10 * math.log10(255.0 ** 2 / mse)


# Just borrow it from skimage
# will implement soon(for assignment request)
def compare(img1, img2):
    mse = mean_squared_error(img1, img2)
    psnr = peak_signal_noise_ratio(mse)
    ssim = structural_similarity(img1, img2, multichannel=True)
    # print('PSNR:{},SSIM:{},MSE:{}'.format(psnr, ssim, mse))
    return psnr, ssim, mse


if __name__ == "__main__":
    for i in range(1, 5):
        globals()[f"input_image{i}"] = cv2.imread(f'./result/input_image_{i}.png')

    # Recording gaussian noise images
    for i in range(1, 5):
        for types in ["channel", "full"]:
            globals()[f"gaussian_img_{types}_{i}"] = cv2.imread(f'./result/gaussian_img_{types}_{i}.png')

    # recording saltpepper noise images
    for i in range(1, 5):
        globals()[f"sp_img_full_{i}"] = cv2.imread(f'./result/sp_img_full_{i}.png',
                                                   )
    for i in range(1, 5):
        globals()[f"low_sp_img_full_{i}"] = cv2.imread(f'./result/low_sp_img_full_{i}.png')

    for i in range(1, 5):
        for f_type, noise in product(["mean", "median", "median_adaptive"], ["gaussian", "sp", "low_sp"]):
            # for noise in ["gaussian", "sp", "low_sp"]:
            for types in ["channel", "full"]:
                if "sp" in noise:
                    types = "full"
                for pad in ["_reflect", ""]:
                    print(f"Reading images in ./result/{f_type}_{noise}_{types}{pad}_{i}.png")
                    globals()[f"{f_type}_{noise}_{types}{pad}_{i}"] = cv2.imread(
                        f'./result/{f_type}_{noise}_{types}{pad}_{i}.png')

    for f_type, noise in product(["mean", "median", "median_adaptive", "no_filter"], ["gaussian", "sp", "low_sp"]):
        for types in ["channel", "full"]:
            if "sp" in noise and types == "channel":
                continue
            for pad in ["_reflect", ""]:
                cnt_psnr, cnt_ssim, cnt_mse = 0, 0, 0
                if f_type == "no_filter":
                    filt = ""
                    img = "img_"
                else:
                    filt = f_type + "_"
                    img = ""
                if f_type == "no_filter" and pad != "":
                    continue
                for i in range(1, 5):
                    cur_psnr, cur_ssim, cur_mse = compare(globals()[f"input_image{i}"],
                                                          globals()[f"{filt}{noise}_{img}{types}{pad}_{i}"])
                    for metric in ["psnr", "ssim", "mse"]:
                        globals()[f"cnt_{metric}"] += globals()[f"cur_{metric}"]
                cnt_psnr, cnt_ssim, cnt_mse = cnt_psnr / 4, cnt_ssim / 4, cnt_mse / 4
                print(f"Difference between {filt}{noise}_{img}{types}{pad}.png and input is "
                      f"\n{cnt_psnr} \n{cnt_ssim} \n{cnt_mse}")
  1. 完成了所有图像的指标测试。

d) 主文件(五个函数及主函数调用)

import os
from typing import Tuple
import cv2
import numpy as np
import sys
from itertools import product

from noise import GaussianNoiseGenerator, SaltPepperNoiseGenerator
from spatial_filter import SpatialFilter
from metric import compare

from IPython import embed


def generate_gaussian_noise(input_img: np.ndarray, mean: float, var: float) -> Tuple[np.ndarray, np.ndarray]:
  """
  :param:     input_img:      Input image.
  :param:     mean:           Mean of gaussian noise.
  :param:     var:            Variance of gaussian noise.
  :return:    output_image:   Output image with gaussian noise added. output_img, output_img_channel_wise
  """
  inner_gaussian_noise_generator = GaussianNoiseGenerator()
  output_img = inner_gaussian_noise_generator.add_all_channel_noise(input_img, mean, var)
  output_img_channel_wise = inner_gaussian_noise_generator.add_channel_wise_noise(input_img, mean, var)
  return output_img, output_img_channel_wise


def generate_saltpepper_noise(prob_1: float, prob_2: float, input_img: np.ndarray) -> np.ndarray:
  """
  :param:     prob_1:         prob_1 of saltpepper noise.
  :param:     prob_2:         prob_2 of saltpepper noise.
  :param:     input_img:      Input image.
  :return:    output_image:   Output image with saltpepper noise added.
  """
  saltpepper_noise_generator = SaltPepperNoiseGenerator()
  output_img = saltpepper_noise_generator.add_saltpepper_noise(prob_1, prob_2, input_img)
  return output_img


def mean_filter(noised_img: np.ndarray, kernel_size: int = None) -> Tuple[np.ndarray, np.ndarray]:
  """
  :param:     input_img:      Input image.
  :param:     kernel_size:    kernel size of filter.
  :return:    output_image:   Output filtered image. output_img_zero, output_img_reflect
  """
  sp_filter = SpatialFilter(noised_img)
  output_img_zero = sp_filter.mean_filter(noised_img, kernel_size=kernel_size, padding_type="zero")
  output_img_reflect = sp_filter.mean_filter(noised_img, kernel_size=kernel_size, padding_type="reflect")
  return output_img_zero, output_img_reflect


def median_filter(noised_img: np.ndarray, kernel_size: int = None) -> Tuple[np.ndarray, np.ndarray]:
  """
  :param:     input_img:      Input image.
  :param:     kernel_size:    kernel size of filter.
  :return:    output_image:   Output filtered image. output_img_zero, output_img_reflect
  """
  sp_filter = SpatialFilter(noised_img)
  output_img_zero = sp_filter.median_filter(noised_img, kernel_size=kernel_size, padding_type="zero")
  output_img_reflect = sp_filter.median_filter(noised_img, kernel_size=kernel_size, padding_type="reflect")
  return output_img_zero, output_img_reflect


def median_adaptive_filter(noised_img: np.ndarray, kernel_size: int = None,
                         max_size: int = None) -> Tuple[np.ndarray, np.ndarray]:
  """
  :param:     input_img:      Input image.
  :param:     kernel_size:    kernel size of filter.
  :param:     max_size:       Max size of adaptive filter.
  :return:    output_image:   Output filtered image. output_img_zero, output_img_reflect
  """
  sp_filter = SpatialFilter(noised_img)
  output_img_zero = sp_filter.adaptive_median_filter(noised_img, kernel_size=kernel_size,
                                                     max_size=max_size, padding_type="zero")
  output_img_reflect = sp_filter.adaptive_median_filter(noised_img, kernel_size=kernel_size,
                                                        max_size=max_size, padding_type="reflect")
  return output_img_zero, output_img_reflect
  1. 两种噪声的生成。

  2. 三种滤波结果,同时包括对称填充和零填充。

  3. 主函数——图像读入与加噪声,添加了高斯噪声、高概率椒盐噪声和低概率椒盐噪声。

def main(test_dir: str = "./test_image"):
    # Reading images
    input_images = os.listdir(test_dir)
    for idx, path in input_images:
        locals()[f"input_image{idx}"] = cv2.imread(os.path.join(test_dir, path), 1)

    # Recording input images
    for i in range(1, 5):
        cv2.imwrite(f'./result/input_image_{i}.png',
                    locals()[f"input_image{i}"])

    # Recording gaussian noise images
    for i in range(1, 5):
        locals()[f"gaussian_img_full_{i}"], locals()[f"gaussian_img_channel_{i}"] = \
            generate_gaussian_noise(locals()[f"input_image{i}"], 0, 0.05)
        for types in ["channel", "full"]:
            cv2.imwrite(f'./result/gaussian_img_{types}_{i}.png',
                        locals()[f"gaussian_img_{types}_{i}"])

    # recording saltpepper noise images
    for i in range(1, 5):
        locals()[f"sp_img_full_{i}"] = generate_saltpepper_noise(0.1, 0.1,
                                                                 locals()[f"input_image{i}"])
        cv2.imwrite(f'./result/sp_img_full_{i}.png',
                    locals()[f"sp_img_full_{i}"])
    for i in range(1, 5):
        locals()[f"low_sp_img_full_{i}"] = generate_saltpepper_noise(0.01, 0.01,
                                                                     locals()[f"input_image{i}"])
        cv2.imwrite(f'./result/low_sp_img_full_{i}.png',
                    locals()[f"low_sp_img_full_{i}"])

    # Filtering and recording the output images
    print(locals().keys())
    for i in range(1, 5):
        for f_type, noise in product(["mean", "median", "median_adaptive"], ["gaussian", "sp", "low_sp"]):
            # for noise in ["gaussian", "sp", "low_sp"]:
            for types in ["channel", "full"]:
                if "sp" in noise and types == "channel":
                    continue
                print(f"Generating images {f_type}_{noise}_{types}_{i}")
                if f_type == "median_adaptive":
                    locals()[f"{f_type}_{noise}_{types}_{i}"], locals()[f"{f_type}_{noise}_{types}_reflect_{i}"] = \
                        getattr(sys.modules[__name__], f"{f_type}_filter")(locals()[f"{noise}_img_{types}_{i}"],
                                                                           kernel_size=3,
                                                                           max_size=7)
                else:
                    locals()[f"{f_type}_{noise}_{types}_{i}"], locals()[f"{f_type}_{noise}_{types}_reflect_{i}"] = \
                        getattr(sys.modules[__name__], f"{f_type}_filter")(locals()[f"{noise}_img_{types}_{i}"],
                                                                           kernel_size=3)
                for pad in ["_reflect", ""]:
                    print(f"Saving images in ./result/{f_type}_{noise}_{types}{pad}_{i}.png")
                    cv2.imwrite(f'./result/{f_type}_{noise}_{types}{pad}_{i}.png',
                                locals()[f"{f_type}_{noise}_{types}{pad}_{i}"])

    for f_type, noise in product(["mean", "median", "median_adaptive", "no_filter"], ["gaussian", "sp", "low_sp"]):
        for types in ["channel", "full"]:
            if "sp" in noise and types == "channel":
                continue
            for pad in ["_reflect", ""]:
                cnt_psnr, cnt_ssim, cnt_mse = 0, 0, 0
                if f_type == "no_filter":
                    filt = ""
                    img = "img_"
                else:
                    filt = f_type + "_"
                    img = ""
                if f_type == "no_filter" and pad != "":
                    continue
                for i in range(1, 5):
                    cur_psnr, cur_ssim, cur_mse = compare(globals()[f"input_image{i}"],
                                                          globals()[f"{filt}{noise}_{img}{types}{pad}_{i}"])
                    for metric in ["psnr", "ssim", "mse"]:
                        globals()[f"cnt_{metric}"] += globals()[f"cur_{metric}"]
                cnt_psnr, cnt_ssim, cnt_mse = cnt_psnr / 4, cnt_ssim / 4, cnt_mse / 4
                print(f"Difference between {filt}{noise}_{img}{types}{pad}.png and input is "
                      f"\n{cnt_psnr} \n{cnt_ssim} \n{cnt_mse}")


if __name__ == '__main__':
    # input_image3 = cv2.imread('test3.jpg', 1)
    # a, b = generate_gaussian_noise(input_image3, 0, 0.05)
    # cv2.imshow('gaussian_noise', a)
    # cv2.imshow('gaussian_noise_channel', b)
    # cv2.waitKey(0)
    main()
  1. 主函数——滤波与指标计算。