最近在阅读OpenCV相关书籍,看到数字水印这个技巧觉得很有意思,于是想分享给大家。

前言

每张图片都是由很多个像素点构成的。在本文中我们采用的载体图像为灰度图,即该图像是一个二维矩阵,其中每个像素点均为8位二进制数,取值范围从00000000(0)-11111111(255)。水印图像为二值图像,即每个像素点只有0和255两个值,其中0代表黑色,255代表白色。

什么是数字水印

数字水印即为最低有效位信息隐藏,指的是将一个需要隐藏的二值图像信息嵌入载体图像的最低有效位,即将载体图像的最低有效位层替换为当前需要隐藏的二值图像,从而实现将二值图像隐藏的目的。由于二值图像处于载体图像的最低有效位上,所以对于载体图像的影响并不明显,其具有较高的隐蔽性。

在必要时直接将载体图像的最低有效位层提取出来,即可得到嵌入在该位上的二值图像,达到提取秘密信息的目的。

下面我们以一个案例讲解一下二值图像的信息隐藏和提取。

案例

1. 源图展示

本文用到的载体图是lena.bmp
载体图 lena.bmp

本文用到的二值水印图是watermark.bmp
二值水印图 watermark.bmp

这两张图片的大小相同,即拥有相同数量的像素点。

2. 图像独取与预处理

lena=cv2.imread("lena.bmp",0) #读取原始载体图像
watermark=cv2.imread("watermark.bmp",0) #读取水印图像
w=watermark[:,:]>0 
watermark[w]=1 #将水印内的255处理为1,以方便嵌入
r,c=lena.shape #读取原始载体图像的shape值

这部分代码比较简单,就是独取图像过程。因为水印图像是一个二值图像,即每一个像素点只有0和255两个值,所以我们把大小为255的像素点赋值为1,即每个元素的值均为0000000000000001,以方便后续嵌入。

3. 嵌入过程

t254=np.ones((r,c),dtype=np.uint8)*254 #生成内部值都是254的数组
lenaH7=cv2.bitwise_and(lena,t254) #获取lena图像的高7位
e=cv2.bitwise_or(lenaH7,watermark) #将watermark嵌入到lenaH7内

第一行语句我们生成了一个r×c的数组t254,数组中的每一个值都为254,即11111110。

第二行语句我们将得到的数组与载体图像进行“与”操作,“与”操作的特点是“有0则为0,无0则为1”。在cv2.bitwise_and(lena,t254)这条语句中,它会将lena与t254中对应位置像素点一一进行“与”操作。由于t254数组中每个元素的高7位均为1,最低位为0,于是相与之后lena中每个元素的高7位得到保留,最低位为0。将得到的结果赋给lenaH7,其中每个元素的值均为xxxxxxx0。

第三行语句我们将得到的lenaH7与步骤二中预处理之后的水印矩阵进行“或”操作,“或”操作的特点是“有1则为1,无1则为0”。在cv2.bitwise_or(lenaH7,watermark)这条语句中,它会将lenaH7与watermark中对应位置像素点一一进行“或”操作。由于lenaH7中每一个元素的最低位均为0,而watermark中每一个元素值均为00000000或00000001,于是相或之后lenaH7中每个元素的高7位得到保留,最低位的值即为watermark的值,于是就实现了嵌入过程。将得到的结果赋给e。

4. 提取过程

t1=np.ones((r,c),dtype=np.uint8) #生成内部值都是1的数组
wm=cv2.bitwise_and(e,t1) #从载体图像内,提取水印图像
w=wm[:,:]>0
wm[w]=255 #将水印内的1处理为255以方便显示

第一行语句我们生成了一个r×c的数组t1,数组中的每一个值都为1,即为00000001。

第二行语句我们将过程三中得到的e与t1进行“与”操作。由于t1中每一个元素高7位是0,最低位是1,所以相与之后e中每个元素的高7位置0,最低位得到保留,得到的结果赋给wm,wm即为预处理后的水印矩阵。

第三、四行语句我们将大小为1的元素赋值为255,即完成了水印图像的提取过程。

5. 显示

cv2.imshow("lena",lena)
cv2.imshow("watermark",watermark*255)   #当前watermark内最大值为1
cv2.imshow("e",e)
cv2.imshow("wm",wm)
cv2.waitKey()
cv2.destroyAllWindows()

在这里插入图片描述
lena是载体图像,watermark是水印图像。

在这里插入图片描述

e是lena嵌入水印后的的含水印载体图像,wm是从水印载体图像中提取到的水印图像wm。

6. 完整代码

import cv2
import numpy as np
#读取原始载体图像
lena=cv2.imread("lena.bmp",0)
#读取水印图像
watermark=cv2.imread("watermark.bmp",0)
#将水印内的255处理为1,以方便嵌入
#后续章节会介绍使用threshold处理。
w=watermark[:,:]>0
watermark[w]=1
#读取原始载体图像的shape值
r,c=lena.shape
#============嵌入过程============
#生成内部值都是254的数组
t254=np.ones((r,c),dtype=np.uint8)*254
#获取lena图像的高7位
lenaH7=cv2.bitwise_and(lena,t254)
#将watermark嵌入到lenaH7内
e=cv2.bitwise_or(lenaH7,watermark)
#============提取过程============
#生成内部值都是1的数组
t1=np.ones((r,c),dtype=np.uint8)
#从载体图像内,提取水印图像
wm=cv2.bitwise_and(e,t1)
print(wm)
#将水印内的1处理为255以方便显示
#后续章节会介绍threshold实现。
w=wm[:,:]>0
wm[w]=255
#============显示============
cv2.imshow("lena",lena)
cv2.imshow("watermark",watermark*255)   #当前watermark内最大值为1
cv2.imshow("e",e)
cv2.imshow("wm",wm)
cv2.waitKey()
cv2.destroyAllWindows()

总结
从图中可以发现,通过肉眼无法观察出含水印载体图像和原始图像的不同,水印的隐蔽性较高。但是,由于该方法过于简单,其安全性并不高,在实际处理中会通过更复杂的方法实现水印的嵌入。本文主要是为了给读者提供一个方法和思路,激发读者对OpenCV的学习热情。

写在最后
我也只是一个刚刚接触OpenCV的小白,今后遇到有意思的案例还会分享出来给大家,希望大家一起来交流经验互相学习!