[python] 基于blind-watermark库添加图片盲水印
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 参考
[python] 基于blind-watermark库添加图片盲水印的更多相关文章
- gd库复制图片做水印
将复制源图片的某个位置复制到目标图片中,不能调整大小 imagecopy(目标图片画布,复制源画布,目标画布左上角x,y,源画布左上角x,y,复制图片的宽,高); 允许调整大小 imagecopyre ...
- 【CTF】图片隐写术 · 盲水印
前言 盲水印同样是CTF Misc中极小的一个知识点,刚刚做到一题涉及到这个考点的题目. 感觉还挺有意思的,就顺便去了解了下盲水印技术. 数字水印 数字水印(Digital Watermark)一种应 ...
- BugKu 2B+基于python的opencv的安装-------CTF 盲水印的套路
BugKu杂项-2B 下载图片后,binwalk下跑一跑,发现有个zip,分离. 值得一提的是,这个zip是伪加密的. 但是你在分离的时候,伪加密的图片也给你分离出来了.这两个图片2B和B2肉眼看起来 ...
- javaCV开发详解之4:转流器实现(也可作为本地收流器、推流器,新增添加图片及文字水印,视频图像帧保存),实现rtsp/rtmp/本地文件转发到rtmp流媒体服务器(基于javaCV-FFMPEG)
javaCV系列文章: javacv开发详解之1:调用本机摄像头视频 javaCV开发详解之2:推流器实现,推本地摄像头视频到流媒体服务器以及摄像头录制视频功能实现(基于javaCV-FFMPEG.j ...
- python中用Pillow库进行图片处理
一.Python中 PIL 图像处理库简介 PIL可以做很多和图像处理相关的事情: 图像归档(Image Archives).PIL非常适合于图像归档以及图像的批处理任务.你可以使用PIL创建缩略图, ...
- Python测试 ——开发工具库
Web UI测试自动化 splinter - web UI测试工具,基于selnium封装. selenium - web UI自动化测试. mechanize- Python中有状态的程序化Web浏 ...
- Python常用的标准库以及第三方库有哪些?
20个必不可少的Python库也是基本的第三方库 读者您好.今天我将介绍20个属于我常用工具的Python库,我相信你看完之后也会觉得离不开它们.他们是: Requests.Kenneth Reitz ...
- Python常用的标准库以及第三方库
Python常用的标准库以及第三方库有哪些? 20个必不可少的Python库也是基本的第三方库 读者您好.今天我将介绍20个属于我常用工具的Python库,我相信你看完之后也会觉得离不开它们.他们 ...
- Python 常用的标准库以及第三方库有哪些?
作者:史豹链接:https://www.zhihu.com/question/20501628/answer/223340838来源:知乎著作权归作者所有.商业转载请联系作者获得授权,非商业转载请注明 ...
- python测试开发工具库汇总(转载)
Web UI测试自动化 splinter - web UI测试工具,基于selnium封装. selenium - web UI自动化测试. mechanize- Python中有状态的程序化Web浏 ...
随机推荐
- jsp页面重定向后地址栏controller名重复而导致报404错误
今天做ssm项目时遇到了这种错误 看看代码: 无关代码省略... 22 <body> 23 <div id="container"> 24 <ifra ...
- Linux 下配置 hosts 并设置免密登录
Linux 下配置 hosts 并设置免密登录 作者:Grey 原文地址: 博客园:Linux 下配置 hosts 并设置免密登录 CSDN:Linux 下配置 hosts 并设置免密登录 说明 实现 ...
- Windows活动目录_票据——敬请期待!
票据:域控&域机子之间的信任密钥 [缺省40天更换一次] 域用户登录过程 域用户的账户密码(用信任密钥加密的)传递至域控: 域控验证账户密码成功后,构造域用户SID和组SID(用信任密钥加密的 ...
- 齐博x1头部底部菜单高亮设置
下面这段是默认模板头部的导航菜单: {php}$menu_choose=config('system_dirname')?config('system_dirname'):'index';{/php} ...
- 齐博x1非正常修改后台入口admin.php导致的问题
如果你不是从后台基础设置修改后台入口admin.php文件名的话,也即强行通过FTP修改admin.php文件的名的话,就会导致网站会运行异常 比如会出现不能上传文件之类的.如下图所示
- 【pytest官方文档】解读- 开发可pip安装的第三方插件
在上一篇的 hooks 函数分享中,开发了一个本地插件示例,其实已经算是在编写插件了.今天继续跟着官方文档学习更多知识点. 一个插件包含一个或多个钩子函数,pytest 正是通过调用各种钩子组成的插件 ...
- React实现一个简易版Swiper
背景 最近在公司内部进行一个引导配置系统的开发中,需要实现一个多图轮播的功能.到这时很多同学会说了,"那你直接用swiper不就好了吗?".但其实是,因为所有引导的展示都是作为np ...
- k8s挂在问题
今天在重启pod这个后发现一直处于 然后去describe发现报错如下 先去手动mount的发现挂在不了 然后去slave节点发现这个没有安装 然后手动去安装后重启pod问题解决
- Fidder 抓包工具
fiddler抓包原理 如上图本文一些 不重要 的鸡肋功能 自行百度 1. 安装与配置 1. 安装 安装地址https://www.telerik.com/download/fiddler可能有点慢 ...
- jmeter执行报错:java.lang.UnsupportedClassVersionError解决办法
做个记录. 问题记录: jmeter版本:5.4.1 本地Java版本:1.8.0_151 执行jmeter,报错: 2022-10-14 12:06:27,372 ERROR o.a.j.JMete ...