Spaces:
Runtime error
Runtime error
#!/usr/bin/env python | |
# coding:UTF-8 | |
"""LSBSteg.py | |
Usage: | |
LSBSteg.py encode -i <input> -o <output> -f <file> | |
LSBSteg.py decode -i <input> -o <output> | |
Options: | |
-h, --help Show this help | |
--version Show the version | |
-f,--file=<file> File to hide | |
-i,--in=<input> Input image (carrier) | |
-o,--out=<output> Output image (or extracted file) | |
""" | |
import cv2 | |
import docopt | |
import numpy as np | |
channel_order = (2, 1, 0, 3) | |
class SteganographyException(Exception): | |
pass | |
class LSBSteg(): | |
def __init__(self, im): | |
self.image = im | |
self.height, self.width, self.nbchannels = im.shape | |
self.size = self.width * self.height | |
self.maskONEValues = [1,2,4,8,16,32,64,128] | |
#Mask used to put one ex:1->00000001, 2->00000010 .. associated with OR bitwise | |
self.maskONE = self.maskONEValues.pop(0) #Will be used to do bitwise operations | |
self.maskZEROValues = [254,253,251,247,239,223,191,127] | |
#Mak used to put zero ex:254->11111110, 253->11111101 .. associated with AND bitwise | |
self.maskZERO = self.maskZEROValues.pop(0) | |
self.curwidth = 0 # Current width position | |
self.curheight = 0 # Current height position | |
self.curchan = 0 # Current channel position | |
def put_binary_value(self, bits): #Put the bits in the image | |
for c in bits: | |
val = list(self.image[self.curheight,self.curwidth]) #Get the pixel value as a list | |
if int(c) == 1: | |
val[self.curchan] = int(val[self.curchan]) | self.maskONE #OR with maskONE | |
else: | |
val[self.curchan] = int(val[self.curchan]) & self.maskZERO #AND with maskZERO | |
self.image[self.curheight,self.curwidth] = tuple(val) | |
self.next_slot() #Move "cursor" to the next space | |
def next_slot(self):#Move to the next slot were information can be taken or put | |
if self.curchan == self.nbchannels-1: #Next Space is the following channel | |
self.curchan = 0 | |
if self.curwidth == self.width-1: #Or the first channel of the next pixel of the same line | |
self.curwidth = 0 | |
if self.curheight == self.height-1:#Or the first channel of the first pixel of the next line | |
self.curheight = 0 | |
if self.maskONE == 128: #Mask 1000000, so the last mask | |
raise SteganographyException("No available slot remaining (image filled)") | |
else: #Or instead of using the first bit start using the second and so on.. | |
self.maskONE = self.maskONEValues.pop(0) | |
self.maskZERO = self.maskZEROValues.pop(0) | |
else: | |
self.curheight +=1 | |
else: | |
self.curwidth +=1 | |
else: | |
self.curchan +=1 | |
def read_bit(self): #Read a single bit int the image | |
val = self.image[self.curheight,self.curwidth,channel_order[self.curchan]] | |
val = int(val) & self.maskONE | |
self.next_slot() | |
if val > 0: | |
return "1" | |
else: | |
return "0" | |
def read_byte(self): | |
return self.read_bits(8) | |
def read_bits(self, nb): #Read the given number of bits | |
bits = "" | |
for i in range(nb): | |
bits += self.read_bit() | |
return bits | |
def byteValue(self, val): | |
return self.binary_value(val, 8) | |
def binary_value(self, val, bitsize): #Return the binary value of an int as a byte | |
binval = bin(val)[2:] | |
if len(binval) > bitsize: | |
raise SteganographyException("binary value larger than the expected size") | |
while len(binval) < bitsize: | |
binval = "0"+binval | |
return binval | |
def encode_text(self, txt): | |
l = len(txt) | |
binl = self.binary_value(l, 16) #Length coded on 2 bytes so the text size can be up to 65536 bytes long | |
self.put_binary_value(binl) #Put text length coded on 4 bytes | |
for char in txt: #And put all the chars | |
c = ord(char) | |
self.put_binary_value(self.byteValue(c)) | |
return self.image | |
def decode_text(self): | |
ls = self.read_bits(16) #Read the text size in bytes | |
l = int(ls,2) | |
i = 0 | |
unhideTxt = "" | |
while i < l: #Read all bytes of the text | |
tmp = self.read_byte() #So one byte | |
i += 1 | |
unhideTxt += chr(int(tmp,2)) #Every chars concatenated to str | |
return unhideTxt | |
def encode_image(self, imtohide): | |
w = imtohide.width | |
h = imtohide.height | |
if self.width*self.height*self.nbchannels < w*h*imtohide.channels: | |
raise SteganographyException("Carrier image not big enough to hold all the datas to steganography") | |
binw = self.binary_value(w, 16) #Width coded on to byte so width up to 65536 | |
binh = self.binary_value(h, 16) | |
self.put_binary_value(binw) #Put width | |
self.put_binary_value(binh) #Put height | |
for h in range(imtohide.height): #Iterate the hole image to put every pixel values | |
for w in range(imtohide.width): | |
for chan in range(imtohide.channels): | |
val = imtohide[h,w][chan] | |
self.put_binary_value(self.byteValue(int(val))) | |
return self.image | |
def decode_image(self): | |
width = int(self.read_bits(16),2) #Read 16bits and convert it in int | |
height = int(self.read_bits(16),2) | |
unhideimg = np.zeros((width,height, 3), np.uint8) #Create an image in which we will put all the pixels read | |
for h in range(height): | |
for w in range(width): | |
for chan in range(unhideimg.channels): | |
val = list(unhideimg[h,w]) | |
val[chan] = int(self.read_byte(),2) #Read the value | |
unhideimg[h,w] = tuple(val) | |
return unhideimg | |
def encode_binary(self, data): | |
l = len(data) | |
if self.width*self.height*self.nbchannels < l+64: | |
raise SteganographyException("Carrier image not big enough to hold all the datas to steganography") | |
self.put_binary_value(self.binary_value(l, 64)) | |
for byte in data: | |
byte = byte if isinstance(byte, int) else ord(byte) # Compat py2/py3 | |
self.put_binary_value(self.byteValue(byte)) | |
return self.image | |
def decode_binary(self): | |
l = int(self.read_bits(64), 2) | |
output = b"" | |
for i in range(l): | |
output += bytearray([int(self.read_byte(),2)]) | |
return output | |
def main(): | |
args = docopt.docopt(__doc__, version="0.2") | |
in_f = args["--in"] | |
out_f = args["--out"] | |
in_img = cv2.imread(in_f, cv2.IMREAD_UNCHANGED) | |
steg = LSBSteg(in_img) | |
lossy_formats = ["jpeg", "jpg"] | |
if args['encode']: | |
#Handling lossy format | |
out_f, out_ext = out_f.split(".") | |
if out_ext in lossy_formats: | |
out_f = out_f + ".png" | |
print("Output file changed to ", out_f) | |
data = open(args["--file"], "rb").read() | |
res = steg.encode_binary(data) | |
cv2.imwrite(out_f, res) | |
elif args["decode"]: | |
raw = steg.decode_binary() | |
with open(out_f, "wb") as f: | |
f.write(raw) | |
if __name__=="__main__": | |
main() | |