Java使用OpenCV:基于DCT变换 实现 图片 数字 的盲水印添加和提取

此水印算法的相关说明:

嵌入图片:
水印图:只能是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);
		
		
	}
}

在这里呢?我之展示嵌入图片的效果:
原图
Java使用OpenCV:基于DCT变换 实现 图片 数字 的盲水印添加和提取_第1张图片
水印图:
在这里插入图片描述
嵌入后的图:
Java使用OpenCV:基于DCT变换 实现 图片 数字 的盲水印添加和提取_第2张图片
提取后的水印图:
在这里插入图片描述

在这里进一步讲解这个算法可以改进的地方。

我们可以看到在这个算法当中有一个嵌入强度这个因素

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

如果觉得对你有帮助的话,那就点个赞吧!!!

你可能感兴趣的:(数字水印)