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<Mat> allPlanes = new ArrayList<Mat>(); 
		
		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<watermark.length;i++) {
			for(int j=0;j<watermark[0].length;j++) {
				
				//block 表示分块 而且为 方阵

				int length = image.rows()/watermark.length;
				Mat block = null;
				//提取每个分块
				block = getImageValue(YMat,i,j,length);
				
				
						
				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);
			
				//对分块进行DCT变换
				Core.dct(block, block);
			
				a = block.get(x1,y1);
				c = block.get(x2,y2);

				if(watermark[i][j] == 1) {	
					block.put(x1,y1, p);
					block.put(x2,y2, 0);
				}
				
				
				if(watermark[i][j] == 0) {	
					block.put(x1,y1, 0);
					block.put(x2,y2, p);
				}
			
			
			//对上面分块进行IDCT变换
			Core.idct(block, block);
			for(int m=0;m<length;m++) {
					for(int t=0;t<length;t++) {
						double[] e = block.get(m, t);
						YMat.put(i*length + m,j*length + t, e);
					}
				}
			}
	
		}
		
		Mat imageOut = new Mat();
		Core.merge(allPlanes,imageOut);
		
		return imageOut;
	}
	
	/**
	 * 提取水印信息
	 * @param image:带提取的图片
	 * @return int[][]
	 */
	public static int[][] getImageWatermarkWithText(Mat image,double p) {
		
		List<Mat> allPlanes = new ArrayList<Mat>();

		
		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<length;i++) {
			for(int j=0;j<length;j++) {
				
				double[] temp = YMat.get(x*length+i, y*length+j); 
				mat.put(i, j, temp);
			}
		}
		return mat;
	}
	
	/**
	 * 获取二值图的信息
	 */
	public static int[][] getInformationOfBinaryGraph(String srcPath){
		int[][] waterMark = new int[64][64];

		Mat mat = Imgcodecs.imread(srcPath);
		
		int width = 64;
		int height = 64;
		double a[] = new double[3];
		for(int i=0;i<width;i++) {
			for(int j=0;j<height;j++) {
				a = mat.get(i, j);
				if((int)a[0] == 255)
					waterMark[i][j] = 1;
				else 
					waterMark[i][j] = 0;
			}
		}

		return waterMark;
	}
	
	
	/**
	 *将水印信息的二维数组转换为一张图片
	 */
	public static void matrixToBinaryPhoto(int[][] watermark,String dstPath) {
		
		int width = 64,height = 64;	
		Mat binaryPhoto  = new Mat(width,height,Imgproc.THRESH_BINARY);
		
		double a[] = new double[] {255,255,255};
		double b[] = new double[] {0,0,0};
		
		for(int i=0;i<width;i++) {
			for(int j=0;j<height;j++) {				
				if(watermark[i][j] == 1)
					binaryPhoto.put(i, j, a);
				else
					binaryPhoto.put(i, j, b);
			}
		}	
		Imgcodecs.imwrite(dstPath, binaryPhoto);
	}
	
	
	/**
	 * 将一张图片压缩成一张64x64的二值图
	 * @param srcPath
	 * @param dstPath
	 */
	public static String getBinaryPhoto(String srcPath,String dstPath)  {
		
		srcPath = thumbnail(srcPath, dstPath,64,64);
		
		//得到原图
		File file = new File(srcPath);
		BufferedImage image = null;
		try {
			image = ImageIO.read(file);
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		int width = image.getWidth();
		int height = image.getHeight();
		//创建原图的二值图
		BufferedImage binaryPhoto = new BufferedImage(width,height,BufferedImage.TYPE_BYTE_BINARY);
		int min = new Color(0,0,0).getRGB();
		int max = new Color(255,255,255).getRGB();
		//判断标记
		int flag = 170;
		for(int i=0;i<width;i++) {
			for(int j=0;j<height;j++) {				
				//像素
				int pixel = image.getRGB(i, j);
				//得到 rgb通道对应的元素
				int r,g,b;
				r = (pixel & 0xff0000) >> 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

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

  • 11
    点赞
  • 55
    收藏
    觉得还不错? 一键收藏
  • 16
    评论
在Spring Boot中使用OpenCV实现添加水印和获取水印,可以参考以下步骤: 1. 添加OpenCV依赖 在pom.xml文件中添加OpenCV依赖,例如: ```xml <dependency> <groupId>org.openpnp</groupId> <artifactId>opencv</artifactId> <version>4.5.1-1</version> </dependency> ``` 2. 实现添加水印 通过OpenCV实现添加水印的步骤如下: - 读取原图像和水印图像; - 将水印图像转换为灰度图像; - 将水印图像缩小到原图像的1/4大小; - 将水印图像嵌入到原图像中; - 保存嵌入水印后的图像。 示例代码如下: ```java import org.opencv.core.Core; import org.opencv.core.Mat; import org.opencv.imgcodecs.Imgcodecs; import org.opencv.imgproc.Imgproc; public class Watermark { public static void addWatermark(String imagePath, String watermarkPath, String outputPath) { // 加载OpenCV库 System.loadLibrary(Core.NATIVE_LIBRARY_NAME); // 读取原图像和水印图像 Mat image = Imgcodecs.imread(imagePath); Mat watermark = Imgcodecs.imread(watermarkPath, Imgcodecs.IMREAD_GRAYSCALE); // 将水印图像缩小到原图像的1/4大小 Imgproc.resize(watermark, watermark, image.size(), 0.25, 0.25, Imgproc.INTER_AREA); // 将水印图像嵌入到原图像中 Core.addWeighted(image, 1.0, watermark, 0.5, 0, image); // 保存嵌入水印后的图像 Imgcodecs.imwrite(outputPath, image); } } ``` 3. 实现获取水印 通过OpenCV实现获取水印的步骤如下: - 读取带水印的图像; - 将图像转换为灰度图像; - 提取水印信息; - 将提取水印信息输出。 示例代码如下: ```java import org.opencv.core.Core; import org.opencv.core.Mat; import org.opencv.imgcodecs.Imgcodecs; import org.opencv.imgproc.Imgproc; public class Watermark { public static void extractWatermark(String imagePath) { // 加载OpenCV库 System.loadLibrary(Core.NATIVE_LIBRARY_NAME); // 读取带水印的图像 Mat image = Imgcodecs.imread(imagePath); // 将图像转换为灰度图像 Imgproc.cvtColor(image, image, Imgproc.COLOR_BGR2GRAY); // 提取水印信息 // TODO: 实现提取水印信息的算法 // 输出提取水印信息 // TODO: 输出提取水印信息 } } ``` 需要注意的是,提取水印需要使用特定的算法,这里只是示例代码,具体实现需要根据实际情况进行调整。
评论 16
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值