参考:
理论知识
在学习这篇博客的内容之前,你需要了解如下内容
1)python图像处理 PIL的使用
2)图片的属性
在灰度图像中,每个像素通常为8位,在RGB图像中,每个通道占8位,共有24位。每位的取值为0或者1,这就是为什么每个通道的最大值为255,每个像素位对图像的贡献值是不同的,这样,把整个图像分为8个位平面,从LSB(最低有效位0)到MSB(最高有效位1)。因为低位(根据一个公式)表达的信息少,所以将水印嵌入在LSB上,这样对不会对图像的质量产生较大的影响。
3)LSB隐写(最低有效位的隐写),是指通过改变图片中像素的最低位来实现信息的隐藏的。这种隐写方式需要图片是无压缩的位图,因此一般用于bmp和png图片。
LSB的优缺点
缺点: 改变最低有效位数据,数字图像进行数学变换等攻击方式使得嵌有数字水印的图片很容易受到攻击,缺乏鲁棒性
优点: 实现简单,隐藏量大。
LSB算法操作步骤
LSB对数字水印的操作主要包括水印嵌入过程和水印提取过程
1)水印嵌入过程
2)水印提取过程
水印嵌入
首先取两张png图片
主图(要嵌入水印的图片)如下
水印图片
然后将水印图片嵌入到主图中去
我们先来看一下每张图片的mode
from PIL import Image
im=Image.open("F:/girl.png")
watermark=Image.open("F:/watermark.png")
print (im)
print (watermark)
我们从打印结果可以看出水印图片的mode是RGBA,将水印图片的mode转换为RGB
watermark=watermark.convert("RGB")
print(watermark)
看到水印图片已经转换为RGB格式了
获取水印图片的每个像素的RGB值
代码
#获取水印图片的每一个像素值,i:指定要检查的像素点的逻辑X轴坐标。j:指定要检查的像素点的逻辑Y轴坐标。
for i in range(watermark.size[0]):
for j in range(watermark.size[1]):
#获取每个像素的RGB值
rgb=watermark.getpixel((i,j))
print(rgb)
因为获取的结果太长就不输出了,截图输出数据的一部分如下
可以获取的RGB值是用三元组来进行保存的
将RGB形式转换为二进制的形式,这样通道的值就转换为8位2进制,RGB有三个通道就转换为二进制就是24位
def plus(str):
return str.zfill(8)
#获取水印图片的每一个像素值,i:指定要检查的像素点的逻辑X轴坐标。j:指定要检查的像素点的逻辑Y轴坐标。
for i in range(watermark.size[0]):
for j in range(watermark.size[1]):
#获取每个像素的RGB值
rgb=watermark.getpixel((i,j))
str=plus(bin(rgb[0]).replace('0b',''))
str=str + plus(bin(rgb[1]).replace('0b',''))
str=str + plus(bin(rgb[2]).replace('0b',''))
print (str)
打印出每一个像素点的RGB值的二进制
其中bin是python的进制转换函数,能将十进制转换为二进制,唯一不足的就是会自动在转换的数字前面加0b,因此以空字符串替换掉0b。此外,十进制转换二进制的时候,如果转换的二进制是以0开头的,那么会自动省略前方的0,这不符合我们的要求。因此以255为标准,二进制为1111 1111,其余的不足8位的在最前方补0,如1就是0000 0001。plus函数就是补足8位。
用水印图片的每一位替换主图中的每个像素点的R、G、B的二进制形式的末尾数字,但是主图的变化值最多为1,主观上是察觉不到的
水印提取
解密过程
1)将水印图片的像素点RGB值还原
2)用水印的宽和高来进行还原。计算公式是宽*高 38
这里我水印图片的宽为111,高为34,根据公式计算相乘得到length=90576
然后从主图像中提取水印的值
def deEncry(img,length):
width=im.size[0]
height=im.size[1]
#计数器
count= 0
wt=""
for i in range(width):
for j in range(height):
# 获取像素点的值
rgb = img.getpixel((i, j))
# 提取R通道的附加值
if count % 3 == 0:
count += 1
wt = wt + str(rgb[0] % 2)
if count == length:
break
# 提取G通道的附加值
if count % 3 == 1:
count += 1
wt = wt + str(rgb[1] % 2)
if count == length:
break
# 提取B通道的附加值
if count % 3 == 2:
count += 1
wt = wt + str(rgb[2] % 2)
if count == length:
break
if count == length:
break
return wt
然后将wt的值还原为RGB的值,并显示图片
def showImage(wt):
# str2初始化为list
str2 = []
#将wt的值转变为RGB值
for i in range(0, len(wt), 8):
# 以每8位为一组二进制,转换为十进制
str2.append(int(wt[i:i + 8], 2))
#绘制和显示水印图片
img_out = Image.new("RGB", (113, 36))
flag = 0
for m in range(0, 111):
for n in range(0, 34):
img_out.putpixel((m, n), (str2[flag], str2[flag + 1], str2[flag + 2]))
flag += 3
img_out.show()
还原到的水印图片是
上图是python代码运行得到的,为了便于观看,Image.new()的时候宽和高增加了两个像素
代码
from PIL import Image
# print(watermark)
def plus(str):
return str.zfill(8)
# 获取水印图片的每一个像素值,i:指定要检查的像素点的逻辑X轴坐标。j:指定要检查的像素点的逻辑Y轴坐标。
def getcode(watermark):
str1 = ""
for i in range(watermark.size[0]):
for j in range(watermark.size[1]):
# 获取每个像素的RGB值
rgb = watermark.getpixel((i, j))
str1 = str1 + plus(bin(rgb[0]).replace('0b', ''))
str1 = str1 + plus(bin(rgb[1]).replace('0b', ''))
str1 = str1 + plus(bin(rgb[2]).replace('0b', ''))
# print (str)
return str1
# 加密
def encry(img, code):
# 计数器
count = 0
codelen = len(code)
print (codelen)
for i in range(img.size[0]):
for j in range(img.size[1]):
# 获取每个像素的RGB值
data = img.getpixel((i, j))
if count == codelen:
break
r = data[0]
g = data[1]
b = data[2]
# print (r)
# print(codelen)#24
r = (r - r % 2) + int(code[count])
count += 1
if count == codelen:
img.putpixel((i, j), (r, g, b))
break
g = (g - g % 2) + int(code[count])
count += 1
if count == codelen:
img.putpixel((i, j), (r, g, b))
break
b = (b - b % 2) + int(code[count])
count += 1
if count == codelen:
img.putpixel((i, j), (r, g, b))
break
# 每3次循环表示一组RGB值被替换完毕,可以进行写入
if count % 3 == 0:
img.putpixel((i, j), (r, g, b))
img.save('F:/watermark_im.png')
im = Image.open("F:/girl.png")
watermark = Image.open("F:/watermark.png")
watermark = watermark.convert("RGB")
code = getcode(watermark)
encry(im, code)
def deEncry(img, length):
width = im.size[0]
height = im.size[1]
# 计数器
count = 0
wt = ""
for i in range(width):
for j in range(height):
# 获取像素点的值
rgb = img.getpixel((i, j))
# 提取R通道的附加值
if count % 3 == 0:
count += 1
wt = wt + str(rgb[0] % 2)
if count == length:
break
# 提取G通道的附加值
if count % 3 == 1:
count += 1
wt = wt + str(rgb[1] % 2)
if count == length:
break
# 提取B通道的附加值
if count % 3 == 2:
count += 1
wt = wt + str(rgb[2] % 2)
if count == length:
break
if count == length:
break
return wt
def showImage(wt):
# str2初始化为list
str2 = []
# 将wt的值转变为RGB值
for i in range(0, len(wt), 8):
# 以每8位为一组二进制,转换为十进制
str2.append(int(wt[i:i + 8], 2))
# 绘制和显示水印图片
img_out = Image.new("RGB", (113, 36))
flag = 0
for m in range(0, 111):
for n in range(0, 34):
img_out.putpixel((m, n), (str2[flag], str2[flag + 1], str2[flag + 2]))
flag += 3
img_out.show()
img1 = Image.open("F:/watermark_im.png")
rgb_img1 = img1.convert('RGB')
length = 90576
wt = deEncry(rgb_img1, length)
showImage(wt)