環境介紹
所使用的開發環境如下
- 系統:Windows7
- Visual Studio版本:2015
- OpenCV版本:3.4.3
水印簡介
針對圖片水印
- A)可以是明水印,也可以是暗水印
- B)明水印直接在圖片上疊加水印背景,簡單,但容易被移除,Google已經分享了一個圖片明水印快速移除算法,移除效果還是不錯的。
- C)暗水印有很多方法,近年來國內外研究也很多,主要包括空間域水印和變換域水印,變換域的方法相對於空間域方法來說魯棒性更強,可以抵抗壓縮操作等攻擊行爲。
- D)空間域暗水印算法舉例:Patchwork算法,任意選擇N對圖像點,增加其一點的亮度的同時,相應降低另一點的亮度值;通過這一調整過程完成水印的嵌入。該算法具有不易察覺性,並且對於有損壓縮編碼(JPEG)和一些惡意攻擊處理等具有抵抗力。
- E)變換域暗水印算法舉例:基於分塊DCT的一種數字水印技術方案是由一個密鑰隨機地選擇圖像的一些分塊,在頻域的中頻上稍稍改變一個三元組以隱藏二進制序列信息。選擇在中頻分量編碼是因爲在高頻編碼易於被各種信號處理方法所破壞,而在低頻編碼則由於人的視覺對低頻分量很敏感,對低頻分量的改變易於被察覺。該數字水印算法對有損壓縮和低通濾波是穩健的。
從rgb圖像轉換到yuv圖
一般來說yuv圖,可以通過讀取本地的.yuv文件來獲取,但是這樣獲取到的可能是yuv文件序列,本blog主要用來演示算法,只需要一張yuv圖,而且最好是yuv420的圖,因此,直接從rgb轉過來是最合適的,OpenCV能夠快速實現這樣的轉換
所要讀取的圖像文件爲l_hires.jpg,這是一張rgb通道的圖,轉換到yuv的代碼如下
#include "iostream"
using namespace std;
#include "opencv/cv.h"
#include "opencv2/core/core.hpp"
#include "opencv2/highgui/highgui.hpp"
#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/video/video.hpp"
using namespace cv;
#pragma comment(lib,"opencv_world343d.lib")
int main(int argc, char *argv[])
{
// l_hires.jpg爲被嵌圖,需要先轉換爲yuv數據
char *img_path = "l_hires.jpg";
cv::Mat img;
img = cv::imread(img_path, 1); // 讀取圖片
cv::imshow("load image", img);
////RBG轉YUV
cv::Mat yuv_img;
cv::cvtColor(img, yuv_img, CV_BGR2YUV_I420);
cv::imshow("yuvImg", yuv_img);
cv::waitKey(100000); // 等待時間,這裏等待時間最好別填零,要不打開的窗口秒退
return 0;
}
yuv的圖像跟rgb是不一樣的,uv的排列是這樣的一種形式(這裏說的是yuv420)
- 圖中可以看出來,先到的y,所佔大小是圖像本身的width*height
- 接下來是u分量,僅僅只有箭頭的左側,右側是轉換到單通道圖後拷貝出來的u,所佔大小爲width/2*height/2
- 再接下來是v分量,跟u分量是一致的,右側是轉換到單通道圖後拷貝出來的u,所佔大小也是width/2*height/2
在yuv圖中打上灰度水印圖
要嵌入的水印圖爲logo.png,但是這裏是沒有考慮其alpha通道的,所以在嵌入完畢後,可以看到是透明背景會變爲白色,
主要思路如下:
首先需要在讀取的時候指定爲讀取灰度圖,然後得到圖像的uchar數據,再覆蓋到yuv圖中y分量的對應位置上
代碼實現部分如下
#include "iostream"
using namespace std;
#include "opencv/cv.h"
#include "opencv2/core/core.hpp"
#include "opencv2/highgui/highgui.hpp"
#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/video/video.hpp"
using namespace cv;
#pragma comment(lib,"opencv_world343d.lib")
int main(int argc, char *argv[])
{
// l_hires.jpg爲被嵌圖,需要先轉換爲yuv數據
char *img_path = "l_hires.jpg";
cv::Mat img;
img = cv::imread(img_path, 1); // 讀取圖片
cv::imshow("load image", img);
////RBG轉YUV
cv::Mat yuv_img;
cv::cvtColor(img, yuv_img, CV_BGR2YUV_I420);
cv::imshow("yuvImg", yuv_img);
// 把yuv的數據全部放到img_data中
int index = 0;
int image_size = yuv_img.cols * yuv_img.rows;
unsigned char* img_data = new unsigned char[image_size];
for (int i = 0; i < yuv_img.rows; i++)
{
for (int j = 0; j < yuv_img.cols; j++)
{
img_data[index] = yuv_img.at<uchar>(i, j); // 從yuv中拿數據,只取出來y分量,所以寬高是一開始rgb的img
index++;
}
}
cv::Mat logo_img_rgb, logo_yuv_img;
logo_img_rgb = cv::imread("logo.png", 0);
cv::imshow("logo_img_rgb", logo_img_rgb);
// 得到水印圖的unsigned char*數據
image_size = logo_img_rgb.cols * logo_img_rgb.rows;
unsigned char* logo_img_data = new unsigned char[image_size];
index = 0;
for (int i = 0; i < logo_img_rgb.rows; i++)
{
for (int j = 0; j < logo_img_rgb.cols; j++)
{
logo_img_data[index] = logo_img_rgb.at<uchar>(i, j); // 從yuv中拿數據,但是寬高是一開始的img
index++;
}
}
// 用logo圖的圖像數據覆蓋被嵌入圖的數據
index = 0;
for (int i = 0; i < logo_img_rgb.rows; i++) // 嵌入y分量
{
memcpy(img_data + yuv_img.cols * index, logo_img_data + logo_img_rgb.cols * index, logo_img_rgb.cols);
index++;
}
// 看看完整的yuv數據是否還在
cv::Mat img_res(yuv_img.rows, yuv_img.cols, CV_8UC1);
index = 0;
for (int i = 0; i < yuv_img.rows; i++)
{
for (int j = 0; j < yuv_img.cols; j++)
{
img_res.at<uchar>(i, j) = (uchar)img_data[index];
index++;
}
}
cv::imshow("img_res", img_res);
// 轉回rgb看看
cv::Mat img_res_rgb;
cv::cvtColor(img_res, img_res_rgb, CV_YUV2BGR_I420); // imread 1的時候纔會有BGR
cv::imshow("img_res_rgb", img_res_rgb);
cv::waitKey(100000); // 等待時間,這裏等待時間最好別填零,要不打開的窗口秒退
return 0;
}
由於嵌入的logo圖是灰度圖,所以出來的顏色參考了原圖的顏色,但是效果已經得到了
在yuv圖中加上rgb水印圖
只要瞭解了嵌入y分量的原理,如果在瞭解uv的存放形式,那麼剩下的事情,就是取出rgb的數據,分別轉換爲yuv後,再嵌入到主圖的uv分量中就完成效果了
實現步驟如下
- 1、讀取logo的rgb圖
- 2、將1轉換爲yuv數據從mat讀取到uchar*數組中
- 3、將logo圖中對應y、u、v數據分別覆蓋到主圖的y、u、v分量中
- 4、查看效果
實現代碼如下(主要難點是理解圖像的偏移即可)
#include "iostream"
using namespace std;
#include "opencv/cv.h"
#include "opencv2/core/core.hpp"
#include "opencv2/highgui/highgui.hpp"
#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/video/video.hpp"
using namespace cv;
#pragma comment(lib,"opencv_world343d.lib")
int main(int argc, char *argv[])
{
// l_hires.jpg爲被嵌圖,需要先轉換爲yuv數據
char *img_path = "l_hires.jpg";
cv::Mat img;
img = cv::imread(img_path, 1); // 讀取圖片
cv::imshow("load image", img);
////RBG轉YUV
cv::Mat yuv_img;
cv::cvtColor(img, yuv_img, CV_BGR2YUV_I420);
cv::imshow("yuvImg", yuv_img);
// 把yuv的數據全部放到img_data中
int index = 0;
int image_size = yuv_img.cols * yuv_img.rows;
unsigned char* img_data = new unsigned char[image_size];
for (int i = 0; i < yuv_img.rows; i++)
{
for (int j = 0; j < yuv_img.cols; j++)
{
img_data[index] = yuv_img.at<uchar>(i, j); // 從yuv中拿數據,只取出來y分量,所以寬高是一開始rgb的img
index++;
}
}
cv::Mat logo_img_rgb = cv::imread("logo.png", 1);
cv::imshow("logo_img_rgb", logo_img_rgb);
cv::Mat logo_yuv_img;
cv::cvtColor(logo_img_rgb, logo_yuv_img, CV_BGR2YUV_I420); // imread 1的時候纔會有BGR
cv::imshow("logo_yuv_img", logo_yuv_img);
// 把水印的yuv數據存放到logo_yuv_data中
int image_size3 = logo_yuv_img.cols * logo_yuv_img.rows;
unsigned char* logo_yuv_data = new unsigned char[image_size3];
index = 0;
for (int i = 0; i < logo_yuv_img.rows; i++)
{
for (int j = 0; j < logo_yuv_img.cols; j++)
{
logo_yuv_data[index] = logo_yuv_img.at<uchar>(i, j); // 從yuv中拿數據,但是寬高是一開始的img
index++;
}
}
// 如果需要嵌入一個rgb的分量,響應的需要先把水印的logo圖從rgb轉到yuv,然後分別插入不同的分量中
index = 0;
for (int i = 0; i < logo_img_rgb.rows; i++) // 嵌入y分量
{
memcpy(img_data + yuv_img.cols * index, logo_yuv_data + logo_img_rgb.cols * index, logo_img_rgb.cols);
index++;
}
//int y = yuv_img.rows * 2 / 3;
int y = img.rows * img.cols;
int y2 = logo_img_rgb.rows * logo_img_rgb.cols;
index = 0;
for (int i = 0; i < logo_img_rgb.rows / 2; i++) // 嵌入u分量
{
// img.rows * yuv_img.cols 表示跳過y分量,他的長度爲原始img的寬和高img.rows,img的寬yuv不發生改變,img.cols與yuv_img.cols相等
memcpy(img_data + y + yuv_img.cols / 2 * index, logo_yuv_data + y2 + logo_img_rgb.cols / 2 * index, logo_img_rgb.cols / 2);
index++;
}
int v = yuv_img.rows * 5 / 6 * yuv_img.cols;
int v2 = logo_yuv_img.rows * 5 / 6 * logo_yuv_img.cols;
index = 0;
for (int i = 0; i < logo_img_rgb.rows / 2; i++) // 嵌入v分量
{
// img.rows * yuv_img.cols 表示跳過y、u分量,跳過的長度爲y+u的數據長度,一共是5/6的高,拷貝的寬僅僅爲原來的1/2
memcpy(img_data + v + yuv_img.cols / 2 * index, logo_yuv_data + v2 + logo_img_rgb.cols / 2 * index, logo_img_rgb.cols / 2);
index++;
}
// 看看完整的yuv數據是否還在
cv::Mat img_res(yuv_img.rows, yuv_img.cols, CV_8UC1);
index = 0;
for (int i = 0; i < yuv_img.rows; i++)
{
for (int j = 0; j < yuv_img.cols; j++)
{
img_res.at<uchar>(i, j) = (uchar)img_data[index];
index++;
}
}
cv::imshow("img_res", img_res);
// 轉回rgb看看
cv::Mat img_res_rgb;
cv::cvtColor(img_res, img_res_rgb, CV_YUV2BGR_I420); // imread 1的時候纔會有BGR
cv::imshow("img_res_rgb", img_res_rgb);
cv::waitKey(100000); // 等待時間,這裏等待時間最好別填零,要不打開的窗口秒退
return 0;
}
參考鏈接
數字水印技術概覽
opencv把jpg圖片轉化成yuv數據_opencv把Mat轉換成yuv
opencv yuv420與Mat互轉
YUV疊加
OpenCV YUV 與 RGB的互轉(草稿)
OpenCV實現RGB與YUV的轉換
opencv cvcvtcolor函數 將RGB轉爲YUV
YUV420P格式圖像疊加,拼接
Opencv7:Mat與unsigned char類型的相互轉換
opencv3/C++視頻中疊加透明圖片的實現
NV12圖像格式疊加(水印原理演示)