微信公众号 Crossin的编程教室 中有这么一篇文章【每周一坑】图像的指纹:数字水印 + 【解答】鸡兔同笼
学习python之余发现有这么一个数字水印的技术,觉得挺好玩的于是便实现出来。
文章中的思路已经说明的很是清楚了,主要思路是我们知道图像是由一个一个的像素点构成的,而每个像素点是由rgb三原色组成,也就是说每个点可以表示为(0-255,0-255,0-255)的一个tuple。由于人眼识别的能力有限,修改最后一位是无法用肉眼发现差异的,如果我们能够将这些数据的最后一位提取出来作为信息位存储信息,那么便能够向图片中嵌入信息啦。
1.我们使用到的python库:
from PIL import Image
import numpy as np
import functools
2.信息嵌入函数:
def embedding_info(picname, savename, text):
text += '#%#' #作为结束标记
im = np.array(Image.open(picname))
rows, columns, colors = im.shape
embed = []
for c in text:
bin_sign = (bin(ord(c))[2:]).zfill(16)
for i in range(16):
embed.append(int(bin_sign[i]))
count = 0
for row in range(rows):
for col in range(columns):
for color in range(colors):
if count < len(embed):
im[row][col][color] = im[row][col][color] // 2 * 2 + embed[count]
count += 1
Image.fromarray(im).save(savename)
首先我们将需要嵌入的信息添加一个结束标志'#%#',用以在提取信息的时候确定何时停止。之后是使用numpy库将图片转换成矩阵的形式,留待之后方便我们修改信息位。对需要嵌入的信息逐字符进行处理,由于我们需要支持中文信息的嵌入,我们这里使用16的bit位来存储信息,因此我们使用 '(bin(ord(c))[2:]).zfill(16)' 来将字符转换成二进制编码并在高位补0至16位。最后将处理后的比特流逐位替换掉原图像中的RGB值的最后一位,调用PIL库的函数对图片进行保存。
3.信息提取函数:
def extract_info(picname):
im = np.array(Image.open(picname))
rows, columns, colors = im.shape
text = ""
extract = np.array([], dtype = int)
count = 0
for row in range(rows):
for col in range(columns):
for color in range(colors):
extract = np.append(extract, im[row][col][color] % 2)
count += 1
if count % 16 == 0:
bcode = functools.reduce(lambda x, y: str(x) + str(y), extract)
cur_char = chr(int(bcode, 2))
text += cur_char
if cur_char == '#' and text[-3:] == '#%#':
return text[:-3]
extract = np.array([], dtype=int)
提取信息则与上面那个函数的思路相反,将各个信息位提取出来,按每16位转换成字符,并且检查是否到达结束标志。这里说明一下'bcode = functools.reduce(lambda x, y: str(x) + str(y), extract)' 这里使用到了 functools.reduce()函数,它的原型是functools.reduce(function, sequence[, initial]) -> value, 意思就是对sequence连续使用function, 如果不给出initial, 则第一次调用传递sequence的两个元素, 以后把前一次调用的结果和sequence的下一个元素传递给function. 如果给出initial, 则第一次传递initial和sequence的第一个元素给function. 我们使用匿名函数来将各个比特位拼接起来之后在转换成字符。
4.使用示例:
text = "Hello, world!"
embedding_info('Before.png', 'After.png', text)
extract_info('After.png')
在我们这个项目中,图片的格式最好为PNG格式,其他形如jpg,jpeg格式的图像文件在处理的过程中由于会对原图进行压缩,可能导致信息位被破坏而失去嵌入其中的信息,现在已经有基于机器学习方面的算法可以实现更好的抗干扰性能。
当然,数字水印是个很大的范畴,有很多实现方式,具有不同的实现难度、信息容量、抗攻击性、对原图的干扰等。我们在这里实现的是最简单的一种方法,其他更加高深的方法还是有待探索的。
代码文件以及在线版本:
Github仓库:Digital Watermarking
在线版本:Schnee_Lab_Text embed
文件说明:
1. build 文件夹下是使用cxfreeze打包好的可执行文件(Windows)
2. text_embed.py 是可执行文件的源代码,增加了用户交互
3. text_embed_origin.py 是原始代码,仅提供原始思路