Files
CourseWork/core/dct.py
Игорь c6ceca94ea работающая стеганография LSB и DCT
я устал... DCT убивает
2026-04-14 11:59:41 +03:00

191 lines
6.6 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
Модуль DCT стеганографии с квантованием (JPEG-style)
"""
import numpy as np
from PIL import Image
from scipy.fftpack import dct, idct
from .utils import text_to_bits, bits_to_text
BLOCK_SIZE = 8
# Стандартная таблица квантования JPEG для качества 50%
QUANT_TABLE = 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]
])
def _get_zigzag_order() -> list:
"""Zigzag порядок для матрицы 8x8."""
zigzag = []
for s in range(15):
if s % 2 == 0:
for i in range(min(s, 7), max(0, s - 7) - 1, -1):
j = s - i
if 0 <= i < 8 and 0 <= j < 8:
zigzag.append(i * 8 + j)
else:
for i in range(max(0, s - 7), min(s, 7) + 1):
j = s - i
if 0 <= i < 8 and 0 <= j < 8:
zigzag.append(i * 8 + j)
return zigzag
ZIGZAG_ORDER = _get_zigzag_order()
MID_FREQ_INDICES = ZIGZAG_ORDER[1:40]
def _dct_quantize(block: np.ndarray) -> np.ndarray:
"""DCT + квантование."""
dct_block = dct(dct(block, axis=0, norm='ortho'), axis=1, norm='ortho')
quantized = np.round(dct_block / QUANT_TABLE)
return quantized.astype(np.int32)
def _idct_dequantize(quantized: np.ndarray) -> np.ndarray:
"""Обратное квантование + IDCT."""
dct_block = quantized * QUANT_TABLE
block = idct(idct(dct_block, axis=0, norm='ortho'), axis=1, norm='ortho')
block = np.round(block)
return np.clip(block, 0, 255).astype(np.uint8)
def _extract_bits_from_block(block_quant: np.ndarray, max_bits: int) -> list:
"""Извлекает биты из квантованных коэффициентов."""
extracted = []
coeff_flat = block_quant.flatten()
for idx in MID_FREQ_INDICES:
if len(extracted) >= max_bits:
break
if idx >= len(coeff_flat):
break
bit = int(coeff_flat[idx]) & 1
extracted.append(bit)
return extracted
def _embed_bits_in_block(block_quant: np.ndarray, bits: list, bit_index: int) -> tuple:
"""Внедряет биты в квантованные коэффициенты."""
modified = block_quant.copy()
current_idx = bit_index
coeff_flat = modified.flatten()
for idx in MID_FREQ_INDICES:
if current_idx >= len(bits):
break
if idx >= len(coeff_flat):
break
new_val = (int(coeff_flat[idx]) & 0xFE) | bits[current_idx]
coeff_flat[idx] = new_val
current_idx += 1
return coeff_flat.reshape(BLOCK_SIZE, BLOCK_SIZE), current_idx
def encode_dct(image_path: str, message: str, output_path: str) -> bool:
"""Скрывает сообщение в изображении методом DCT."""
img = Image.open(image_path).convert('L')
pixels = np.array(img, dtype=np.float64)
height, width = pixels.shape
# Кодируем сообщение в байты
message_bytes = message.encode('utf-8')
msg_length = len(message_bytes)
# Формат: [длина: 32 бита] + [сообщение]
# 32 бита = 4 байта, достаточно для сообщений до 4 ГБ
length_bits = format(msg_length, '032b')
message_bits = text_to_bits(message)
# Собираем всё вместе
all_bits = length_bits + message_bits
bit_list = [int(b) for b in all_bits]
total_bits = len(bit_list)
# Проверяем вместимость
blocks_per_row = width // BLOCK_SIZE
blocks_per_col = height // BLOCK_SIZE
max_bits = blocks_per_row * blocks_per_col * len(MID_FREQ_INDICES)
if total_bits > max_bits:
print(f"Ошибка: сообщение слишком большое.")
print(f"Доступно: {max_bits}, требуется: {total_bits}")
return False
modified_pixels = pixels.copy()
bit_index = 0
for i in range(0, height - BLOCK_SIZE + 1, BLOCK_SIZE):
for j in range(0, width - BLOCK_SIZE + 1, BLOCK_SIZE):
if bit_index >= total_bits:
break
block = pixels[i:i+BLOCK_SIZE, j:j+BLOCK_SIZE]
quant_block = _dct_quantize(block)
quant_block, bit_index = _embed_bits_in_block(quant_block, bit_list, bit_index)
new_block = _idct_dequantize(quant_block)
modified_pixels[i:i+BLOCK_SIZE, j:j+BLOCK_SIZE] = new_block
if bit_index >= total_bits:
break
result_img = Image.fromarray(modified_pixels.astype(np.uint8), mode='L')
result_img.save(output_path)
print(f"Успешно! Спрятано {total_bits} бит ({total_bits // 8} байт)")
return True
def decode_dct(image_path: str) -> str:
"""Извлекает сообщение из изображения методом DCT."""
img = Image.open(image_path).convert('L')
pixels = np.array(img, dtype=np.float64)
height, width = pixels.shape
# Извлекаем все биты
all_bits = []
for i in range(0, height - BLOCK_SIZE + 1, BLOCK_SIZE):
for j in range(0, width - BLOCK_SIZE + 1, BLOCK_SIZE):
block = pixels[i:i+BLOCK_SIZE, j:j+BLOCK_SIZE]
quant_block = _dct_quantize(block)
bits_from_block = _extract_bits_from_block(quant_block, len(MID_FREQ_INDICES))
all_bits.extend(bits_from_block)
# Читаем длину сообщения (первые 32 бита)
if len(all_bits) < 32:
return ""
length_bits = all_bits[:32]
length_str = ''.join(str(b) for b in length_bits)
msg_length = int(length_str, 2)
# Проверяем, что длина разумная
if msg_length > len(all_bits) // 8:
return ""
# Читаем сообщение
message_bits = all_bits[32:32 + msg_length * 8]
# Дополняем до кратности 8 (хотя уже должно быть кратно)
remainder = len(message_bits) % 8
if remainder != 0:
message_bits.extend([0] * (8 - remainder))
if len(message_bits) == 0:
return ""
bits_string = ''.join(str(b) for b in message_bits)
try:
return bits_to_text(bits_string)
except Exception as e:
print(f"Ошибка при декодировании: {e}")
return ""