Python 3.4
- Бонус 1: самообращение: повторное восстановление исходного изображения.
- Необязательный ключевой образ: исходное изображение может быть восстановлено только при повторном использовании того же ключевого изображения.
- Бонус 2: Создание шаблона на выходе: изображение ключа аппроксимируется в зашифрованных пикселях.
При достижении бонуса 2 при использовании дополнительного ключевого изображения бонус 1 не теряется. Программа по-прежнему самообращена при условии, что она снова запускается с тем же ключевым изображением.
Стандартное использование
Тестовое изображение 1:
Тестовое изображение 2:
Запуск программы с одним файлом изображения в качестве аргумента сохраняет файл изображения с равномерно скремблированными пикселями по всему изображению. Повторный запуск с зашифрованным выводом сохраняет файл изображения с примененным скремблированием, который восстанавливает оригинал, поскольку процесс скремблирования имеет свой собственный обратный процесс.
Процесс скремблирования является самообращенным, потому что список всех пикселей разбит на 2 цикла, так что каждый пиксель заменяется одним и только одним другим пикселем. Второй запуск приводит к замене каждого пикселя на тот, с которым он был вначале заменен, и все возвращается к тому, как это началось. Если есть нечетное количество пикселей, будет один, который не перемещается.
Благодаря ответу mfvonh как первому предложить 2 цикла.
Использование с ключевым изображением
Скремблирующее тестовое изображение 1 с тестовым изображением 2 в качестве ключевого изображения
Скремблирующее тестовое изображение 2 с тестовым изображением 1 в качестве ключевого изображения
Запуск программы со вторым аргументом файла изображения (изображение ключа) делит исходное изображение на области на основе изображения ключа. Каждая из этих областей разделена на 2 цикла по отдельности, так что все скремблирование происходит внутри областей, и пиксели не перемещаются из одной области в другую. Это распределяет пиксели по каждой области, и таким образом области становятся однородным крапчатым цветом, но с немного другим средним цветом для каждой области. Это дает очень грубое приближение к ключевому изображению в неправильных цветах.
При повторном запуске меняются одинаковые пары пикселей в каждой области, поэтому каждая область восстанавливается в исходное состояние, и изображение в целом снова появляется.
Благодаря EDC65 ответ как первый предложил разделить изображение на регионы. Я хотел бы расширить это, чтобы использовать произвольные регионы, но подход замены всего в области 1 на все в области 2 означал, что регионы должны быть одинакового размера. Мое решение состоит в том, чтобы сохранить регионы изолированными друг от друга и просто перетасовать каждый регион в себя. Поскольку области больше не должны быть одинакового размера, становится проще применять области произвольной формы.
Код
import os.path
from PIL import Image # Uses Pillow, a fork of PIL for Python 3
from random import randrange, seed
def scramble(input_image_filename, key_image_filename=None,
number_of_regions=16777216):
input_image_path = os.path.abspath(input_image_filename)
input_image = Image.open(input_image_path)
if input_image.size == (1, 1):
raise ValueError("input image must contain more than 1 pixel")
number_of_regions = min(int(number_of_regions),
number_of_colours(input_image))
if key_image_filename:
key_image_path = os.path.abspath(key_image_filename)
key_image = Image.open(key_image_path)
else:
key_image = None
number_of_regions = 1
region_lists = create_region_lists(input_image, key_image,
number_of_regions)
seed(0)
shuffle(region_lists)
output_image = swap_pixels(input_image, region_lists)
save_output_image(output_image, input_image_path)
def number_of_colours(image):
return len(set(list(image.getdata())))
def create_region_lists(input_image, key_image, number_of_regions):
template = create_template(input_image, key_image, number_of_regions)
number_of_regions_created = len(set(template))
region_lists = [[] for i in range(number_of_regions_created)]
for i in range(len(template)):
region = template[i]
region_lists[region].append(i)
odd_region_lists = [region_list for region_list in region_lists
if len(region_list) % 2]
for i in range(len(odd_region_lists) - 1):
odd_region_lists[i].append(odd_region_lists[i + 1].pop())
return region_lists
def create_template(input_image, key_image, number_of_regions):
if number_of_regions == 1:
width, height = input_image.size
return [0] * (width * height)
else:
resized_key_image = key_image.resize(input_image.size, Image.NEAREST)
pixels = list(resized_key_image.getdata())
pixel_measures = [measure(pixel) for pixel in pixels]
distinct_values = list(set(pixel_measures))
number_of_distinct_values = len(distinct_values)
number_of_regions_created = min(number_of_regions,
number_of_distinct_values)
sorted_distinct_values = sorted(distinct_values)
while True:
values_per_region = (number_of_distinct_values /
number_of_regions_created)
value_to_region = {sorted_distinct_values[i]:
int(i // values_per_region)
for i in range(len(sorted_distinct_values))}
pixel_regions = [value_to_region[pixel_measure]
for pixel_measure in pixel_measures]
if no_small_pixel_regions(pixel_regions,
number_of_regions_created):
break
else:
number_of_regions_created //= 2
return pixel_regions
def no_small_pixel_regions(pixel_regions, number_of_regions_created):
counts = [0 for i in range(number_of_regions_created)]
for value in pixel_regions:
counts[value] += 1
if all(counts[i] >= 256 for i in range(number_of_regions_created)):
return True
def shuffle(region_lists):
for region_list in region_lists:
length = len(region_list)
for i in range(length):
j = randrange(length)
region_list[i], region_list[j] = region_list[j], region_list[i]
def measure(pixel):
'''Return a single value roughly measuring the brightness.
Not intended as an accurate measure, simply uses primes to prevent two
different colours from having the same measure, so that an image with
different colours of similar brightness will still be divided into
regions.
'''
if type(pixel) is int:
return pixel
else:
r, g, b = pixel[:3]
return r * 2999 + g * 5869 + b * 1151
def swap_pixels(input_image, region_lists):
pixels = list(input_image.getdata())
for region in region_lists:
for i in range(0, len(region) - 1, 2):
pixels[region[i]], pixels[region[i+1]] = (pixels[region[i+1]],
pixels[region[i]])
scrambled_image = Image.new(input_image.mode, input_image.size)
scrambled_image.putdata(pixels)
return scrambled_image
def save_output_image(output_image, full_path):
head, tail = os.path.split(full_path)
if tail[:10] == 'scrambled_':
augmented_tail = 'rescued_' + tail[10:]
else:
augmented_tail = 'scrambled_' + tail
save_filename = os.path.join(head, augmented_tail)
output_image.save(save_filename)
if __name__ == '__main__':
import sys
arguments = sys.argv[1:]
if arguments:
scramble(*arguments[:3])
else:
print('\n'
'Arguments:\n'
' input image (required)\n'
' key image (optional, default None)\n'
' number of regions '
'(optional maximum - will be as high as practical otherwise)\n')
Запись изображения JPEG
Файлы .jpg обрабатываются очень быстро, но за счет слишком горячей работы. Это оставляет сгоревшее изображение после восстановления оригинала:
А если серьезно, формат с потерями приведет к тому, что некоторые цвета пикселей будут слегка изменены, что само по себе делает вывод недействительным. Когда используется ключевое изображение и перетасовка пикселей ограничена областями, все искажения сохраняются в той области, в которой они произошли, а затем равномерно распределяются по этой области при восстановлении изображения. Разница в среднем искажении между областями оставляет видимую разницу между ними, поэтому области, используемые в процессе скремблирования, все еще видны на восстановленном изображении.
Преобразование в формат .png (или любой другой формат без потерь) перед скремблированием гарантирует, что нешифрованное изображение идентично оригиналу без прожигания или искажения:
Маленькие детали
- Минимальный размер 256 пикселей накладывается на регионы. Если изображение было разрешено разделить на области, которые слишком малы, то исходное изображение все еще будет частично видимым после скремблирования.
- Если существует более одной области с нечетным числом пикселей, то один пиксель из второй области переназначается первому и т. Д. Это означает, что может быть только одна область с нечетным количеством пикселей, и поэтому только один пиксель останется расшифрованным.
- Существует третий необязательный аргумент, который ограничивает количество регионов. Например, установка этого параметра на 2 даст два скремблированных изображения. Это может выглядеть лучше или хуже в зависимости от изображения. Если здесь указано число, изображение можно восстановить только с использованием того же номера снова.
- Количество различных цветов в исходном изображении также ограничивает количество областей. Если исходное изображение имеет два тона, то независимо от ключевого изображения или третьего аргумента может быть не более 2 областей.