直接对图片嵌入普通水印会破坏图片,且不美观。而本文介绍的盲水印可以将图片傅里叶变换后向图片的频域中写入水印信息,再逆变换回一张正常的图片。嵌入盲水印后的图片与原图看起来并不会有太大的差别,而且鲁棒性很强。
向图片嵌入盲水印#
blind_watermark 库#
这是一个基于 Python 的直接能用的盲水印库 ↗。
可以直接输入以下命令安装:
pip install blind-watermark
bash也可以克隆仓库到本地后安装。
使用方式非常简单,可以直接在命令行中使用,也可以在 Python 中导入这个库去使用。项目文档都有介绍的。但是在 Python 中使用的话要注意处理异常,在嵌入和提取水印时只要有一点问题,都会抛出异常。
我写了一个比较简单的脚本,用于批量向图片嵌入文字信息,也是很简单的复制粘贴后就可以跑起来:
from blind_watermark import WaterMark
import os
def embed_wm(in_dir, out_dir, passwd_img, passwd_wm, wm, d1, wm_mode='str'):
if in_dir[-1] != '\\' or in_dir[-1] != '/':
in_dir += '\\'
if out_dir[-1] != '\\' or out_dir[-1] != '/':
out_dir += '\\'
image_files = os.listdir(in_dir)
image_files = list(filter(lambda f: os.path.isfile(in_dir + f), image_files))
len_wm = 0
for file in image_files:
file_fullname = in_dir + file
bwm = WaterMark(password_img=passwd_img, password_wm=passwd_wm)
bwm.bwm_core.d1 = d1
bwm.read_img(file_fullname)
bwm.read_wm(wm, mode=wm_mode)
try:
bwm.embed(out_dir + file)
except AssertionError as ae:
print(f'{file} 嵌入水印出错: {ae}')
continue
len_wm = len(bwm.wm_bit)
print(f'{file} Put down the length of wm_bit {len_wm}')
print(f'提取水印需要的信息:')
print(f'图片密码: {passwd_img}')
print(f'水印密码: {passwd_wm}')
print(f'水印长度: {len_wm}')
with open(f'{out_dir}out_info.txt', 'w') as f:
f.write(f'图片密码: {passwd_img}\n')
f.write(f'水印密码: {passwd_wm}\n')
f.write(f'水印长度: {len_wm}\n')
print('水印嵌入完成,已将提取水印需要的信息写入输出文件夹')
def extract_wm(in_dir, passwd_img, passwd_wm, shape, wm_mode='str'):
if in_dir[-1] != '\\' or in_dir[-1] != '/':
in_dir += '/'
image_files = os.listdir(in_dir)
image_files = list(filter(lambda f: os.path.isfile(in_dir + f), image_files))
for file in image_files:
file_fullname = in_dir + file
bwm = WaterMark(password_img=passwd_img, password_wm=passwd_wm)
try:
wm_extract = bwm.extract(file_fullname, wm_shape=shape, mode=wm_mode)
print(f'{file}: {wm_extract}')
except:
print(f'{file} 读取水印失败')
if __name__ == '__main__':
mode = 0
while not (mode == 1 or mode == 2):
mode = int(input('模式 1(嵌入水印), 2(提取水印): '))
input_path = input('输入文件夹: ')
password_img = int(input('图片密码(必须为整数)(不重要): '))
password_wm = int(input('水印密码(必须为整数): '))
if mode == 1:
output_path = input('输出文件夹: ')
wm_text = input('水印文字: ')
d = int(input('水印强度(默认32): ') or 32)
embed_wm(input_path, output_path, password_img, password_wm, wm_text, d)
if mode == 2:
wm_shape = int(input('水印长度: '))
extract_wm(input_path, password_img, password_wm, wm_shape)
python隐写效果和测试#
这里有一张金门大桥的原图(457 KB):
运行脚本,输入以下信息:
- 图片密码: 1
- 水印密码: 123456
- 水印文字: hello ym
- d1(水印强度?): 32
嵌入水印后的图片(938 KB):
图片粗看还是根原图没什么区别的,但是细看可以发现处理后的图片显得更脏了,在大色块的区域特别明显。由此可见盲水印并不适合嵌入纯色较多的图片(截图之类)。
如果不想让图片变脏,可以将 d1 参数调低,经测试,将这张图片的 d1 参数调至 4,已经比较难观察到与原图的差别了,而且可以正确的解出水印信息。但是将 d1 调低后会降低图片的抗攻击能力,需要维持好观感与抗攻击能力的平衡。
攻击测试#
仅测试了遮挡:
实测还是可以提取出水印信息的。
如果是截图攻击等其它改变了图片大小的攻击,需要将图片还原到原来的样子。代码在这里:https://github.com/guofei9987/blind_watermark/blob/b5ad80c1f38f01b0ed70e42f584f5a597ae6797c/examples/example_str.py#L38 ↗
拓展:如何抹掉水印#
如果你怀疑一张图片被嵌入了数字盲水印,最简单的破坏水印方式是通过屏幕显示这张图片,然后拍摄这块屏幕,这样就得到了一张无盲水印的图片。(前提是你的相机不会会偷偷植入盲水印)
也有人想过将图片打印出来然后放进扫描仪扫描得到新的图片,但没有实践过,未证实是否可行。
拓展:关于隐写术的一些其它资料#
视频 隐写术鉴赏 ↗