一、opencv实现yuv图像中明水印

文章目录

  • 环境介绍
  • 水印简介
  • 从rgb图像转换到yuv图
  • 在yuv图中打上灰度水印图
  • 在yuv图中加上rgb水印图
  • 参考链接

环境介绍

所使用的开发环境如下

  • 系统: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能够快速实现这样的转换

一、opencv实现yuv图像中明水印_第1张图片

所要读取的图像文件为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(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(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(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图是灰度图,所以出来的颜色参考了原图的颜色,但是效果已经得到了

一、opencv实现yuv图像中明水印_第2张图片

在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(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(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(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图像格式叠加(水印原理演示)

你可能感兴趣的:(视频处理相关,opencv,计算机视觉,图像处理,数字水印,图像水印)