首页 > 编程知识 正文

给片加数字水印,基于dwt的数字水印技术

时间:2023-05-04 13:18:40 阅读:284591 作者:417

由于之前在网上看到的关于LSB的方法大都是以MATLAB||Python写的,于是博主基于其思路改编后得出以下的内容:

一、原理: 1.首先准备一张彩色的图片(2px*2px)


为了方便展示,我用PS准备了一张(2px * 2px)的图片,图片的每个像素里的RGB值分别为:

位置RGB[0,0]10100200[0,1]20010010[1,0]20010100[1,1]10010200

图片格式保存为bmp(无损压缩),保存jpg时会对图片内容产生压缩

2.在终端上查看这张图片

需要注意的是这里查看的代码不是cv::imshow(),而是用cout

int main(int argc, char* argv[]) {if (argc < 2) return -1;cv::Mat img = imread(argv[1],cv::IMREAD_COLOR);cout << img << endl;return 0;}

$ ./a.out test.bmp

[200, 100, 10, 10, 100, 200; 100, 10, 200, 200, 10, 100]

由此我们可以知道:

Mat里面彩色图片的每个像素都按照B、G、R的图层顺序存储的存储的每个单位的值都介于0~255之间一行中的所有像素以及每个像素的三个图层之间都以逗号分开行与行之间以分号隔开

得知上述信息后就好办了~为了方便解释,我们将上面得到的矩阵用二进制的形式展示一下:

[11001000, 01100100, 00001010, 00001010, 01100100, 11001000; 01100100, 00001010, 11001000, 11001000, 00001010, 01100100]

到了这一步就可以来愉快的解释LSB了,假设我们把每个单元里的最低有效位都归零,然后再按照需求填入我们想要的信息

如果改变的是最后一位,那在最终的数值上只是±1,对图片的颜色而言,肉眼几乎不会感到有什么变化。即便修改的是倒数第二位,最终的数值上也就±3,感觉还可以(在最后测试的时候会放上效果图)。修改倒数第三位的话,最终的数值±5,以此类推……

3.准备一张水印图片(黑白)


为了方便演示,我将水印图和原图的大小设为一样的了。下方的代码不用担心水印图和原图尺寸不一致的问题~
假设上图是我要加的水印,信息只有黑白两色,那么我们可以将黑色定义为0;白色定义为1。然后把信息写入原图当中~

4.为原图添加水印信息 原图:[11001000, 01100100, 00001010, 00001010, 01100100, 11001000; 01100100, 00001010, 11001000, 11001000, 00001010, 01100100] 水印图:[00000001, 00000001, 00000001, 00000000, 00000000, 00000000; 00000000, 00000000, 00000000, 00000001, 00000001, 00000001] 原图 + 水印图:[11001001, 01100101, 00001011, 00001010, 01100100, 11001000; 01100100, 00001010, 11001000, 11001001, 00001011, 01100101] 原图:

添加水印后的图片:

还原之后的水印:


这样水印的信息就已经添加到了图片里面,但肉眼几乎看不出有什么区别。
需要注意的是,图片的保存格式最好选择bmp格式,若以其它格式保存(jpeg/png)会压缩失真。
失真后的图片水印也不会像之前那么清楚,这也是这个方法测试下来的一大缺点!

二、源码C: /************************************************************************* > File Name: 001_waterMarkLSB.cpp > Author: PeterShen > Mail: peter704678976@126.com > Created Time: 2019年08月25日 星期日 00时00分09秒 ************************************************************************/#include <stdlib.h>#include <stdio.h>#include <string>#include <typeinfo>#include <opencv2/core/utility.hpp>#include <opencv2/video/tracking.hpp>#include <opencv2/videoio.hpp>#include <opencv2/highgui.hpp>#include "opencv2/imgproc/types_c.h"using namespace cv;template<typename _Tp>vector<_Tp> convertMatToVector(const Mat &mat) {return (vector<_Tp>)(mat.reshape(1, 1));}template<typename _Tp>cv::Mat convertVectorToMat(vector<_Tp> v, int channels, int rows) {cv::Mat mat = cv::Mat(v);cv::Mat dest = mat.reshape(channels, rows).clone();dest.convertTo(dest, CV_8U);return dest;}void showImageLSBWatermark(cv::Mat image, int num) {cv::Mat dst_img;if (num > 7 || num < 0) num = 0;vector<int> v = convertMatToVector<int>(image);vector<int> u;int series = pow(2, num);int j = 0, k = 0;for (int i = 0; i < v.size() / 3; ++i) {j = 0, k = 0;v[3*i] / series % 2 == 0 ? ++j : ++k;v[3*i+1] / series % 2 == 0 ? ++j : ++k;v[3*i+2] / series % 2 == 0 ? ++j : ++k;u.push_back(j > k ? 0 : 255);}dst_img = convertVectorToMat(u, 1, image.rows);cv::imshow("dst_img", dst_img);}template<typename _Tp>vector<_Tp> drawWatermarkOnImage(vector<_Tp> v, vector<_Tp> w, int num) {if (num > 7 || num < 0) return v;int series = pow(2, num);for (int i = 0; i < v.size(); ++i) {if (v[i] / series % 2 != 0) v[i] -= series;v[i] += pow(2, num) * w[i];}return v;}cv::Mat imageLSB(cv::Mat src_img, cv::Mat mrk_img, int num) {if (src_img.size() != mrk_img.size()) {cv::copyMakeBorder(mrk_img, mrk_img, 0, src_img.rows - mrk_img.rows,0, src_img.cols - mrk_img.cols,cv::BORDER_WRAP, Scalar::all(0));}cv::Mat dst_img; vector<int> src_v = convertMatToVector<int>(src_img); vector<int> mrk_v = convertMatToVector<int>(mrk_img); for (int i = 0; i < mrk_v.size(); ++i) {mrk_v[i] = mrk_v[i] < 120 ? 0 : 1 ;}src_v = drawWatermarkOnImage(src_v, mrk_v, num);dst_img = convertVectorToMat(src_v, 1 , src_img.rows);return dst_img;}int main (int argc, char* argv[]) {if (argc < 2) {cout << "Please choose enc/show" << endl;return -1;}if (strcmp(argv[1], "enc") == 0) { if (argc < 5) { cout << "Please enter src_img && watermark && rank(0~7)" << endl; return -1; } cv::Mat img_src = imread(argv[2],cv::IMREAD_COLOR); cv::Mat img_mrk = imread(argv[3],cv::IMREAD_GRAYSCALE); std::vector<cv::Mat> imgs; cv::split(img_src, imgs);int num = atoi(argv[4]);//给三个图层分别添加,也可一选择其中一个加水印for (int i = 0; i < 3; ++i) {imgs[i] = imageLSB(imgs[i], img_mrk, num);} cv::Mat img_dst; cv::merge(imgs, img_dst); cv::imshow("src",img_src); cv::imshow("dst",img_dst);cout << "33[32m[TIPS]: WRITING IMAGE 33[0m" << endl;//无损压缩,其他格式保存会失真cv::imwrite("LSB.bmp", img_dst); } else if (strcmp(argv[1], "show") == 0) {if (argc < 4) {cout << "Please enter src_img && rank(0~7)" << endl;return -1;}cv::Mat img_src = imread(argv[2],cv::IMREAD_COLOR);cv::imshow("src", img_src);int num = atoi(argv[3]);showImageLSBWatermark(img_src, num);} cv::waitKey(0); return 0;} 三、测试: 水印图:

原图:

在最低位嵌入水印(±1):

在倒数第二位嵌入水印(±3):

在倒数第三位嵌入水印(±7):

在倒数第四位嵌入水印(±15):


第三位时已经有肉眼明显可见的水印样子出来了,第四位的时候更加明显,第二位时需要仔细看才能看的到而第一位基本看不到了
而以上所有图片,把加水印的那一层提取出来,都能得到很清晰的水印:

目前图片格式为bmp,将图片通过PS转换为jpg格式后提取到的水印为:
(此处使用的是在倒数第二位加水印)

将图片缩放后测试水印:


对图片进行裁切后测试:

四、总结: 优点:

1.添加水印后即不影响图片的整体美观,又能留住水印里的信息
2.对图片进行裁切、涂改编辑后,保留的原图部分依然能提取到清晰的水印信息

缺点:

1.对图片进行压缩编辑后,水印信息会受到较大干扰
2.水印信息可以在后期进行擦除,可以很容易的被破坏

版权声明:该文观点仅代表作者本人。处理文章:请发送邮件至 三1五14八八95#扣扣.com 举报,一经查实,本站将立刻删除。