妙妙小豆子

胆子大一点,想开一点。长风破浪会有时,直挂云帆济沧海

基于 DWT 和 DCT 域的盲水印算法 金牌收录

程序简介

基于 DWT 和 DCT 域的盲水印算法。加水印和提水印阶段涉及小波变换(DWT),离散余弦变换(DCT),Zig-Zag 扫描。攻击阶段涉及亮度,对比度,直方图均衡化,椒盐噪声,剪切。

原作有四个阶段,实验图像 512512256,水印 64642。
阶段一、水印置乱。使用 Arnold 变换。
阶段二、水印嵌入。先小波变换(DWT);对 LL 子图(256256)以 88 的大小进行分块(共 3232 块);计算方差;对 3232 个块进行 22 分组(共 1616 组);记录每组最大方差的块(共 1616 块);对 1616 个块进行离散余弦变换(DCT);对 1616 个块进行 Zig-Zag 扫描,从每块 88 的 DCT 系数中提取中高频段共 16 个系数(共 161616 个系数,对应水印 64*64 个系数);水印嵌入(改系数);离散余弦反变换(IDCT);小波反变换(IDWT)。
阶段三、水印攻击。
阶段四、水印提取。执行嵌入步骤到提取中高频系数;水印提取(根据系数判断)。

本程序未翻译阶段一。Arnold 变换相比水印算法,更接近图像加密算法,将放到图像加密的部分中。
阶段二中,DWT 后,也可以不只用 LL 子图,用 HL,LH,HH 子图都可以尝试一下嵌入水印,不同域对不同的噪声敏感度肯定不一致。程序上,需要同步改 DWT 后,IDWT 前,中间所有函数的操作范围。
阶段二中,DCT 后生成的图像像素值和 DCT 系数不一致,那只是方便展示,得知 DCT 阶段已结束的,不能保存图像,在读取进行反变换。因为 DCT 系数不是整数,也会有负数,也可能上千。
阶段二中,Zig-Zag 扫描的本质是一种系数的位置顺序,我们的目的是从 8*8 的系数中选 16 个进行修改,而本实验的 DCT 系数的性质是左上低频,右下高频,故我们标记远离左上的 16 个点即可,至于这些点是连续的还是跳跃的,不是影响结果的关键。关键是嵌入和提取时的 16 个点位置是一致的即可。
阶段二中,原作水印嵌入时,生成了一组随机序列来辅助嵌入,并充当密钥,使用了一个水印强度系数来适应不同的载体图像。由于原作使用二值水印图像,我根据这个性质修改了这一部分,使用加密白/黑点赋值参数,这两个参数直接嵌入,并使用他们来适应不同的载体图像,灵活度更高,不需要传递密钥(但使图像不同区域的篡改程度变地不一致了)。
阶段四中,原作水印提取时,是用密钥算相关系数,对比平均值进行提取。我根据嵌入的修改,设置了一个解密阈值参数来提取,这样就能调整它来适应不同的载体图像,甚至根据攻击方式来调整它以达到提高鲁棒性的目的(这个效果聊胜于无)。

由于程序步骤较多,不适合太自由,就集成为加密阶段,攻击阶段,解密阶段了。这样可以不进行攻击,或进行多次组合攻击。载体和水印可以使用同一张图像。
在完整进程函数 completeprocessing() 中可以设置四个参数来适应不同载体图像,没有单独做按钮了。
本程序也没有再用代码进行水印图像质量评价分析了,采用肉眼进行分析。

程序运行展示

完整源代码

////////////////////////////////////////
// 程序:基于 DWT 和 DCT 域的盲水印算法
// 作者:Gary
// 编译环境:Visual C++ 2010,EasyX_20211109
// 编写日期:2022-5-11

#include <graphics.h>
#include <string>
#include <ShlObj.h>
#include <math.h>
#include <time.h>

static HWND hOut;						// 画布
static double pi;						// 圆周率

// 图像处理
double img_m[513][513];					// 大图图像
double img_b[513][513];					// 大图图像
double img_k[64][64];					// 小图水印

// 定义一个结构体,按钮
struct Node1
{
	int posx1, posy1, posx2, posy2;		// 坐标
	LPCTSTR text;						// 文字
};

// 定义一个类
class Gary
{
public:
	void carry();					// 主进程
	void initialization();			// 初始化
	void move();					// 窗口主视角
	void draw();					// 绘制函数
	void completeprocessing();		// 完整过程

	int variance_block();						// 最大公差块寻找函数
	int DCT(IMAGE* img_input);					// 离散余弦变换		512*512
	int watermark_reading(IMAGE* img_input);	// 水印读取函数		64*64
	int encryption_image();						// 加密函数			512*512 + 64*64

	IMAGE* DWT(IMAGE* img_input);									// 小波变换			512*512 -> 512*512
	IMAGE* IDCT(IMAGE* img_input);									// 离散余弦反变换	512*512 -> 512*512
	IMAGE* IDWT(IMAGE* img_input);									// 小波反变换		512*512 -> 512*512
	IMAGE* watermark_attack(IMAGE* img_input, int num_attack_mode);	// 攻击函数			512*512 -> 512*512
	IMAGE* decrypt_image();											// 解密函数			512*512 -> 64*64

	IMAGE* encryption_process(IMAGE* img_input_image, IMAGE* img_input_watermark);	// 加密过程 512*512 + 64*64 -> 512*512
	IMAGE* attack_process(IMAGE* img_input, int num_attack_mode);					// 攻击过程 512*512 -> 512*512
	IMAGE* decryption_process(IMAGE* img_input);									// 解密过程 512*512 -> 64*64

	int num_button;					// 按钮数量
	int exit_carry;					// 进程控制
	int exit_move;					// 进程控制
	int num_variance_block[256];	// 最大公差块坐标编号

	int num_encryption_white;		// 加密白点赋值参数
	int num_encryption_black;		// 加密黑点赋值参数
	int num_encryption_threshold;	// 加密阈值
	int num_decrypt_threshold;		// 解密阈值

	IMAGE* img_output;				// 输出图像
	IMAGE* img_temporary;			// 大图图像 512*512
	IMAGE* img_watermark;			// 水印图像 64*64

	TCHAR address_load_image[MAX_PATH];		// 图像地址
	TCHAR address_load_watermark[MAX_PATH];	// 水印地址
	TCHAR address_save_image[MAX_PATH];		// 保存大图
	TCHAR address_save_watermark[MAX_PATH];	// 保存水印

	Node1 boxm[30];					// 按钮
};

// 完整进程函数
void Gary::completeprocessing()
{
	// 加密白点赋值参数
	num_encryption_white = -10;
	// 加密黑点赋值参数
	num_encryption_black = 10;
	// 加密阈值
	num_encryption_threshold = 128;
	// 解密阈值
	num_decrypt_threshold = 1;
	// 加密过程
	img_temporary = encryption_process(img_temporary, img_watermark);
	// 攻击过程
	img_temporary = attack_process(img_temporary, 0);
	// 解密过程
	img_watermark = decryption_process(img_temporary);
}

// 加密过程函数
IMAGE* Gary::encryption_process(IMAGE* img_input_image, IMAGE* img_input_watermark)
{
	int i;
	// 小波变换
	img_temporary = DWT(img_input_image);
	// 最大公差块寻找函数
	i = variance_block();
	// 离散余弦变换
	i = DCT(img_temporary);
	// 水印读取函数
	i = watermark_reading(img_input_watermark);
	// 加密函数
	i = encryption_image();
	// 离散余弦反变换
	img_temporary = IDCT(img_temporary);
	// 小波反变换
	img_temporary = IDWT(img_temporary);
	// 返回嵌入还原后的大图
	return img_temporary;
}

// 解密过程函数
IMAGE* Gary::decryption_process(IMAGE* img_input)
{
	int i;
	// 小波变换
	img_temporary = DWT(img_input);
	// 最大公差块寻找函数
	i = variance_block();
	// 离散余弦变换
	i = DCT(img_temporary);
	// 解密函数
	img_watermark = decrypt_image();
	// 返回解密后的水印小图
	return img_watermark;
}

// 攻击过程函数
IMAGE* Gary::attack_process(IMAGE* img_input, int num_attack_mode)
{
	// 攻击函数
	img_temporary = watermark_attack(img_input, num_attack_mode);
	// 返回受攻击后的大图
	return img_temporary;
}

// 攻击函数
IMAGE* Gary::watermark_attack(IMAGE* img_input, int num_attack_mode)
{
	// 加载输入图像
	putimage(0, 0, img_input);
	// 处理
	int i, j, k;
	// 存储
	for(i = 0; i < 512; i++)
	{
		for(j = 0; j < 512; j++)
		{
			img_b[i][j] = getpixel(i, j) / 256 / 256;
			// 根据攻击模式参数进行攻击
			switch(num_attack_mode)
			{
				// 图像变亮
			case 0:
			{
				img_b[i][j] = 127.0 + 128.0 * img_b[i][j] / 255.0;
				break;
			}
			// 图像变暗
			case 1:
			{
				img_b[i][j] = 0.0 + 128.0 * img_b[i][j] / 255.0;
				break;
			}
			// 降低对比度
			case 2:
			{
				img_b[i][j] = ( img_b[i][j] <= 128 ? 78.0 + 50.0 * img_b[i][j] / 128.0 : 128.0 + 50.0 * ( img_b[i][j] - 128.0 ) / 128.0 );
				break;
			}
			// 提高对比度
			case 3:
			{
				img_b[i][j] = ( img_b[i][j] <= 128 ? 0.0 + 50.0 * img_b[i][j] / 128.0 : 205.0 + 50.0 * ( img_b[i][j] - 128.0 ) / 128.0 );
				break;
			}
			// 椒盐噪声
			case 4:
			{
				k = rand() % 50;
				if(k == 0)
				{
					img_b[i][j] = 255;
				}
				else if(k == 25)
				{
					img_b[i][j] = 0;
				}
			}
			default:break;
			}
		}
	}
	// 直方图均衡化
	if(num_attack_mode == 5)
	{
		double g[256];
		double g1[256];
		// 初始化灰度值
		for(i = 0; i < 256; i++)
		{
			g[i] = 0;
		}
		// 统计
		for(i = 0; i < 512 * 512; i++)
		{
			g[int(img_b[i % 512][i / 512])]++;
		}
		// 累计概率
		g1[0] = double(g[0]) / 512.0 / 512.0;
		for(i = 1; i < 256; i++)
		{
			g1[i] = double(g[i]) / 512.0 / 512.0 + g1[i - 1];
		}
		// 均衡化
		j = 0;
		for(i = 0; i < 256; i++)
		{
			if(g1[i] >= double(j) / 256.0 && g1[i]<double(j + 1) / 256.0)
			{
				g1[i] = j;
			}
			else
			{
				i--;
				j++;
			}
		}
		// 重新赋值
		for(i = 0; i < 512 * 512; i++)
		{
			img_b[i % 512][i / 512] = g1[int(img_b[i % 512][i / 512])];
		}
	}
	// 剪切攻击-中间
	else if(num_attack_mode == 6)
	{
		for(i = 0; i < 512; i++)
		{
			for(j = 0; j < 512; j++)
			{
				if(i > 128 && j > 128 && i < 384 && j < 384)
				{
					img_b[i][j] = 0;
				}
			}
		}
	}
	// 剪切攻击-角
	else if(num_attack_mode == 7)
	{
		for(i = 0; i < 512; i++)
		{
			for(j = 0; j < 512; j++)
			{
				if(i + j > 500)
				{
					img_b[i][j] = 0;
				}
			}
		}
	}
	// 剪切攻击-对半
	else if(num_attack_mode == 8)
	{
		for(i = 0; i < 512; i++)
		{
			for(j = 0; j < 512; j++)
			{
				if(j < 256)
				{
					img_b[i][j] = 0;
				}
			}
		}
	}

	// 新建输出图像
	img_output = new IMAGE(512, 512);
	// 初始化处理区域
	SetWorkingImage(img_output);
	setbkcolor(BLACK);
	cleardevice();
	// 绘制受攻击后的图像
	for(i = 0; i < 512; i++)
	{
		for(j = 0; j < 512; j++)
		{
			putpixel(i, j, RGB(img_b[i][j], img_b[i][j], img_b[i][j]));
		}
	}
	SetWorkingImage();
	// 显示
	putimage(0, 0, img_output);
	// 返回输出图像
	return img_output;
}

// 解密函数
IMAGE* Gary::decrypt_image()
{
	int i, j, k;
	// zig_zag 扫描算法
	int zig_zag[16];
	// 选频段
	zig_zag[0] = 55; zig_zag[1] = 62; zig_zag[2] = 61; zig_zag[3] = 54;
	zig_zag[4] = 47; zig_zag[5] = 39; zig_zag[6] = 46; zig_zag[7] = 53;
	zig_zag[8] = 60; zig_zag[9] = 59; zig_zag[10] = 52; zig_zag[11] = 45;
	zig_zag[12] = 38; zig_zag[13] = 31; zig_zag[14] = 23; zig_zag[15] = 58;

	// 解密,通过 DCT 后中低频段的值判断水印点的值
	// 256 个最大方差块
	for(k = 0; k < 256; k++)
	{
		// 在每块中的中高频段选 16 个点
		for(i = 0; i < 16; i++)
		{
			// 根据 img_m 的值附 img_k 的值
			// 这个阈值应当根据需要进行更改
			if(img_m[num_variance_block[k] % 32 * 8 + zig_zag[i] % 8][num_variance_block[k] / 32 * 8 + zig_zag[i] / 8] > num_decrypt_threshold)
			{
				img_k[k / 4][k % 4 * 16 + i] = 250;
			}
			else
			{
				img_k[k / 4][k % 4 * 16 + i] = 0;
			}

		}
	}
	// 清空绘图区
	setfillcolor(WHITE);
	fillrectangle(0, 0, 550, 512);
	// 水印绘制
	// 新建输出图像
	img_output = new IMAGE(64, 64);
	// 初始化处理区域
	SetWorkingImage(img_output);
	setbkcolor(BLACK);
	cleardevice();
	// 绘制提取出的水印
	for(i = 0; i < 64; i++)
	{
		for(j = 0; j < 64; j++)
		{
			putpixel(i, j, RGB(img_k[i][j], img_k[i][j], img_k[i][j]));
		}
	}
	SetWorkingImage();
	// 显示
	putimage(275 - 32, 256 - 32, img_output);
	// 返回输出图像
	return img_output;
}

// 离散余弦反变换
IMAGE* Gary::IDCT(IMAGE* img_input)
{
	// 处理
	int i, j;
	int flag;
	int posx, posy;
	int m, n;
	// 根据 DCT 存储的值,即根据 img_m 的值进行反变换
	for(posx = 0; posx < 512 / 2; posx += 8)
	{
		for(posy = 0; posy < 512 / 2; posy += 8)
		{
			// 仅对组内最大方差块进行 IDCT 变换
			// 判断当前块是否是组内最大方差块
			for(flag = 0; flag < 512 / 2; flag++)
			{
				if(num_variance_block[flag] % 32 * 8 == posx && num_variance_block[flag] / 32 * 8 == posy)
				{
					break;
				}
			}
			// 是
			if(flag < 256)
			{
				for(m = posx; m < posx + 8; m++)
				{
					for(n = posy; n < posy + 8; n++)
					{
						// 初始化像素值
						img_b[m][n] = 0;
						// 反变换
						for(i = 0; i < 8; i++)
						{
							for(j = 0; j < 8; j++)
							{
								img_b[m][n] += img_m[posx + i][posy + j] * cos(double(( 2 * ( m - posx ) + 1 ) * i) * pi / 16.0) * cos(double(( 2 * ( n - posy ) + 1 ) * j) * pi / 16.0) / 8.0 * ( i != 0 ? sqrt(2.0) : 1.0 ) * ( j != 0 ? sqrt(2.0) : 1.0 );
							}
						}
					}
				}
			}
		}
	}
	// 新建输出图像
	img_output = new IMAGE(512, 512);
	// 初始化处理区域
	SetWorkingImage(img_output);
	setbkcolor(BLACK);
	cleardevice();
	// 覆盖一层大图
	putimage(0, 0, img_input);
	// 绘制 LL 区
	for(i = 0; i < 256; i++)
	{
		for(j = 0; j < 256; j++)
		{
			putpixel(i, j, RGB(img_b[i][j], img_b[i][j], img_b[i][j]));
		}
	}
	SetWorkingImage();
	// 显示
	putimage(0, 0, img_output);
	// 返回输出图像
	return img_output;
}

// 小波反变换
IMAGE* Gary::IDWT(IMAGE* img_input)
{
	// 加载输入图像
	putimage(0, 0, img_input);
	// 处理
	int i, j, k;
	// 存储
	for(i = 0; i < 512; i++)
	{
		for(j = 0; j < 512; j++)
		{
			img_b[i][j] = getpixel(i, j) / 256 / 256;
		}
	}
	// 变换
	k = 512;
	// 纵向延展
	for(i = 0; i < k; i++)
	{
		for(j = 0; j < k / 2; j++)
		{
			img_m[i][j * 2] = img_b[i][j] + img_b[i][j + k / 2 + 1];
			img_m[i][j * 2 + 1] = img_b[i][j] - img_b[i][j + k / 2 + 1];
		}
		for(j = 0; j < k; j++)
		{
			img_b[i][j] = img_m[i][j];
		}
	}
	// 横向延展
	for(j = 0; j < k; j++)
	{
		for(i = 0; i < k / 2; i++)
		{
			img_m[i * 2][j] = img_b[i][j] + img_b[i + k / 2 + 1][j];
			img_m[i * 2 + 1][j] = img_b[i][j] - img_b[i + k / 2 + 1][j];
		}
		for(i = 0; i < k; i++)
		{
			img_b[i][j] = img_m[i][j];
		}
	}
	// 新建输出图像
	img_output = new IMAGE(512, 512);
	// 初始化处理区域
	SetWorkingImage(img_output);
	setbkcolor(BLACK);
	cleardevice();
	// 绘制
	for(i = 0; i < 512; i++)
	{
		for(j = 0; j < 512; j++)
		{
			putpixel(i, j, RGB(img_b[i][j], img_b[i][j], img_b[i][j]));
		}
	}
	SetWorkingImage();
	// 显示
	putimage(0, 0, img_output);
	// 返回输出图像
	return img_output;
}

// 加密函数
int Gary::encryption_image()
{
	// zig_zag 扫描算法
	int zig_zag[16];
	// 选频段
	zig_zag[0] = 55; zig_zag[1] = 62; zig_zag[2] = 61; zig_zag[3] = 54;
	zig_zag[4] = 47; zig_zag[5] = 39; zig_zag[6] = 46; zig_zag[7] = 53;
	zig_zag[8] = 60; zig_zag[9] = 59; zig_zag[10] = 52; zig_zag[11] = 45;
	zig_zag[12] = 38; zig_zag[13] = 31; zig_zag[14] = 23; zig_zag[15] = 58;

	// 该函数根据水印值改变 DCT 的值,即根据 img_k 改变 img_m
	int i, k;
	// 256 个最大方差块
	for(k = 0; k < 256; k++)
	{
		// 在每块中的中高频段选 16 个点
		for(i = 0; i < 16; i++)
		{
			// 根据 img_k 改变 img_m
			if(img_k[k / 4][k % 4 * 16 + i] > num_encryption_threshold)
			{
				// 白点改为 一个值
				img_m[num_variance_block[k] % 32 * 8 + zig_zag[i] % 8][num_variance_block[k] / 32 * 8 + zig_zag[i] / 8] = num_encryption_white;
			}
			else
			{
				// 黑点改为 一个值
				img_m[num_variance_block[k] % 32 * 8 + zig_zag[i] % 8][num_variance_block[k] / 32 * 8 + zig_zag[i] / 8] = num_encryption_black;
			}
		}
	}
	// 改这个值有一个基本原则,即反变换后尽量所有像素值在 0 ~ 255 的正常范围,不需要额外归一化了
	// 针对不同图像应适合不同值
	return 0;
}

// 水印读取函数
int Gary::watermark_reading(IMAGE* img_input)
{
	int i, j;
	// 加载输入图像
	putimage(0, 0, img_input);
	// 存储
	for(i = 0; i < 64; i++)
	{
		for(j = 0; j < 64; j++)
		{
			img_k[i][j] = getpixel(i, j) / 256 / 256;
		}
	}
	// 该函数负责记录水印信息,不输出图像
	// 结果存在于 img_k[][] 之中
	return 0;
}

// 离散余弦变换
int Gary::DCT(IMAGE* img_input)
{
	int i, j;
	int posx, posy;
	int m, n;
	int flag;
	// 加载输入图像
	putimage(0, 0, img_input);
	// 存储
	for(i = 0; i < 512; i++)
	{
		for(j = 0; j < 512; j++)
		{
			img_b[i][j] = getpixel(i, j) / 256 / 256;
		}
	}
	// 变换,以块为单位进行变换
	for(posx = 0; posx < 512 / 2; posx += 8)
	{
		for(posy = 0; posy < 512 / 2; posy += 8)
		{
			// 仅对组内最大方差块进行 DCT 变换
			// 判断当前块是否是组内最大方差块
			for(flag = 0; flag < 256; flag++)
			{
				if(num_variance_block[flag] % 32 * 8 == posx && num_variance_block[flag] / 32 * 8 == posy)
				{
					break;
				}
			}
			// 是
			if(flag < 256)
			{
				// 二维 DCT 变换,形式上类似于二维 FFT
				for(m = posx; m < posx + 8; m++)
				{
					for(n = posy; n < posy + 8; n++)
					{
						// 初始化
						img_m[m][n] = 0;
						// 公式变换
						for(i = 0; i < 8; i++)
						{
							for(j = 0; j < 8; j++)
							{
								img_m[m][n] += img_b[posx + i][posy + j] * cos(double(( 2 * i + 1 ) * ( m - posx )) * pi / 16.0) * cos(double(( 2 * j + 1 ) * ( n - posy )) * pi / 16.0) / 8.0 * ( ( m - posx ) != 0 ? sqrt(2.0) : 1.0 ) * ( ( n - posy ) != 0 ? sqrt(2.0) : 1.0 );
							}
						}
						// 绘制,需要强调的是,DWT 的结果不是在 0 ~ 255 中的,两端都超过
						// 该次绘制只是视觉效果,无法输出图像
						putpixel(m, n, RGB(img_m[m][n], img_m[m][n], img_m[m][n]));
					}
				}
			}
		}
	}
	// 该函数负责记录 DCT 的结果
	// 结果存在于 img_m[][] 之中
	return 0;
}

// 最大公差块寻找函数
int Gary::variance_block()
{
	// 对 LL 部分 8*8 分块算方差,对 32*32 再分 2*2,取 4 个中最大值的块标记
	int i, j;
	double k;
	int posx, posy;
	// 记录方差的结构体
	struct Node
	{
		double fc, avg;
	};
	Node box[32][32];
	// 存储
	for(i = 0; i < 512; i++)
	{
		for(j = 0; j < 512; j++)
		{
			img_b[i][j] = getpixel(i, j) / 256 / 256;
		}
	}
	// 分块算方差
	for(posx = 0; posx < 512 / 2; posx += 8)
	{
		for(posy = 0; posy < 512 / 2; posy += 8)
		{
			// 初始化方差,均值
			box[posx / 8][posy / 8].fc = 0;
			box[posx / 8][posy / 8].avg = 0;
			// 算均值
			for(i = posx; i < posx + 8; i++)
			{
				for(j = posy; j < posy + 8; j++)
				{
					box[posx / 8][posy / 8].avg += img_b[i][j];
				}
			}
			box[posx / 8][posy / 8].avg /= 64.0;
			// 算方差
			for(i = posx; i < posx + 8; i++)
			{
				for(j = posy; j < posy + 8; j++)
				{
					box[posx / 8][posy / 8].fc += pow(img_b[i][j] - box[posx / 8][posy / 8].avg, 2);
				}
			}
			box[posx / 8][posy / 8].fc /= 64.0;
		}
	}
	// 再分组,四选一
	int flag = 0;
	for(posx = 0; posx < 512 / 2; posx += 16)
	{
		for(posy = 0; posy < 512 / 2; posy += 16)
		{
			// 找最大方差块
			k = 0;
			for(i = posx / 8; i < posx / 8 + 2; i++)
			{
				for(j = posy / 8; j < posy / 8 + 2; j++)
				{
					// 记录
					if(k < box[i][j].fc)
					{
						k = box[i][j].fc;
						num_variance_block[flag] = i + j * 32;
					}
				}
			}
			// 绘制,显示组内最大方差块,不输出图像
			// 可见方差大的块集中在图像梯度变化大的边缘,改变这些块的频域值肉眼不易捕捉,符合 HVS 的要求
			setfillcolor(RED);
			setlinecolor(BLACK);
			line(posx + 16, posy, posx + 16, posy + 16);
			line(posx, posy + 16, posx + 16, posy + 16);
			fillcircle(num_variance_block[flag] % 32 * 8 + 4, num_variance_block[flag] / 32 * 8 + 4, 4);
			// 标记数加一,共 16*16 个块,块内有 8*8 个像素
			flag++;
		}
	}
	// 延迟显示时间
	Sleep(1000);
	// 该函数负责记录块编号,不输出图像
	// 结果存在于 num_variance_block[] 之中
	return 0;
}

// 小波变换
IMAGE* Gary::DWT(IMAGE* img_input)
{
	// 加载输入图像
	putimage(0, 0, img_input);
	// 处理
	int i, j, k;
	// 存储
	for(i = 0; i < 512; i++)
	{
		for(j = 0; j < 512; j++)
		{
			img_b[i][j] = getpixel(i, j) / 256 / 256;
		}
	}

	k = 512;
	// 横向折叠
	for(j = 0; j < k; j++)
	{
		for(i = 0; i < k / 2; i++)
		{
			img_m[i][j] = ( img_b[i * 2][j] + img_b[i * 2 + 1][j] ) / 2.0;
		}
		for(i = k / 2; i < k; i++)
		{
			img_m[i][j] = ( img_b[( i - ( k / 2 + 1 ) ) * 2][j] - img_b[( i - ( k / 2 + 1 ) ) * 2 + 1][j] ) / 2.0;
		}
		for(i = 0; i < k; i++)
		{
			img_b[i][j] = img_m[i][j];
		}
	}
	// 纵向折叠
	for(i = 0; i < k; i++)
	{
		for(j = 0; j < k / 2; j++)
		{
			img_m[i][j] = ( img_b[i][j * 2] + img_b[i][j * 2 + 1] ) / 2.0;
		}
		for(j = k / 2; j < k; j++)
		{
			img_m[i][j] = ( img_b[i][( j - ( k / 2 + 1 ) ) * 2] - img_b[i][( j - ( k / 2 + 1 ) ) * 2 + 1] ) / 2.0;
		}
		for(j = 0; j < k; j++)
		{
			img_b[i][j] = img_m[i][j];
		}
	}

	// 新建输出图像
	img_output = new IMAGE(512, 512);
	// 初始化处理区域
	SetWorkingImage(img_output);
	setbkcolor(BLACK);
	cleardevice();
	// 绘制
	for(i = 0; i < 512; i++)
	{
		for(j = 0; j < 512; j++)
		{
			putpixel(i, j, RGB(img_b[i][j], img_b[i][j], img_b[i][j]));
		}
	}
	SetWorkingImage();
	// 显示
	putimage(0, 0, img_output);
	// 返回输出图像
	return img_output;
}

// 绘制函数,主要绘制按钮
void Gary::draw()
{
	int i, j;

	// 主界面
	setlinecolor(BLACK);
	setfillcolor(WHITE);
	setlinestyle(PS_SOLID, 1);
	fillrectangle(550, 0, 800, 512);

	// 根据按钮数量绘制	
	settextcolor(BLACK);
	for(i = 0; i < num_button; i++)
	{
		// 边框
		fillrectangle(boxm[i].posx1, boxm[i].posy1, boxm[i].posx2, boxm[i].posy2);
		// 文字
		outtextxy(boxm[i].posx1 + ( boxm[i].posx2 - boxm[i].posx1 ) / 2 - textwidth(boxm[i].text) / 2, boxm[i].posy1 + 10, boxm[i].text);
	}

	// 设置参数
	setbkcolor(WHITE);
	settextcolor(BLACK);
	setlinecolor(BLACK);

	// 变量绘制
	j = 30;

	// 图像地址
	i = 4;
	outtextxy(boxm[i].posx1 + ( boxm[i].posx2 - boxm[i].posx1 ) / 2 - textwidth(boxm[i].text) / 2, boxm[i].posy1 + j, address_load_image);

	// 水印地址
	i = 5;
	outtextxy(boxm[i].posx1 + ( boxm[i].posx2 - boxm[i].posx1 ) / 2 - textwidth(boxm[i].text) / 2, boxm[i].posy1 + j, address_load_watermark);

	// 保存大图
	i = 6;
	outtextxy(boxm[i].posx1 + ( boxm[i].posx2 - boxm[i].posx1 ) / 2 - textwidth(boxm[i].text) / 2, boxm[i].posy1 + j, address_save_image);

	// 保存水印
	i = 7;
	outtextxy(boxm[i].posx1 + ( boxm[i].posx2 - boxm[i].posx1 ) / 2 - textwidth(boxm[i].text) / 2, boxm[i].posy1 + j, address_save_watermark);

}

// 初始化函数
void Gary::initialization()
{
	int i;
	// 参数初始化
	pi = acos(-1.0);
	num_encryption_white = 10;		// 加密白点赋值参数
	num_encryption_black = -10;		// 加密黑点赋值参数
	num_encryption_threshold = 128;	// 加密阈值
	num_decrypt_threshold = 0;		// 解密阈值

	// 随机种子初始化
	srand((unsigned)time(NULL));

	// 地址初始化
	TCHAR s1[30] = _T("t1.png");
	for(i = 0; i < 30; i++) { address_load_image[i] = s1[i]; }

	TCHAR s2[30] = _T("sy.png");
	for(i = 0; i < 30; i++) { address_load_watermark[i] = s2[i]; }

	TCHAR s3[30] = _T("image.png");
	for(i = 0; i < 30; i++) { address_save_image[i] = s3[i]; }

	TCHAR s4[30] = _T("sy_new.png");
	for(i = 0; i < 30; i++) { address_save_watermark[i] = s4[i]; }

	// 按钮的初始化
	num_button = 10;

	// 坐标
	for(i = 0; i < num_button; i++)
	{
		boxm[i].posx1 = 560 + 120 * ( i % 2 );
		boxm[i].posy1 = 15 + 100 * ( i / 2 );
		boxm[i].posx2 = 670 + 120 * ( i % 2 );
		boxm[i].posy2 = 85 + 100 * ( i / 2 );
	}

	// 内容
	boxm[0].text = _T("加密过程");		boxm[1].text = _T("攻击过程");
	boxm[2].text = _T("解密过程");		boxm[3].text = _T("完整过程");
	boxm[4].text = _T("加载载体地址");	boxm[5].text = _T("加载水印地址");
	boxm[6].text = _T("保存载体地址");	boxm[7].text = _T("保存水印地址");
	boxm[8].text = _T("重置");			boxm[9].text = _T("退出");

	// 第一次加载
	img_temporary = new IMAGE(512, 512);
	img_watermark = new IMAGE(64, 64);

	// 第一次加载
	loadimage(img_temporary, address_load_image, 512, 512);
	loadimage(img_watermark, address_load_watermark, 64, 64);

	// 图片显示
	putimage(0, 0, img_temporary);

	// 第一次绘制
	draw();
}

// 主进程函数
void Gary::carry()
{
	// 窗口定义
	hOut = initgraph(801, 513);
	SetWindowText(hOut, _T("基于 DWT 和 DCT 域的盲水印算法"));
	// 背景绘制
	setbkcolor(WHITE);
	cleardevice();
	// 进程控制
	exit_carry = 0;
	while(exit_carry == 0)
	{
		initialization();
		move();
	}
	closegraph();
}

// 窗口主视角函数,获取用户操作
void Gary::move()
{
	TCHAR ss[15];
	// 鼠标定义
	ExMessage m;
	int i;
	exit_move = 0;
	while(exit_move == 0)
	{
		// 鼠标信息
		if(peekmessage(&m, EM_MOUSE | EM_KEY))
		{
			// 左键单击判断
			if(m.message == WM_LBUTTONDOWN)
			{
				// 判断是否点击了有效按钮
				for(i = 0; i < num_button; i++)
				{
					if(m.x > boxm[i].posx1 && m.y > boxm[i].posy1 && m.x < boxm[i].posx2 && m.y < boxm[i].posy2)
					{
						break;
					}
				}

				// 点击矩形按钮
				switch(i)
				{
					// 加密过程
				case 0:
				{
					img_temporary = encryption_process(img_temporary, img_watermark);
					break;
				}
				// 攻击过程
				case 1:
				{
					// 输入
					InputBox(ss, 10, _T("输入攻击模式 0.变亮 1. 变暗 2.对比度变低 3.对比度变高 4.椒盐噪声 5.直方图均衡化 6 ~ 8.剪切"));
					_stscanf_s(ss, _T("%d"), &i);
					if(i >= 0 && i <= 8)
					{
						img_temporary = attack_process(img_temporary, i);
					}
					else { MessageBox(hOut, _T("输入错误,不在范围内"), _T("来自小豆子的提醒"), MB_OK); }
					break;
				}
				// 解密过程
				case 2:
				{
					img_watermark = decryption_process(img_temporary);
					break;
				}
				// 完整过程
				case 3:
				{
					completeprocessing();
					break;
				}
				// 加载图片地址
				case 4:
				{
					i = MessageBox(hOut, _T("是否默认地址"), _T("来自小豆子的提醒"), MB_YESNO);
					if(i == 7)
					{
						// 打开文件对话框
						OPENFILENAME ofn;
						// 保存文件完整路径
						TCHAR szFileName[MAX_PATH] = { 0 };
						// 设置过滤条件
						TCHAR szFilter[] = TEXT("PNG Files (*.png)\0*.png\0") \
							TEXT("All Files (*.*)\0*.*\0\0");
						ZeroMemory(&ofn, sizeof(ofn));
						// 保存文件完整路径
						ofn.lpstrFile = szFileName;
						ofn.nMaxFile = MAX_PATH;
						// 保存文件名
						ofn.lpstrFileTitle = address_load_image;
						ofn.nMaxFileTitle = MAX_PATH;
						ofn.lpstrFilter = szFilter;
						// 默认扩展名
						ofn.lpstrDefExt = _T("png");
						ofn.lpstrTitle = NULL;
						ofn.Flags = OFN_HIDEREADONLY | OFN_PATHMUSTEXIST;
						ofn.lStructSize = sizeof(OPENFILENAME);
						// 拥有该对话框的窗口句柄
						ofn.hwndOwner = hOut;
						GetOpenFileName(&ofn);
					}
					// 重置,初始化
					img_temporary = new IMAGE(512, 512);
					img_watermark = new IMAGE(64, 64);
					// 加载
					loadimage(img_temporary, address_load_image, 512, 512);
					loadimage(img_watermark, address_load_watermark, 64, 64);
					// 图片显示
					putimage(0, 0, img_temporary);
					break;
				}
				// 加载水印地址
				case 5:
				{
					i = MessageBox(hOut, _T("是否默认地址"), _T("来自小豆子的提醒"), MB_YESNO);
					if(i == 7)
					{
						// 打开文件对话框
						OPENFILENAME ofn;
						// 保存文件完整路径
						TCHAR szFileName[MAX_PATH] = { 0 };
						// 设置过滤条件
						TCHAR szFilter[] = TEXT("PNG Files (*.png)\0*.png\0") \
							TEXT("All Files (*.*)\0*.*\0\0");
						ZeroMemory(&ofn, sizeof(ofn));
						// 保存文件完整路径
						ofn.lpstrFile = szFileName;
						ofn.nMaxFile = MAX_PATH;
						// 保存文件名
						ofn.lpstrFileTitle = address_load_watermark;
						ofn.nMaxFileTitle = MAX_PATH;
						ofn.lpstrFilter = szFilter;
						// 默认扩展名
						ofn.lpstrDefExt = _T("png");
						ofn.lpstrTitle = NULL;
						ofn.Flags = OFN_HIDEREADONLY | OFN_PATHMUSTEXIST;
						ofn.lStructSize = sizeof(OPENFILENAME);
						// 拥有该对话框的窗口句柄
						ofn.hwndOwner = hOut;
						GetOpenFileName(&ofn);
					}
					img_watermark = new IMAGE(64, 64);
					// 加载
					loadimage(img_watermark, address_load_watermark, 64, 64);
					break;
				}
				// 保存图片
				case 6:
				{
					i = MessageBox(hOut, _T("是否默认地址"), _T("来自小豆子的提醒"), MB_YESNO);
					if(i == 7)
					{
						InputBox(address_save_image, 10, _T("输入图片相对位置及名称"));
					}
					saveimage(address_save_image, img_temporary);
					break;
				}
				// 保存图片
				case 7:
				{
					i = MessageBox(hOut, _T("是否默认地址"), _T("来自小豆子的提醒"), MB_YESNO);
					if(i == 7)
					{
						InputBox(address_save_watermark, 10, _T("输入图片相对位置及名称"));
					}
					saveimage(address_save_watermark, img_watermark);
					break;
				}
				// 重置
				case 8:
				{
					exit_move = 1;
					break;
				}
				// 退出
				case 9:
				{
					exit_move = 1;
					exit_carry = 1;
					break;
				}
				default:break;
				}
				draw();
			}
		}
	}
}

// 主函数
int main(void)
{
	Gary G;
	G.carry();
	return 0;
}

参考资料

基于 DWT 和 DCT 域的盲水印算法

数字图像处理-数字水印的嵌入与提取

Arnold 变换详解

HVS 人眼视觉系统

详解离散余弦变换(DCT)

Zig-zag 扫描

所用素材

图片素材

添加评论