图像噪声是图像在获取或传输过程中受到随机信号干扰,妨碍人们对图像理解及分析处理的信号。噪声一般分为分为加性噪声和乘性噪声:
f ( x , y ) = g ( x , y ) + q ( x , y ) f(x,y)=g(x,y)+q(x,y) f(x,y)=g(x,y)+q(x,y) f ( x , y ) = g ( x , y ) × q ( x , y ) f(x,y)=g(x,y)\times q(x,y) f(x,y)=g(x,y)×q(x,y) 其中, f ( x , y ) f(x,y) f(x,y)表示图像, g ( x , y ) g(x,y) g(x,y)表示没有噪声的图像部分, q ( x , y ) q(x,y) q(x,y)表示噪声。 很多时候将图像噪声看作是多维随机过程,因而描述图像噪声的方法完全可以借助随机过程的描述,即使用其概率密度函数和分布函数。在很多情况下,这些函数很难测定和描述,甚至无法得到,所以常用统计特征来描述噪声,如:均值,方差和相关函数。 常见的几种噪声有: 高斯噪声、 瑞利噪声、 泊松噪声和 椒盐噪声。除椒盐噪声以外其他三种均属于加性噪声。椒盐噪声既不属于加性噪声也不属于乘性噪声。 在实际应用中,为了验证滤波算法的有效性,有时需要在图像上模拟添加各种不同类型的噪声。MATLAB中的imnosie函数可以为图像添加不同类型的噪声,本节将介绍几种噪声的添加方法,代码实现借鉴了imnoise函数。为图像添加噪声既可以使用C++标准库random,也可以使用opencv提供的函数。无论哪一种方法都涉及到随机数的相关知识,这里进行简要介绍,主要是涉及C++中的random标准库,具体请参考《C++primer(第五版)》。理论上计算机只能产生均匀分布,其他分布都是通过对均匀分布进行各种变换得到的。常用的变换方法有逆变换、拒绝采样等等。具体算法可以参考Luc Devroye, Non-Uniform Random Variate Generation(Springer-Verlag, New York, 1986)、左飞的《图像处理中的数学修炼》(清华大学出版社)和维基百科。 C++11新增了random库来产生随机数,通过使用随机数引擎类和随机数分布类生成符合要求的随机数。
random-number engines:随机数引擎类,生成均匀分布的无符号整数(需要设置种子,不然每次生成的随机数都一样)。random-number distribution:随机数分布类,将生成器生成的数字序列转换为服从特定分布的数字序列,例如均匀分布,正态分布、二项分布、泊松分布。随机数引擎是函数对象类,它们定义了一个调用运算符,该运算符不接受参数并返回一个随机无符号整数。我们可以通过调用一个随机数引擎对象来生成原始随机数。
default_random_engine e; for(size_t i = 0; i < 10; i++) { cout << e() << " "; }标准库定义了多个随机数引擎类,区别在于性能和随机性质量不同。每个编译器都会指定其中一个作为default_random_engine类型。此类型一般具有最常用的特性。下表列出了随机数引擎操作。
操作描述Engine e;默认构造函数:使用该引擎类型默认的种子Engine e(s);使用整型值s作为种子e.seed(s)使用种子s重置引擎状态e.min()此引擎可生成的最小值和最大值e.max()Engine::result_type此引擎生成的unsigned整型类型e.discard(u)将此引擎推进u步;u的类型为unsigned long long大多数情况下,随机数引擎的输出是不能直接使用的,必须要转换为合适的范围、类型和分布。
一个给定的随机数发生器一直会生成相同的随机数序列,这一特性在调试中很有用。但是,一旦我们的程序调试完毕,我们通常希望每次运行程序都会生成不同的随机结果,可以通过提供一个种子(seed)来达到这个目的。种子就是一个数值,引擎可以利用它从序列中一个新位置重新开始生成随机数。 为引擎设置种子有两种方式:在创建引擎对象时提供种子,或者调用引擎的 seed 成员:
default_random_engine e1; // 使用默认种子 default_random_engine e2(2147483646); // 使用给定的种子值 // e3 和 e4 将会生成相同的序列,因为他们使用了相同的种子 default_random_engine e3; e3.seed(32767); //调用 seed 设置为一个新种子值 default_random_engine e4(32767); // 将种子值设置为 32767 for(size_t i = 0; i != 10; i++) { if (e1() == e2()) cout << "unseeded match at iteeration: " << i << endl; if (e3() != e4()) cout << "seeded differs at itertion: " << i << endl; }设置种子最常用的方法是调用系统函数 time(),它返回一个特定时刻到当前经过了多少秒。函数 time 接受单个指针参数,它指向用于写入时间的数据结构。如果此指针为空,则函数简单的返回时间:
default_random_engine e1(time(0)); // 稍微随机些的种子由于 time 返回以秒计的时间,因此这种方式只适用于生成种子的间隔为秒级或更长的应用。
类似引擎类型,分布类型也是函数对象类。分布类型定义了一个调用运算符,它接受一个随机数引擎作为参数。分布对象使用它的引擎参数生成随机数,并将其映射到指定的分布。例如,为了得到一个在指定范围内的数,我们使用一个分布类型的对象
//生成 0 到 9 之间(包含)均匀分布的随机数 uniform_int_distribution<unsigned> u(0, 9); default_random_engine e; // 生成无符号随机整数 for (size_t i = 0; i < 10; i++) // 将 u 作为随机数源 // 每个调用返回在指定范围内并服从均匀分布的值 cout << u(e) << " ";上面的程序中,我们将 u 定义为 uniform_int_distribution<unsigned>。此类型生成均匀分布的unsigned值。当我们定义一个这种类型的对象时,可以提供想要的最小值和最大值。在上面这段代码中,u(0,9) 表示我们希望得到[0,9]之间的数。随机数分布类会使用包含的范围,从而我们可以得到给定整型类型的每个可能值。 注意,我们传递给分布对象的是引擎对象本身,也就是 u(e)。如果我们将调用写为 u(e()),含义就变为将 e 生成的下一个值传递给 u,会导致一个编译错误。我们传递的是引擎本身,而不是它生成的下一个值,原因是某些分布可能需要调用引擎多次才能得到一个值。 分布类型都是模板,通过设置类型参数,可以得到不同类型的随机数。实际上,random库定义了20种分布类型,分布的名字与他们的数学性质相对应。在下面的描述中,我们通过将类型说明为template_name来指出分布生成浮点数。对这些模板,可以使用float、double或long double代替RealT。类似的,IntT表示要求一个内置整型类型,但不包括bool类型或任何char类型。可以用来代替IntT的类型是short、int、long、long long、unsigned short、unsigned int、unsigend long或unsigned long long。 分布模板定义了一个默认模板类型参数。整型分布的默认参数是int,生成浮点数的模板的默认参数是double。
生成指定类型的,在给定包含范围内的值。m(或x)是可以返回的最小值;n(或y)是最大值。m默认为0;n默认为类型IntT对象可以表示的最大值。x默认为0.0,y默认为1.0。