此水印算法的相关说明:
嵌入图片:
水印图:只能是64 * 64的二值图
原图:只能是512 * 512的图片
嵌入数字:
数字:只能是0或者1的数字嵌入到图片中,可以嵌入64 * 64=4096个0或者1
原图:只能是512 * 512的图片
改算法目前只能抵抗:
亮度,压缩,对比度,饱和度,缩放这些攻击。
攻击算法实现
对于缩放而言需要注意的是,任何缩放,不管它缩放的比例是多少,我们在最后提取水印的时候必须将其变成512*512的图片,这样才能提取水印成功。
因为在做数字水印时,我们这个项目,不需要考虑到,裁剪这个攻击所以就没有对算法进行优化,所以不能抵抗裁剪攻击。
所需工具:
opencv-410
算法如下:
创建工具类 ImgWatermarkUtil.java
package ImageWaterMark;
import java.awt.Color;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import javax.imageio.ImageIO;
import org.opencv.core.Core;
import org.opencv.core.CvType;
import org.opencv.core.Mat;
import org.opencv.core.Size;
import org.opencv.imgcodecs.Imgcodecs;
import org.opencv.imgproc.Imgproc;
public class ImageWaterMarkUtil {
static{
//加载opencv动态库
System.loadLibrary(Core.NATIVE_LIBRARY_NAME);
}
public static void main(String[] args) {
// TODO Auto-generated method stub
}
/**
* 嵌入水印信息
* @param image:原图
* @param watermark:水印信息
* @param p:嵌入强度
*/
public static Mat addImageWatermarkWithText(Mat image, int[][] watermark,double p) {
List allPlanes = new ArrayList();
Mat Ycbcr=new Mat(image.rows(),image.cols(),CvType.CV_8UC1);
Imgproc.cvtColor(image, Ycbcr,Imgproc.COLOR_RGB2YCrCb);
Core.split(image, allPlanes);
//获取YMat矩阵
Mat YMat = allPlanes.get(0);
//分成4096块
for(int i=0;i allPlanes = new ArrayList();
Mat Ycbcr=new Mat(image.rows(),image.cols(),CvType.CV_8UC1);
Imgproc.cvtColor(image, Ycbcr,Imgproc.COLOR_RGB2YCrCb);
Core.split(image, allPlanes);
Mat YMat = allPlanes.get(0);
int watermark[][] = new int[64][64];
//分成64块,提取每块嵌入的水印信息
for(int i=0;i<64;i++) {
for(int j=0;j<64;j++) {
//block 表示分块 而且为 方阵
int length = image.rows()/watermark.length;
Mat block = null;
//提取每个分块
block = getImageValue(YMat,i,j,length);
//对分块进行DCT变换
Core.dct(block, block);
//用于容纳DCT系数
double[] a = new double[1];
double[] c = new double[1];
int x1 = 1, y1 = 2;
int x2 = 2, y2 = 1;
a = block.get(x1,y1);
c = block.get(x2,y2);
if(a[0]>=c[0])
watermark[i][j] = 1;
}
}
return watermark;
}
/**
* 提取每个分块
* @param YMat:原分块
* @param x:x与y联合表示第几个块
* @param y:x与y联合表示第几个块
* @param length:每个块的长度
* @return
*/
public static Mat getImageValue(Mat YMat,int x,int y,int length) {
Mat mat = new Mat(length,length,CvType.CV_32F);
for(int i=0;i> 16;
g = (pixel & 0xff00) >> 8;
b = (pixel & 0xff);
int avg = (r + g + b)/3;
if(avg <= flag)
binaryPhoto.setRGB(i, j, min);
else
binaryPhoto.setRGB(i, j, max);
}
}
try {
ImageIO.write(binaryPhoto, "bmp", new File(dstPath));
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return dstPath;
}
/**
* 将图片变成指定大小的图片
*/
public static String thumbnail(String srcImagePath, String desImagePath,int w,int h) {
Mat src = Imgcodecs.imread(srcImagePath);
Mat dst = src.clone();
Imgproc.resize(src, dst, new Size(w, h));
Imgcodecs.imwrite(desImagePath, dst);
return desImagePath;
}
}
测试 ImgWatermarkUtil.java
package ImageWaterMark;
import java.io.IOException;
import org.opencv.core.Core;
import org.opencv.core.Mat;
import org.opencv.imgcodecs.Imgcodecs;
public class ImageWaterMarkMain {
static{
//加载opencv动态库
System.loadLibrary(Core.NATIVE_LIBRARY_NAME);
}
/**
* 嵌入图片的盲水印
*/
public static void photo() {
//用于存储水印信息
int[][] watermark = new int[64][64];
//原图
String src = "C:\\Users\\Fenix\\Desktop\\cxf\\stzz.bmp";
//嵌入后的图的保存路径
String dst = "C:\\Users\\Fenix\\Desktop\\cxf\\stzz-out.bmp";
//水印路径
String waterMarkPath = "C:\\Users\\Fenix\\Desktop\\cxf\\sign.bmp";
//保存提取到的图片水印
String waterDstPath = "C:\\Users\\Fenix\\Desktop\\cxf\\sign-out.bmp";
//嵌入强度
int p = 75;
//---------嵌入图片水印信息-------------
//将水印图片中的Y通道提取出来,保存到watermark数组中
watermark = ImageWaterMarkUtil.getInformationOfBinaryGraph(waterMarkPath);
//读取原图
Mat image = Imgcodecs.imread(src);
//保存嵌入后的图
Mat imageOut = ImageWaterMarkUtil.addImageWatermarkWithText(image,watermark,p);
Imgcodecs.imwrite(dst, imageOut);
//---------提取图片水印信息-------------
//读取嵌入后的图
Mat watermarkOut = Imgcodecs.imread(dst);
//得到水印的数组信息
int[][] watermark_out = ImageWaterMarkUtil.getImageWatermarkWithText(watermarkOut, p);
//将水印的数组信息转化为图片
ImageWaterMarkUtil.matrixToBinaryPhoto(watermark_out, waterDstPath);
}
/**
* 嵌入数字的盲水印
*/
public static void number() {
//创建数字水印
int[][] watermark = new int[64][64];
for(int i=0;i<64;i++) {
for(int j=0;j<64;j++) {
if(i%2 == 0)
watermark[i][j] = 1;
}
}
//原图
String src = "C:\\Users\\Fenix\\Desktop\\cxf\\stzz.bmp";
//嵌入后的图的保存路径
String dst = "C:\\Users\\Fenix\\Desktop\\cxf\\stzz-out.bmp";
//嵌入强度
int p = 75;
//---------嵌入数字水印信息-------------
//读取原图
Mat image = Imgcodecs.imread(src);
//保存嵌入后的图
Mat imageOut = ImageWaterMarkUtil.addImageWatermarkWithText(image,watermark,p);
Imgcodecs.imwrite(dst, imageOut);
//---------提取数字水印信息-------------
//读取嵌入后的图
Mat watermarkOut = Imgcodecs.imread(dst);
//得到水印的数组信息
int[][] watermark_out = ImageWaterMarkUtil.getImageWatermarkWithText(watermarkOut, p);
//计算提取率
int val = 0;
for(int i=0;i<64;i++) {
for(int j=0;j<64;j++) {
if(watermark_out[i][j] == watermark[i][j])
val++;
}
}
System.out.println("提取率: "+(val * 1.0 / 4096));
}
public static void main(String[] args) throws IOException {
// TODO Auto-generated method stub
//嵌入图片的盲水印
//photo();
//嵌入数字的盲水印
number();
//如果没有64*64的二值图可调用方法
//ImageWaterMarkUtil.getBinaryPhoto(srcPath, dstPath);
//如果没有512*512的原图可调用方法
//ImageWaterMarkUtil.thumbnail(srcImagePath, desImagePath, 512, 512);
}
}
在这里呢?我之展示嵌入图片的效果:
原图
水印图:
嵌入后的图:
提取后的水印图:
在这里进一步讲解这个算法可以改进的地方。
我们可以看到在这个算法当中有一个嵌入强度这个因素
int p = 75;
在这里我想说的是,改变强度p的大小,那么会影响嵌入后的效果,当p值越大,那么鲁棒性越强,
但嵌入后的图片效果越差,当p值越小,那么鲁棒性越弱,但嵌入后的图片效果越强,就是经过许多测试,我感觉在 75 左右的效果比较好。所以这是这个算法第一个可以改进的地方
接着我们看看另一个可以改进的地方:
如下所示:
在 ImgWatermarkUtil.java 中,
addImageWatermarkWithText( )方法中
int x1 = 1, y1 = 2;
int x2 = 2, y2 = 1;
可以改变这两个坐标的值,同时改变了这里后,也需要改变,
getImageWatermarkWithText( ) 方法中
int x1 = 1, y1 = 2;
int x2 = 2, y2 = 1;
为什么?改变这两个坐标的值,可以提高性能呢?可以看看我写的这篇文章?
数字水印的处理心得
然后呢,我们在这个算法中对于数字而言,我们只能嵌入和提取0或者1,那么要怎样才能嵌入汉字呢?
我觉得我们可以对汉字进行编码,将一个汉字对应成一段二进制数,然后将二进数嵌入到图片中去,提取也是这样,提取出相应的二进制数,然后找到对应的汉字就可以了。
我们如何再次增加这个算法嵌入的容量呢?
这个我们只能将原图变大,在这个算法中原图是512 * 512 ,每 8 * 8 对应一个0或者1,所以只有增大原图的大小,从而就可以提高算法的容量。
最后呢?我说说这个算法怎样才能抵抗裁剪,或者说抵抗裁剪怎么实现?
在这里我用64 * 64 的二值图作为举例,抵抗裁剪,我们可以先将 64 * 64 的二值图嵌入到原图,然后我们将得到 嵌入后的图片,然后我们在将这个嵌入后的图片 再次 嵌入 64 * 64 的二值图,但是,这个时候,的 64 * 64 的二值图,需要进行一定的处理,怎么处理呢?就是将 二值图 得到的二维矩阵 进行 转置 操作,然后在嵌入,就可以了。
总之就是, 8 * 8 的像素块需要,改变 4 个DCT系数(原来只是改变2个DCT系数),那么有没有什么方法可以改进呢?
有,我们可以将其改进到 改变2个DCT系数,就是,找一个点作为中间点,改变另外两个DCT系数(这里a,b表示)即可,
对于a点:
如果要嵌入0,则让a的DCT系数 = 中间点的DCT系数 - P(嵌入强度(常数)),
如果要嵌入1,则让a的DCT系数 = 中间点的DCT系数 + P(嵌入强度(常数))。
对于b点:
如果要嵌入0,则让b的DCT系数 = 中间点的DCT系数 - P(嵌入强度(常数)),
如果要嵌入1,则让b的DCT系数 = 中间点的DCT系数 + P(嵌入强度(常数))。
提取就是,
比较相应DCT的大小,若大于中间点的DCT系数,表示1,若小于中间点的DCT系数,表示0
这就是可以抗裁剪的大致实现过程。
最后呢?
我在来说一说,我的这个算法是,改变了2个DCT系数,如果不需要抵抗裁剪攻击的话,可以向上面抵抗裁剪攻击那样进行优化,可以优化到改变1个DCT系数,
小彩蛋
这是这个算法 p = 75 时在亮度,压缩,对比度,饱和度,缩放这些攻击下,的提取效果图。因为图片过多,所以我就不再展示,所以就用百度云吧!!!
百度云,提取码是,uiru
如果觉得对你有帮助的话,那就点个赞吧!!!