将图像进行奇异值分解后,通过对对角矩阵进行一系列操作,可以达到压缩图像以及信息隐藏的目的。不仅如此,随着计算机网络和网络技术的不断发展,数字图像、音频和视频产品越来越需要一种有效的版权保护方案。利用奇异值分解实现数字图像水印技术,将水印图片置乱再隐写入图像的方法可以在一定程度上避免被破译;为了进一步防止破译,结合离散余弦变化以及奇异值分解的数字水印技术更加安全。
一张灰度图是由m*n的矩阵构成的,矩阵的每一个元素是处于0~255的整数,每一个元素就代表着对应像素点的灰度。而一张彩色图像可以转化为灰度图rgb2gray()
,即一个非负矩阵A。
奇异值分解svd()
可以理解为利用正交向量的性质,将矩阵A包含的**“信息空间”分解为r个相互正交的“子信息空间”,每一个“子信息空间”中都包含了一定的原图像信息。相应的,删除k个“子信息空间”可以达到降维的目的,k取合适的值时,人类肉眼无法清楚分辨是否有信息损失,就可以达到兼顾保留图像信息和压缩图像文件大小**的目的。
矩阵A的奇异值分解定义如下:
A = U S V T A=USV^T A=USVT
具体步骤是:
A 1 = U 1 S 1 V 1 T A_1=U_1S_1V_1^T A1=U1S1V1T
clear;
% 使用SVD压缩图像
%% k=1000
k = 1000;
img = double(rgb2gray(imread("Always like this.png")));
[U, S, V] = svd(img);
U_1 = U(:, 1:k);
SS = diag(S);
SSS = SS(1:k);
S_1 = diag(SSS);
V_1 = V(:, 1:k);
img_1000 = U_1*S_1*V_1';
figure(1);
subplot(1, 3, 1);
imshow(mat2gray(img_1000));
title('k=1000');
%% k=100
k = 100;
U_2 = U(:, 1:k);
SSS = SS(1:k);
S_2 = diag(SSS);
V_2 = V(:, 1:k);
img_100 = U_2*S_2*V_2';
subplot(1, 3, 2);
imshow(mat2gray(img_100));
title('k=100');
%% k=50
k = 50;
U_3 = U(:, 1:k);
SSS = SS(1:k);
S_3 = diag(SSS);
V_3 = V(:, 1:k);
img_50 = U_3*S_3*V_3';
subplot(1, 3, 3);
imshow(mat2gray(img_50));
title('k=50');
将SVD应用于数字图像水印技术的主要理论背景是:
具体步骤:
A = > U S V T A => USV^T A=>USVT
S + α W = > U 1 S 1 V 1 T S+\alpha W => U_1S_1V_1^T S+αW=>U1S1V1T
A ^ = U S 1 V T \hat{A}=US_1V^T A^=US1VT
如果想要提取水印,则只需要进行简单的求逆,再次不多赘述。
此处的水印图我使用的是二维码,采用Python中qrcode
库生成。
import qrcode
qr = qrcode.QRCode(
version = 2,
box_size = 8,
border = 0
)
qr.make('Life is fantastic')
img = qr.make_image();
with open('watermark.png', 'wb') as f:
img.save(f)
Arnold置乱(也称作cat置乱)可以对图像进行置乱,使得原本有意义的图像变成一张无意义的图像。其矩阵变换公式是:
( x 1 y 1 ) = [ 1 1 1 2 ] ( x y ) m o d ( N ) \begin{pmatrix}x_1\\y_1\\\end{pmatrix}=\begin{bmatrix} 1&1\\1&2\\\end{bmatrix}\begin{pmatrix}x\\y\\\end{pmatrix}mod(N) (x1y1)=[1112](xy)mod(N)
其中mod是取模运算,N是正方形图像的边长, ( x 1 , y 1 ) (x_1, y_1) (x1,y1)是 ( x , y ) (x, y) (x,y)变换之后的坐标位置。
对于图像而言,这里的位置移动,实际上是对应点的灰度值或者RGB值的移动,即将原来 ( x , y ) (x, y) (x,y)的灰度值或者RGB颜色分量移动到 ( x 1 , y 1 ) (x_1, y_1) (x1,y1)如果对一个数字图像迭代地使用离散化的Arnold变换,即将左端输出的 ( x 1 , y 1 ) (x_1, y_1) (x1,y1)作为下一次Arnold变换的输入,则可以重复这个过程一直做下去。当迭代到某一步时,如果出现的图像呈现出杂乱无章、无法辨识的情况,那么就得到了一幅置乱图。
相应的反变换公式如下:
( x 2 y 2 ) = [ 2 − 1 − 1 1 ] ( x 1 y 1 ) m o d ( N ) \begin{pmatrix}x_2\\y_2\\\end{pmatrix}=\begin{bmatrix} 2&-1\\-1&1\\\end{bmatrix}\begin{pmatrix}x_1\\y_1\\\end{pmatrix}mod(N) (x2y2)=[2−1−11](x1y1)mod(N)
为了提高加密程度,我们在插入水印之前先对水印图像进行预处理,即对它进行Arnold置乱,然后再执行基于奇异值分解的数字水印插入。
clear;
%% 图片预处理以及将水印图片加密进目标图片
% 首先将目标图像转化为灰度图
img = imread('Always like this.png');
watermark = double(imread('watermark.jpg'));
img_grayed = double(rgb2gray(img));
imwrite(uint8(img_grayed), 'AlwaysLikeThis_grayed.jpg');
figure(1);
subplot(2, 3, 1);
imshow(uint8(img_grayed));
title("原始图像");
subplot(2, 3, 2);
imshow(watermark);
title("原始水印");
key = 10; % key是对watermark进行Arnold置乱的次数
a = 3;b = 5;
[prow, pcol] = size(img_grayed);
[wrow, wcol] = size(watermark);
% 进行arnold置乱
watermark_arnolded = zeros(wrow, wcol);
for i = 1:key
for y = 1:wrow
for x = 1:wcol
xx = mod((x-1)+b*(y-1), wrow)+1;
yy = mod(a*(x-1)+(a*b+1)*(y-1), wrow)+1;
watermark_arnolded(yy, xx) = watermark(y, x);
end
end
watermark = watermark_arnolded;
end
subplot(2, 3, 3);
imshow(watermark_arnolded);
title("arnold置乱后的watermark");
% 对原始图像进行奇异值分解
[U, S, V] = svd(img_grayed);
S_copy = S;
alpha = 0.1; % 叠加强度
for i = 1:wrow
for j = 1:wcol
S(i, j) = S(i, j) + alpha * watermark_arnolded(i, j);
end
end
[U_1, S_1, V_1] = svd(S);
img_watered = U*S_1*V';
subplot(2, 3, 4);
imshow(uint8(img_watered));
title("添加水印的图像");
%% 将水印图片提取
[U_2, S_2, V_2] = svd(img_watered);
D = U_1 * S_2 * V_1';
watermark_res = zeros(wrow, wcol);
for i = 1:wrow
for j = 1:wcol
watermark_res(i, j) = (1/alpha)*(D(i, j) - S_copy(i, j));
end
end
subplot(2, 3, 5);
imshow(watermark_res);
title('提取出的watermark');
% 还原watermark
watermark_recovery = zeros(wrow, wcol);
for i = 1:key
for y = 1:wrow
for x = 1:wcol
xx=mod((a*b+1)*(x-1)-b*(y-1), wrow)+1;
yy=mod(-a*(x-1)+(y-1), wrow)+1;
watermark_recovery(yy, xx) = watermark_res(y, x);
end
end
watermark_res = watermark_recovery;
end
subplot(2, 3, 6);
imshow(watermark_recovery);
title("还原提取出的watermark");
离散余弦变换 DCT是数据通信中最常用到的 变换编码的方法。图像经过DCT变换后其系数有较 好的感觉容量,利用此特点可以将水印信息嵌入到 DCT系数中,而不会引起图像质量的较大改变。
DCT系数分为直流分量和交流分量。直流分量 代表图像的亮度值,交流分量又分为高频带,中频带 和低频带。高频系数一般很小,人眼对高频部分失真 不太敏感,在图像做Jpeg等压缩处理时会将高频部分 舍弃,若在高频嵌入水印鲁棒性会降低,所以一般选 择将水印嵌入低频中。低频是数据块中能量集中区 域,也就是说经过DCT变换后,图像的大部分重要信 息都集中在DCT的低频系数里,所以在嵌入水印是 要控制好强度否则会造成图像质量下降。
二维离散余弦变换公式:
F ( u , v ) = c ( u ) c ( v ) ∑ x = 0 N − 1 ∑ y = 0 N − 1 f ( x , y ) c o s ( 2 x + 1 2 N u π ) c o s ( 2 y + 1 2 N v π ) x , y , u , v = 0 , 1 , . . . , N − 1 c ( u ) = c ( v ) = { 1 2 u = 0 , v = 0 1 其 他 F(u, v)=c(u)c(v)\sum_{x=0}^{N-1}\sum_{y=0}^{N-1}f(x, y)cos(\frac{2x+1}{2N}u\pi)cos(\frac{2y+1}{2N}v\pi)\ \ \ \ x,y,u,v=0,1,...,N-1 \\ c(u)=c(v)=\begin{cases} \frac{1}{\sqrt{2}}\ \ u=0,v=0\\ 1 \ \ 其他 \end{cases} F(u,v)=c(u)c(v)x=0∑N−1y=0∑N−1f(x,y)cos(2N2x+1uπ)cos(2N2y+1vπ) x,y,u,v=0,1,...,N−1c(u)=c(v)={21 u=0,v=01 其他
二维离散余弦逆变换公式:
f ( x , y ) = 2 N ∑ u = 0 N − 1 ∑ v = 0 N − 1 F ( u , v ) c o s ( 2 x + 1 2 N u π ) c o s ( 2 y + 1 2 N v π ) x , y , u , v = 0 , 1 , . . . , N − 1 c ( u ) = c ( v ) = { 1 2 u = 0 , v = 0 1 其 他 f(x, y)=\frac{2}{N} \sum_{u=0}^{N-1} \sum_{v=0}^{N-1}F(u, v)cos(\frac{2x+1}{2N}u\pi)cos(\frac{2y+1}{2N}v\pi)\ \ \ \ x,y,u,v=0,1,...,N-1 \\ c(u)=c(v)=\begin{cases} \frac{1}{\sqrt{2}}\ \ u=0,v=0\\ 1 \ \ 其他 \end{cases} f(x,y)=N2u=0∑N−1v=0∑N−1F(u,v)cos(2N2x+1uπ)cos(2N2y+1vπ) x,y,u,v=0,1,...,N−1c(u)=c(v)={21 u=0,v=01 其他
其中 F ( u , v ) F(u,v) F(u,v)为频域函数, f ( u , v ) f(u,v) f(u,v)为空域函数。
下列为 m × n m\times n m×n的灰度图原始载体图像和 k × h k\times h k×h的二值水印图像的水印嵌入过程。
下面代码的重要前提条件是:载体图像的长宽能被水印图像的长宽整除,否则就会出现bug。
clear;
img = imread("Always like this.png");
watermark = double(imread('watermark.png'));
img_grayed = double(rgb2gray(img));
key = 10; % key是对watermark进行Arnold置乱的次数
a = 1;b = 1;
[prow, pcol] = size(img_grayed);
[wrow, wcol] = size(watermark);
% 进行arnold置乱
watermark_arnolded = zeros(wrow, wcol);
for i = 1:key
for y = 1:wrow
for x = 1:wcol
xx = mod((x-1)+b*(y-1), wrow)+1;
yy = mod(a*(x-1)+(a*b+1)*(y-1), wrow)+1;
watermark_arnolded(yy, xx) = watermark(y, x);
end
end
watermark = watermark_arnolded;
end
%% 嵌入水印
img_block = mat2cell(img_grayed, (prow/wrow)*ones(1, wrow), (pcol/wcol)*ones(1, wcol));
q = 0.5;
img_res = zeros(prow, pcol);
for i = 1:wrow
for j = 1:wcol
temp_matrix = dct2(cell2mat(img_block(i, j)));
[U, S, V] = svd(temp_matrix);
z = rem(S(1, 1), q);
if watermark_arnolded(i, j) == 1
S(1, 1) = S(1, 1)-z+q;
elseif watermark_arnolded(i, j) == 0
S(1, 1) = S(1, 1)-z+0.5*q;
end
temp_matrix = U*S*V';
temp_matrix = idct2(temp_matrix);
for k = 1:(prow/wrow)
for h = 1:(pcol/wcol)
img_res((i-1)*(prow/wrow)+k, (j-1)*(pcol/wcol)+h) = temp_matrix(k, h);
end
end
end
end
subplot(1, 3, 1);
imshow(uint8(img_res));
title('加入水印后的图像') ;
%% 对水印进行提取
img_block = mat2cell(img_res, (prow/wrow)*ones(1, wrow), (pcol/wcol)*ones(1, wcol));
watermark_recovery = zeros(wrow, wcol);
for i = 1:wrow
for j=1:wcol
temp_matrix = cell2mat(img_block(i, j));
temp_matrix = dct2(temp_matrix);
[U, S, V] = svd(temp_matrix);
if rem(S(1, 1), q) > 0
watermark_recovery(i, j) = 0;
else
watermark_recovery(i, j) = 1;
end
end
end
subplot(1, 3, 2);
imshow(watermark_recovery);
title('提取出的水印图像');
%% 对水印反置乱
a = 1;b = 1;
key = 10;
watermark_res = zeros(wrow, wcol);
for i = 1:key
for y = 1:wrow
for x = 1:wcol
xx=mod((a*b+1)*(x-1)-b*(y-1), wrow)+1;
yy=mod(-a*(x-1)+(y-1), wrow)+1;
watermark_res(yy, xx) = watermark_recovery(y, x);
end
end
watermark_recovery = watermark_res;
end
figure(3);
imshow(watermark_res);
title('反置乱之后的水印图像');
可以看到反置乱之后的水印充满各种噪点,经过python.cv2简单降噪之后手机仍然无法识别二维码,噪点出现的原因暂时不知道。(以后可能会解决?)
[1]刘瑞祯,谭铁牛.基于奇异值分解的数字图像水印方法[J].电子学报,2001(02):168-171.
[2]丁珊,张定会,周雄葵.一种基于离散余弦变化和奇异值分解的数字水印算法[J].数据通信,2015(04):51-54.