图像降噪算法——稀疏表达:K-SVD算法

图像降噪算法——稀疏表达:K-SVD算法

  • 图像降噪算法——稀疏表达:K-SVD算法
    • 1. 基本原理
    • 2. python代码
    • 3. 结论

图像降噪算法——稀疏表达:K-SVD算法

为了完善下自己降噪算法知识版图的完整性,我打算花一个周末的时间再了解下基于稀疏表达和低秩聚类这两种原理实现的图像降噪算法,因为学习的时间并不长,也没有花太多时间去做实验,所以对算法理解得可能比较肤浅,还愿读者见谅。
这里我分享几篇很优秀的博客,我对稀疏表达概念的认识目前也就是来自于这几篇博客:
字典学习(Dictionary Learning, KSVD)详解
从稀疏表示到K-SVD,再到图像去噪
稀疏表达的意义在于?为什么稀疏表达得到广泛的应用?

1. 基本原理

这里我们先来回答几个问题:
(1)什么是稀疏表达?
稀疏表达的定义是用较少的基本信号的线性组合来表达大部分或者全部的原始信号。在稀疏表达算法中有"字典"这样一个概念,这个“字典”和我们现实生活中使用的字典作用是相同的,现实生活中,我们通过对词条进行组合可以表达我们想要的信息,同样地,在稀疏表达算法中,我们对“原子”进行线性组合就可以还原我们想要的信号。稀疏表达在机器学习领域有着广泛的应用,诸如压缩感知、特征提取等,图像去噪只是其应用的一个方面。

(2)稀疏表达为什么可以进行图像去噪?
噪声图像是原始图像和噪声合成的图像,原始图像认为是可稀疏的,即可以通过有限个"原子"进行表示,而噪声是随机而不可稀疏的,因此通过提取图像的稀疏成分,再利用稀疏成分来重构图像,在这个过程中,噪声被当做观测图像和重构图像之间的残差而被丢弃,从而起到降噪的作用。

在图像去噪领域,尽管CNN的方法已经占据了半壁江山,但是CNN方法的一个弊端就是需要大量训练数据,在难以采集大量训练数据的场景中,例如某些医学图像,稀疏表达的方法仍然起到重要作用。

(3)稀疏表达和转换域滤波方法的区别?
转换域滤波指的是离散傅里叶变换、小波变换这样一些方法,这些方法获得的是一系列正交基,而稀疏表达获得的“字典”则是一些列非正交基。正交基往往只能表示图像的某一个特征而不能够同时表示其他特征,因此正交基的稀疏性不及非正交基。

下面咱来开始讨论基于稀疏滤波的K-SVD算法,K-SVD算法中最核心的部分就是字典学习,字典学习的过程就是基于样本集 Y = { y i } i = 1 M \mathbf{Y}=\{\mathbf{y}_i\}^M_{i=1} Y={ yi}i=1M寻找一组“超完备”的基向量作为字典矩阵 D = { d i } i = 1 K \mathbf{D}=\{\mathbf{d}_i\}^{K}_{i=1} D={ di}i=1K其中 M M M为样本数, y i ∈ R N \mathbf{y}_{i} \in R^{N} yiRN为单个样本,是一个 N N N维的特征向量, M M M个单个样本按列组成样本集矩阵, d i ∈ R N \mathbf{d}_i \in R^{N} diRN为基向量,维度也是 N N N维, K K K个基向量按列组成字典矩阵,样本集中的任意样本都可以根据字典求得起对应的稀疏表达形式。当 K > N K > N K>N时为超完备字典,即我们字典学习的通常情况。
字典学习的目标是学习一个字典矩阵 D \mathbf{D} D,使得 Y ≈ D ∗ X \mathbf{Y} \approx \mathbf{D} * \mathbf{X} YDX同时要 X X X尽可能稀疏。这里我们注意 D ∈ R N × K \mathbf{D} \in \mathbf{R}^{N \times K} DRN×K Y ∈ R N × M \mathbf{Y} \in \mathbf{R}^{N \times M} YRN×M,因此 X ∈ R K × M \mathbf{X} \in \mathbf{R}^{K \times M} XRK×M。如下图所示
图像降噪算法——稀疏表达:K-SVD算法_第1张图片
D \mathbf{D} D矩阵中不同颜色代表不同的基向量, X \mathbf{X} X矩阵中不同的颜色代表对应基向量的系数, Y \mathbf{Y} Y矩阵中某一样本(灰色块)就是由 D \mathbf{D} D矩阵的基向量通过 X \mathbf{X} X矩阵中黑框中的系数进行线性组合获得的,由于是稀疏组合,所以中间有很多系数会是零,我们用白色块表示。

这个问题用数学问题描述为 min ⁡ D , X ∥ Y − D X ∥ F 2 ,  s.t.  ∀ i , ∥ x i ∥ 0 ≤ T 0 \min _{\mathbf{D}, \mathbf{X}}\|\mathbf{Y}-\mathbf{D} \mathbf{X}\|_{F}^{2}, \quad \text { s.t. } \forall i,\left\|\mathbf{x}_{i}\right\|_{0} \leq T_{0} D,XminYDXF2, s.t. i,xi0T0或者 min ⁡ D , x ∑ i ∥ x i ∥ 0 ,  s.t.  min ⁡ D , x ∥ Y − D X ∥ F 2 ≤ ϵ \min _{\mathbf{D}, \mathbf{x}} \sum_{i}\left\|\mathbf{x}_{i}\right\|_{0}, \quad \text { s.t. } \min _{\mathbf{D}, \mathbf{x}}\|\mathbf{Y}-\mathbf{D} \mathbf{X}\|_{F}^{2} \leq \epsilon D,xminixi0, s.t. D,xminYDXF2ϵ其中 x i \mathbf{x}_i xi为稀疏矩阵 X \mathbf{X} X的行向量,代表字典矩阵的系数,和上面彩图中是对应的。这里值得注意的是 ∥ x i ∥ 0 \left\|\mathbf{x}_{i}\right\|_{0} xi0是零阶范数,它表示向量中不为0的数的个数。

上面两个公式是带有约束的优化问题,可以利用拉格朗日乘子法将其转化为无约束优化问题 min ⁡ D , x ∥ Y − D X ∥ F 2 + λ ∥ x i ∥ 1 \min _{\mathbf{D}, \mathbf{x}}\|\mathbf{Y}-\mathbf{D} \mathbf{X}\|_{F}^{2}+\lambda\left\|\mathbf{x}_{i}\right\|_{1} D,xminYDXF2+λxi1其中主要是将 ∥ x i ∥ 0 \left\|\mathbf{x}_{i}\right\|_{0} xi0 ∥ x i ∥ 1 \left\|\mathbf{x}_{i}\right\|_{1} xi1代替,这样更加便于求解。这里要优化 D \mathbf{D} D X \mathbf{X} X两个变量,而样本集 Y \mathbf{Y} Y是已知的,我们做法是固定一个变量,优化另一个,交替进行。下面分开来说:
已知 D \mathbf{D} D,优化 X \mathbf{X} X:这个问题有许多经典算法可以进行求解,例如Lasso,OMP,我们之后再进行补充,K-SVD中比较有特点是下面这种情况。
已知 X \mathbf{X} X,优化 D \mathbf{D} D:我们需要逐列来更新字典,记 d k \mathbf{d}_k dk为字典 D \mathbf{D} D的第 k k k列,其实就是某个基向量, x T k \mathbf{x}_{T}^{k} xTk为稀疏矩阵 X \mathbf{X} X的第 k k k行,按照本文之前的定义,其实就是该基向量不同样本对应的系数。那么有:
∥ Y − D X ∥ F 2 = ∥ Y − ∑ j = 1 K d j x T j ∥ F 2 = ∥ ( Y − ∑ j ≠ k d j x T j ) − d k x T k ∥ F 2 = ∥ E k − d k x T k ∥ F 2 \begin{aligned}\|\mathbf{Y}-\mathbf{D} \mathbf{X}\|_{F}^{2} &=\left\|\mathbf{Y}-\sum_{j=1}^{K} \mathbf{d}_{j} \mathbf{x}_{T}^{j}\right\|_{F}^{2} \\ &=\left\|\left(\mathbf{Y}-\sum_{j \neq k} \mathbf{d}_{j} \mathbf{x}_{T}^{j}\right)-\mathbf{d}_{k} \mathbf{x}_{T}^{k}\right\|_{F}^{2} \\ &=\left\|\mathbf{E}_{k}-\mathbf{d}_{k} \mathbf{x}_{T}^{k}\right\|_{F}^{2} \end{aligned} YDXF2=Yj=1KdjxTjF2=Yj=kdjxTjdkxTkF2=EkdkxTkF2上式中定义残差 E k = Y − ∑ j ≠ k d j x T j \mathbf{E}_{k}=\mathbf{Y}-\sum_{j \neq k} \mathbf{d}_{j} \mathbf{x}_{T}^{j} Ek=Yj=kdjxTj就是除了当前更新基向量外其它基向量组合与样本集的误差,我们的目的就是让该残差项最小,因此有 min ⁡ d k , x T k ∥ E k − d k x T k ∥ F 2 \min _{\mathbf{d}_{k}, \mathbf{x}_{T}^{k}}\left\|\mathbf{E}_{k}-\mathbf{d}_{k} \mathbf{x}_{T}^{k}\right\|_{F}^{2} dk,xTkminEkdkxTkF2这个目标函数如何求呢?这里采用的就是SVD方法,如下: E k ′ = U Σ V T \mathbf{E}_{k}^{\prime}=U \Sigma V^{T} Ek=UΣVT其中 Σ \Sigma Σ矩阵中的奇异值是从大到小排列的,我们要取最大奇异值对应的向量作为最优的 d k , x T k \mathbf{d}_{k}, \mathbf{x}_{T}^{k} dk,xTk,那么就是取矩阵 U U U的第一个列向量 u 1 = U ( ⋅ , 1 ) \mathbf{u}_{1}=U(\cdot, 1) u1=U(,1)作为 d k \mathbf{d}_{k} dk,取矩阵 V V V的第一个行向量与第一个奇异值的乘积作为 x T ′ k \mathbf{x}_{T}^{\prime k} xTk,将其更新到 x T k \mathbf{x}_{T}^{k} xTk,这里值得注意的是,这里更新 x T k \mathbf{x}_{T}^{k} xTk并不能保证其稀疏性,要保证稀疏性还是需要回到上一种情况使用OMP算法进行稀疏编码

这里为什么可以通过SVD算法求得最优解呢?关于这一点之后再补充,那么以上就完成了字典学习的过程,那么来看一段代码印证下KSVD的流程。

2. python代码

这段代码是我从博客K-SVD字典学习及其实现(Python)扒过来的,该了一下opencv的接口,这里要注意的是,如果直接按照我这个代码把整张图片输入的话其实降噪效果不理想的,为什么呢?

#!/usr/bin/python
# -*- coding: UTF-8 -*-
import numpy as np
from sklearn import linear_model
import scipy.misc
from matplotlib import pyplot as plt
import cv2


class KSVD(object):
    def __init__(self, n_components, max_iter=30, tol=5000,
                 n_nonzero_coefs=None):
        """
        稀疏模型Y = DX,Y为样本矩阵,使用KSVD动态更新字典矩阵D和稀疏矩阵X
        :param n_components: 字典所含原子个数(字典的列数)
        :param max_iter: 最大迭代次数
        :param tol: 稀疏表示结果的容差
        :param n_nonzero_coefs: 稀疏度
        """
        self.dictionary = None
        self.sparsecode = None
        self.max_iter = max_iter
        self.tol = tol
        self.n_components = n_components
        self.n_nonzero_coefs = n_nonzero_coefs

    def _initialize(self, y):
        """
        初始化字典矩阵
        """
        u, s, v = np.linalg.svd(y)
        self.dictionary = u[:, :self.n_components]
        print(self.dictionary.shape)

    def _update_dict(self, y, d, x):
        """
        使用KSVD更新字典的过程
        """
        for i in range(self.n_components):
            index = np.nonzero(x[i, :])[0]
            if len(index) == 0:
                continue

            d[:, i] = 0
            r = (y - np.dot(d, x))[:, index]
            u, s, v = np.linalg.svd(r, full_matrices=False)
            d[:, i] = u[:, 0].T
            x[i, index] = s[0] * v[0, :]
        return d, x

    def fit(self, y):
        """
        KSVD迭代过程
        """
        self._initialize(y)
        for i in range(self.max_iter):
            x = linear_model.orthogonal_mp(self.dictionary, y, n_nonzero_coefs=self.n_nonzero_coefs)
            e = np.linalg.norm(y - np.dot(self.dictionary, x))
            if e < self.tol:
                break
            self._update_dict(y, self.dictionary, x)

        self.sparsecode = linear_model.orthogonal_mp(self.dictionary, y, n_nonzero_coefs=self.n_nonzero_coefs)
        return self.dictionary, self.sparsecode


if __name__ == '__main__':
    im_ascent = cv2.imread("./input.png", 0).astype(np.float)
    print(im_ascent.shape)
    ksvd = KSVD(300)
    dictionary, sparsecode = ksvd.fit(im_ascent)

    output = dictionary.dot(sparsecode)
    output = np.clip(output, 0, 255)
    cv2.imwrite("./output.png", output.astype(np.uint8))

从代码中可以看到,我们输入的样本集 Y \mathbf{Y} Y矩阵就是整张图片,这样的话,单个样本就是图片的每一列的数据,这样合理吗?当然不合理,我觉得这就是造成仅仅通过上面这段代码降噪效果不理想的原因,合理的做法应该是从图像中提取patch,将patch转换成一列一列的样本数据,组成样本集矩阵,然后再利用K-SVD算法从样本集矩阵学习字典和稀疏矩阵。感兴趣的同学可以根据该思路尝试改下代码,或者参考代码nel215/ksvd

3. 结论

  1. KSVD只是众多基于稀疏表达降噪的方法中最经典的一种,类似的还有LSSC,NCSR等,KSVD算法是2006年提出的,所以效果有限,对这个方向感兴趣的同学可以多关注后续的一些paper
  2. 在Image Denoising Via Sparse and Redundant Representations Over Learned Dictionaries中展示了KSVD算法的效果,这篇论文中描述的KSVD算法就是基于patch实现的图像降噪算法——稀疏表达:K-SVD算法_第2张图片
    其中,Global Trained Dictionary指的是从一堆无噪声的图片中抽取的patch学习的字典,Adaptive Dictionary指的是从原噪声图像patch中学习的字典,从结果看,Adaptive Dictionary是要优于Global Trained Dictionary的

这篇博客没有做太多实验去验证实验效果,有问题欢迎交流

你可能感兴趣的:(图像降噪,图像传感器,计算机视觉,KSVD,稀疏表达,图像降噪)