I am trying to hide a small text within an image using DWT and subsequent LSB substitution of the data in the LL-Subband. After substition of the data in the LL-Subband in each pixel, when I try to take the DWT of the image that I get after applying IDWT on the new LL-subband coefficients after LSB substitution, I get completely different data which is not correct.
I am attaching the code below for embedding and extraction of the data:
class WaveletBasedAes(BaseClass):
def __init__(self, image_path, secret_message) -> None:
super().__init__(image_path, secret_message)
def image_steganography(self):
gray_image = cv2.imread(self.image_path, 0)
# encrypted_text = self.aes_encrypt()
message_bits = ' '.join(bin(ord(b))[2:] for b in self.secret_message)
wavelet = 'haar'
coeffs = dwt2(gray_image,wavelet)
ll_coeffs = coeffs[0]
embedded_coeffs = self.embed_message_in_ll(ll_coeffs.copy(), ''.join(message_bits.split()))
new_coeffs = (embedded_coeffs, coeffs[1])
# Reconstruct stego-image
stego_image = self.reconstruct_image(new_coeffs, wavelet)
return stego_image
def decrypt_and_extract(self,stego_image_path):
# Load stego-image
try:
stego_image = cv2.imread(stego_image_path, 0)
except FileNotFoundError:
print("Error: Image file not found")
return None
# Perform DWT
wavelet = 'haar'
coeffs = dwt2(stego_image, wavelet)
# Extract embedded bits from LL sub-band
ll_coeffs = coeffs[0]
extracted_bits = self.extract_message_from_ll(ll_coeffs)
# Decrypt extracted bits with AES
message = self.aes_decrypt(bitstring_to_bytes( extracted_bits))
return message
These are the helper functions embed_message_in_ll()
and extract_message_from_ll()
def embed_message_in_ll(self, ll_subband, message):
# Flatten the LL subband into a 1D array
ll_flat = ll_subband.flatten()
length_of_message_in_bin = ''.join(string_to_binary(str(len(message))).split())
# Check if the message can fit into the LL subband
if len(message+length_of_message_in_bin) > len(ll_flat):
raise ValueError("Message is too long to fit into the LL subband")
# Embed the length of the message into the LL subband
for i in range(18):
# Convert the current LL coefficient to binary
coeff_bin = format(int(ll_flat[i]), '08b')
# Replace the LSB of the coefficient with the current message bit
new_coeff_bin = coeff_bin[:-1] + length_of_message_in_bin[i]
# Convert the new coefficient back to an integer
new_coeff = int(new_coeff_bin, 2)
# Replace the original coefficient with the new one
ll_flat[i] = new_coeff
# Embed the message into the LSB of the LL subband
for i in range(len(message)):
# Convert the current LL coefficient to binary
coeff_bin = format(int(ll_flat[i+18]), '08b')
# Replace the LSB of the coefficient with the current message bit
new_coeff_bin = coeff_bin[:-1] + message[i]
# Convert the new coefficient back to an integer
new_coeff = int(new_coeff_bin, 2)
# Replace the original coefficient with the new one
ll_flat[i+18] = new_coeff
# Reshape the flat LL subband back into its original 2D shape
new_ll_subband = ll_flat.reshape(ll_subband.shape)
return new_ll_subband
def extract_message_from_ll(self, ll_coeffs):
# Flatten the LL subband into a 1D array
ll_flat = ll_coeffs.flatten()
length_of_message = ''
for i in range(18):
# Extract the LSB from the current LL coefficient
length_of_message += str(int(ll_flat[i]) & 1)
# Initialize an empty string to hold the extracted bits
extracted_bits = ''
# Extract the LSB from each coefficient in the LL subband
for coeff in ll_flat:
# Convert the coefficient to binary
coeff_bin = format(coeff, '08b')
# Extract the LSB and append it to the extracted bits
extracted_bits += coeff_bin[-1]
return extracted_bits
def reconstruct_image(self, coeffs, wavelet):
return idwt2(coeffs, wavelet=wavelet)
I am directly stuck after the ll_coeffs=coeffs[0]
method, as it gives wrong LL-subband values, which makes it impossible to make sense of the LSB data.