[python] 基于blind-watermark库添加图片盲水印

本文介绍了一个Python库blind-watermark,该库支持在图片中添加和解析基于频域的数字盲水印。文章详细介绍了如何使用该库嵌入二进制数据、图片和文字作为盲水印,并展示了在多种图像攻击下恢复水印的方法。
摘要由CSDN通过智能技术生成

blind-watermark是一个能够给图片添加/解析基于频域的数字盲水印的Python库。图像水印image watermark是指在图片里添加文本或图形,以标记图片的来源。但是图像水印会破坏原图。因此另外一种水印形式,即图像盲水印blind image watermark在实践中更多地用于标记图像来源。图像盲水印是一种肉眼不可见的水印,以不可见的形式添加到原始图像中,不会对原始图像的质量产生很大影响。图像盲水印的具体原理见给你的图片加上盲水印

blind-watermark安装命令如下:

pip install blind-watermark

1 使用说明

1.1 嵌入二进制数据

下面的代码会读取图片并加入二进制数据盲水印。

import blind_watermark
# 关闭输出消息
blind_watermark.bw_notes.close()
from blind_watermark import att
from blind_watermark import WaterMark
import cv2
from blind_watermark import WaterMarkCore
import numpy as np


# 水印的长宽wm_shape
bwm = WaterMark(password_img=1, password_wm=1)

# 读取原图
imgpath = 'input.jpg'
bwm.read_img(imgpath)

wm = [True, False, True, False, True, False, True, False, True, False]
# 嵌入二进制bit数据
bwm.read_wm(wm, mode='bit')

# 打上盲水印
outputpath = 'output.png'
# 保存输出图片
bwm.embed(outputpath)

# 解水印需要用到长度
len_wm = len(wm)  
#  抗攻击需要知道原图的shape
ori_img_shape = cv2.imread(imgpath).shape[:2]  

上面的代码会往图片中添加二进制数据的盲水印,对比原图和加入盲水印的图片,可以发现虽然看不到水印,但是实际上图像质量有一定下降。

from PIL import Image  
# 展示原图
image = Image.open(imgpath)
image.show()
# 展示添加盲水印后的图
image = Image.open(outputpath)
image.show()

在这里插入图片描述

在这里插入图片描述

以下代码会从加入盲水印的图像中提取水印结果。

# 注意设定水印的长宽wm_shape
bwm1 = WaterMark(password_img=1, password_wm=1)
# 提取水印
wm_extract = bwm1.extract(outputpath, wm_shape=len_wm, mode='bit')
print("不攻击的提取结果:", wm_extract)

assert np.all(wm == wm_extract), '提取水印和原水印不一致'
不攻击的提取结果: [ True False  True False  True False  True False  True False]

以下代码展示了对添加水印的图片进行截图后依然能够提取水印,这种方式只是将非截取区域用白色遮挡,不是真正的截图。

# 截取区域设置
# 截取方式x1, y1, x2, y2 = shape[0] * loc[0][0], shape[1] * loc[0][1], shape[0] * loc[1][0], shape[1] * loc[1][1]
# (x1,y1),(x2,y2)
loc = ((0.3, 0.1), (0.7, 0.9))

outputpath_ = '截屏攻击.png'
# 保存截屏后的图片
att.cut_att(input_filename=outputpath, output_file_name=outputpath_, loc=loc)

bwm1 = WaterMark(password_wm=1, password_img=1)
wm_extract = bwm1.extract(outputpath_, wm_shape=len_wm, mode='bit')
print("截屏攻击{loc}后的提取结果:".format(loc=loc), wm_extract)
assert np.all(wm == wm_extract), '提取水印和原水印不一致'

# 展示添加攻击后的盲水印图
image = Image.open(outputpath_)
image.show()
截屏攻击((0.3, 0.1), (0.7, 0.9))后的提取结果: [ True False  True False  True False  True False  True False]

在这里插入图片描述

以下代码展示了对添加水印的图片进行横向剪裁后依然能够提取水印。

r = 0.5
outputpath = 'output.png'
outputpath_ = '横向裁剪攻击.png'
outputpath_r = '横向裁剪攻击_填补.png'
att.cut_att_width(input_filename=outputpath, output_file_name=outputpath_, ratio=r)
# 需要填补图像,用空白填补图像
att.anti_cut_att(input_filename=outputpath_, output_file_name=outputpath_r,
                 origin_shape=ori_img_shape)


# extract:
bwm1 = WaterMark(password_wm=1, password_img=1)
wm_extract = bwm1.extract(outputpath_r, wm_shape=len_wm, mode='bit')
print(f"横向裁剪攻击r={r}后的提取结果:", wm_extract)
横向裁剪攻击r=0.5后的提取结果: [ True False  True False  True False  True False  True False]
# 展示添加横向裁剪攻击后的盲水印图
image = Image.open(outputpath_)
print(image.size)
image.show()
(177, 354)

在这里插入图片描述

# 展示添加横向裁剪攻击_填补后的盲水印图,缺失区域用白色填充,以保持和原图尺寸一致
image = Image.open(outputpath_r)
print(image.size)
image.show()
(354, 354)

在这里插入图片描述

以下代码展示了对添加水印的图片进行遮挡后依然能够提取水印。

outputpath_ = '遮挡攻击.png'
n = 60
att.shelter_att(input_filename=outputpath, output_file_name=outputpath_, ratio=0.1, n=n)

# 提取
bwm1 = WaterMark(password_wm=1, password_img=1)
wm_extract = bwm1.extract(outputpath_, wm_shape=len_wm, mode='bit')
print(f"遮挡攻击{n}后的提取结果:", wm_extract)
assert np.all(wm == wm_extract), '提取水印和原水印不一致'

# 展示添加攻击后的盲水印图
image = Image.open(outputpath_)
image.show()
遮挡攻击60后的提取结果: [ True False  True False  True False  True False  True False]

在这里插入图片描述

以下代码展示了对添加水印的图片进行旋转后依然能够提取水印,但是需要将旋转后的图片再旋转回来。

outputpath_ = '旋转攻击.png'
outputpath_r = '旋转攻击还原.png'
att.rot_att(input_filename=outputpath, output_file_name=outputpath_, angle=45)
att.rot_att(input_filename=outputpath_, output_file_name=outputpath_r, angle=-45)

# 提取水印
bwm1 = WaterMark(password_wm=1, password_img=1)
wm_extract = bwm1.extract(outputpath_r, wm_shape=len_wm, mode='bit')
print("旋转攻击后的提取结果:", wm_extract)
assert np.all(wm == wm_extract), '提取水印和原水印不一致'

# 展示添加攻击后的盲水印图
image = Image.open(outputpath_)
image.show()
旋转攻击后的提取结果: [ True False  True False  True False  True False  True False]

在这里插入图片描述

总之,blind_watermark提供了很稳定的盲水印添加和恢复方式,还有其他不同的攻击效果,比如亮度椒盐缩放。具体可以查看代码blind_watermark_bit。但是要注意的是,对于特定图像,添加某些图像处理效果blind_watermark是没法准确提取水印的。

1.2 嵌入图片数据

下面的代码会读取图片并加入水印图片,水印图片不能大于1.936kb,恢复后的水印图片会丢失色彩信息。

import cv2

from blind_watermark import WaterMark

bwm = WaterMark(password_wm=1, password_img=1)
# 读取原图
imgpath = 'input.jpg'
bwm.read_img(filename = imgpath)
# 设置水印图片,水印图片不能大于1.936kb
markimgpath = 'watermark.bmp'
bwm.read_wm(markimgpath, mode='img')

outputpath = 'output.png'
# 打上盲水印
bwm.embed(outputpath)
wm_shape = cv2.imread(markimgpath, flags=cv2.IMREAD_GRAYSCALE).shape

bwm1 = WaterMark(password_wm=1, password_img=1)
# 注意需要设定水印的长宽wm_shape
wm_extract = bwm1.extract(outputpath, wm_shape=wm_shape, out_wm_name='wm_extracted.png', mode='img')
# 展示盲水印图
image = Image.open(outputpath)
image.show()

在这里插入图片描述

# 展示添加的水印图
image = Image.open(markimgpath)
image.show()

在这里插入图片描述

# 展示提取的水印图
image = Image.open('wm_extracted.png')
image.show()

在这里插入图片描述

1.3 嵌入文字数据

下面的代码会读取图片并加入文字数据盲水印,这种方式也是最常见添加水印方法。

bwm = WaterMark(password_img=1, password_wm=1)
imgpath = 'input.jpg'
bwm.read_img(imgpath)
wm = 'hello 世界!'
bwm.read_wm(wm, mode='str')
outputpath = 'output.png'
bwm.embed(outputpath)

len_wm = len(bwm.wm_bit)  # 解水印需要用到长度
print('Put down the length of wm_bit {len_wm}'.format(len_wm=len_wm))

ori_img_shape = cv2.imread(outputpath).shape[:2]  

# 解水印
bwm1 = WaterMark(password_img=1, password_wm=1)
wm_extract = bwm1.extract(outputpath, wm_shape=len_wm, mode='str')
print("不攻击的提取结果:", wm_extract)

assert wm == wm_extract, '提取水印和原水印不一致'
Put down the length of wm_bit 119
不攻击的提取结果: hello 世界!

当然对存入水印后的图片进行图像变换也是可以恢复水印结果,具体使用可以参考blind_watermark_str

以下代码展示了对添加水印的图片进行椒盐效果添加后依然能够提取水印。

# 往水印图片添加椒盐效果
# ratio是椒盐概率,太高恢复不了
ratio = 0.02
outputpath_ = '椒盐攻击.png'
att.salt_pepper_att(input_filename=outputpath, output_file_name=outputpath_, ratio=ratio)

# 提取
wm_extract = bwm1.extract(outputpath_, wm_shape=len_wm, mode='str')
print(f"椒盐攻击ratio={ratio}后的提取结果:", wm_extract)
assert np.all(wm == wm_extract), '提取水印和原水印不一致'

# 展示添加椒盐水印后的盲水印图
image = Image.open(outputpath_)
image.show()
椒盐攻击ratio=0.02后的提取结果: hello 世界!

在这里插入图片描述

以下代码展示了对添加水印的图片进行纵向剪裁后依然能够提取水印。

# 纵向剪裁图片
r = 0.4
outputpath = 'output.png'
outputpath_ = '纵向裁剪攻击.png'
outputpath_r = '纵向裁剪攻击_填补.png'
att.cut_att_height(input_filename=outputpath, output_file_name=outputpath_, ratio=r)
# 需要填补图像,用空白填补图像
att.anti_cut_att(input_filename=outputpath_, output_file_name=outputpath_r,
                 origin_shape=ori_img_shape)

# extract:
bwm1 = WaterMark(password_wm=1, password_img=1)
wm_extract = bwm1.extract(outputpath_r, wm_shape=len_wm, mode='str')
print(f"纵向裁剪攻击r={r}后的提取结果:", wm_extract)
纵向裁剪攻击r=0.4后的提取结果: hello 世界!
# 展示添加纵向裁剪攻击后的盲水印图
image = Image.open(outputpath_)
print(image.size)
image.show()
(354, 141)

在这里插入图片描述

# 展示添加纵向裁剪攻击_填补后的盲水印图,缺失区域用白色填充,以保持和原图尺寸一致
image = Image.open(outputpath_r)
print(image.size)
image.show()
(354, 354)

在这里插入图片描述

2 参考

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值