算法说明
实验选取一种典型的Logistic 混沌序列,如下:
x
n
+
1
=
λ
x
n
(
1
−
x
n
)
,
x
n
∈
[
0
,
1
]
x_{n+1}= \lambda x_{n}\left ( 1-x_{n} \right ),x_{n}\in [0,1]
xn+1=λxn(1−xn),xn∈[0,1]
其中
3.569946
…
≤
λ
≤
4
,
0
<
x
n
<
1
3.569946… \leq \lambda \leq 4 , 0 < x_{n} < 1
3.569946…≤λ≤4,0<xn<1
可见水印嵌入过程:
- 步骤1:打开原图像I、二值可见水印图像W
- 步骤2:输入密钥key,利用logistic映射由密钥key生成二值混沌序列P。
- 步骤3:在图像I中嵌入可见水印W,
- 步骤4:将P与W进行异或生成加密水印序列W1。
- 步骤5:将加密水印信号W1按比特位依次嵌入到数字图像I 的LSB中去,得到含水印图像 。
可见水印去除过程:
基本是水印嵌入逆过程。
- 步骤1:打开含水印图像 。
- 步骤2:输入密钥key,利用logistic映射由密钥key生成二值混沌序列P。
- 步骤3:含水印图像 的LSB中提取加密水印信号W1。
- 步骤4:将P与W1进行异或得到序列W。
- 步骤5:根据以下策略去除水印,恢复原图像。
效果图
原图
水印图
加了水印的图
密码正确移除水印的图
密码不正确移除水印的图
代码
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
public class Main {
public static void main(String[] args) {
File I=new File("C:\\Users\\Coyamo\\Desktop\\raw.bmp");
File W=new File("C:\\Users\\Coyamo\\Desktop\\mark1.bmp");
File IW=new File("C:\\Users\\Coyamo\\Desktop\\out.bmp");
File NI=new File("C:\\Users\\Coyamo\\Desktop\\lena_new.bmp");
int pwd=1234;
encode(I,W,pwd,IW);
decode(IW,pwd,NI);
}
//加密
public static void encode(File I,File W,int pwd,File IW){
float x0;
float u = 4f;
x0 = (pwd % 10000) * 0.0001f;
try {
BufferedImage imageI = ImageIO.read(I);
BufferedImage imageW = ImageIO.read(W);
int widthI = imageI.getWidth(),
heightI = imageI.getHeight();
int sizeI=widthI*heightI;
//
float[] x = new float[sizeI];
x[0] = x0;
for (int i = 0; i < 500; i++)
x[0] = u * x[0] * (1 - x[0]);
for (int i = 0; i < sizeI - 1; i++)
x[i + 1] = u * x[i] * (1 - x[i]);
//gray w
int grayW[]=new int[sizeI];
int avg=0;
for(int i=0;i<widthI;i++){
for(int j=0;j<heightI;j++){
int color=imageW.getRGB(i, j);
int r = (color >> 16) & 0xff;
int g = (color >> 8) & 0xff;
int b = color & 0xff;
grayW[j*heightI+i]= (r*30+g*60+b*10)/100;
avg+=grayW[j*heightI+i];
}
}
avg=avg/sizeI;
//水印转化二值图像插入
int rgb[]=new int[sizeI];
for(int i=0;i<widthI;i++){
for(int j=0;j<heightI;j++){
int gray= grayW[j*heightI+i]>avg?0:1;
int color=imageI.getRGB(i, j);
int r = (color >> 16) & 0xff;
int g = (color >> 8) & 0xff;
int b = color & 0xff;
if(gray==1){
rgb[j*heightI+i]=(255-r)<<16|(255-g)<<8|(255-b);
}else{
rgb[j*heightI+i]=color;
}
rgb[j*heightI+i]=rgb[j*heightI+i]&0xfffffffe|(gray^(int)(x[j*heightI+i]+0.5f));
}
}
BufferedImage image = new BufferedImage(widthI, heightI, imageI.getType());
image.setRGB(0, 0, widthI, heightI, rgb, 0, widthI);
String suffix = I.getName().substring(I.getName().lastIndexOf('.') + 1);
ImageIO.write(image, suffix, IW);
image.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
public static void decode(File IW,int pwd,File I){
float x0;
float u = 4f;
x0 = (pwd % 10000) * 0.0001f;
try {
BufferedImage imageIW = ImageIO.read(IW);
int widthIW = imageIW.getWidth(),
heightIW = imageIW.getHeight();
int sizeIW=widthIW*heightIW;
float[] x = new float[sizeIW];
x[0] = x0;
for (int i = 0; i < 500; i++)
x[0] = u * x[0] * (1 - x[0]);
for (int i = 0; i < sizeIW - 1; i++)
x[i + 1] = u * x[i] * (1 - x[i]);
int W[]=new int[sizeIW];
for(int i=0;i<widthIW;i++){
for(int j=0;j<heightIW;j++){
int color=imageIW.getRGB(i, j);
W[j*heightIW+i]=(color&1)^(int)(0.5+x[j*heightIW+i]);
}
}
int rgb[]=new int[sizeIW];
for(int i=0;i<widthIW;i++){
for(int j=0;j<heightIW;j++){
int gray= W[j*heightIW+i];
int color=imageIW.getRGB(i, j);
int r = (color >> 16) & 0xff;
int g = (color >> 8) & 0xff;
int b = color & 0xff;
if(gray==1){
rgb[j*heightIW+i]=(255-r)<<16|(255-g)<<8|(255-b);
}else{
rgb[j*heightIW+i]=color;
}
}
}
BufferedImage image = new BufferedImage(widthIW, heightIW, imageIW.getType());
image.setRGB(0, 0, widthIW, heightIW, rgb, 0, widthIW);
String suffix = I.getName().substring(I.getName().lastIndexOf('.') + 1);
ImageIO.write(image, suffix, I);
image.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
}
总结
算法主要是为图像打上加密的水印,水印信息就保存在图像像素的最低位。要完全移除则需要知道密码。代码实现的要求水印和图像一样大小。算法安全性并不高,可能出现密码不正确,却移除大部分水印的情况。理想情况下密码错误是不能移除水印的。