python开发图片_python实现简单的图片隐写术

本文转载自Python3图片隐写术

载体文件相对隐秘文件的大小越大,隐藏后者就越加容易。因为这个原因,数字图像在因特网和其他传媒上被广泛用于隐藏消息。例如一个24位的位图中的每个像素的三个颜色分量(红,绿和蓝)各使用8个比特来表示,如果我们只考虑蓝色的话,就有2^8种不同的数值来表示深浅不同的蓝色。而像11111111和11111110这两个值所表示的蓝色,人眼几乎无法区分。因此,这个最低有效位就可以用来存储颜色之外的信息,而且在某种程度上几乎是检测不到的。如果对红色和绿色进行同样的操作,就可以在差不多三个像素中存储一个字节的信息。本实验便是利用RGBA(RGB+Alpha透明度)的最低有效位(Least Significant Bit)来隐藏文字。

1.开发环境

Python3+Pillow库

2.实验过程

开始编写我们的代码。

def encodeDataInImage(image, data):

evenImage = makeImageEven(image)

binary = ''.join(map(constLenBin,bytearray(data, 'utf-8')))

if len(binary) > len(image.getdata()) * 4:

raise Exception("Error: Can't encode more than " + len(evenImage.getdata()) * 4 + " bits in this image. ")

encodedPixels = [(r+int(binary[index*4+0]),g+int(binary[index*4+1]),b+int(binary[index*4+2]),t+int(binary[index*4+3])) if index*4 < len(binary) else (r,g,b,t) for index,(r,g,b,t) in enumerate(list(evenImage.getdata()))]

encodedImage = Image.new(evenImage.mode, evenImage.size)

encodedImage.putdata(encodedPixels)

return encodedImage

将隐藏信息编码到图片中的encodeDataInImage函数有两个参数,分别是用作载体的图片对象和需要被隐藏的字符串。首先调用makeImageEven函数获得最低有效位为0的图片副本,然后调用constLenBin函数将需要被隐藏的字符串转换成二进制字符串。如果不能编码全部数据,则抛出异常。接下来将binary中的二进制字符串信息编码进像素里,创建新图片以存放编码后的像素,最后在新图片中添加编码后的数据。

bytearray函数将字符串转换为整数值序列。

499_5de_7ca.jpg

makeImageEven函数通过一个移位操作把所有值改成偶数。

def makeImageEven(image):

pixels = list(image.getdata())

evenPixels = [(r>>1<<1,g>>1<<1,b>>1<<1,t>>1<<1) for [r,g,b,t] in pixels]

evenImage = Image.new(image.mode, image.size)

evenImage.putdata(evenPixels)

return evenImage

map(constLenBin,bytearray(data,'utf-8'))对数值序列中的每一个值应用constLenBin函数,去掉bin返回的二进制字符串中的0b,并在左边补足0直到字符串长度为8,从而将十进制数值序列转换为二进制字符串序列。

def constLenBin(int):

binary = "0"*(8-(len(bin(int))-2))+bin(int).replace('0b','')

return binary

decodeImage函数返回图片解码后的隐藏文字,其接受一个图片对象参数。找到数据截止处所用的字符串0000000000000000很有意思,它的长度为16,而不是直觉上的8,因为两个包含数据的字节的接触部分可能有8个0。

def decodeImage(image):

pixels = list(image.getdata())

binary = ''.join([str(int(r>>1<<1!=r))+str(int(g>>1<<1!=g))+str(int(b>>1<<1!=b))+str(int(t>>1<<1!=t)) for (r,g,b,t) in pixels])

locationDoubleNull = binary.find('0000000000000000')

endIndex = locationDoubleNull+(8-(locationDoubleNull % 8)) if locationDoubleNull%8 != 0 else locationDoubleNull

data = binaryToString(binary[0:endIndex])

return data

binaryToString函数将提取出来的二进制字符串转换为隐藏的文本。

def binaryToString(binary):

index = 0

string = []

rec = lambda x, i: x[2:8] + (rec(x[8:], i-1) if i>1 else '') if x else ''

fun = lambda x, i: x[i+1:8] + rec(x[8:], i-1)

while index + 1 < len(binary):

chartype = binary[index:].index('0')

length = chartype*8 if chartype else 8

string.append(chr(int(fun(binary[index:index+length],chartype),2)))

index += length

return ''.join(string)

要看明白这个,必须要先搞懂UTF-8编码的方式。UTF-8是unicode的一种变长度的编码表达方式,也就是说一个字符串中,不同的字符所占的字节数不一定相同。如果我们要支持中文的话,这就给我们的工作带来了一点复杂度。

500_2ad_943.jpg

在上图中,只有x所在的位置(也即是字节中第一个0之后的数据)存储的是真正的字符数据,因此我们使用下面两个匿名函数来提取出这些数据。

rec = lambda x, i: x[2:8] + (rec(x[8:], i-1) if i>1 else '') if x else ''

fun = lambda x, i: x[i+1:8] + rec(x[8:], i-1)

fun接收两个参数,第一个参数为一个字符的二进制字符串,这个二进制字符串可能有不同的长度(8、16、24……48);第二个参数为这个字符占多少个字节。lambda x, i: x[i+1:8] + rec(x[8:], i-1)中x[i+1:8]获得第一个字节的数据,然后调用rec,以递归的方式提取后面字节中的数据。字符的字节数据中,第一个字节开头1的数目便是字符所占的字节数。

string.append(chr(int(fun(binary[index:index+length],chartype),2)))这一行中用到的函数int和chr的作用如下。

chr:接收一个参数,参数为int值,返回值这个int值的字符。

int:接收两个参数,第一个参数为数字字符串,第二个参数为这个数字字符串代表的数字的进制。

完整的代码如下。

from PIL import Image

def makeImageEven(image):

pixels = list(image.getdata())

evenPixels = [(r>>1<<1,g>>1<<1,b>>1<<1,t>>1<<1) for [r,g,b,t] in pixels]

evenImage = Image.new(image.mode, image.size)

evenImage.putdata(evenPixels)

return evenImage

def constLenBin(int):

binary = "0"*(8-(len(bin(int))-2))+bin(int).replace('0b','')

return binary

def encodeDataInImage(image, data):

evenImage = makeImageEven(image)

binary = ''.join(map(constLenBin,bytearray(data,'utf-8')))

if len(binary) > len(image.getdata()) * 4:

raise Exception("Error: Can't encode more than " + len(evenImage.getdata()) * 4 + " bits in this image. ")

encodedPixels = [(r+int(binary[index*4+0]),g+int(binary[index*4+1]),b+int(binary[index*4+2]),t+int(binary[index*4+3])) if index*4 < len(binary) else (r,g,b,t) for index,(r,g,b,t) in enumerate(list(evenImage.getdata()))]

encodedImage = Image.new(evenImage.mode, evenImage.size)

encodedImage.putdata(encodedPixels)

return encodedImage

def binaryToString(binary):

index = 0

string = []

rec = lambda x, i: x[2:8] + (rec(x[8:], i-1) if i>1 else '') if x else ''

fun = lambda x, i: x[i+1:8] + rec(x[8:], i-1)

while index + 1 < len(binary):

chartype = binary[index:].index('0')

length = chartype*8 if chartype else 8

string.append(chr(int(fun(binary[index:index+length],chartype),2)))

index += length

return ''.join(string)

def decodeImage(image):

pixels = list(image.getdata())

binary = ''.join([str(int(r>>1<<1!=r))+str(int(g>>1<<1!=g))+str(int(b>>1<<1!=b))+str(int(t>>1<<1!=t)) for (r,g,b,t) in pixels])

locationDoubleNull = binary.find('0000000000000000')

endIndex = locationDoubleNull+(8-(locationDoubleNull % 8)) if locationDoubleNull%8 != 0 else locationDoubleNull

data = binaryToString(binary[0:endIndex])

return data

encodeDataInImage(Image.open("coffee.png"), 'hello word你好!').save('encodeImage.png')

print(decodeImage(Image.open("encodeImage.png")))

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值