Patchwork原意为一种用各种颜色和形状拼接成的布料。 Patchwork算法最早由麻省理工学院研发,在空间域上通过大量的模式冗余来实现鲁棒的数字水印技术,期初多用于打印票据的防伪。
基础原理:Patchwork将水印信息隐藏在图像数据的亮度统计特性中,给出了一种原始的扩频调制机制。该算法根据给定的key随机选择N对像素点(ai, bi),然后将每个ai点的亮度值加δ(通常取256的1~5%),每个bi点的亮度值减δ,这样整个图像的平均亮度保持不变。 验证:根据验证用的key取得随机N对像素点,计算每一对像素点的亮度差并累加。如果key正确,最终的累加结果应该接近2 * N * δ;否则,最终累加结果会接近于0。
特点:
-
数据量较小,为了不破坏原始图像,一般只能存储1bit的数据(?存疑);
-
隐藏性好;
-
鲁棒性强,可以抵抗缩放、剪裁等的攻击;
借鉴: 甘霖,杨榆.基于变换域的Patchwork水印改进算法[J].成都信息工程大学学报,2017,32(06):623-627. 数字水印 改进的patchwork算法 实现_夏荷影的博客-CSDN博客_patchwork算法(MATLAB实现)
改进思路:
(1)离散余弦变换(Discrete Cosine Transform)与离散傅里叶变换类似,可以将图像从空间域变换到频率域。经过DCT变换后的低频能量会集中在左上角。DCT变换比DFT变换后的能量更加集中。 在水印算法中引入图像的变换域用于嵌入水印,以此增强水印的透明性。
(2)人类视觉系统特性人类的视觉系统对不同颜色的敏感程度不同。由亮度方程 y = 0.299R + 0.587G + 0.144B可知,人眼对绿色光最为敏感,对红色光的敏感程度次之,对于蓝色光最不敏感。人眼对红色光和蓝色光的敏感程度之和与对绿色光的敏感程度较为接近。 使用红色和蓝色光部分作为 Pacthwork 算法的 A 集合,绿色光作为 B 集合,两个集合在嵌入信息时使用相逆的操作,可以在一定程度上互相抵消嵌入信息引起的图像视觉上的变化,提高水印透明性。
算法描述——嵌入水印载体图像 I 与水印图像W选取正方形的 RGB 图像,并将水印图像压缩到载体图像的边长为 1/8 。
将载体图像 I 的 3 个颜色通道分离到iBGR数组中,数组的三个元素分别为Bule、Greed、Red 3个颜色分量;
将水印图像W的 3 个颜色通道分离wBGR数组中,数组的三个元素分别为Bule、Greed、Red 3个颜色分量;
对水印图像的三个颜色分量wBGR分别进行Arnold变换得到wBGRA。
对载体图像iBGR中的三个颜色分量进行如下操作:各分量以8×8的大小为一个单位划分为若干子块,对每一个子块分别应用DCT 变换,最终得到三个颜色分量的分块DCT变换矩阵,放入iBGR_DCT中;
(上图为分块DCT变换后生成的图像,可以看到图中有明暗点阵,较亮的点隐约将原图形的轮廓勾勒了出来。)
然后对每个颜色分量的分块DCT变换矩阵取出其中的每一个子块左上角(0, 0)位置的直流分量组成一个新矩阵,该矩阵称为直流分量矩阵iBGRD,该矩阵的边长应为原图像边长的1/8;
在直流分量矩阵上嵌入水印,嵌入方法是增加/减去置乱的水印图像分量k倍的亮度。分量提取公式如下:
将直流分量矩阵还原回分块DCT矩阵的各直流分量处。之后对修改过的iBGR_DCT作逆DCT变换,获得三个颜色分量的矩阵。
合并三个颜色分量矩阵,即可得到嵌入数字水印后的图像。
算法描述——提取水印ps:美中不足的是该算法不能仅仅依靠嵌有数字水印的图像来提取水印,还需要未嵌入水印的原图。
将含有数字水印的载体图像 P 和原始载体图像 I 的 3 个颜色通道分离;
对P和I的颜色分量矩阵都做DCT变换,并提取出直流分量矩阵pBGRD和iBGRD;
利用直流分量矩阵作差并除以嵌入时设定的系数k,提取水印图片的Arnold变换图像;
对Arnold置换图像作逆Arnold置换后再合并即可得到嵌入的水印图片。
实验选取k=0.162、0.05、0.03、0.01作为系数,测试嵌入水印的效果如下:
可以看出,k值过大会影响嵌入后的图像以及水印图像的显示效果,k值过小只会影响提取出的水印图像的效果。最终发现k=0.03最为合适。
攻击测试
选取k=0.03时嵌入的图像,进行格式压缩、尺寸放大、尺寸缩小、剪裁的攻击方式。对比提取出的水印图像的效果。(除去剪裁之外,嵌入水印的图像均无明显变化)
其中可以看出,剪裁和缩小攻击对提取出的水印质量影响最大。
C++源码#pragma once #include#include #include #include "DCTransform.hpp" using namespace std; namespace Patchwork { static class PatchworkAlgorithm { private: static int Offset; static int NumberOfColor; static int NumberOfIterations; static int Sigma; static Mat DCTFunc(const Mat& img); static Mat IDCTFunc(const Mat& img); public: static Mat ArnoldReplace(Mat img); //static Mat ArnoldReplace(Mat img, int a=1, int b=1, int c=1, int d=2); //static Mat DeArnoldReplace(Mat img, int a = 2, int b = -1, int c = -1, int d = 1); static Mat DeArnoldReplace(Mat img); static bool SetWatermark(cv::Mat &picture, cv::Mat &watermark, float k = 0.1); static bool GetWatermark(cv::Mat &picture, cv::Mat &originImg, cv::Mat &watermark, float k = 0.1); }; int PatchworkAlgorithm::Offset = 54; int PatchworkAlgorithm::NumberOfColor = 3; int PatchworkAlgorithm::NumberOfIterations = 10000; int PatchworkAlgorithm::Sigma = 3; //嵌入水印核心算法,k为系数 bool PatchworkAlgorithm::SetWatermark(cv::Mat &picture, cv::Mat &watermark, float k) { //对图片进行缩放,使载体图片和水印图片均为正方形,且载体图片的长宽等于水印图片长宽的8倍 int pic_size = max(picture.rows, picture.cols); if (picture.rows != picture.cols) { cv::resize(picture, picture, Size(pic_size, pic_size)); } if (watermark.rows != watermark.cols || watermark.rows != (picture.rows / 8) || watermark.cols != (picture.cols / 8)) { cv::resize(watermark, watermark, Size(pic_size / 8, pic_size / 8)); } //分离RGB通道 vector iBGR, wBGR; cv::split(picture, iBGR); cv::split(watermark, wBGR); //DCT变换后的原矩阵 vector iBGR_DCT(3); vector iBGRD(3), wBGRA(3); for (int i = 0; i < 3; i++) { //对水印图像的三个分量分别作Arnold变换 wBGR[i].convertTo(wBGR[i], CV_32FC1, 1 / 256.0); wBGRA[i] = ArnoldReplace(wBGR[i]); //对载体图像的三个分量分别作DCT变换 iBGR[i].convertTo(iBGR[i], CV_32FC1, 1 / 256.0); iBGR_DCT[i] = DCTFunc(iBGR[i]); //取出三个载体图像DCT变换的直流分量 iBGRD[i] = DCTransform::GetDirectComponent(iBGR_DCT[i]); } //在直流分量中插入水印图片的值 iBGRD[0] = iBGRD[0] + k * wBGRA[0]; //Blue iBGRD[1] = iBGRD[1] - k * wBGRA[1]; //Green iBGRD[2] = iBGRD[2] + k * wBGRA[2]; //Red for (int i = 0; i < 3; i++) { //将直流分量放回原DCT变换图中 DCTransform::SetDirectComponent(iBGR_DCT[i], iBGRD[i]); //作DCT逆变换 iBGR[i] = IDCTFunc(iBGR_DCT[i]); iBGR[i].convertTo(iBGR[i], CV_8UC1, 256.0); } picture = cv::Mat(Size(pic_size, pic_size), CV_8UC3, Scalar(0)); //合并RGB分量 cv::merge(iBGR, picture); return true; } //提取水印核心算法 bool PatchworkAlgorithm::GetWatermark(cv::Mat &picture, cv::Mat &originImg, cv::Mat &watermark, float k) { int pic_size = picture.rows; vector wBGR(3), wBGRA(3); vector pBGR, pBGR_DCT(3), pBGRD(3); vector iBGR, iBGR_DCT(3), iBGRD(3); cv::split(picture, pBGR); cv::split(originImg, iBGR); for (int i = 0; i < 3; i++) { pBGR[i].convertTo(pBGR[i], CV_32FC1, 1 / 256.0); iBGR[i].convertTo(iBGR[i], CV_32FC1, 1 / 256.0); //DCT变换 pBGR_DCT[i] = DCTFunc(pBGR[i]); iBGR_DCT[i] = DCTFunc(iBGR[i]); //提取直流分量 pBGRD[i] = DCTransform::GetDirectComponent(pBGR_DCT[i]); iBGRD[i] = DCTransform::GetDirectComponent(iBGR_DCT[i]); } //提取水印图片的Arnold变换图像 wBGRA[0] = (pBGRD[0] - iBGRD[0]) / k; wBGRA[1] = (iBGRD[1] - pBGRD[1]) / k; wBGRA[2] = (pBGRD[2] - iBGRD[2]) / k; for (int i = 0; i < 3; i++) { //进行Arnold逆变换 wBGR[i] = DeArnoldReplace(wBGRA[i]); wBGR[i].convertTo(wBGR[i], CV_8UC1, 256.0); } watermark = Mat(Size(pic_size / 8, pic_size / 8), CV_8UC3, Scalar(0)); cv::merge(wBGR, watermark); cv::resize(watermark, watermark, Size(pic_size, pic_size)); return true; } //Arnold变换 Mat PatchworkAlgorithm::ArnoldReplace(Mat img) { int cols = img.cols; int rows = img.rows; //创建一个等大小的空白图 Mat result(Size(rows, cols), CV_32FC1, Scalar(0)); int a = 1; int b = 1; for (int i = 0; i < rows; i++) { for(int j = 0; j < cols; j++) { int x = (i + b * j) % rows; int y = (a * i + (a * b + 1) * j) % cols; result.at (x, y) = img.at (i, j); } } return result; } //Arnold反变换 Mat PatchworkAlgorithm::DeArnoldReplace(Mat img) { int cols = img.cols; int rows = img.rows; Mat result(Size(rows, cols), CV_32FC1, Scalar(0)); int a = 1; int b = 1; for (int i = 0; i < rows; i++) { for (int j = 0; j < cols; j++) { int x = ((a * b + 1) * i - b * j) % rows; int y = (-a * i + j) % cols; x = x < 0 ? x + rows : x; y = y < 0 ? y + rows : y; result.at (x, y) = img.at (i, j); } } return result; } Mat PatchworkAlgorithm::DCTFunc(const Mat& img) { float msk[8][8] = { {16,11,10,16,24,40,51,61}, {12,12,14,19,26,58,60,55}, {14,13,16,24,40,57,69,56}, {14,17,22,29,51,87,80,62}, {18,22,37,56,68,109,103,77}, {24,35,55,64,81,104,113,92}, {49,64,78,87,103,121,120,101}, {72,92,95,98,112,100,103,99} }; Mat mask(8, 8, CV_32FC1, msk); Mat A(8, 8, CV_32FC1); DCTransform::InitDctMat(A); return DCTransform::DCT(img, A, mask); } Mat PatchworkAlgorithm::IDCTFunc(const Mat& img) { float msk[8][8] = { {16,11,10,16,24,40,51,61}, {12,12,14,19,26,58,60,55}, {14,13,16,24,40,57,69,56}, {14,17,22,29,51,87,80,62}, {18,22,37,56,68,109,103,77}, {24,35,55,64,81,104,113,92}, {49,64,78,87,103,121,120,101}, {72,92,95,98,112,100,103,99} }; Mat mask(8, 8, CV_32FC1, msk); Mat A(8, 8, CV_32FC1); DCTransform::InitDctMat(A); return DCTransform::IDCT(img, A, mask); } }
#pragma once #include#include using namespace cv; namespace Patchwork { class DCTransform { private: static const float PI; public: static void InitDctMat(Mat& A); static Mat DCT(const Mat& src, const Mat& A, const Mat& mask); //求DCT变换后的矩阵 static Mat IDCT(const Mat& src, const Mat& A, const Mat& mask); //求DCT逆变换后的矩阵 static Mat GetDirectComponent(Mat &DCT); //求直流分量 static void SetDirectComponent(Mat &DCT, Mat &DirectComponent); //设置直流分量 }; const float DCTransform::PI = 3.1415926; void DCTransform::InitDctMat(Mat& A) { for (int i = 0; i < 8; ++i) { for (int j = 0; j < 8; ++j) { float a; if (i == 0) a = sqrt(1.0 / 8.0); else a = sqrt(2.0 / 8.0); A.ptr (i)[j] = a * cos((j + 0.5) * PI * i / 8); } } } Mat DCTransform::DCT(const Mat& src, const Mat& A, const Mat& mask) { int rows = src.rows; int cols = src.cols; Mat res = src.clone(); for (int i = 0; i < rows; i += 8) { for (int j = 0; j < cols; j += 8) { //std::cout << "i:" << i << std::endl; //std::cout << "j:" << j << std::endl; res(Range(i, i + 8), Range(j, j + 8)) = A * res(Range(i, i + 8), Range(j, j + 8)) * A.t(); //dct res(Range(i, i + 8), Range(j, j + 8)) /= mask; //量化 //std::cout << "直流通量:" << res(Range(i, i + 8), Range(j, j + 8)).at (0, 0) << std::endl; } } return res; } Mat DCTransform::IDCT(const Mat& src, const Mat& A, const Mat& mask) { int rows = src.rows; int cols = src.cols; Mat res = src.clone(); for (int i = 0; i < rows; i += 8) { for (int j = 0; j < cols; j += 8) { res(Range(i, i + 8), Range(j, j + 8)) = res(Range(i, i + 8), Range(j, j + 8)).mul(mask); res(Range(i, i + 8), Range(j, j + 8)) = A.t() * res(Range(i, i + 8), Range(j, j + 8)) * A; } } return res; } Mat DCTransform::GetDirectComponent(Mat &DCT) { int rows = DCT.rows; int cols = DCT.cols; Mat res(Size(rows / 8, cols/ 8), CV_32FC1); for (int i = 0; i < rows; i += 8) { for (int j = 0; j < cols; j += 8) { res.at (i / 8, j / 8) = DCT(Range(i, i + 8), Range(j, j + 8)).at (0, 0); } } return res; } void DCTransform::SetDirectComponent(Mat &DCT, Mat &DirectComponent) { int rows = DCT.rows; int cols = DCT.cols; for (int i = 0; i < rows; i += 8) { for (int j = 0; j < cols; j += 8) { DCT(Range(i, i + 8), Range(j, j + 8)).at (0, 0) = DirectComponent.at (i / 8, j / 8); } } } }
真男人当然要用C++,用什么MATLAB,用什么Python!弱爆了!C++功能强大,语法严格,最主要的是还能噎磨同学,可谓是一举三得!(bushi)



