点击下方卡片,关注“新机器视觉”公众号
重磅干货,第一时间送达
是的,这我们这次真的好见了,各位撇学生 。
作为一个图像处理领域的老饕,很久没有更新和图像处理相关的知识了。刘老师的风格你们懂得,要么更新一次完整的,要么不更新。 这次带来的项目是印刷图像的水印嵌入与提取。 为什么要加水印:当然是防盗、版权意识。 其实这是一个很简单项目,奈何系统的介绍很少,因此我决定整理一下之前的代码。做成一个系统的推文供大家参考。 老规矩:本次代码依旧放在我的Github上: https://github.com/ImageVisioner/Watermark_System_In_Matlab
通过本推文,您能学到图像处理的所有基本操作,与相关的信号处理操作。在具体讲解之前需要各位了解一下一些基本常识。 印刷领域一般使用*.tif/*.tiff格式保存图像,TIFF 以任何颜色深度存储单个光栅图像。为什么不使用*.jpg作为图像印刷的格式,是因为jpg易产生杂边,杂点,并且会压缩数据点、产生色差。反正记住、高档印刷都是*.tif/*.tiff。
因此我这里有两张印刷格式的图像: 看看其图像信息: 这两张印刷图像就是我们将被嵌入的图像,我们称之为宿主图像(host image)或者载体图像。正如生物链一般,有宿主就会有寄生。在图像处理中,我们将其成为水印图像(cover image)。 因此介绍一下我们本次的水印图像: 因此本文的任务就是将“M”类型的图像嵌入到宿主图像之中。 那我们开始吧~ 宿主图像的格式更换
读入图像: filepath='N1A.TIF';
file=imread(filepath)
但是对于印刷图像,对于使用tiff格式之外,为了保证颜色的不失真,通常采用CMYK颜色空间模式。CMYK是印刷四色模式,它利用色料的三原色混色原理,加上黑色油墨,共计四种颜色混合叠加,形成所谓"全彩印刷"。 不过Matlab中对于CMYK或者lab色彩空间无法直接进行操作,需要进行转换为RGB色彩空间后进行操作。在Matlab中使用makecform函数可以对颜色空间进行无损转换。 makecform函数支持由国际照明委员会(国际照明委员会或 CIE)makecform定义的与设备无关的色彩空间系列成员之间的转换。还支持与 sRGB和CMYK颜色空间之间的转换。 CC = makecform('cmyk2srgb'); %srgb(标准rgb)
I_rgb = applycform(file,CC);
因此我们可以通过此标准将CMYK色彩空间图像转换到RGB空间下。 整体流程技术路线
既然已经学会了如何读取和转换图像,那么简单的介绍一下整个的水印技术路线是如何的。 这路线不会有人看不懂吧。
宿主图像的分解
Matlab默认是RGB颜色空间,通过对单个空间进行单独嵌入水印后,进行通道叠加,会有更稳定的效果。
提取宿主图像的RGB三个通道,代码如下: I_rgb_R=I_rgb(:,:,1); %RED通道
I_rgb_G=I_rgb(:,:,2); %GREEN通道
I_rgb_B=I_rgb(:,:,3); %Blue通道
嵌入图像设计算法
此部分分为两个部分进行进行,1、对宿主图像进行DWT+SVD分解,保证嵌入的稳定性 2、对水印图像进行置乱,保证水印图像不被破解。
对宿主图像进行操作
其中DWT(Discrete Wavelet Transform),为离散小波分解,是信号处理中对矩阵的一种分解形式。 若对一张图像进行DWT分解,能得到四个信息A是低频信息,H是水平高频信息,V是垂直高频信息、D是对角高频信息。其中A中低频部分存储这大部分的能量,包含了图像的大量信息。
由于A中低频部分存储这大部分的能量,因此考虑在分解出的A矩阵中进一步SVD分解。用来保证破解难度。SVD原理如下链接解释: https://heleifz.github.io/15084626290253.html
SVD分解简图: 篇幅有限给出部分代码,完整代码函数请参考Github上:
['haar'); ]=dwt2(watermark,
img_water=w_LL;
Red_water=img_water(:,:,1);
Green_water=img_water(:,:,2);
Blue_water=img_water(:,:,3);
%svd分解
%分解出幺正矩阵
[ ]= svd(Red_water);
[ ]= svd(Green_water);
[ ]= svd(Blue_water);
['haar'); ]=dwt2(cover,
对水印图像进行操作
function arnoldImg = arnold(massage,a,b,n)
%猫脸置乱算法
灰度图像 a,b为参数 n为变换次数
size(massage); =
N=height;
arnoldImg = zeros(height,weight);
for i=1:n
for y=1:height
for x=1:weight
到 N-1
xx=mod((x-1)+b*(y-1),N)+1;
yy=mod(a*(x-1)+(a*b+1)*(y-1),N)+1;
massage(y,x); =
end
end
massage=arnoldImg;
end
arnoldImg = uint8(arnoldImg);
end
置乱十次之后的水印图像效果
宿主图像嵌入的结果:
水印提取图像设计算法
水印的提取与嵌入互为逆过程,核心代码如下,完整代码如Githubs所示:
function ExactImage=exact(cover,watermark,watermarked)
[U_imgr1,S_imgr1,V_imgr1]= svd(red1);
[U_imgg1,S_imgg1,V_imgg1]= svd(green1);
[U_imgb1,S_imgb1,V_imgb1]= svd(blue1);
%watermark
[w_LL,w_LH,w_HL,w_HH]=dwt2(watermark,'haar');
img_wat=w_LL;
%分离通道提取
red2=img_wat(:,:,1);
green2=img_wat(:,:,2);
blue2=img_wat(:,:,3);
[U_imgr2,S_imgr2,V_imgr2]= svd(red2);
[U_imgg2,S_imgg2,V_imgg2]= svd(green2);
[U_imgb2,S_imgb2,V_imgb2]= svd(blue2);
%watermarked
[wm_LL,wm_LH,wm_HL,wm_HH]=dwt2(watermarked,'haar');
img_w=wm_LL;
red3=img_w(:,:,1);
green3=img_w(:,:,2);
blue3=img_w(:,:,3);
[U_imgr3,S_imgr3,V_imgr3]= svd(red3);
[U_imgg3,S_imgg3,V_imgg3]= svd(green3);
[U_imgb3,S_imgb3,V_imgb3]= svd(blue3);
S_ewatr=(S_imgr3-S_imgr1)/0.01;
S_ewatg=(S_imgg3-S_imgg1)/0.01;
S_ewatb=(S_imgb3-S_imgb1)/0.01;
ewatr = U_imgr2*S_ewatr*V_imgr2';
ewatg = U_imgg2*S_ewatg*V_imgg2';
ewatb = U_imgb2*S_ewatb*V_imgb2';
end
从下图可以看出,左边为原图水印,右边为提取出的水印,由于各种嵌入和分解的限制,导致会出现噪声和干扰的情况。见箭头的方框图位置。属于正常现象,任何水印的提取都无法达到无损提取的状况。
图像的攻击评价
对于一个鲁棒性的算法,我们需要对其多种攻击试验,才能验证其算法的稳定性,因此在水印算法中,有如下的测试算法稳定性的方式。
目前常用的攻击有:滤波攻击(高斯滤波、中值滤波)、噪声攻击(椒盐噪声、高斯噪声)和剪切等攻击方式。 书写自定义函数AttackImage,其中cover参数为宿主图像,k为攻击方式:其中本函数预设了五钟攻击方式如下所示,各位可以根据自己的喜好,自行添加各种的图像攻击算法,在这里就不一一进行列举。 function Attackimg=AttackImage(cover,k)cover=uint8(cover);if k==1 noise=randn(size(cover))*100; noise=uint8(noise); cover=cover+noise; Attackimg=cover; elseif k==2 %高斯滤波攻击 w= fspecial('gaussian',[7,7],1); cover=imfilter(cover,w,'replicate'); Attackimg=cover;elseif k==3 %均值 Average=fspecial('average',[13,13]); cover=imfilter(cover,Average); Attackimg=cover; elseif k==4 %并且剪切 [m,n]=size(cover); cover_r=cover(:,:,1); cover_r(1:m/3,1:n/3)=256; cover_g=cover(:,:,2); cover_g(1:m/3,1:n/3)=256; cover_b=cover(:,:,3); cover_b(1:m/3,1:n/3)=256; %合并 cover(:,:,1)=cover_r; cover(:,:,2)=cover_g; cover(:,:,3)=cover_b; Attackimg=cover;elseif k==5 %旋转攻击 cover=imrotate(cover,30,'bilinear','crop'); Attackimg=cover;else Attackimg=cover;endend
在使用AttackImage函数对嵌入水印后的宿主图像进行攻击,判断攻击后的前后图像的差异性。 其中 MSE:均方差误差,反映了图像像素值与均值的离散程度,标准差越⼤说明图像的质量越好(说明图像边缘清晰)。 PSNR:峰值信噪比,是一种评价图像的客观标准,它具有局限性,一般是用 于最大值信号和背景噪音之间的一个工程项目。 NcValue:相关系数,用来衡量两个图像之间的相似程度。 根据上述公式书写代码,由于篇幅限制,完整的代码放在Github上。下列代码只给出核心代码区域。 function [MSE,PSNR,NcValue] =ImageEvaluation(cover,EmbedImage)
psnr_red=10*log10(255^2/MSE_RED); %红色通道的psnr
psnr_green=10*log10(255^2/MSE_Green); %绿色通道的psnr
psnr_blue=10*log10(255^2/MSE_Blue);
MSE=(MSE_Blue+MSE_Green+MSE_RED)/3;
PSNR=(psnr_blue+psnr_red+psnr_green)/3;
%如果变成灰度图 那么两幅图相等 得到的Ncvalue为NAN
rgb2gray(cover); =
EmbedImage_gray=rgb2gray(EmbedImage);
NcValue1=corr2(cover_gray,EmbedImage_gray);
%内部进行打印
NcValue_Red=corr2(cover_red,Embed_red);
NcValue_Blue=corr2(cover_blue,Embed_blue);
NcValue_green=corr2(cover_green,Embed_green);
(NcValue_Red+NcValue_Blue+NcValue_green)/3; =
end
查看结果:
【注意】:MSE越小,则PSNR越大;所以PSNR越大,代表着图像质量越好。PSNR值高于40dB说明图像质量极好(即非常接近原始图像),在30—40dB通常表示图像质量是好的(即失真可以察觉但可以接受)在20—30dB说明图像质量差。PSNR低于20dB图像不可接受,一般来说NC系数在±1之间 ,其绝对值靠近1为两者的相关性强。
图像的一些小修补
经历过水印嵌入的图像,在亮度上会发生些许改变,因此我们需要对其图像的亮度进行归一化处理。
嵌入水印后的图像,明显看到左右图像会有亮度色差的差异。 function BrightnesImage=EqualBrightness(cover,EmbedImage)
%亮度规格化算法
%cover为载体图像
%EmbedImage为嵌入水印的照片
aa=mean2(rgb2gray(cover));
bb=mean2(rgb2gray(EmbedImage));
BrightnesImage=EmbedImage+abs((aa-bb));
end
本次项目,没有给同学们阐述盲水印 最小位数嵌入水印这种概念,只是从基本的思路给大家阐述了如何嵌入。各位若想了解更多数字水印的基本概念。可以多去看看文献和论文,不是很难,难在如何用代码表达。 老规矩:本次代码依旧放在我的Github上: https://github.com/ImageVisioner/Watermark_System_In_Matlab
使用方式,运行主函数main即可。 本文仅做学术分享,如有侵权,请联系删文。
—THE END—