C++:图片数字水印-基于OpenCV+LSB

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


一、原理:

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

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

位置 R G B
[0,0] 10 100 200
[0,1] 200 100 10
[1,0] 200 10 100
[1,1] 100 10 200

图片格式保存为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]

由此我们可以知道:

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

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

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

到了这一步就可以来愉快的解释LSB了,假设我们把每个单元里的最低有效位都归零,然后再按照需求填入我们想要的信息
C++:图片数字水印-基于OpenCV+LSB_第1张图片
如果改变的是最后一位,那在最终的数值上只是±1,对图片的颜色而言,肉眼几乎不会感到有什么变化。即便修改的是倒数第二位,最终的数值上也就±3,感觉还可以(在最后测试的时候会放上效果图)。修改倒数第三位的话,最终的数值±5,以此类推……

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

一张2*2像素的图片
为了方便演示,我将水印图和原图的大小设为一样的了。下方的代码不用担心水印图和原图尺寸不一致的问题~
假设上图是我要加的水印,信息只有黑白两色,那么我们可以将黑色定义为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]
原图:

一张2*2像素的图片

添加水印后的图片:

LSB生成后的图片

还原之后的水印:

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

二、源码C:

/*************************************************************************
    > File Name: 001_waterMarkLSB.cpp
    > Author: PeterShen
    > Mail: [email protected] 
    > Created Time: 2019年08月25日 星期日 00时00分09秒
 ************************************************************************/

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#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 << "\033[32m[TIPS]: WRITING IMAGE \033[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;
}

三、测试:

水印图:

watermark

原图:

C++:图片数字水印-基于OpenCV+LSB_第2张图片

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

C++:图片数字水印-基于OpenCV+LSB_第3张图片

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

C++:图片数字水印-基于OpenCV+LSB_第4张图片

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

C++:图片数字水印-基于OpenCV+LSB_第5张图片

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

C++:图片数字水印-基于OpenCV+LSB_第6张图片
第三位时已经有肉眼明显可见的水印样子出来了,第四位的时候更加明显,第二位时需要仔细看才能看的到而第一位基本看不到了
而以上所有图片,把加水印的那一层提取出来,都能得到很清晰的水印:
C++:图片数字水印-基于OpenCV+LSB_第7张图片
目前图片格式为bmp,将图片通过PS转换为jpg格式后提取到的水印为:
(此处使用的是在倒数第二位加水印)
C++:图片数字水印-基于OpenCV+LSB_第8张图片
将图片缩放后测试水印:
C++:图片数字水印-基于OpenCV+LSB_第9张图片
C++:图片数字水印-基于OpenCV+LSB_第10张图片
对图片进行裁切后测试:
C++:图片数字水印-基于OpenCV+LSB_第11张图片
C++:图片数字水印-基于OpenCV+LSB_第12张图片

四、总结:

优点:

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

缺点:

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

你可能感兴趣的:(C语言,LSB,OpenCV,数字水印)