Как извлечь сообщение из изображения и сделать его читаемым без матрицы квантования?

Писал программу на python, которая считывает сообщение, которое надо встроить из файла to_encode.txt и встраивает его в изображение container.bmp с помощью НЗБ в коэффициент 0,0 ДКП. То есть используется дискретное косинусное преобразование. В ходе работы программы формируются блоки пикселей 8х8, проводится ДКП, после чего они делятся на матрицу квантования для сжатия изображения и чтобы сообщение норм встроилось, после встраивается бит сообщения в НЗБ (наименее значимый бит) коэффициента 0 0 блок умножается на матрицу квантования, и происходит обратное ДКП и формируется "модифицированное" изображение. Все хорошо работает при использовании этой самой матрицы. Но мне сказали что оказывается надо сделать без нее. А попытки ее убрать делают сообщение нечитаемым. Как мне это исправить? Вот код, со всеми комментариями. Искал инфу, нигде не написано про то, что можно без матрицы квантования это делать. Нигде.

Очень молю о вашей помощи, потому что это буквально вопрос жизни и смерти... Все написано на python.

Файл dct_dealer.py

import os
import sys
import math
import cv2
import numpy as np


def options():
    choice = int(input("Выберите действие программы: 1 - Закодировать файл, 2 - Декодировать файл, 3 - Выход\n"))
    if choice == 1:
        main_enc()
    elif choice == 2:
        main_dec()
    elif choice == 3:
        exit()
    else:
        print("У нас таких штук нет")

quant_matrix = np.array([[8, 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]]) #матрица квантования, нужна нам для сжатия


def extract_text_from_doc (to_encode): #извлечение сообщения из файла в строку
    to_encode_len = os.stat('to_encode.txt').st_size #размер файла
    index = 0
    text_from_doc = '' #закидываем туда сообщение из файла 
    while index <= to_encode_len: #Цикл переноса посимвольного из файла в строку
        text_from_doc += to_encode.read(index)
        index+=1
        if index > to_encode_len:
            print("Исходное сообщение " + str(text_from_doc) + '\n')
    return text_from_doc



def encode_letter(letter): #Перевод буквы сообщения в бин
    return bin(ord(letter))[2:].zfill(16)

def create_blocks(channel, width, height): #Функция работы с блоков пикселей
    channel = np.float32(channel) # Перевод channel в Объект типа данных float
    blocks = channel.reshape(int(width * height / 64), 8, 8) #Изменение формы массива данных в блок 8х8
    blocks = [cv2.dct(block) for block in blocks] # DCT
    return [block / quant_matrix for block in blocks]

def encode(text_from_doc): #перевод сообщения в бинарь
    
    encoded_text = '' #Тут будет строка бинарного типа
    t_len = len(text_from_doc) #Вычислим длину сообщения
    print(t_len)

    bits_to_length = math.ceil(math.log(t_len + 1, 2)) #Выделим бит на сообщение
    print("Выделено " + str(bits_to_length) + " бит" + '\n')

    for letter in text_from_doc: #Кодирование
        encoded_text += encode_letter(letter)
    return bin(t_len)[2:].zfill(bits_to_length) + encoded_text

def hide(container): #Функция непосредственно встраивания
    
    to_encode = open('to_encode.txt', 'r') #посимвольное прочтение
    text_from_doc = extract_text_from_doc(to_encode) #Закидываем в функцию
    text_from_doc = encode(text_from_doc) #Тоже
    t_len = len(text_from_doc)
    
    
    width, height = container.shape[:2] #возврат значений Ш и В
    index = 0

    if width * height * 3 / 64 < t_len: #Вычисляем максимально возможную длину сообщения для встраивания
        print('Текст большой, углепластик, так охладите пыл.')
        exit(1)
    print(width * height * 3 / 64)

    channels = cv2.split(container) #Разбиение массива
    for i in range(3): #Для каждого из 3 каналов делаем:
        if index == len(text_from_doc):
            break

        blocks = create_blocks(channels[i], width, height) #Выполнение операций из соответствующей функции
        for block in blocks: # Для каждого блока:
            b = block[0, 0].astype('int32') # Переводим в целое число
            b = b & ~1 | int(text_from_doc[index]) # Изменяем НЗБ 
            block[0, 0] = b.astype('float32') # Обратно в float
            index += 1
            if index == len(text_from_doc):
                break

        blocks = [block * quant_matrix for block in blocks] # Восстанавливаем изображение: умножаем на таблицу квантования
        blocks = [cv2.idct(block) for block in blocks] # Восстанавливаем изображение: обратное ДКП
        channels[i] = np.array(blocks).reshape(width, height).astype('uint8') # Из блоков 8х8 собираем двумерный массив

    return cv2.merge(channels) # Объединяем каналы в изображении

def decode_sequence(sequence): #Декодируем последовательность (Переводим в символьное)
    return chr(int(sequence, 2))

def extract(to_decode): #Функция извлечения сообщения

    width, height = to_decode.shape[:2] 

    message = ''
    
    for channel in cv2.split(to_decode): #Разбиение на блоки 
        blocks = create_blocks(channel, width, height) #Прогон через функцию

        for block in blocks: # Для каждого из блока:
            b = block[0, 0].astype('int32') # Переводим число в целое
            message += str(1 - int(bin(b)[-1])) # Берем последний бит
    return decode(message)

def decode(text): #Функция декода
    print("Сколько бит Выделено под сообщение?")
    bits_to_length = int(input()) # Нужно для корректного извлечения
    size = int(text[:bits_to_length], 2) # Находим длину сообщения
    text = text[bits_to_length:] # Отсекаем от сообщения информацию о длине
    decoded_text = ''
    for i in range(size):
        decoded_text += decode_sequence(text[i * 16: (i + 1) * 16]) # Делим сообщение на последовательности по 16 бит и декодируем
    return decoded_text

def main_enc ():
    
    container = cv2.imread("container.bmp")
    to_encode = open('to_encode.txt', 'r') #посимвольное прочтение
    container = hide(container)
    cv2.imwrite("encoded.bmp", container)
    exit()

def main_dec():
    
    to_decode = cv2.imread("encoded.bmp")
    msg = extract(to_decode)
    decoded = open('decoded.txt', 'w')
    for x in msg:
        decoded.write(str(x))
        print(str(x), end='')
    print('\n')
    print("Сообщение успешно извлечено. На этом все.\n")
    exit()

options()

Ответы (1 шт):

Автор решения: MBo

Матрица квантования ведь содержит не абстрактные значения, а рассчитанные по какой-то формуле. Например, здесь (просто первое, что попалось) приводится

Q[i,j] = 1 + (i+j)*R

и ничего не мешает использовать эту формулу - ну разве что неудобно в numpy каждый элемент делить-умножать на функцию.

→ Ссылка