1. 问题描述
实现一个水印模型。基于svd的方法为图片加入水印。并能正确检测出加入了对应水印的图片。
需要用到的材料是,相对应的水印图片,原载体图像,加入水印信号后的图片。
需要实现的算法:
- 水印嵌入算法(编码过程)
- 水印验证、提取和检测算法(解码过程)
2. 算法实现
嵌入算法
假设A是载体图像,W是要嵌入的水印,$\alpha$是水印强度参数,按照如下方式构造水印图像Aw
$$
\begin{cases}
A \Rightarrow USV^T \\
L \Leftarrow S + \alpha W \\
L \Rightarrow U_1S_1V_1^T \\
A_w \Leftarrow US_1V^T
\end{cases}
$$
这里的W和S可能矩阵大小不一样,我默认将W这部分加在S的左上角。或者一开始将水印图像补充到和载体图像同样大小。提取算法
假设P为待检测图像,嵌入过程中的$U_1,V_1,\alpha,S$是保留的参数提取过程如下
$$
\begin{cases}
P \Rightarrow U_PS_pV_p^T \\
F \Leftarrow U_1S_pV_1^T \\
W_E \Leftarrow (F-S)/\alpha
\end{cases}
$$
最后提取出来的WE和整个原载体图像一样大小,我之前是将水印加入到了左上角,那么提取左上角那部分。或者一开始将水印图像补充到和载体图像同样大小。对合成图像的误差分析
对数字水印算法误差进行分析,先引入如下三条引理
若$A = [a_{ij}] \in R^{m*n}$, 则$||A||_2 = \sigma_{max}$
若$U \in R^{m∗m}, V \in R^{n∗n}$,是正交矩阵,并且$A=[a_{ij}] \in R^{m∗n}$,则有$||UAV||_2 = ||A||_2$ (矩阵2-范数的正交不变性)
令$A \in R^{m∗m}, \delta A$是A的误差矩阵,并且记$A_w = A+\delta A$。若$A,A_w$的奇异值从大到小分别排序为$S_i(A), S_i(A_w)$,则有
$$
|S_i(A) - S_i(A_w)| < ||\delta A||_2 \quad i=1,2,…,n
$$
根据如上所述,我们可以从嵌入算法中根据引理2看出
$$
\begin{aligned}
||A_w||_2 &= ||US_1V^T||_2 = ||S1||_2 \\
&= ||U1^TLV_1||_2 = ||L||_2 \\
&= ||S+\alpha W||_2 = ||U^TAV + \alpha W||_2
\end{aligned}
$$
再根据引理3,同时结合引理2可得:
$$
|S_i(U^TAV) - S_i(U^TAV+\alpha W)| = |S_i(A) - S_i(A_w)| \le \alpha ||W||_2 \quad i=1,2,…,n
$$
检测算法
目的是为了区分图片解码之后得到的WE也就是提取出来的水印图片是否满足要求。所以选择对图片进行解码,提取得到的水印图像和原水印图像相似度达到某个阈值的情况下认为是正确的加入过水印的图像。
比较两个矩阵的相似性。根据前面的误差分析很容易看出奇异值是最能体现一个矩阵的特征的。所以对比两个矩阵的奇异值向量序列来判定二者的相似性。
用相关系数(corrcoef)来进行度量。度量结果绝对值越接近1说明二者越相近。
3. 程序实现
一些辅助矩阵变换的函数
12345678910111213141516171819202122232425# 将s序列转化成m*n大小的对角矩阵def diagonal_mat(m, n, s):v = np.zeros((m, n))for i in range(len(s)):v[i, i] = s[i]return v# 不同大小的矩阵相加,默认加在左上角的部分def add_mat(a, b):(ma, na) = a.shape(mb, nb) = b.shapec = np.zeros((max(ma, mb), max(na, nb)))for i in range(max(ma, mb)):for j in range(max(na, nb)):if i < ma and j < na:c[i, j] += a[i, j]if i < mb and j < nb:c[i, j] += b[i, j]return c# 截取矩阵中左上角m*n的部分def extract_mat(a, m, n):assert(m<a.shape[0] and n<a.shape[1])b = a[:m, :n]return b嵌入水印
1234567891011121314151617181920# 对图片a,加水印图片b,通过svd生成新的图片的,a,b均为文件名, alpha为水印强度# (1) 嵌入(编码)过程def encoding_watermark(a, b, alpha):A = cv2.imread(a, cv2.IMREAD_GRAYSCALE)W = cv2.imread(b, cv2.IMREAD_GRAYSCALE)print('A shape: {}'.format(A.shape))print('W shape: {}'.format(W.shape))u, s, vt = np.linalg.svd(A, full_matrices=True)s = diagonal_mat(u.shape[0], vt.shape[0], s) # 原s是个向量,转化成对角矩阵print(np.allclose(A, np.dot(np.dot(u, s), vt))) # 判断是否相同L = add_mat(s, alpha*W)u1, s1, v1t = np.linalg.svd(L)s1 = diagonal_mat(u1.shape[0], v1t.shape[0], s1)aw = np.dot(np.dot(u, s1), vt)print('generate aw shape: {}'.format(aw.shape))# cv2.imwrite("generate.jpg", aw)return aw从待检测图像中提取水印
1234567891011121314151617181920212223# 提取(解码)过程,a是原图片的文件名, b是水印图片的文件名, c是嵌入过程中加过水印后的文件名# return 从c图片提取出来的水印矩阵def decoding_watermark(a, b, c, alpha):A = cv2.imread(a, cv2.IMREAD_GRAYSCALE)W = cv2.imread(b, cv2.IMREAD_GRAYSCALE)u, s, vt = np.linalg.svd(A, full_matrices=True)s = diagonal_mat(u.shape[0], vt.shape[0], s) # 原s是个向量,转化成对角矩阵print(np.allclose(A, np.dot(np.dot(u, s), vt))) # 判断是否相同L = add_mat(s, alpha*W)u1, s1, v1t = np.linalg.svd(L)'''前面那部分和encoding中的处理一样,获得对应的一些矩阵'''P = cv2.imread(c, cv2.IMREAD_GRAYSCALE)up, sp, vpt = np.linalg.svd(P)sp = diagonal_mat(up.shape[0], vpt.shape[0], sp)F = np.dot(np.dot(u1, sp), v1t)WE = (F-s) / alphaWE = extract_mat(WE, W.shape[0], W.shape[1])return WE判断两个矩阵的相关系数计算
12345# 计算两个矩阵奇异值序列的相关系数def cal_similar_of_matrix(a, b):ua, sa, vat = np.linalg.svd(a)ub, sb, vbt = np.linalg.svd(b)return np.corrcoef(sa, sb)求出一个待检测图像的相关系数
123456# 检测部分,c为检测图像文件, a是原图片的文件名, b是水印图片的文件名, alpha是水印强度# return p提取出来的水印和实际水印的相关系数def check(a, b, c, alpha):WE = decoding_watermark(a, b, c, alpha)W = cv2.imread(b, cv2.IMREAD_GRAYSCALE)return cal_similar_of_matrix(WE, W)流程main实现
1234567891011121314151617181920# init variablesalpha = 0.5 # 1.0, 0.5, 0.2watermark1 = 'image/watermark1.png'base = 'image/base.jpg'generate1 = 'image/encoding0_5.jpg'decoding_watermark1 = 'image/decoding0_5.jpg'# 生成水印加密的图片aw = encoding_watermark(base, watermark1, alpha)cv2.imwrite(generate1, aw)# 解码得到水印图片cv2.imwrite(decoding_watermark1, decoding_watermark(base, watermark1, generate1, alpha))# 用生成的水印图片解密和两种水印进行对比,计算矩阵相似度similarity1 = check(base, watermark1, base, alpha)similarity2 = check(base, watermark1, generate1, alpha)print('水印强度为: {}'.format(alpha))print('用原载体图像解码得到的水印和原水印的相关系数为: {}'.format(similarity1))print('用附加水印(编码后的)图像解码得到的水印和原水印的相关系数为: {}'.format(similarity2))
4. 实验结果
原图像地址:[]
|
|
水印强度为1.0时,生成的图像和提取的水印如下
水印强度为0.5时,生成的图像和提取的水印如下
水印强度为0.2时,生成的图像和提取的水印如下
5.实验分析
我制作的水印图像素与原图接近,所以在加密之后对原图影响看上去较大。
当然也很显然,水印强度越大,对原图的影响越大。而且提取出的水印和原水印相差越大。
根据不同的alpha,得到相关系数的分析。可以设定不同的相关系数的阈值,来区别图像是否具有相应的水印。
我尝试了将加入了别的水印的图片和加了相对应水印的图片进行比较,甚至会出现加了不正确水印的那张图提取出来的水印图片与原水印图更接近,从而会影响判别。这是这个方法不足的地方。