Python 3.4
- Bono 1: Auto inverso: la repetición restaura la imagen original.
- Imagen clave opcional: la imagen original solo se puede restaurar utilizando la misma imagen clave nuevamente.
- Bonus 2: Producción de patrón en la salida: la imagen clave se aproxima en los píxeles codificados.
Cuando se alcanza la bonificación 2, al usar una imagen clave adicional, la bonificación 1 no se pierde. El programa todavía es auto inverso, siempre que se ejecute con la misma imagen clave nuevamente.
Uso estándar
Imagen de prueba 1:
Imagen de prueba 2:
Ejecutar el programa con un solo archivo de imagen como argumento guarda un archivo de imagen con los píxeles revueltos uniformemente sobre toda la imagen. Ejecutarlo nuevamente con la salida codificada guarda un archivo de imagen con la codificación aplicada nuevamente, lo que restaura el original ya que el proceso de codificación es inverso.
El proceso de aleatorización es inverso a sí mismo porque la lista de todos los píxeles se divide en 2 ciclos, de modo que cada píxel se intercambia con uno y solo otro píxel. Ejecutarlo por segunda vez intercambia cada píxel con el píxel con el que se intercambió por primera vez, volviendo todo a cómo comenzó. Si hay un número impar de píxeles, habrá uno que no se mueva.
Gracias a la respuesta de mfvonh como la primera en sugerir 2 ciclos.
Uso con una imagen clave
Aleatorización de la imagen de prueba 1 con la imagen de prueba 2 como imagen clave
Aleatorización de la imagen de prueba 2 con la imagen de prueba 1 como imagen clave
Ejecutar el programa con un segundo argumento de archivo de imagen (la imagen clave) divide la imagen original en regiones basadas en la imagen clave. Cada una de estas regiones se divide en 2 ciclos por separado, de modo que toda la codificación ocurre dentro de las regiones, y los píxeles no se mueven de una región a otra. Esto extiende los píxeles sobre cada región y, por lo tanto, las regiones se convierten en un color moteado uniforme, pero con un color promedio ligeramente diferente para cada región. Esto proporciona una aproximación muy aproximada de la imagen clave, en los colores incorrectos.
La ejecución de nuevo intercambia los mismos pares de píxeles en cada región, por lo que cada región se restaura a su estado original y la imagen en su conjunto vuelve a aparecer.
Gracias a la respuesta de edc65 como la primera en sugerir dividir la imagen en regiones. Quería ampliar esto para usar regiones arbitrarias, pero el enfoque de intercambiar todo en la región 1 con todo en la región 2 significaba que las regiones tenían que ser del mismo tamaño. Mi solución es mantener las regiones aisladas unas de otras, y simplemente mezclar cada región en sí misma. Como las regiones ya no necesitan tener un tamaño similar, se vuelve más simple aplicar regiones con formas arbitrarias.
Código
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')
Grabación de imagen JPEG
Los archivos .jpg se procesan muy rápidamente, pero a costa de correr demasiado caliente. Esto deja una imagen posterior quemada cuando se restaura el original:
Pero en serio, un formato con pérdida dará como resultado que algunos de los colores de los píxeles se cambien ligeramente, lo que en sí mismo invalida la salida. Cuando se usa una imagen clave y la combinación aleatoria de píxeles se restringe a regiones, toda la distorsión se mantiene dentro de la región en la que sucedió, y luego se extiende uniformemente sobre esa región cuando se restaura la imagen. La diferencia en la distorsión promedio entre regiones deja una diferencia visible entre ellas, por lo que las regiones utilizadas en el proceso de codificación aún son visibles en la imagen restaurada.
La conversión a .png (o cualquier formato sin pérdidas) antes de codificar garantiza que la imagen sin codificar sea idéntica a la original sin quemaduras ni distorsiones:
Pequeños detalles
- Se impone un tamaño mínimo de 256 píxeles en las regiones. Si se permitiera que la imagen se dividiera en regiones que son demasiado pequeñas, la imagen original aún sería parcialmente visible después de la codificación.
- Si hay más de una región con un número impar de píxeles, un píxel de la segunda región se reasigna a la primera, y así sucesivamente. Esto significa que solo puede haber una región con un número impar de píxeles, por lo que solo un píxel permanecerá sin codificar.
- Hay un tercer argumento opcional que restringe el número de regiones. Establecer esto en 2, por ejemplo, dará dos imágenes codificadas en tonos. Esto puede verse mejor o peor dependiendo de las imágenes involucradas. Si se especifica un número aquí, la imagen solo se puede restaurar utilizando el mismo número nuevamente.
- El número de colores distintos en la imagen original también limita el número de regiones. Si la imagen original es de dos tonos, independientemente de la imagen clave o del tercer argumento, solo puede haber un máximo de 2 regiones.