Каждый пиксель изображения имеет три составляющих трёх разных цветов (красный, зелёный и синий), и значение каждой составляющей может меняться от 0 до 255. Разница между, например, 140 и 150 заметна простым глазом, но очень невелика. А в промежуток между 140 и 150 можно запихать ещё десять разных чисел (включая ноль)! Можно взять одно изображение, и все значения всех составляющих каждого пикселя округлить до десятков. А у второго изображения все значения всех составляющих поделить на 26, чтоб получились числа от 0 до 9. И всё это сложить точка за точкой и цвет за цветом, таким образом запихав одно изображение в щели в другом.
Вот в этой картинке я буду прятать другую. Выбор отстойный - большая площадь однотонная (будет заметно, что там что-то прорисовывается постороннее), да и сама картинка чёрно-белая. Будет странно заметить в ней ставшие чуть более цветными участки.

А вот эту картинку я буду прятать.

Промежуточный этап - базовое изображение с округлёнными значениями составляющих всех цветов.

А вот базовое изображение, которое уже содержит в себе скрытое.

Видите, если присмотреться, то видно местами, что там что-то ещё просвечивает? Также советую обратить внимание, что это формат PNG. Дело в том, что JPEG - формат с компрессией с потерями, и высокий уровень сжатия как раз достигается за счёт потерь. А в качестве потерь теряется как раз малозначительная и малозаметная информация - в которой как раз второе изображение и спрятано! Извлечение скрытого изображения: обратная операция. Для каждого пикселя для каждой составляющей взять остаток от деления на десять, и умножить его на 26.
Извлечённое изображение.

А теперь - примеры того, почему нельзя использовать JPEG.
Что получится, если извлечь скрытое изображение из картинки, сохранённой в джипеге с качеством 100:

95:

80:

Примеры кода.
encode.py
#!/bin/env/python import sys import math from PIL import Image if len(sys.argv) != 3: print("Usage: encode.py <base image> <image to hide>") exit(0) try: base_image = Image.open(sys.argv[1]) base_w, base_h = base_image.size except: print("cannot open image " + sys.argv[1]) exit(0) try: hide_image = Image.open(sys.argv[2]) hide_w, hide_h = hide_image.size except: print("cannot open image " + sys.argv[2]) exit(0) for y in range(0, base_h - 1): for x in range(0, base_w - 1): if x < hide_w and y < hide_h: hp = list(hide_image.getpixel((x, y))) hp[0] = math.floor(float(hp[0]) / 26) hp[1] = math.floor(float(hp[1]) / 26) hp[2] = math.floor(float(hp[2]) / 26) bp = list(base_image.getpixel((x, y))) bp[0] = max(0, min(255, bp[0] - bp[0] % 10 + hp[0])) bp[1] = max(0, min(255, bp[1] - bp[1] % 10 + hp[1])) bp[2] = max(0, min(255, bp[2] - bp[2] % 10 + hp[2])) base_image.putpixel((x, y), tuple(bp)) base_image.save('encocded_image.png', 'PNG')
decode.py
#!/bin/env/python import sys from PIL import Image if len(sys.argv) != 2: print("Usage: decode.py <encoded image>") exit(0) try: base_image = Image.open(sys.argv[1]) base_w, base_h = base_image.size except: print("cannot open image " + sys.argv[1]) exit(0) for y in range(0, base_h - 1): for x in range(0, base_w - 1): p = list(base_image.getpixel((x, y))) p[0] = max(0, min(255, 26 * (p[0] % 10))) p[1] = max(0, min(255, 26 * (p[1] % 10))) p[2] = max(0, min(255, 26 * (p[2] % 10))) base_image.putpixel((x, y), tuple(p)) base_image.save('decoded_result.jpg', 'JPEG', quality=95)
Комментариев нет:
Отправить комментарий
Ублюдочный Гугл поломал форму комментариев. Извините.
Примечание. Отправлять комментарии могут только участники этого блога.