本repo实现的是扩频水印的嵌入,以及相应的水印检测、解码、以及理论的蒙特卡洛仿真。在实现水印嵌入之前,我们需要进行实验以验证理论分析的正确性。当然我们可以直接用真实图像来进行实验,但却有点不现实,因为读取图像很花时间,而实验又需要大量的实验图像,因此实验可能需要大量的时间。因此我们通常可以用Monte-Carlo
仿真来验证理论结果的正确性。Monte-Carlo
仿真就是通过伪随机数发生器产生原始数据(模拟宿主数据X),而水印的嵌入和检测就是在这些数据中进行的。
为了生成这些数据,我们需要编写基本的概率分布生成函数,包括高斯分布、指数分布、二项分布、伽马分布、广义高斯分布。在真实的实验中,我们在图像的 DCT 系数上嵌入信息,图像为常见的512×512
大小的Lena灰度图
,该图也是数字图像领域使用最广泛的图像。嵌入的信息为一二进制Logo图(即该图只有两种颜色:黑和白),该图大小为32×32
,共1024
个 Bit。
下面我们看看如何随机产生[a, b]之间的任意整数呢,较常用的有线性同余发生器(Linear Congruential Genertor)和梅森扭搅器(Mersenne Twister)。目前较流行且效果较好的是Mersenne Twister
。程序语言中也提供了产生随机数的方法,例如 VC++提供的 rand 函数,该函数是利用线性同余法产生了0
到RAND_MAX
的整数,但RAND_MAX
却相对比较小,大小只有32767(0x7fff)
,因此如何产生更大的数呢?
很简单的方法就是将两个随机数组合起来,比方我们分别调用rand()
函数产生了X
和Y
,且X
和Y
相互独立,那么
Z = X ⋅(RAND_MAX +1)+Y
就是一个更大的随机数。对于给定的任意一个 z,相应 x, y 也就唯一确定了,即
y=z mod (RAND_MAX+1) x = ( z − y ) /( RAND _ MAX + 1)
因此由于X和Y相互独立
P(Z = z) = P(X = x,Y = y) = P(X = x)P(Y = y) = 1 (RAND_MAX +1)2
所以 Z 是一个[0,(RAND_MAX+1)2 −1]
均匀分布的整数,然后我就可以变换成更加精确的[0,1]
均匀分布。
Monte-Carlo
仿真就是通过伪随机数发生器产生原始数据(模拟宿主数据X
),而水印的嵌入和检测就是在这些数据中进行的。比方讲如果X可以用高斯分布来描述,那么我们就产生一组高斯随机数X = {X1, X2, X3...}
。由于理论分析涉及到概率,它必须通过大量的数据来进行。例如如果我们需要验证的误检概率pfa
精度为1E−5
,那么我们通常至少需要100,000
组X
,因为只有这么多数据才能达到这个精度,当然数据越多,效果越好。
由于我们的理论分析没有将w
看成一个随机变量,所以实验时我们无需改变w
。当然如果在真实图像中进行实验,由于无法用很多图像进行实验,我们也可以采用不同的w,而固定x的方法,不过必须要注意这时候理论分析和实验结果会有一定的偏差,因为此时我们把W
看成是随机的。 如果产生不同的W
呢?
其实很简单的方法就是将W
设为{1, 1, 1, ..., −1, −1, −1,...}
,然后对W
进行轮换。
Randomize-In-Place:
Randomize-In-Place(W) 1 N ← length[W]
2 fori←1toN
3 do swap W[i] ↔ W[Random(i, N )], where Random generates a random number i ≤ x ≤ N
水印嵌入流程
为了嵌入水印,当然大家首先需要读取图像文件,每种图像文件都有一种文件格式,比较简单的如BMP
格式,大家的实验可以用灰度的BMP
图像来进行。嵌入信息为一二进制Logo图(即该图只有两种颜色:黑和白),当然如果是黑色,我们表示 b = 0
,白色表示 b = 1
。为了方便嵌入,我们将 b = 0
用 b = −1
表示,而 b = 1
还是用 b = 1
表示,此时 b 就代表了扩频算法中的 b。
该二进制图大小为 32×32
,共 1024 个 Bit,表示需要嵌入的数据为 1024 bits。当然二进制图读取时也是按 byte 读,对应于 C 语言中的 unsigned char
,此时一 个 byte 表示了 8 个像素,因此读取后还要提取其中的每一个 bit。
DCT变换:DCT是离散余弦变换(Discrete Cosine Transform)。DCT 可以采用 N×N(我们采用 N = 8)的变换,如果原始数据用矩阵 X 表示,变换后的数据用 Y 表示,那么 DCT 变换就是这样的一种变换:Y=TXT’
。T 是一个正交矩阵满足 TT’ = E,其中 T’是 T 的转置矩阵。反变换因此是X = T’YT
。
因此实验中我们首先对图像(像素)进行变换,求得变换后的 DCT 系数,然后我们用 ASS 在其中嵌 入信息,嵌入完后,我们还需要反 DCT 变换,变换成相应的图像(像素)。
我们看到经过 8×8 变换后,DCT 系数可以用上图表示,那么我们选择那些系数来嵌入水印呢? C0,0
表示最左上角的系数,称为直流DC系数,它表示图像的平均值(亮度),为了不使水印嵌入影响图像的亮度,我们通常不在DC系数中嵌入水印。我们将DCT系数按照上图所示的路线排列 起来,排在前面的称为低频系数,中间位置的为中频系数,而后面的称为高频系数,当然中频系数是一个模糊的概念。为了保证嵌入水印不影响知觉失真和足够的健壮性,我们通常在中频系数中嵌入水印。
水印嵌入:下面我们具体谈谈嵌入的方法。
(a) 我们首先将整个图像分成8×8
的小块,假设这样共得到M
小块。例如512×512
的 Lena 图,我们可以得到M = 4096
。
(b) 然后我们对每小块进行DCT变换,取出其中按Zigzag排列的中频系数,假设每块我们选取K
个系数用来嵌入数据。当然选取的原则我们已经在前面讲过了,K 可以根据要求确定。按照这样做, 我们共得到 M×K
个 DCT
系数可用于水印嵌入,这些系数就是扩频算法中的 x = {x1, x2, ..., xM×K}
。
(c) 接下来我们开始嵌入数据,为了保证足够的健壮性,扩频算法通常需要使用多个(N 个)系数 来嵌入一位 b
。假设我们需要嵌入 L
位数据,那么 N = M×K / L
。假设这 L
位数据分别表示成 b1, b2, ..., bL
,因此嵌入的过程就是
s =x +baw, i=1,2,...,N ii1i
si =xi +b2awi,i=N+1,N+2,...,2N
...
si =xi +bLawi, i=(L−1)N+1,(L−1)N+2,...,LN
此处 a
位嵌入强度,它的大小可以自己选,当然越大健壮性越好,但是必须要保证不影响图像的感观质量。
(d) 将 si
放回原来的位置,即如果 xi
是取自第一块 C4,3
(参考上图)位置上的数据,si
还应该放在原来这个位置。
反 DCT 变换:最后对嵌入水印后的 DCT 系数进行反变换就完成了嵌入过程,反变换过程中需要注意,DCT 系数反变换后的数据(像素值)通常不是整数,因此我们需要求其最近的整数,例如假 设反变换后的数据 x = 243.7
,那么我们将其变为 244
;而如果 x > 255
,我将其取为 255
; x < 0
, 我 们将其取为 0
。
解码的过程和嵌入的过程相似,解码时使用的 DCT 系数和嵌入时的必须一致。将解码出的数
据按照顺序读入二进制图中,可以看看此时的 LOGO 和原始的 LOGO 的差别。