使用xlang开发隐形水印制作工具

作为xlang的项目示例之一, 这篇博文主要讲制作一个简单的隐形水印工具。
隐形水印就是把一些信息秘密地嵌入到音频、视频、图片等载体中,用户角度上看不到,但可以使用特定算法检测出来,用户传播媒体,该水印也不会消失,这样可以实现追踪。

本文主要讲通过fftw在图片频域嵌入水印。
FFTW是用做快速傅里叶变换的库,关于使用傅里叶变换进行时频域转换这里做大概介绍,时域和频域是信号的基本性质,图像也是一种信号,用户角度观察到的是时域图像,而使用fftw则可得到频域图像,我们的水印就是在图像信号的频域进行处理,想了解时频域相关详细资料自行查询, 这里不多做解释(不了解详细也不影响,知道这个大概原理即可)。

开发原理: 使用fftw库对图像的数据进行变换,然后对变换后的数据进行处理,最后还原图像。
需要支持文字图像或者自定义的图案,并且需要手动调节频域信号的能量,使之具有一定的抗攻击性。

接下来使用XStudio新建功能创建一个form应用.
新建项目

并编辑ui界面文件(xlang使用了qt作为基本界面库, 所以编辑界面是直接使用qtdesigner的,还是比较方便的)。

在设计器中编辑一下界面
编辑成最终想要的样子:
编辑好的界面

然后在SecretWatermark.xcs中定义一系列控件和事件关联,详细过程略去,需要的在文章末尾下载源代码自行查看。

紧接着我们给项目引入fftw包,点击菜单【工具】-》【包管理】,找到并选中 xfftw 然后点击添加到项目,如下图:

导入fftw包

我们需要得到图像的像素数据,在这里使用QXImage 类加载图片, 然后使用QXImage的getdata方法即可获得像素数据:

		QXImage tmp = nilptr;
		
		try{
			// 从文件加载图片
			tmp = new QXImage(file, nilptr);
        }catch(Exception e){
        	// 加载出错了
            return tmp;
        }
        //获取像素数据
        byte [] data = tmp.getdata();
		//图片宽
        int w = tmp.width();
        //图片高
        int h = tmp.height();
然后对所有像素按字节进行归一化, 处理255.f 转为 0~1的浮点数。
	// 像素数据是ARGB8888的  所以一个像素有四个字节,则 length / 4是像素总数
	int l = data.length / 4;
				
	//读取数据并归一化
	double []r = new double[l];
	double []g = new double[l];
	double []b = new double[l];
				
	for (int i = 0; i < l; i ++){
		r[i] = data[(i * 4) + 2] / 255.f;
		g[i] = data[(i * 4) + 1] / 255.f;
		b[i] = data[(i * 4) + 0] / 255.f;
	}
把所有数据归一化放到一个double的数组中去,进行傅里叶变换:
		// 傅里叶正变换
		double[][] rf = fftw.fft2(h, w, r, nilptr);
		double[][] gf = fftw.fft2(h, w, g, nilptr);
		double[][] bf = fftw.fft2(h, w, b, nilptr);

同样,把水印的图像也使用QXImage加载并取得其像素数据, 得到一个byte数组。

		QXImage wmpic = nilptr;
		
		try{
			// 从文件加载水印图片
			wmpic = new QXImage(markfile, nilptr);
        }catch(Exception e){
        	// 加载出错了
        	QXMessageBox.Critical("注意","无效的图像文件:" + markfile,QXMessageBox.Ok, QXMessageBox.Ok);
            return ;
        }
        //获取像素数据
        byte [] wm = water.getdata();
		//图片宽
        int mw = water.width();
        //图片高
        int mh = water.height();

然后将水印的byte数组中的像素数据直接与频域double数组进行混合.

			// pf 是水印能量系数
			for (int x = 0; x < mw; x++){
				for (int y = 0; y < mh; y++){
					int ry = h - (y + sh + 1);
					int rx = w - (x + st + 1);

					int mi = y * mw + x;
					
					int oi = ((y + sh)* w) + x + st;
					int ni = (ry * w) + rx;
					
					rf[0][oi] += ( wm[mi * 4 + 0]) * pf;
					gf[0][oi] += ( wm[mi * 4 + 1]) * pf;
					bf[0][oi] += ( wm[mi * 4 + 2]) * pf;

					rf[0][ni] += ( wm[mi * 4 + 0]) * pf;
					gf[0][ni] += ( wm[mi * 4 + 1]) * pf;
					bf[0][ni] += ( wm[mi * 4 + 2]) * pf;
				}
			}

最后使用傅里叶逆变换还原图像:

		// 傅里叶逆变换
		double [][]rt = fftw.ifft2(h,w,rf[0],rf[1]);
		double [][]gt = fftw.ifft2(h,w,gf[0],gf[1]);
		double [][]bt = fftw.ifft2(h,w,bf[0],bf[1]);
		
		double pv = 255.f / l;
		
		// 转换回byte数组
		byte []rgb = new byte [w * h * 4];
		for (int i = 0; i < l; i ++){
			rgb[2+ (i * 4)] = Math.max(0, Math.min(255,(int)(rt[0][i] * pv)));
			rgb[1+ (i * 4)] = Math.max(0, Math.min(255,(int)(gt[0][i] * pv)));
			rgb[0+ (i * 4)] = Math.max(0, Math.min(255,(int)(bt[0][i] * pv)));
			rgb[3+ (i * 4)] = 0xff;
		}
		
        runOnUi(new Runnable(){
			void run()override{
				// 通过byte数组构造QXImage还原图像
				doneimg = new QXImage(rgb, w, h, QXImage.Format_RGB32);   
				//刷新界面显示图像
				widgetdon.update();
             }
        });

然后我们来测试一下

这是原图:

原图

我们载入原图看一下:
原图频域
原图的频域是一些无意义的噪点,可以认为是空白。

这是水印图片:

水印图片

选择水印图片再看看效果:
效果

调整水印的位置和能量,更新一下

效果图
中间的图像是最终生成的预览图,可以发现从原图上几乎看不出来,然后点保存,存为bmp文件。

然后重新运行程序,加载上面保存的bmp文件看看。
查看水印
频域的水印清晰可见, 然后我们把这个bmp转换为png和jpg等其他格式试试遭遇攻击的情况下水印的效果如何。

在使用ps打开bmp文件,然后另存为png 和 jpg文件。

先看png压缩.
PNG压缩

再看jpg压缩:

JPG压缩

jpg压缩变形比较严重,但是水印识辨度仍比较高。

源码下载地址: 点此去下载

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值