赞
踩
为了完善下自己降噪算法知识版图的完整性,我打算花一个周末的时间再了解下基于稀疏表达和低秩聚类这两种原理实现的图像降噪算法,因为学习的时间并不长,也没有花太多时间去做实验,所以对算法理解得可能比较肤浅,还愿读者见谅。
这里我分享几篇很优秀的博客,我对稀疏表达概念的认识目前也就是来自于这几篇博客:
字典学习(Dictionary Learning, KSVD)详解
从稀疏表示到K-SVD,再到图像去噪
稀疏表达的意义在于?为什么稀疏表达得到广泛的应用?
这里我们先来回答几个问题:
(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}
yi∈RN为单个样本,是一个
N
N
N维的特征向量,
M
M
M个单个样本按列组成样本集矩阵,
d
i
∈
R
N
\mathbf{d}_i \in R^{N}
di∈RN为基向量,维度也是
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}
Y≈D∗X同时要
X
X
X尽可能稀疏。这里我们注意
D
∈
R
N
×
K
\mathbf{D} \in \mathbf{R}^{N \times K}
D∈RN×K,
Y
∈
R
N
×
M
\mathbf{Y} \in \mathbf{R}^{N \times M}
Y∈RN×M,因此
X
∈
R
K
×
M
\mathbf{X} \in \mathbf{R}^{K \times M}
X∈RK×M。如下图所示
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,Xmin∥Y−DX∥F2, s.t. ∀i,∥xi∥0≤T0或者 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,xmini∑∥xi∥0, s.t. D,xmin∥Y−DX∥F2≤ϵ其中 x i \mathbf{x}_i xi为稀疏矩阵 X \mathbf{X} X的行向量,代表字典矩阵的系数,和上面彩图中是对应的。这里值得注意的是 ∥ x i ∥ 0 \left\|\mathbf{x}_{i}\right\|_{0} ∥xi∥0是零阶范数,它表示向量中不为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,xmin∥Y−DX∥F2+λ∥xi∥1其中主要是将
∥
x
i
∥
0
\left\|\mathbf{x}_{i}\right\|_{0}
∥xi∥0用
∥
x
i
∥
1
\left\|\mathbf{x}_{i}\right\|_{1}
∥xi∥1代替,这样更加便于求解。这里要优化
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
这里为什么可以通过SVD算法求得最优解呢?关于这一点之后再补充,那么以上就完成了字典学习的过程,那么来看一段代码印证下KSVD的流程。
这段代码是我从博客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
这篇博客没有做太多实验去验证实验效果,有问题欢迎交流
此外,这里我写一个各种算法的总结目录图像降噪算法——图像降噪算法总结,对图像降噪算法感兴趣的同学欢迎参考
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。