/DataHiding

扩频水印嵌入;蒙特卡洛仿真;基本的概率分布实现

Primary LanguageC++

扩频水印嵌入

coverage build

本repo实现的是扩频水印的嵌入,以及相应的水印检测、解码、以及理论的蒙特卡洛仿真。在实现水印嵌入之前,我们需要进行实验以验证理论分析的正确性。当然我们可以直接用真实图像来进行实验,但却有点不现实,因为读取图像很花时间,而实验又需要大量的实验图像,因此实验可能需要大量的时间。因此我们通常可以用Monte-Carlo仿真来验证理论结果的正确性。Monte-Carlo仿真就是通过伪随机数发生器产生原始数据(模拟宿主数据X),而水印的嵌入和检测就是在这些数据中进行的。

为了生成这些数据,我们需要编写基本的概率分布生成函数,包括高斯分布、指数分布、二项分布、伽马分布、广义高斯分布。在真实的实验中,我们在图像的 DCT 系数上嵌入信息,图像为常见的512×512大小的Lena灰度图,该图也是数字图像领域使用最广泛的图像。嵌入的信息为一二进制Logo图(即该图只有两种颜色:黑和白),该图大小为32×32,共1024个 Bit。

Table of Contents

伪随机数发生器

下面我们看看如何随机产生[a, b]之间的任意整数呢,较常用的有线性同余发生器(Linear Congruential Genertor)和梅森扭搅器(Mersenne Twister)。目前较流行且效果较好的是Mersenne Twister。程序语言中也提供了产生随机数的方法,例如 VC++提供的 rand 函数,该函数是利用线性同余法产生了0RAND_MAX的整数,但RAND_MAX却相对比较小,大小只有32767(0x7fff),因此如何产生更大的数呢?

很简单的方法就是将两个随机数组合起来,比方我们分别调用rand()函数产生了XY,且XY相互独立,那么

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

Monte-Carlo仿真就是通过伪随机数发生器产生原始数据(模拟宿主数据X),而水印的嵌入和检测就是在这些数据中进行的。比方讲如果X可以用高斯分布来描述,那么我们就产生一组高斯随机数X = {X1, X2, X3...}。由于理论分析涉及到概率,它必须通过大量的数据来进行。例如如果我们需要验证的误检概率pfa精度为1E−5,那么我们通常至少需要100,000X,因为只有这么多数据才能达到这个精度,当然数据越多,效果越好。

由于我们的理论分析没有将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

ASS水印嵌入

水印嵌入流程

img

读取文件

为了嵌入水印,当然大家首先需要读取图像文件,每种图像文件都有一种文件格式,比较简单的如BMP格式,大家的实验可以用灰度的BMP图像来进行。嵌入信息为一二进制Logo图(即该图只有两种颜色:黑和白),当然如果是黑色,我们表示 b = 0,白色表示 b = 1。为了方便嵌入,我们将 b = 0b = −1 表示,而 b = 1 还是用 b = 1 表示,此时 b 就代表了扩频算法中的 b。

该二进制图大小为 32×32,共 1024 个 Bit,表示需要嵌入的数据为 1024 bits。当然二进制图读取时也是按 byte 读,对应于 C 语言中的 unsigned char,此时一 个 byte 表示了 8 个像素,因此读取后还要提取其中的每一个 bit。

DCT变换

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 变换,变换成相应的图像(像素)。

img

我们看到经过 8×8 变换后,DCT 系数可以用上图表示,那么我们选择那些系数来嵌入水印呢? C0,0表示最左上角的系数,称为直流DC系数,它表示图像的平均值(亮度),为了不使水印嵌入影响图像的亮度,我们通常不在DC系数中嵌入水印。我们将DCT系数按照上图所示的路线排列 起来,排在前面的称为低频系数,中间位置的为中频系数,而后面的称为高频系数,当然中频系数是一个模糊的概念。为了保证嵌入水印不影响知觉失真和足够的健壮性,我们通常在中频系数中嵌入水印。

水印嵌入

水印嵌入:下面我们具体谈谈嵌入的方法。

(a) 我们首先将整个图像分成8×8的小块,假设这样共得到M小块。例如512×512的 Lena 图,我们可以得到M = 4096

(b) 然后我们对每小块进行DCT变换,取出其中按Zigzag排列的中频系数,假设每块我们选取K个系数用来嵌入数据。当然选取的原则我们已经在前面讲过了,K 可以根据要求确定。按照这样做, 我们共得到 M×KDCT 系数可用于水印嵌入,这些系数就是扩频算法中的 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 系数进行反变换就完成了嵌入过程,反变换过程中需要注意,DCT 系数反变换后的数据(像素值)通常不是整数,因此我们需要求其最近的整数,例如假 设反变换后的数据 x = 243.7,那么我们将其变为 244;而如果 x > 255,我将其取为 255; x < 0, 我 们将其取为 0

水印解码

解码的过程和嵌入的过程相似,解码时使用的 DCT 系数和嵌入时的必须一致。将解码出的数

据按照顺序读入二进制图中,可以看看此时的 LOGO 和原始的 LOGO 的差别。