由于之前在网上看到的关于LSB的方法大都是以MATLAB||Python写的,于是博主基于其思路改编后得出以下的内容:
为了方便展示,我用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时会对图片内容产生压缩
需要注意的是这里查看的代码不是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]
由此我们可以知道:
得知上述信息后就好办了~为了方便解释,我们将上面得到的矩阵用二进制的形式展示一下:
[11001000, 01100100, 00001010, 00001010, 01100100, 11001000;
01100100, 00001010, 11001000, 11001000, 00001010, 01100100]
到了这一步就可以来愉快的解释LSB了,假设我们把每个单元里的最低有效位都归零,然后再按照需求填入我们想要的信息
如果改变的是最后一位,那在最终的数值上只是±1,对图片的颜色而言,肉眼几乎不会感到有什么变化。即便修改的是倒数第二位,最终的数值上也就±3,感觉还可以(在最后测试的时候会放上效果图)。修改倒数第三位的话,最终的数值±5,以此类推……
为了方便演示,我将水印图和原图的大小设为一样的了。下方的代码不用担心水印图和原图尺寸不一致的问题~
假设上图是我要加的水印,信息只有黑白两色,那么我们可以将黑色定义为0;白色定义为1。然后把信息写入原图当中~
原图:
[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)会压缩失真。
失真后的图片水印也不会像之前那么清楚,这也是这个方法测试下来的一大缺点!
/*************************************************************************
> 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;
}
在第三位时已经有肉眼明显可见的水印样子出来了,第四位的时候更加明显,第二位时需要仔细看才能看的到而第一位基本看不到了
而以上所有图片,把加水印的那一层提取出来,都能得到很清晰的水印:
目前图片格式为bmp,将图片通过PS转换为jpg格式后提取到的水印为:
(此处使用的是在倒数第二位加水印)
将图片缩放后测试水印:
对图片进行裁切后测试:
1.添加水印后即不影响图片的整体美观,又能留住水印里的信息
2.对图片进行裁切、涂改编辑后,保留的原图部分依然能提取到清晰的水印信息
1.对图片进行压缩编辑后,水印信息会受到较大干扰
2.水印信息可以在后期进行擦除,可以很容易的被破坏