main, lsb и dct под все типы сообщений и картинок
This commit is contained in:
67
core/dct.py
67
core/dct.py
@@ -1,5 +1,6 @@
|
|||||||
"""
|
"""
|
||||||
Модуль DCT стеганографии с квантованием (JPEG-style)
|
Модуль DCT стеганографии с квантованием (JPEG-style)
|
||||||
|
Оптимизирован для длинных сообщений
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
@@ -9,16 +10,16 @@ from .utils import text_to_bits, bits_to_text
|
|||||||
|
|
||||||
BLOCK_SIZE = 8
|
BLOCK_SIZE = 8
|
||||||
|
|
||||||
# Стандартная таблица квантования JPEG для качества 50%
|
# Более мягкая таблица квантования (качество ~70-80%)
|
||||||
QUANT_TABLE = np.array([
|
QUANT_TABLE = np.array([
|
||||||
[16, 11, 10, 16, 24, 40, 51, 61],
|
[8, 6, 5, 8, 12, 20, 26, 31],
|
||||||
[12, 12, 14, 19, 26, 58, 60, 55],
|
[6, 6, 7, 10, 13, 29, 30, 28],
|
||||||
[14, 13, 16, 24, 40, 57, 69, 56],
|
[7, 7, 8, 12, 20, 29, 35, 28],
|
||||||
[14, 17, 22, 29, 51, 87, 80, 62],
|
[7, 9, 11, 15, 26, 44, 40, 31],
|
||||||
[18, 22, 37, 56, 68, 109, 103, 77],
|
[9, 11, 19, 28, 34, 55, 52, 39],
|
||||||
[24, 35, 55, 64, 81, 104, 113, 92],
|
[12, 18, 28, 32, 41, 52, 57, 46],
|
||||||
[49, 64, 78, 87, 103, 121, 120, 101],
|
[25, 32, 39, 44, 52, 61, 60, 51],
|
||||||
[72, 92, 95, 98, 112, 100, 103, 99]
|
[36, 46, 48, 49, 56, 50, 52, 50]
|
||||||
])
|
])
|
||||||
|
|
||||||
|
|
||||||
@@ -40,7 +41,8 @@ def _get_zigzag_order() -> list:
|
|||||||
|
|
||||||
|
|
||||||
ZIGZAG_ORDER = _get_zigzag_order()
|
ZIGZAG_ORDER = _get_zigzag_order()
|
||||||
MID_FREQ_INDICES = ZIGZAG_ORDER[1:40]
|
# Используем больше коэффициентов (1-50)
|
||||||
|
MID_FREQ_INDICES = ZIGZAG_ORDER[1:50]
|
||||||
|
|
||||||
|
|
||||||
def _dct_quantize(block: np.ndarray) -> np.ndarray:
|
def _dct_quantize(block: np.ndarray) -> np.ndarray:
|
||||||
@@ -93,28 +95,21 @@ def _embed_bits_in_block(block_quant: np.ndarray, bits: list, bit_index: int) ->
|
|||||||
|
|
||||||
def encode_dct(image_path: str, message: str, output_path: str) -> bool:
|
def encode_dct(image_path: str, message: str, output_path: str) -> bool:
|
||||||
"""Скрывает сообщение в изображении методом DCT."""
|
"""Скрывает сообщение в изображении методом DCT."""
|
||||||
img = Image.open(image_path).convert('L')
|
img = Image.open(image_path).convert('RGB')
|
||||||
pixels = np.array(img, dtype=np.float64)
|
pixels = np.array(img, dtype=np.float64)
|
||||||
height, width = pixels.shape
|
height, width, channels = pixels.shape
|
||||||
|
|
||||||
# Кодируем сообщение в байты
|
msg_bytes = message.encode('utf-8')
|
||||||
message_bytes = message.encode('utf-8')
|
msg_length = len(msg_bytes)
|
||||||
msg_length = len(message_bytes)
|
|
||||||
|
|
||||||
# Формат: [длина: 32 бита] + [сообщение]
|
|
||||||
# 32 бита = 4 байта, достаточно для сообщений до 4 ГБ
|
|
||||||
length_bits = format(msg_length, '032b')
|
length_bits = format(msg_length, '032b')
|
||||||
message_bits = text_to_bits(message)
|
message_bits = text_to_bits(message)
|
||||||
|
|
||||||
# Собираем всё вместе
|
|
||||||
all_bits = length_bits + message_bits
|
all_bits = length_bits + message_bits
|
||||||
bit_list = [int(b) for b in all_bits]
|
bit_list = [int(b) for b in all_bits]
|
||||||
total_bits = len(bit_list)
|
total_bits = len(bit_list)
|
||||||
|
|
||||||
# Проверяем вместимость
|
|
||||||
blocks_per_row = width // BLOCK_SIZE
|
blocks_per_row = width // BLOCK_SIZE
|
||||||
blocks_per_col = height // BLOCK_SIZE
|
blocks_per_col = height // BLOCK_SIZE
|
||||||
max_bits = blocks_per_row * blocks_per_col * len(MID_FREQ_INDICES)
|
max_bits = blocks_per_row * blocks_per_col * len(MID_FREQ_INDICES) * 3
|
||||||
|
|
||||||
if total_bits > max_bits:
|
if total_bits > max_bits:
|
||||||
print(f"Ошибка: сообщение слишком большое.")
|
print(f"Ошибка: сообщение слишком большое.")
|
||||||
@@ -129,16 +124,20 @@ def encode_dct(image_path: str, message: str, output_path: str) -> bool:
|
|||||||
if bit_index >= total_bits:
|
if bit_index >= total_bits:
|
||||||
break
|
break
|
||||||
|
|
||||||
block = pixels[i:i+BLOCK_SIZE, j:j+BLOCK_SIZE]
|
for c in range(3):
|
||||||
|
if bit_index >= total_bits:
|
||||||
|
break
|
||||||
|
|
||||||
|
block = pixels[i:i+BLOCK_SIZE, j:j+BLOCK_SIZE, c]
|
||||||
quant_block = _dct_quantize(block)
|
quant_block = _dct_quantize(block)
|
||||||
quant_block, bit_index = _embed_bits_in_block(quant_block, bit_list, bit_index)
|
quant_block, bit_index = _embed_bits_in_block(quant_block, bit_list, bit_index)
|
||||||
new_block = _idct_dequantize(quant_block)
|
new_channel = _idct_dequantize(quant_block)
|
||||||
modified_pixels[i:i+BLOCK_SIZE, j:j+BLOCK_SIZE] = new_block
|
modified_pixels[i:i+BLOCK_SIZE, j:j+BLOCK_SIZE, c] = new_channel
|
||||||
|
|
||||||
if bit_index >= total_bits:
|
if bit_index >= total_bits:
|
||||||
break
|
break
|
||||||
|
|
||||||
result_img = Image.fromarray(modified_pixels.astype(np.uint8), mode='L')
|
result_img = Image.fromarray(modified_pixels.astype(np.uint8), mode='RGB')
|
||||||
result_img.save(output_path)
|
result_img.save(output_path)
|
||||||
print(f"Успешно! Спрятано {total_bits} бит ({total_bits // 8} байт)")
|
print(f"Успешно! Спрятано {total_bits} бит ({total_bits // 8} байт)")
|
||||||
return True
|
return True
|
||||||
@@ -146,21 +145,20 @@ def encode_dct(image_path: str, message: str, output_path: str) -> bool:
|
|||||||
|
|
||||||
def decode_dct(image_path: str) -> str:
|
def decode_dct(image_path: str) -> str:
|
||||||
"""Извлекает сообщение из изображения методом DCT."""
|
"""Извлекает сообщение из изображения методом DCT."""
|
||||||
img = Image.open(image_path).convert('L')
|
img = Image.open(image_path).convert('RGB')
|
||||||
pixels = np.array(img, dtype=np.float64)
|
pixels = np.array(img, dtype=np.float64)
|
||||||
height, width = pixels.shape
|
height, width, channels = pixels.shape
|
||||||
|
|
||||||
# Извлекаем все биты
|
|
||||||
all_bits = []
|
all_bits = []
|
||||||
|
|
||||||
for i in range(0, height - BLOCK_SIZE + 1, BLOCK_SIZE):
|
for i in range(0, height - BLOCK_SIZE + 1, BLOCK_SIZE):
|
||||||
for j in range(0, width - 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]
|
for c in range(3):
|
||||||
quant_block = _dct_quantize(block)
|
channel_block = pixels[i:i+BLOCK_SIZE, j:j+BLOCK_SIZE, c]
|
||||||
|
quant_block = _dct_quantize(channel_block)
|
||||||
bits_from_block = _extract_bits_from_block(quant_block, len(MID_FREQ_INDICES))
|
bits_from_block = _extract_bits_from_block(quant_block, len(MID_FREQ_INDICES))
|
||||||
all_bits.extend(bits_from_block)
|
all_bits.extend(bits_from_block)
|
||||||
|
|
||||||
# Читаем длину сообщения (первые 32 бита)
|
|
||||||
if len(all_bits) < 32:
|
if len(all_bits) < 32:
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
@@ -168,14 +166,11 @@ def decode_dct(image_path: str) -> str:
|
|||||||
length_str = ''.join(str(b) for b in length_bits)
|
length_str = ''.join(str(b) for b in length_bits)
|
||||||
msg_length = int(length_str, 2)
|
msg_length = int(length_str, 2)
|
||||||
|
|
||||||
# Проверяем, что длина разумная
|
if msg_length > (len(all_bits) - 32) // 8:
|
||||||
if msg_length > len(all_bits) // 8:
|
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
# Читаем сообщение
|
|
||||||
message_bits = all_bits[32:32 + msg_length * 8]
|
message_bits = all_bits[32:32 + msg_length * 8]
|
||||||
|
|
||||||
# Дополняем до кратности 8 (хотя уже должно быть кратно)
|
|
||||||
remainder = len(message_bits) % 8
|
remainder = len(message_bits) % 8
|
||||||
if remainder != 0:
|
if remainder != 0:
|
||||||
message_bits.extend([0] * (8 - remainder))
|
message_bits.extend([0] * (8 - remainder))
|
||||||
|
|||||||
119
core/lsb.py
119
core/lsb.py
@@ -1,132 +1,93 @@
|
|||||||
"""
|
"""
|
||||||
Модуль LSB (Least Significant Bit) стеганографии.
|
Модуль LSB (Least Significant Bit) стеганографии.
|
||||||
|
Поддержка цветных изображений и русских текстов.
|
||||||
Скрывает текст в изображении путем замены последних битов пикселей.
|
|
||||||
Каждый пиксель RGB хранит 3 бита информации (по одному в каждом канале).
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
from .utils import text_to_bits, bits_to_text
|
from .utils import text_to_bits, bits_to_text
|
||||||
|
|
||||||
|
|
||||||
def encode_lsb(image_path: str, message: str, output_path: str) -> bool:
|
def encode_lsb(image_path: str, message: str, output_path: str) -> bool:
|
||||||
"""
|
"""Скрывает сообщение в изображении методом LSB."""
|
||||||
Скрывает текстовое сообщение в изображении методом LSB.
|
|
||||||
|
|
||||||
Алгоритм:
|
|
||||||
1. Преобразует текст в битовую строку
|
|
||||||
2. Добавляет стоп-маркер (8 нулевых битов) для обозначения конца
|
|
||||||
3. Проходит по всем пикселям изображения
|
|
||||||
4. В каждом канале (R,G,B) заменяет последний бит на бит сообщения
|
|
||||||
|
|
||||||
Аргументы:
|
|
||||||
image_path (str): Путь к исходному изображению
|
|
||||||
message (str): Текст для сокрытия
|
|
||||||
output_path (str): Путь для сохранения результата
|
|
||||||
|
|
||||||
Возвращает:
|
|
||||||
bool: True если успешно, False если сообщение слишком длинное
|
|
||||||
"""
|
|
||||||
# Загружаем изображение
|
|
||||||
img = Image.open(image_path).convert('RGB')
|
img = Image.open(image_path).convert('RGB')
|
||||||
pixels = np.array(img, dtype=np.uint8)
|
pixels = np.array(img, dtype=np.uint8)
|
||||||
height, width, channels = pixels.shape
|
height, width, channels = pixels.shape
|
||||||
|
|
||||||
# Преобразуем сообщение в биты и добавляем стоп-маркер
|
# Формат: [длина сообщения: 32 бита] + [сообщение]
|
||||||
bits = text_to_bits(message) + '00000000'
|
msg_bytes = message.encode('utf-8')
|
||||||
|
msg_length = len(msg_bytes)
|
||||||
|
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)
|
||||||
|
|
||||||
# Проверяем, поместится ли сообщение
|
# Проверка вместимости
|
||||||
max_bits = height * width * channels
|
max_bits = height * width * channels
|
||||||
if len(bits) > max_bits:
|
if total_bits > max_bits:
|
||||||
print(f"Ошибка: сообщение слишком большое.")
|
print(f"Ошибка: сообщение слишком большое.")
|
||||||
print(f"Доступно битов: {max_bits}, требуется: {len(bits)}")
|
print(f"Доступно: {max_bits} бит, требуется: {total_bits}")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# Прячем биты в изображение
|
# Внедрение битов
|
||||||
bit_index = 0
|
bit_index = 0
|
||||||
total_bits = len(bits)
|
|
||||||
|
|
||||||
for i in range(height):
|
for i in range(height):
|
||||||
for j in range(width):
|
for j in range(width):
|
||||||
for c in range(channels):
|
for c in range(channels):
|
||||||
if bit_index >= total_bits:
|
if bit_index >= total_bits:
|
||||||
break
|
break
|
||||||
|
|
||||||
pixel_value = pixels[i, j, c]
|
pixel_value = pixels[i, j, c]
|
||||||
bit = int(bits[bit_index])
|
bit = bit_list[bit_index]
|
||||||
|
|
||||||
# Обнуляем последний бит и устанавливаем нужный
|
|
||||||
pixels[i, j, c] = (pixel_value & 0xFE) | bit
|
pixels[i, j, c] = (pixel_value & 0xFE) | bit
|
||||||
bit_index += 1
|
bit_index += 1
|
||||||
|
|
||||||
if bit_index >= total_bits:
|
if bit_index >= total_bits:
|
||||||
break
|
break
|
||||||
if bit_index >= total_bits:
|
if bit_index >= total_bits:
|
||||||
break
|
break
|
||||||
|
|
||||||
# Сохраняем измененное изображение
|
|
||||||
result_img = Image.fromarray(pixels, mode='RGB')
|
result_img = Image.fromarray(pixels, mode='RGB')
|
||||||
result_img.save(output_path)
|
result_img.save(output_path)
|
||||||
|
|
||||||
print(f"Успешно! Спрятано {total_bits} бит ({total_bits // 8} байт)")
|
print(f"Успешно! Спрятано {total_bits} бит ({total_bits // 8} байт)")
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
def decode_lsb(image_path: str) -> str:
|
def decode_lsb(image_path: str) -> str:
|
||||||
"""
|
"""Извлекает сообщение из изображения методом LSB."""
|
||||||
Извлекает скрытое сообщение из изображения методом LSB.
|
|
||||||
|
|
||||||
Алгоритм:
|
|
||||||
1. Проходит по всем пикселям изображения
|
|
||||||
2. Из каждого канала извлекает последний бит
|
|
||||||
3. Собирает биты в байты
|
|
||||||
4. Останавливается при обнаружении стоп-маркера (8 нулевых битов)
|
|
||||||
|
|
||||||
Аргументы:
|
|
||||||
image_path (str): Путь к изображению со скрытым сообщением
|
|
||||||
|
|
||||||
Возвращает:
|
|
||||||
str: Извлеченное сообщение
|
|
||||||
"""
|
|
||||||
# Загружаем изображение
|
|
||||||
img = Image.open(image_path).convert('RGB')
|
img = Image.open(image_path).convert('RGB')
|
||||||
pixels = np.array(img, dtype=np.uint8)
|
pixels = np.array(img, dtype=np.uint8)
|
||||||
height, width, channels = pixels.shape
|
height, width, channels = pixels.shape
|
||||||
|
|
||||||
# Извлекаем биты
|
# Извлекаем все биты
|
||||||
extracted_bits = []
|
all_bits = []
|
||||||
stop_counter = 0
|
|
||||||
stop_marker_length = 8
|
|
||||||
|
|
||||||
for i in range(height):
|
for i in range(height):
|
||||||
for j in range(width):
|
for j in range(width):
|
||||||
for c in range(channels):
|
for c in range(channels):
|
||||||
# Извлекаем последний бит
|
all_bits.append(pixels[i, j, c] & 1)
|
||||||
bit = pixels[i, j, c] & 1
|
|
||||||
extracted_bits.append(bit)
|
|
||||||
|
|
||||||
# Проверяем стоп-маркер
|
# Читаем длину сообщения (первые 32 бита)
|
||||||
if bit == 0:
|
if len(all_bits) < 32:
|
||||||
stop_counter += 1
|
|
||||||
if stop_counter == stop_marker_length:
|
|
||||||
# Убираем стоп-маркер из результата
|
|
||||||
extracted_bits = extracted_bits[:-stop_marker_length]
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
stop_counter = 0
|
|
||||||
|
|
||||||
if stop_counter == stop_marker_length:
|
|
||||||
break
|
|
||||||
if stop_counter == stop_marker_length:
|
|
||||||
break
|
|
||||||
|
|
||||||
if len(extracted_bits) == 0:
|
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
# Преобразуем биты в строку и декодируем
|
length_bits = all_bits[:32]
|
||||||
bits_string = ''.join(str(bit) for bit in extracted_bits)
|
length_str = ''.join(str(b) for b in length_bits)
|
||||||
|
msg_length = int(length_str, 2)
|
||||||
|
|
||||||
|
# Проверяем, что длина разумная
|
||||||
|
if msg_length > (len(all_bits) - 32) // 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:
|
try:
|
||||||
return bits_to_text(bits_string)
|
return bits_to_text(bits_string)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|||||||
5
main.py
5
main.py
@@ -81,7 +81,7 @@ def encode_command(args) -> None:
|
|||||||
print("Ошибка: укажите сообщение через -m или -f")
|
print("Ошибка: укажите сообщение через -m или -f")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
# Проверяем, поместится ли сообщение (только для LSB)
|
# Проверяем вместимость (только для LSB)
|
||||||
if args.method == 'lsb':
|
if args.method == 'lsb':
|
||||||
try:
|
try:
|
||||||
capacity = calculate_capacity(args.image)
|
capacity = calculate_capacity(args.image)
|
||||||
@@ -104,14 +104,12 @@ def encode_command(args) -> None:
|
|||||||
success = encode_lsb(args.image, message, args.output)
|
success = encode_lsb(args.image, message, args.output)
|
||||||
elif args.method == 'dct':
|
elif args.method == 'dct':
|
||||||
success = encode_dct(args.image, message, args.output)
|
success = encode_dct(args.image, message, args.output)
|
||||||
success = False
|
|
||||||
else:
|
else:
|
||||||
print(f"Неизвестный метод: {args.method}")
|
print(f"Неизвестный метод: {args.method}")
|
||||||
success = False
|
success = False
|
||||||
|
|
||||||
if success:
|
if success:
|
||||||
print("Кодирование завершено успешно!")
|
print("Кодирование завершено успешно!")
|
||||||
|
|
||||||
if args.psnr:
|
if args.psnr:
|
||||||
show_psnr(args.image, args.output)
|
show_psnr(args.image, args.output)
|
||||||
else:
|
else:
|
||||||
@@ -127,7 +125,6 @@ def decode_command(args) -> None:
|
|||||||
message = decode_lsb(args.image)
|
message = decode_lsb(args.image)
|
||||||
elif args.method == 'dct':
|
elif args.method == 'dct':
|
||||||
message = decode_dct(args.image)
|
message = decode_dct(args.image)
|
||||||
message = None
|
|
||||||
else:
|
else:
|
||||||
print(f"Неизвестный метод: {args.method}")
|
print(f"Неизвестный метод: {args.method}")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|||||||
Reference in New Issue
Block a user