文章目录
噪音的产生
所谓噪音,就是指在原始信号中出现了我们不希望的信号,或者干扰。了解噪音的生成方法,是为了方便我们更好的评估去噪函数。通常,在图像学领域,由于传感器的原因,会出现3种比较常见的噪音。
分别是椒盐噪音、高斯噪音以及泊松噪音。现在就来分别了解下这些噪音的产生原因,以及手工实现噪音产生的方法。
噪音函数
之所以降噪,是因为在图像数据的存储、传输过程中,通常会因为电子元器件之间的电磁干扰产生,又或者图像数据在传输过程中遇到来自自然界、或者人为的干扰。
举个例子来说,过去黑白电视机经常由于电信号干扰,而在图像中产生雪花。又或者是拿一个强磁,对着电子管的电视机进行干扰,而在图像中出现一些水纹波。
那么,工程师们根据经验,把常见噪音进行了以下几种的分类。
1. 椒盐噪音(Salt Pepper Noise)
椒盐噪声(salt-and-pepper noise)又称脉冲噪声,它随机改变一些像素值,在二值图像上表现为使一些像素点变白,一些像素点变黑。 是由图像传感器,传输信道,解码处理等产生的黑白相间的亮暗点噪声,也就是老人们比较熟悉的所谓“雪花”。
N = { 0 p e p p e r 255 s a l t N = \left\{\begin{matrix} 0 & pepper\\ 255 & salt \end{matrix}\right. N={0255peppersalt
def salt_pepper_noise(image, ratio):
output = np.zeros(image.shape, np.uint8)
for i in range(image.shape[0]):
for j in range(image.shape[1]):
rand = random.random()
if rand < ratio: # salt pepper noise
if random.random() > 0.5: # change the pixel to 255
output[i][j] = 255
else:
output[i][j] = 0
else:
output[i][j] = image[i][j]
return output
输出效果:
2. 高斯噪音(Gaussian Noise)
文字的描述比较枯燥,我这里就直接贴一张图来说明什么叫高斯噪音
首先从一维来介绍,假设某一种有效信号它表示为
μ
\mu
μ,而随着时间的延续,比方说这个信号是来自某种温度传感器,由于传感器老化或者传输信号出现了某种干扰,这些干扰的信号范围在
[
−
3
σ
,
3
σ
]
[-3\sigma, 3\sigma]
[−3σ,3σ]之间,然后我们把这段时间内噪音的振幅收集并且整理后,发现它符合正态分布曲线,亦或者称为高斯曲线,那么这样的噪音就称为高斯噪音。
f ( x ) = 1 2 π σ e ( − ( x − μ ) 2 2 σ 2 ) f(x) = \frac{1}{ \sqrt {2 \pi } \sigma } e^(- \frac{(x- \mu )^2}{2 \sigma^2} ) f(x)=2πσ1e(−2σ2(x−μ)2)
这个公式简单的说一下:
- 曲线关于 x = μ x = \mu x=μ 对称,通常这个值对应的是信号本号
- x = μ x = \mu x=μ 处为正态分布的最大值,振幅为 1 2 π σ \frac{1}{ \sqrt {2 \pi } \sigma } 2πσ1,也就是信号本号自身的强度,亦或者说是期望值
- 而 σ \sigma σ所决定的,就是噪音信号的分布情况,数学上称这玩意为标准差
- 当标准差越小,信号噪音就越集中在期望值附近,换句话说也就是信号受噪音影响程度越小,而标准差越大,信号就越模糊。
所以,如果将信号挪到了0上,而噪音分布的标准差为1的时候,这个就是小学二年级所说的标准正态分布了。
现在我们徒手撸一个高斯分布函数的实现:
def gaussian_noise_kernel(x, mu, sigma):
exp = math.exp(-1 * (
math.pow(x - mu, 2) / (2 * math.pow(sigma, 2))
))
peak = (math.sqrt(2 * 3.14159) * sigma)
return exp / peak
如果用numpy,代码可以写的更简单:
def gaussian_noise_kernel(x, mu, sigma):
return np.exp(-1*((x-mu)**2)/(2*(sigma**2)))/(math.sqrt(2*np.pi) * sigma)
然后,我们使用numpy的linspace函数,生成X轴坐标,关于这个函数怎么使用的,你可以网上搜素一下它的函数说明。
用一种比较笨的方法计算Y轴坐标:
mu1, sig1 = 0, 1 # standard distribution
mu2, sig2 = 1, 1 # move the chart to right
mu3, sig3 = 0, 0.5 # increase the noise coverage
mu4, sig4 = 0, 2.5 # increase the noise coverage
x = np.linspace(-5, 5, 100)
y1, y2, y3, y4 = [], [], [], []
for i in range(50):
t1 = gaussian_noise_kernel(x[i], mu1, sig1)
t2 = gaussian_noise_kernel(x[i], mu2, sig2)
t3 = gaussian_noise_kernel(x[i], mu3, sig3)
t4 = gaussian_noise_kernel(x[i], mu4, sig4)
y1.append(t1)
y2.append(t2)
y3.append(t3)
y4.append(t4)
plt.plot(x, y1, 'r', label='mu1, sig1 = 0, 1')
plt.plot(x, y2, 'g', label='mu2, sig2 = 1, 1')
plt.plot(x, y3, 'b', label='mu3, sig3 = 0, 0.5')
plt.plot(x, y4, 'm', label='mu4, sig4 = 0, 2.5')
plt.legend()
plt.grid()
plt.show()
plt.legend()
plt.grid()
plt.show()
现在我们已经成功的绘制处高斯分布曲线,然后把这个分布曲线函数引入到图像中,用来生成随机噪点。
首先明确一点,高斯噪音的特点是增加或减少原始信号,使得原始信号出现了随机“抖动”,并且抖动范围服从高斯分布(正态分布)。所以如果要生成高斯噪音,那么我们需要在原来高斯核的基础上,增加一个随机数生成器,并且把高斯函数调整为对于X=0轴上的正态分布(不一定是标准正态分布)。
所以,如果有一个正弦信号,那么叠加上高斯噪音后,输出的结果就会呈现上图所示的样子。现在放上实现的代码,它的执行效果不是最好的,但你应该是能看明白噪音的叠加方式。
def gaussian_noise(image, ratio, sigma):
# generate gaussian kernel
x = np.linspace(- 4 * sigma, 4 * sigma, 100)
kernel = gaussian_noise_kernel(x, 0, sigma)
# output image
output = np.zeros(image.shape, np.uint8)
for i in range(image.shape[0]):
for j in range(image.shape[1]):
rand = random.random()
if rand < ratio: # apply gaussian noise
pos = int(100 * random.random()
if x[pos] < 0:
temp = image[i][j] * (1 - kernel[pos])
if temp < 0:
output[i][j] = 0
else:
output[i][j] = temp
continue
if x[pos] >= 0:
temp = image[i][j] * (1 + kernel[pos])
if temp > 255:
output[i][j] = 255
else:
output[i][j] = temp
else:
output[i][j] = image[i][j]
return output
3. 泊松噪音(Poisson Noise)
简单的说就是满足泊松分布的噪音,你会觉得它和正态分布很相似,其实如果我们采集的数据越多,精度越密,其形态上它越发接近高斯分布函数,也就是正态分布,是常见的一种满足指数函数分布的离散模型。
泊松分布(Poisson Distribution)
泊松噪音存在的根本原因是因为光是由离散的光子构成(光的粒子性)。光源发出的光子打在CMOS上,从而形成一个可见的光点。光源每秒发射的光子到达CMOS的越多,则该像素的灰度值越大。但是因为光源发射和CMOS接收之间都有可能存在一些因素导致单个光子并没有被CMOS接收到或者某一时间段内发射的光子特别多,所以这就导致了灰度值会有波动,也就是所谓的散粒噪声。举例而言,在光源强度比较低的时候,比如说设定光强为每秒5个光子的时候,那么每秒实际CMOS接受到的光子数可能从0到10(服从泊松分布)1。
那么,泊松分布或者泊松噪音的基本形式是怎样的呢?
P ( x = k ) = e − λ λ k k ! P(x=k) = \frac{e^{- \lambda} \lambda ^ k}{k!} P(x=k)=k!e−λλk
这里, λ \lambda λ 表示的是期望值,既某过程中,在它的给定时间内,事件的发生次数,比方说假设世界杯赛场上,每场比赛的进球数大约在2.5个球,那么 λ = 2.5 \lambda=2.5 λ=2.5,因此假设对于一场比赛,分别发生0次进球,1次进球,2次进球的概率是2?
出现0次进球
P
(
x
=
0
)
=
2.
5
0
0
!
e
−
2.5
≈
0.082
P(x=0) = \frac{2.5^0}{0!} e^{-2.5} \approx 0.082
P(x=0)=0!2.50e−2.5≈0.082
出现1次进球
P
(
x
=
1
)
=
2.
5
1
1
!
e
−
2.5
≈
0.205
P(x=1) = \frac{2.5^1}{1!} e^{-2.5} \approx 0.205
P(x=1)=1!2.51e−2.5≈0.205
出现2次进球
P
(
x
=
2
)
=
2.
5
2
2
!
e
−
2.5
≈
0.257
P(x=2) = \frac{2.5^2}{2!} e^{-2.5} \approx 0.257
P(x=2)=2!2.52e−2.5≈0.257
出现3次进球
P
(
x
=
3
)
=
2.
5
3
3
!
e
−
2.5
≈
0.213
P(x=3) = \frac{2.5^3}{3!} e^{-2.5} \approx 0.213
P(x=3)=3!2.53e−2.5≈0.213
⋯ \cdots ⋯
现在我们把数据绘制到图表上,根据我们推算的进球概率,这样一个概率分布,就是泊松分布了。
泊松分布与光通量
接下来,我们把这个进球的过程换成单位时间内,有多少光子打到像素传感器上的过程3。为了说明这个问题,我们先解释一下照片成像的过程。我们在初中阶段的光学课程上,知道以前的老式相机是光束通过透镜,与胶片上的光感材料作用,把影像印在了胶片上,这个过程和小孔成像很类似。
而现代数码相机的普及,最大的区别就是胶片变成了光传感器。也就是CMOS传感器。由于它是一种光敏材料,在被一定量的光子照射后会产生电子,继而引起数字信号的变化。而在一个CMOS传感器上,有极多的传感单元,而这些传感单元,你可以理解为我们平常提到的像素。
而光子作用在每一个传感单元上所产生的信号不同,最终组合在一起成为了像素照片。所以如果我们把这个过程简化后,假设在单位时间内,有一束光包含有400个光子打到了相机的CMOS传感器上,从微观上说,这些光子是随机的落入到不同的像素传感器内。
因此对应一个像素传感器来说,如果我们统计一个像素在单位时间
t
t
t内,会有多少个光子落入到一个像素传感器内,那么可能会得出
[
0
,
4
]
[0,4]
[0,4]这样的一个结果。那么统计平均落入的光子数,并且绘制它的落入分布情况,我们可能会得出一个泊松分布。
假设这个情况的
λ
=
1.05
\lambda=1.05
λ=1.05,也就是平均每个像素传感器落入1.05个光子,那么它的概率分布图,也就是下面这个样子了:
那么这个模型的标准差
σ
=
∑
(
x
i
−
x
ˉ
)
n
≈
1.024
\sigma = \sqrt{\frac{\sum (x_i - \bar{x})}{n}} \approx 1.024
σ=n∑(xi−xˉ)≈1.024,而这里的1.024就是光子散粒噪音(photon shot noise)
由于光子极微小,尽管我们假设从物体反射进入到CMOS传感器上的光束在单位时间内为400个光子,但这数量本质上来说是估算,或者说是平均数。因此实际上在单位时间 t t t内,传入CMOS的光子数可能是401,也可能是387个光子。
尽管从宏观上光子传播沿直线传播,但在微观上也就是光子运动轨迹存在一定程度的不确定性,这必然导致了噪音的产生。
所以我们可以直观的凭本能知道,如果这样一些带着随机运动的粒子,在击打到某个平面位置时,尽管大范围上符合某个范围的分布区间,但如果细致的观察,每一个落点存在着一定的误差(“噪音”)。
这就像是用霞弹枪、或者抛洒石灰,或者颜料,即便用同一种颜料,同一个角度抛洒到画布上,任然无法做出两幅一样的画的那种奇妙的感觉。
那么,光子到达图像传感器的这些波动如何影响我们的图像?噪点会使照片的视觉细节失真。而下图显示了一组模拟的图像,其中指定了每个像素的平均粒子数或光子数。 这表明对于低数量的光子,噪声占主导地位,但是随着光子数量(光通量)的增加,图像结构变得更加明显。
所以,如果我们要试图模拟泊松噪音,也就是单位像素传感器
p
p
p在单位时间
t
t
t内捕捉到的有效光子量,那么这个过程就是一个所谓的 “泊松过程(Poisson Process)”。
泊松过程(Poisson Process)
现在,我们已经知道了一个概率事件它的随机概率发生分布情况,如果近似符合二项分布的,就是一个泊松分布。现在我们需要反过来,去做一个符合这个分布的随机事件发生过程,而它就是所谓的泊松过程了。
回到上面的例子,如果在单位事件内 t t t,向CMOS传感器发射了(或者说物体反射了)一束包含400个光子光束,落在单个像素传感器上的光子数为 P s ∈ [ 0 , 4 ] P_s \in[0, 4] Ps∈[0,4] ,我们在不考虑这些光子激发的电子有效率的情况下,假设1个光子落入了像素传感器就会产生1个电子。
也就是说,在单位时间内,一个像素可能接收到0个、1个、2个、3个、4个光子4。而如果我们把观察时间微分为 Δ t \Delta t Δt,如果假设在某一个时间内,发生了1个光子击中像素传感器,那么接下来再发生一次光子击中像素传感器的概率就会下降;如果再次发生一次击中,那么等待第三次发生的概率就会更小,直到全部观察时间结束。
这一过程符合于指数,通常情况下,我们用下式表示这个过程,用通俗的话来描述:就是当某事件发生后,后续事件再次发生的概率,随着时间增加而减少
P ( t w a i t > t e v e n t ) = e − σ ⋅ t P(t_{wait} > t_{event}) = e^{- \sigma \cdot t} P(twait>tevent)=e−σ⋅t
这里 t t t表示时间变量, σ \sigma σ表示单位时间内 Δ t \Delta t Δt事件怕平均发生次数,或者说是期望。那么,我们同样可以得出某事件发生前的前面的事件发生概率,随着时间减少而增加
P ( t w a i t ≤ t e v e n t ) = 1 − e − σ ⋅ t P(t_{wait} \leq t_{event}) = 1 - e^{- \sigma \cdot t} P(twait≤tevent)=1−e−σ⋅t
明白了这个公式的概念,接下来我们就可以来模拟这个泊松过程了,那么受限于篇幅,生成泊松随机数的过程我就放在下一章里进行介绍了。