I am trying to perform DCT Steganography by converting the image to HSV and applying DCT and quantizing it and hiding the text message and stiching the image. In decoding process, applying DCT and quantizing it and extracting the text message from it. But, here I am getting incorrect answer in it. I am using HSV for getting the same image color similar to original image. I used saturation channel to hide the text in it. Now, I am stuck in it and not getting the correct answer. Please help me out in this.
Here is the code:
# -*- coding: utf-8 -*-
"""
Created on Tue Oct 19 11:13:50 2021
#author: SM
"""
from PIL import Image
import numpy as np
import itertools
import types
import cv2
from Crypto.Cipher import AES
#creation of quantization matrix of quality factor as 50
quant = np.array([[16,11,10,16,24,40,51,61],
[12,12,14,19,26,58,60,55],
[14,13,16,24,40,57,69,56],
[14,17,22,29,51,87,80,62],
[18,22,37,56,68,109,103,77],
[24,35,55,64,81,104,113,92],
[49,64,78,87,103,121,120,101],
[72,92,95,98,112,100,103,99]])
class DiscreteCosineTransform:
#created the constructor
def __init__(self):
self.message = None
self.bitMessage = None
self.oriCol = 0
self.oriRow = 0
self.numBits = 0
#utility and helper function for DCT Based Steganography
#helper function to stich the image back together
def chunks(self,l,n):
m = int(n)
for i in range(0,len(l),m):
yield l[i:i+m]
#function to add padding to make the function dividable by 8x8 blocks
def addPadd(self,img,row,col):
img = cv2.resize(img,(col+(8-col%8),row+(8-row%8)))
return img
#function to transform the message that is wanted to be hidden from plaintext to a list of bits
def toBits(self):
bits = []
for char in self.message:
binval = bin(char)[2:].rjust(8,'0')
#print('bin '+binval)
bits.append(binval)
self.numBits = bin(len(bits))[2:].rjust(8,'0')
return bits
#main part
#encoding function
#applying dct for encoding
def DCTEncoder(self,img,secret):
self.message = str(len(secret)).encode()+b'*'+secret
self.bitMessage = self.toBits()
#get the size of the image in pixels
row, col = img.shape[:2]
self.oriRow = row
self.oriCol = col
if((col/8)*(row/8)<len(secret)):
print("Error: Message too large to encode in image")
return False
if(row%8!=0 or col%8!=0):
img = self.addPadd(img,row,col)
row,col = img.shape[:2]
#split image into RGB channels
hImg,sImg,vImg = cv2.split(img)
#message to be hid in blue channel so converted to type float32 for dct function
#print(bImg.shape)
sImg = np.float32(sImg)
#breaking the image into 8x8 blocks
imgBlocks = [np.round(sImg[j:j+8,i:i+8]-128) for (j,i) in itertools.product(range(0,row,8),range(0,col,8))]
#print(imgBlocks[0])
#blocks are run through dct / apply dct to it
dctBlocks = [np.round(cv2.dct(ib)) for ib in imgBlocks]
#print('DCT Blocks')
#print(dctBlocks[0])
#blocks are run through quantization table / obtaining quantized dct coefficients
quantDCT = [np.round(dbk/quant) for dbk in dctBlocks]
#print('Quant Blocks')
#print(quantDCT[0])
#set LSB in DC value corresponding bit of message
messIndex=0
letterIndex=0
print(self.bitMessage)
for qb in quantDCT:
#find LSB in DCT cofficient and replace it with message bit
#print(len(qb))
DC = qb[0][0]
#print(DC.shape)
DC = np.uint8(DC)
#print(DC)
DC = np.unpackbits(DC)
#print(DC[0])
#print(self.bitMessage[messIndex][letterIndex])
#print(DC[7])
#print(type(DC[7]))
#print(DC[7].shape)
#print(type(self.bitMessage))
#a=self.bitMessage[messIndex][letterIndex]
#print(a)
DC[7] = self.bitMessage[messIndex][letterIndex]
DC = np.packbits(DC)
DC = np.float32(DC)
DC = DC - 255
qb[0][0] = DC
letterIndex = letterIndex + 1
if (letterIndex == 8):
letterIndex = 0
messIndex = messIndex + 1
if (messIndex == len(self.message)):
break
#writing the stereo image
#blocks run inversely through quantization table
sImgBlocks = [quantizedBlock *quant+128 for quantizedBlock in quantDCT]
#blocks run through inverse DCT
#sImgBlocks = [cv2.idct(B)+128 for B in quantizedDCT]
#puts the new image back together
aImg=[]
for chunkRowBlocks in self.chunks(sImgBlocks, col/8):
for rowBlockNum in range(8):
for block in chunkRowBlocks:
aImg.extend(block[rowBlockNum])
print(len(aImg))
aImg = np.array(aImg).reshape(row, col)
#converted from type float32
aImg = np.uint8(aImg)
#show(sImg)
aImg = cv2.merge((hImg,aImg,vImg))
return aImg
#decoding
#apply dct for decoding
def DCTDecoder(self,img):
row, col = img.shape[:2]
messSize = None
messageBits = []
buff = 0
#split the image into RGB channels
hImg,sImg,vImg = cv2.split(img)
#message hid in blue channel so converted to type float32 for dct function
sImg = np.float32(sImg)
#break into 8x8 blocks
imgBlocks = [sImg[j:j+8,i:i+8]-128 for (j,i) in itertools.product(range(0,row,8),range(0,col,8))]
#dctBlocks = [np.round(cv2.dct(ib)) for ib in imgBlocks]
# the blocks are run through quantization table
quantDCT = [ib/quant for ib in imgBlocks]
i=0
flag = 0
nb = ''
#message is extracted from LSB of DCT coefficients
for qb in quantDCT:
DC = qb[0][0]
DC = np.uint8(DC)
#unpacking of bits of DCT
DC = np.unpackbits(DC)
#print('DC',DC,end=' ')
if (flag == 0):
if (DC[7] == 1):
buff+=(0 & 1) << (7-i)
elif (DC[7] == 0):
buff+=(1&1) << (7-i)
else:
if (DC[7] == 1):
nb+='0'
elif (DC[7] == 0):
nb+='1'
i=1+i
#print(i)
if (i == 8):
#print(buff,end=' ')
if (flag == 0):
messageBits.append(buff)
#print(buff,end=' ')
buff = 0
else:
messageBits.append(nb)
#print(nb,end=' ')
nb = ''
i =0
if (messageBits[-1] == 42 and messSize is None):
try:
flag = 1
messSize = int(str(chr(messageBits[0]))+str(chr(messageBits[1])))#int(''.join(messageBits[:-1]))
print(messSize,'a')
except:
print('b')
pass
if (len(messageBits) - len(str(messSize)) - 1 == messSize):
#print(''.join(messageBits)[len(str(messSize))+1:])
return messageBits
pass
print(messageBits)
return ''
def msg_encrypt(msg,cipher):
if (len(msg)%16 != 0):
#a = len(msg)%16 != 0
#print(a)
msg = msg + ' '*(16 - len(msg)%16)
#nonce = cipher.nonce
t1 = msg.encode()
enc_msg = cipher.encrypt(t1)
return enc_msg
def msg_decrypt(ctext,cipher):
dec_msg = cipher.decrypt(ctext)
msg1 = dec_msg.decode()
return msg1
image = cv2.imread('C://Users//hp//Desktop//Lenna.jpg',cv2.IMREAD_UNCHANGED)
image = cv2.cvtColor(image,cv2.COLOR_BGR2HSV_FULL)
#image = cv2.cvtColor(image,cv2.COLOR_RGB2HSV)
secret_msg = 'Shaina'
print(secret_msg)
key = b'Sixteen byte key'
#encryption of message
cipher = AES.new(key,AES.MODE_ECB)
enc_msg = msg_encrypt(secret_msg,cipher)
print(enc_msg)
d = DiscreteCosineTransform()
dct_img_encoded = d.DCTEncoder(image, enc_msg)
dct_img_encoded = cv2.cvtColor(dct_img_encoded,cv2.COLOR_HSV2BGR_FULL)
#dct_img_encoded = cv2.cvtColor(dct_img_encoded,cv2.COLOR_BGR2RGB)
cv2.imwrite('C://Users//hp//Desktop//DCT1.png',dct_img_encoded)
eimg = cv2.imread('C://Users//hp//Desktop//DCT1.png',cv2.IMREAD_UNCHANGED)
eimg = cv2.cvtColor(eimg,cv2.COLOR_BGR2HSV_FULL)
#eimg = cv2.cvtColor(eimg,cv2.COLOR_RGB2HSV)
text = d.DCTDecoder(eimg)
ntext = []
print(text)
for i in range(len(text)):
if(type(text[i]) == str):
ntext.append(text[i])
print(ntext)
#print(type(text))
#print(next)
#binary_data = ''.join([ format(ord(i), "08b") for i in next ])
#all_bytes = [ binary_data[i: i+8] for i in range(0,len(binary_data),8)]
decoded_data = b''
for byte in next:
try:
decoded_data += int (byte,2).to_bytes (len(byte) // 8, byteorder='big')
except Exception as e:
print(byte)
break
print(decoded_data)
#decryption of message
dtext = msg_decrypt(decoded_data,cipher)
print(dtext)
The result I am getting it as:
Please help me out with this.
OK, I've simplified a lot of things and made some changes, and this seems to work with my sample images.
The biggest overall problem you seem to be facing is that the RGB/HSV conversion screws up your least significant bits, thereby losing the embedded message. I'm not convinced manipulating the saturation is the right method. What I've done here is left it in RGB, and I'm manipulating the green band. I'm not doing the quantizing, because I don't think that's a correct method, but I am embedding the message in the bottom 5 bits of the 0th DCT element. That way, I can do some rounding during the decode to allow for a few bits of slop.
Maybe this can help you move forward.
from PIL import Image
import numpy as np
import itertools
import cv2
class DiscreteCosineTransform:
#created the constructor
def __init__(self):
self.message = None
self.numBits = 0
#utility and helper function for DCT Based Steganography
#helper function to stich the image back together
def chunks(self,l,n):
m = int(n)
for i in range(0,len(l),m):
yield l[i:i+m]
#function to add padding to make the function dividable by 8x8 blocks
def addPadd(self,img,row,col):
img = cv2.resize(img,(col+(8-col%8),row+(8-row%8)))
return img
#main part
#encoding function
#applying dct for encoding
def DCTEncoder(self,img,secret):
self.message = str(len(secret)).encode()+b'*'+secret
#get the size of the image in pixels
row, col = img.shape[:2]
if((col/8)*(row/8)<len(secret)):
print("Error: Message too large to encode in image")
return False
if row%8 or col%8:
img = self.addPadd(img,row,col)
row,col = img.shape[:2]
#split image into RGB channels
hImg,sImg,vImg = cv2.split(img)
#message to be hid in saturation channel so converted to type float32 for dct function
#print(bImg.shape)
sImg = np.float32(sImg)
#breaking the image into 8x8 blocks
imgBlocks = [np.round(sImg[j:j+8,i:i+8]-128) for (j,i) in itertools.product(range(0,row,8),range(0,col,8))]
#print('imgBlocks',imgBlocks[0])
#blocks are run through dct / apply dct to it
dctBlocks = [np.round(cv2.dct(ib)) for ib in imgBlocks]
print('imgBlocks', imgBlocks[0])
print('dctBlocks', dctBlocks[0])
#blocks are run through quantization table / obtaining quantized dct coefficients
quantDCT = dctBlocks
print('quantDCT', quantDCT[0])
#set LSB in DC value corresponding bit of message
messIndex=0
letterIndex=0
print(self.message)
for qb in quantDCT:
#find LSB in DCT cofficient and replace it with message bit
bit = (self.message[messIndex] >> (7-letterIndex)) & 1
DC = qb[0][0]
DC = (int(DC) & ~31) | (bit * 15)
qb[0][0] = np.float32(DC)
letterIndex += 1
if letterIndex == 8:
letterIndex = 0
messIndex += 1
if messIndex == len(self.message):
break
#writing the stereo image
#blocks run inversely through quantization table
#blocks run through inverse DCT
sImgBlocks = [cv2.idct(B)+128 for B in quantDCT]
#puts the new image back together
aImg=[]
for chunkRowBlocks in self.chunks(sImgBlocks, col/8):
for rowBlockNum in range(8):
for block in chunkRowBlocks:
aImg.extend(block[rowBlockNum])
aImg = np.array(aImg).reshape(row, col)
#converted from type float32
aImg = np.uint8(aImg)
#show(sImg)
return cv2.merge((hImg,aImg,vImg))
#decoding
#apply dct for decoding
def DCTDecoder(self,img):
row, col = img.shape[:2]
messSize = None
messageBits = []
buff = 0
#split the image into RGB channels
hImg,sImg,vImg = cv2.split(img)
#message hid in saturation channel so converted to type float32 for dct function
sImg = np.float32(sImg)
#break into 8x8 blocks
imgBlocks = [sImg[j:j+8,i:i+8]-128 for (j,i) in itertools.product(range(0,row,8),range(0,col,8))]
dctBlocks = [np.round(cv2.dct(ib)) for ib in imgBlocks]
# the blocks are run through quantization table
print('imgBlocks',imgBlocks[0])
print('dctBlocks',dctBlocks[0])
quantDCT = dctBlocks
i=0
flag = 0
#message is extracted from LSB of DCT coefficients
for qb in quantDCT:
if qb[0][0] > 0:
DC = int((qb[0][0]+7)/16) & 1
else:
DC = int((qb[0][0]-7)/16) & 1
#unpacking of bits of DCT
buff += DC << (7-i)
i += 1
#print(i)
if i == 8:
messageBits.append(buff)
#print(buff,end=' ')
buff = 0
i =0
if messageBits[-1] == 42 and not messSize:
try:
messSize = int(chr(messageBits[0])+chr(messageBits[1]))
print(messSize,'a')
except:
print('b')
if len(messageBits) - len(str(messSize)) - 1 == messSize:
return messageBits
print("msgbits", messageBits)
return None
image = cv2.imread('20210827_092821.jpg',cv2.IMREAD_UNCHANGED)
enc_msg = b'Shaina Sixteen byte key'
#print(enc_msg)
d = DiscreteCosineTransform()
dct_img_encoded = d.DCTEncoder(image, enc_msg)
cv2.imwrite('2021_encoded.png',dct_img_encoded)
eimg = cv2.imread('2021_encoded.png',cv2.IMREAD_UNCHANGED)
text = d.DCTDecoder(eimg)
print(text)
decoded = bytes(text[3:])
print(decoded)
Related
I want to calculate variance, gabor and entropy filters to some images, but the images have blank areas that I donĀ“t want to apply the filters. I try to use a np.ma.array option but return this error: "'MaskedArray' object is not callable"
this is the code:
def bandas_img (image, array1, array2):
imagenRGB = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)
return cv2.inRange(imagenRGB, array1, array2)
def rescale_by_width(image, target_width, method=cv2.INTER_LANCZOS4):
h = int(round(target_width * image.shape[0] / image.shape[1]))
return cv2.resize(image, (target_width, h), interpolation=method)
#Resized image by width
target_width = 400
#To mask null values
mask_image = True
hue = 20
sat = 57
value = 116
toleranciaH = 150
toleranciaS = 150
toleranciaV = 150
lower = np.array ([hue - toleranciaH, sat - toleranciaS, value - toleranciaV])
upper = np.array ([hue + toleranciaH, sat + toleranciaS, value + toleranciaV])
#working directory where the csv files are
os.chdir("C:/Users/Mariano/Documents/3 - Visual studio code/Prueba filtrar mascara/filtrada") ##ojoooo las barras van /// y no D:/OMAN/BHI TEXTURES/U-2
file_extension = '.png' #Check Extension
all_filenames = [i for i in glob.glob(f"*{file_extension}")]
for f in all_filenames:
image = cv2.imread(f,1)
#resized Image
resized1 = rescale_by_width(image, target_width)
#Set f value (image name)
f = f.replace(".png", "")
#Save Image
plt.imsave(f+"_resized.png", resized1)
#Create mask for null values
if mask_image == True:
mask = bandas_img(resized1, lower, upper)
cv2.imwrite(f+"_mask.png", mask)
resized2 = io.imread(f+"_resized.png", as_gray=True)
resized3 = resized2.copy()
#First Try
resized3[mask == 0] = np.nan
resized3[mask != 0] = resized2[mask != 0]
#Second Try
mask1 = (resized3 == np.nan)
resized_Mask = np.ma.array(resized3, mask = mask1)
#Varianza
k=6
img_mean = ndimage.uniform_filter(resized_Mask, (k, k))
img_sqr_mean = ndimage.uniform_filter(resized_Mask**2, (k, k))
img_var = img_sqr_mean - img_mean**2
img_var[mask == 0] = 1
plt.imsave(f+"_varianza.png", img_var)
I've an issues to implement PixelRL. PixelRL github. I'd like to reading and saving an image to raw data with int16 ( -32768 ~ 32767 ). But I don't find how to make cv2.imwrite() support it. When using p = (p[0]*65535+0.5).astype(np.int16), saving 8bit images. Is there a way to write the image to int16 and raw data?
Thank you.
class MiniBatchLoader(object):
def __init__(self, train_path, test_path, image_dir_path, crop_size):
# load data paths
self.training_path_infos = self.read_paths(train_path, image_dir_path)
self.testing_path_infos = self.read_paths(test_path, image_dir_path)
self.crop_size = crop_size
# test ok
#staticmethod
def path_label_generator(txt_path, src_path):
for line in open(txt_path):
line = line.strip()
src_full_path = os.path.join(src_path, line)
if os.path.isfile(src_full_path):
yield src_full_path
# test ok
#staticmethod
def count_paths(path):
c = 0
for _ in open(path):
c += 1
return c
# test ok
#staticmethod
def read_paths(txt_path, src_path):
cs = []
for pair in MiniBatchLoader.path_label_generator(txt_path, src_path):
cs.append(pair)
return cs
def load_training_data(self, indices):
return self.load_data(self.training_path_infos, indices, augment=True)
def load_testing_data(self, indices):
return self.load_data(self.testing_path_infos, indices)
# test ok
def load_data(self, path_infos, indices, augment=False):
mini_batch_size = len(indices)
in_channels = 1
if augment:
xs = np.zeros((mini_batch_size, in_channels, self.crop_size, self.crop_size)).astype(np.float32)
for i, index in enumerate(indices):
path = path_infos[index]
img = cv2.imread(path,0)
if img is None:
raise RuntimeError("invalid image: {i}".format(i=path))
h, w = img.shape
if np.random.rand() > 0.5:
img = np.fliplr(img)
if np.random.rand() > 0.5:
angle = 10*np.random.rand()
if np.random.rand() > 0.5:
angle *= -1
M = cv2.getRotationMatrix2D((w/2,h/2),angle,1)
img = cv2.warpAffine(img,M,(w,h))
rand_range_h = h-self.crop_size
rand_range_w = w-self.crop_size
x_offset = np.random.randint(rand_range_w)
y_offset = np.random.randint(rand_range_h)
img = img[y_offset:y_offset+self.crop_size, x_offset:x_offset+self.crop_size]
xs[i, 0, :, :] = (img/255).astype(np.float32)
elif mini_batch_size == 0:
for i, index in enumerate(indices):
path = path_infos[index]
img = cv2.imread(path,0)
if img is None:
raise RuntimeError("invalid image: {i}".format(i=path))
h, w = img.shape
xs = np.zeros((mini_batch_size, in_channels, h, w)).astype(np.float16)
xs[0, 0, :, :] = (img/255).astype(np.float16)
else:
raise RuntimeError("mini batch size must be 1 when testing")
return xs
def test(loader, agent, fout):
sum_psnr = 0
sum_reward = 0
test_data_size = MiniBatchLoader.count_paths(TESTING_DATA_PATH)
current_state = State.State((TEST_BATCH_SIZE,1,CROP_SIZE,CROP_SIZE), MOVE_RANGE)
for i in range(0, test_data_size, TEST_BATCH_SIZE):
raw_x = loader.load_testing_data(np.array(range(i, i+TEST_BATCH_SIZE)))
raw_n = np.random.normal(MEAN,SIGMA,raw_x.shape).astype(raw_x.dtype)/255
current_state.reset(raw_x,raw_n)
reward = cp.zeros(raw_x.shape, raw_x.dtype)*255
for t in range(0, EPISODE_LEN):
previous_image = current_state.image.copy()
action, inner_state = agent.act(current_state.tensor)
current_state.step(action, inner_state)
reward = np.square(raw_x - previous_image)*255 - np.square(raw_x - current_state.image)*255
sum_reward += cp.mean(reward)*cp.power(GAMMA,t)
agent.stop_episode()
p = np.maximum(0,current_state.image)
p = np.minimum(1,p)
p = (p[0]*65535+0.5).astype(np.int16)
p = cp.transpose(p,(1,2,0))
cv2.imwrite(output_save_path+'/'+str(i)+'.tiff',p)
is there a way ... ?
No.
https://docs.opencv.org/3.4/d4/da8/group__imgcodecs.html#gabbc7ef1aa2edfaa87772f1202d67e0ce
In general, only 8-bit single-channel or 3-channel ... images can be saved using this function, with these exceptions:
16-bit unsigned (CV_16U) images can be saved ...
You might want to munge your signed 16-bit values into CV_16U format.
Alternatively, you might consider writing 32-bit images, and then invoking a post-processing program to shrink them to your favorite 16-bit format.
I'm currently working on a steagnographic application,
and i'm taking each pixel value and embedding data into it one by one
this sequencial processing is taking a long time to process,
the code:
import config_loader
import numpy as np
from PIL import Image
import encryption
import time
def byte2bin(bytestring):
# print("\n from byte 2 bin\n")
# print(bytestring)
bitstring = bin(int.from_bytes(bytestring, byteorder="big"))
return bitstring[2:]
def insert_data_in_pixel(raw_data, string, ptr, bits=1): # this function takes a pixel's data and then converts it to
# binary and then change the last bit to the secret
color = bin(int(raw_data))[2:]
# old = color # troubleshooting lines
color = color[:len(color) - bits]
color = color + string[ptr: ptr + bits]
# print("original-> ", old,"| |added bits ",string[ptr: ptr+bits],"| |Modified-> ", color) # troubleshooting lines
return np.uint8(int(color, 2))
def insert_length(length, new_img): # inserts length of our secret and the length itself is obfuscated
secret_string_len = '<l>' + str(int(length / 4) + 16) + '<l>' # Added ambiguity
secret_string_len = ''.join(format(_, '08b') for _ in bytearray(str(secret_string_len), encoding='utf-8'))
length = len(secret_string_len)
str_len_ptr = 0
for y in range(length):
x = 0
if str_len_ptr < length:
new_img[x][y][0] = insert_data_in_pixel(new_img[x][y][0], secret_string_len, str_len_ptr, bits=3)
str_len_ptr += 3
if str_len_ptr == length:
break
new_img[x][y][1] = insert_data_in_pixel(new_img[x][y][1], secret_string_len, str_len_ptr, bits=3)
str_len_ptr += 3
if str_len_ptr == length:
break
new_img[x][y][2] = insert_data_in_pixel(new_img[x][y][2], secret_string_len, str_len_ptr, bits=2)
str_len_ptr += 2
if str_len_ptr == length:
break
def secret_Loader(): # loads secret from a file
with open('Message.txt', 'r', encoding='utf-8', errors='ignore') as file:
lines = file.readlines()
message = ''.join(lines)
key = config_loader.read('''data['key']''')
# print(key)
enc_message = encryption.encrypt(message, key)
return enc_message
def insert():
start = time.time()
image_path = config_loader.read('''data['environment']['cover_image']''')
photo = Image.open(image_path).convert('RGB') # just insert the image name here
data = np.asarray(photo).copy()
width, height = photo.size
secret = byte2bin(secret_Loader())
secret_pointer = 0
lensecret = len(secret)
insert_length(lensecret, data)
insertion = time.time()
for x in range(1, height):
for y in range(width):
if lensecret > secret_pointer:
# RED
data[x][y][0] = insert_data_in_pixel(data[x][y][0], secret, secret_pointer, bits=2)
secret_pointer += 2
if lensecret == secret_pointer:
break
# Green
data[x][y][1] = insert_data_in_pixel(data[x][y][1], secret, secret_pointer, bits=2)
secret_pointer += 2
if lensecret == secret_pointer:
break
# Blue
data[x][y][2] = insert_data_in_pixel(data[x][y][2], secret, secret_pointer, bits=1)
secret_pointer += 1
if lensecret == secret_pointer:
break
print("data insertion",time.time()-insertion)
generation = time.time()
# print(data)
data = Image.fromarray(data)
print("image generation in ", time.time()-generation)
# data.show()
_ = time.time()
data = data.save(r'stg.PNG')
print("saving time ", time.time()-_)
print('Exectuted in->', time.time() - start)
if __name__ == '__main__':
insert()
the timings
encryption in 1.0841524600982666
data insertion 9.439783811569214
image generation in 0.039893388748168945
saving time 6.283206939697266
Exectuted in-> 17.11327576637268
I thought about multithreading but that is unreliable as every bit in the data is important and it's position in the sequence is also important.
P.S the data insertion time is for 10000
lines of this
this is a message to test the limit of the program let's check when it breaks and how, also i'm running out of words0
so this isn't bad but if it can be improved how can i achieve it?
I found a Python script that I'm trying to convert to Lua. I believe I have it just about converted, but the code isn't quite working properly, so I need assistance as I do not know Python at all, and can only guess at the intentions. This is merely a color converter to convert RGB color to xterm 256. The table is quite huge, so I've truncated it for ease of reading.
Python code:
import sys, re
CLUT = [ # color look-up table
# 8-bit, RGB hex
# Primary 3-bit (8 colors). Unique representation!
('00', '000000'),
('01', '800000'),
('02', '008000'),
('03', '808000'),
('04', '000080'),
('05', '800080'),
('06', '008080'),
('07', 'c0c0c0'),
]
def _str2hex(hexstr):
return int(hexstr, 16)
def _strip_hash(rgb):
# Strip leading `#` if exists.
if rgb.startswith('#'):
rgb = rgb.lstrip('#')
return rgb
def _create_dicts():
short2rgb_dict = dict(CLUT)
rgb2short_dict = {}
for k, v in short2rgb_dict.items():
rgb2short_dict[v] = k
return rgb2short_dict, short2rgb_dict
def short2rgb(short):
return SHORT2RGB_DICT[short]
def print_all():
""" Print all 256 xterm color codes.
"""
for short, rgb in CLUT:
sys.stdout.write('\033[48;5;%sm%s:%s' % (short, short, rgb))
sys.stdout.write("\033[0m ")
sys.stdout.write('\033[38;5;%sm%s:%s' % (short, short, rgb))
sys.stdout.write("\033[0m\n")
print "Printed all codes."
print "You can translate a hex or 0-255 code by providing an argument."
def rgb2short(rgb):
""" Find the closest xterm-256 approximation to the given RGB value.
#param rgb: Hex code representing an RGB value, eg, 'abcdef'
#returns: String between 0 and 255, compatible with xterm.
>>> rgb2short('123456')
('23', '005f5f')
>>> rgb2short('ffffff')
('231', 'ffffff')
>>> rgb2short('0DADD6') # vimeo logo
('38', '00afd7')
"""
rgb = _strip_hash(rgb)
incs = (0x00, 0x5f, 0x87, 0xaf, 0xd7, 0xff)
# Break 6-char RGB code into 3 integer vals.
parts = [ int(h, 16) for h in re.split(r'(..)(..)(..)', rgb)[1:4] ]
res = []
for part in parts:
i = 0
while i < len(incs)-1:
s, b = incs[i], incs[i+1] # smaller, bigger
if s <= part <= b:
s1 = abs(s - part)
b1 = abs(b - part)
if s1 < b1: closest = s
else: closest = b
res.append(closest)
break
i += 1
#print '***', res
res = ''.join([ ('%02.x' % i) for i in res ])
equiv = RGB2SHORT_DICT[ res ]
#print '***', res, equiv
return equiv, res
RGB2SHORT_DICT, SHORT2RGB_DICT = _create_dicts()
#---------------------------------------------------------------------
if __name__ == '__main__':
import doctest
doctest.testmod()
if len(sys.argv) == 1:
print_all()
raise SystemExit
arg = sys.argv[1]
if len(arg) < 4 and int(arg) < 256:
rgb = short2rgb(arg)
sys.stdout.write('xterm color \033[38;5;%sm%s\033[0m -> RGB exact \033[38;5;%sm%s\033[0m' % (arg, arg, arg, rgb))
sys.stdout.write("\033[0m\n")
else:
short, rgb = rgb2short(arg)
sys.stdout.write('RGB %s -> xterm color approx \033[38;5;%sm%s (%s)' % (arg, short, short, rgb))
sys.stdout.write("\033[0m\n")
And my nearly complete translated Lua code:
CLUT = {
-- Primary 3-bit (8 colors). Unique representation!
['00'] = '000000',
['01'] = '800000',
['02'] = '008000',
['03'] = '808000',
['04'] = '000080',
['05'] = '800080',
['06'] = '008080',
['07'] = 'c0c0c0',
}
function _str2hex(hexstr)
return tonumber(hexstr, 16)
end
function _strip_hash(rgb)
-- Strip leading # if exists
return rgb:gsub("^#", "")
end
function _create_dicts()
short2rgb_dict = CLUT
rgb2short_dict = {}
for k,v in pairs(short2rgb_dict) do
rgb2short_dict[v] = k
end
return rgb2short_dict, short2rgb_dict
end
function short2rgb(short)
return short2rgb_dict[short]
end
function rgb2short(rgb)
-- Find closest xterm-256 approximation to the given RGB value
_create_dicts()
rgb = _strip_hash(rgb)
local res = ""
local equiv = ""
local incs = {"0x00", "0x5f", "0x87", "0xaf", "0xd7", "0xff"}
for part in string.gmatch(rgb, "(..)") do
part = tonumber(part, 16)
i = 1
while i < #incs - 1 do
s, b = tonumber(incs[i]), tonumber(incs[i+1])
if s <= part and part <= b then
s1 = math.abs(s - part)
b1 = math.abs(b - part)
end
if s1 < b1 then
closest = s
else
closest = b
res = res .. closest
break
end
i = i + 1
end
end
equiv = rgb2short_dict[res]
return equiv, res
end
I realize that I'm missing the printing portion of the code, but I wasn't sure if that was at all relevant, and I know some of the code I've translated is not correct at all, as the script would be working otherwise. The failures I get are with the rgb2short function with it not returning the proper equiv and res values. How far off am I with my revision? What changes do I need to make to make it absolutely work?
I wound up figuring it out on my own after some hardcore trial and error. The function rgb2short should have been:
function rgb2short(rgb)
-- Find closest xterm-256 approximation to the given RGB value
_create_dicts()
rgb = _strip_hash(rgb)
local res = ""
local equiv = ""
local incs = {"0x00", "0x5f", "0x87", "0xaf", "0xd7", "0xff"}
for part in string.gmatch(rgb, "(..)") do
part = tonumber(part, 16)
i = 1
while i < #incs-1 do
s, b = tonumber(incs[i]), tonumber(incs[i+1])
if s <= part and part <= b then
s1 = math.abs(s - part)
b1 = math.abs(b - part)
--break
--end
if s1 < b1 then
closest = s
else
closest = b
end
res = res .. string.format("%02x", closest)
break
end
i = i + 1
end
end
equiv = rgb2short_dict[res]
return equiv, res
end
I am writing several functions, the first one inserts a greyscale bitmap image into another colour bitmap image. Now my aim is basically to take each digit of the greyscale pixel image (eg. 123) and replace the end digit of every RGB pixel (244, 244, 244), so it would basically end up like this (241, 242, 243). Essentially this is watermarking the colour image with the greyscale image.
The following code is what I have so far, i'm able to return the tuple values in a list, i just do not know how to manipulate a space the size of a smaller greyscale image in a larger image.
def add_watermark():
image = Image.open()
pixels = list(image.getdata())
image.putdata()
image.save()
for i in range(img.size[0]):
for j in range(img.size[1]):
pixels[i,j] = (i, j, 100)
Can anyone offer some advice?
You're on the right track. That's how you manipulate pixels, though you can do it a little faster using pixel access objects like I've shown below.
It's all pretty straightforward except for extracting and setting the correct digits. In this example, I've done that by dividing by powers of 10 and by using the modulo operator, though there are other ways. Hopefully the comments explain it well enough.
from PIL import Image
def add_watermark(watermark_path, image_in_path, image_out_path):
# Load watermark and image and check sizes and modes
watermark = Image.open(watermark_path)
assert watermark.mode == 'L'
image = Image.open(image_in_path)
assert image.mode == 'RGB'
assert watermark.size == image.size
# Get pixel access objects
watermark_pixels = watermark.load()
image_pixels = image.load()
# Watermark each pixel
for x in range(image.size[0]):
for y in xrange(image.size[1]):
# Get the tuple of rgb values and convert to a list (mutable)
rgb = list(image_pixels[x, y])
for i, p in enumerate(rgb):
# Divide the watermark pixel by 100 (r), then 10 (g), then 1 (b)
# Then take it modulo 10 to get the last digit
watermark_digit = (watermark_pixels[x, y] / (10 ** (2 - i))) % 10
# Divide and multiply value by 10 to zero the last digit
# Then add the watermark digit
rgb[i] = (p / 10) * 10 + watermark_digit
# Convert back to a tuple and store in the image
image_pixels[x, y] = tuple(rgb)
# Save the image
image.save(image_out_path)
If you are interested in watermarking images, you might want to take a look at steganography. As an example, Digital_Sight is a working demonstration of the concept and could be used as a basis for storing text used as a watermark. To study how modifying various pixel-bits in an image can alter its quality, you might want to play around with Color_Disruptor before deciding what data to overwrite.
Digital_Sight
import cStringIO
from PIL import Image
import bz2
import math
################################################################################
PIXELS_PER_BLOCK = 4
BYTES_PER_BLOCK = 3
MAX_DATA_BYTES = 16777215
################################################################################
class ByteWriter:
"ByteWriter(image) -> ByteWriter instance"
def __init__(self, image):
"Initalize the ByteWriter's internal variables."
self.__width, self.__height = image.size
self.__space = bytes_in_image(image)
self.__pixels = image.load()
def write(self, text):
"Compress and write the text to the image's pixels."
data = bz2.compress(text)
compressed_size = len(data)
if compressed_size > self.__space:
raise MemoryError('There is not enough space for the data!')
size_data = self.__encode_size(compressed_size)
tail = '\0' * ((3 - compressed_size) % 3)
buffer = size_data + data + tail
self.__write_buffer(buffer)
#staticmethod
def __encode_size(number):
"Convert number into a 3-byte block for writing."
data = ''
for _ in range(3):
number, lower = divmod(number, 256)
data = chr(lower) + data
return data
def __write_buffer(self, buffer):
"Write the buffer to the image in blocks."
addr_iter = self.__make_addr_iter()
data_iter = self.__make_data_iter(buffer)
for trio in data_iter:
self.__write_trio(trio, addr_iter.next())
def __make_addr_iter(self):
"Iterate over addresses of pixels to write to."
addr_group = []
for x in range(self.__width):
for y in range(self.__height):
addr_group.append((x, y))
if len(addr_group) == 4:
yield tuple(addr_group)
addr_group = []
#staticmethod
def __make_data_iter(buffer):
"Iterate over the buffer a block at a time."
if len(buffer) % 3 != 0:
raise ValueError('Buffer has a bad size!')
data = ''
for char in buffer:
data += char
if len(data) == 3:
yield data
data = ''
def __write_trio(self, trio, addrs):
"Write a 3-byte block to the pixels addresses given."
duo_iter = self.__make_duo_iter(trio)
tri_iter = self.__make_tri_iter(duo_iter)
for (r_duo, g_duo, b_duo), addr in zip(tri_iter, addrs):
r, g, b, a = self.__pixels[addr]
r = self.__set_two_bits(r, r_duo)
g = self.__set_two_bits(g, g_duo)
b = self.__set_two_bits(b, b_duo)
self.__pixels[addr] = r, g, b, a
#staticmethod
def __make_duo_iter(trio):
"Iterate over 2-bits that need to be written."
for char in trio:
byte = ord(char)
duos = []
for _ in range(4):
byte, duo = divmod(byte, 4)
duos.append(duo)
for duo in reversed(duos):
yield duo
#staticmethod
def __make_tri_iter(duo_iter):
"Group bits into their pixel units for writing."
group = []
for duo in duo_iter:
group.append(duo)
if len(group) == 3:
yield tuple(group)
group = []
#staticmethod
def __set_two_bits(byte, duo):
"Write a duo (2-bit) group to a pixel channel (RGB)."
if duo > 3:
raise ValueError('Duo bits has to high of a value!')
byte &= 252
byte |= duo
return byte
################################################################################
class ByteReader:
"ByteReader(image) -> ByteReader instance"
def __init__(self, image):
"Initalize the ByteReader's internal variables."
self.__width, self.__height = image.size
self.__pixels = image.load()
def read(self):
"Read data out of a picture, decompress the data, and return it."
compressed_data = ''
addr_iter = self.__make_addr_iter()
size_block = self.__read_blocks(addr_iter)
size_value = self.__block_to_number(size_block)
blocks_to_read = math.ceil(size_value / 3.0)
for _ in range(blocks_to_read):
compressed_data += self.__read_blocks(addr_iter)
if len(compressed_data) != blocks_to_read * 3:
raise ValueError('Blocks were not read correctly!')
if len(compressed_data) > size_value:
compressed_data = compressed_data[:size_value]
return bz2.decompress(compressed_data)
def __make_addr_iter(self):
"Iterate over the pixel addresses in the image."
addr_group = []
for x in range(self.__width):
for y in range(self.__height):
addr_group.append((x, y))
if len(addr_group) == 4:
yield tuple(addr_group)
addr_group = []
def __read_blocks(self, addr_iter):
"Read data a block at a time (4 pixels for 3 bytes)."
pixels = []
for addr in addr_iter.next():
pixels.append(self.__pixels[addr])
duos = self.__get_pixel_duos(pixels)
data = ''
buffer = []
for duo in duos:
buffer.append(duo)
if len(buffer) == 4:
value = 0
for duo in buffer:
value <<= 2
value |= duo
data += chr(value)
buffer = []
if len(data) != 3:
raise ValueError('Data was not decoded properly!')
return data
#classmethod
def __get_pixel_duos(cls, pixels):
"Extract bits from a given group of pixels."
duos = []
for pixel in pixels:
duos.extend(cls.__extract_duos(pixel))
return duos
#staticmethod
def __extract_duos(pixel):
"Retrieve the bits stored in a pixel."
r, g, b, a = pixel
return r & 3, g & 3, b & 3
#staticmethod
def __block_to_number(block):
"Convert a block into a number (size of data buffer)."
value = 0
for char in block:
value <<= 8
value |= ord(char)
return value
################################################################################
def main(picture, mode, text):
"Dispatch the various operations that can be requested."
image = Image.open(picture)
if image.mode != 'RGBA':
image = image.convert('RGBA')
if mode == 'Evaluate':
evaluate(image)
elif mode == 'Simulate':
simulate(image, text)
elif mode == 'Encode':
encode(image, text)
elif mode == 'Decode':
decode(image)
else:
raise ValueError('Mode %r was not recognized!' % mode)
################################################################################
def evaluate(image):
"Display the number of bytes available in the image."
print 'Usable bytes available =', bytes_in_image(image)
def bytes_in_image(image):
"Calculate the number of usable bytes in an image."
blocks = blocks_in_image(image)
usable_blocks = blocks - 1
usable_bytes = usable_blocks * BYTES_PER_BLOCK
return min(usable_bytes, MAX_DATA_BYTES)
def blocks_in_image(image):
"Find out how many blocks are in an image."
width, height = image.size
pixels = width * height
blocks = pixels / PIXELS_PER_BLOCK
return blocks
################################################################################
def simulate(image, text):
"Find out how much space the text takes in the image that was given."
compressed_data = bz2.compress(text)
compressed_size = len(compressed_data)
usable_bytes = bytes_in_image(image)
space_leftover = usable_bytes - compressed_size
if space_leftover > 0:
print 'You still have %s more bytes for storage.' % space_leftover
elif space_leftover < 0:
print 'You overfilled the image by %s bytes.' % -space_leftover
else:
print 'This is a perfect fit!'
################################################################################
def encode(image, text):
"Encodes text in image and returns picture to the browser."
mutator = ByteWriter(image)
mutator.write(text)
output = cStringIO.StringIO()
image.save(output, 'PNG')
output.seek(0)
print 'Content-Type: image/PNG'
print output.read()
################################################################################
def decode(image):
"Extract the original message and deliver it to the client."
accessor = ByteReader(image)
buffer = accessor.read()
print buffer
################################################################################
if __name__ == '__builtin__':
try:
main(cStringIO.StringIO(PICTURE), MODE, TEXT)
except SystemExit:
pass
Color_Disruptor
from cStringIO import StringIO
from PIL import Image
from random import randrange
def main(data, r_bits, g_bits, b_bits, a_bits):
image = Image.open(data)
if image.mode != 'RGBA':
image = image.convert('RGBA')
width, height = image.size
array = image.load()
data.close()
for x in range(width):
for y in range(height):
r, g, b, a = array[x, y]
r ^= randrange(r_bits)
g ^= randrange(g_bits)
b ^= randrange(b_bits)
a ^= randrange(a_bits)
array[x, y] = r, g, b, a
data = StringIO()
image.save(data, 'PNG')
print 'Content-Type: image/PNG'
print data.getvalue()
if __name__ == '__builtin__':
main(StringIO(DATA), *map(lambda bits: 1 << int(bits), (R, G, B, A)))