Python 3.4
- Bonus 1: Inverse de soi: répéter répète l'image d'origine.
- Image clé optionnelle: l'image d'origine ne peut être restaurée qu'en utilisant à nouveau la même image clé.
- Bonus 2: Motif de production dans la sortie: l'image clé est approchée en pixels brouillés.
Lorsque le bonus 2 est atteint, en utilisant une image clé supplémentaire, le bonus 1 n'est pas perdu. Le programme est toujours auto-inverse, à condition qu'il soit exécuté à nouveau avec la même image clé.
Utilisation standard
Image test 1:
Test image 2:
L'exécution du programme avec un seul fichier image comme argument enregistre un fichier image avec les pixels embrouillés de manière uniforme sur toute l'image. Le relancer avec la sortie brouillée enregistre un fichier image avec le brouillage appliqué à nouveau, ce qui restaure le fichier original puisque le processus de brouillage est son propre inverse.
Le processus d'embrouillage est auto-inverse car la liste de tous les pixels est divisée en deux cycles, de sorte que chaque pixel est échangé avec un et un seul autre pixel. Son exécution une seconde fois permet d'échanger chaque pixel avec le pixel avec lequel elle a été échangée pour la première fois, en reprenant le principe initial. S'il y a un nombre impair de pixels, il y en aura un qui ne bouge pas.
Merci à la réponse de mfvonh en tant que premier à suggérer 2 cycles.
Utilisation avec une image clé
Scrambling Test image 1 avec l'image test 2 comme image clé
Scrambling Test image 2 avec l'image test 1 comme image clé
L'exécution du programme avec un deuxième argument de fichier image (l'image clé) divise l'image d'origine en régions basées sur l'image clé. Chacune de ces régions est divisée en 2 cycles séparément, de sorte que tout le brouillage se produit dans les régions et que les pixels ne sont pas déplacés d'une région à une autre. Cela répartit les pixels sur chaque région et les régions deviennent alors une couleur uniforme mouchetée, mais avec une couleur moyenne légèrement différente pour chaque région. Cela donne une approximation très approximative de l'image clé, dans les mauvaises couleurs.
Une nouvelle exécution permute les mêmes paires de pixels dans chaque région. Ainsi, chaque région retrouve son état d'origine et l'image dans son ensemble réapparaît.
Grâce à la réponse de edc65, le premier à suggérer de diviser l’image en régions. Je voulais développer cela pour utiliser des régions arbitraires, mais l'approche consistant à tout permuter dans la région 1 avec tout dans la région 2 impliquait que les régions soient de taille identique. Ma solution est de garder les régions isolées les unes des autres et de mélanger simplement chaque région. Puisque les régions n'ont plus besoin d'être de taille similaire, il devient plus simple d'appliquer des régions de forme arbitraire.
Code
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')
Gravure d'image JPEG
Les fichiers .jpg sont traités très rapidement, mais au prix d'une surchauffe. Cela laisse une image après gravure lorsque l'original est restauré:
Mais sérieusement, un format avec perte entraînera une légère modification de certaines couleurs de pixel, ce qui rend la sortie invalide. Lorsqu'une image clé est utilisée et que le mélange de pixels est limité à des régions, toute la distorsion est conservée dans la région dans laquelle elle s'est produite, puis répartie uniformément sur cette région lorsque l'image est restaurée. La différence de distorsion moyenne entre les régions laisse une différence visible entre elles, de sorte que les régions utilisées dans le processus d'embrouillage sont toujours visibles dans l'image restaurée.
La conversion au format .png (ou à tout autre format sans perte) avant le brouillage garantit que l'image non brouillée est identique à l'original, sans gravure ni distorsion:
Petits détails
- Une taille minimale de 256 pixels est imposée aux régions. Si l’on permettait à l’image de se diviser en régions trop petites, l’image d’origine serait toujours partiellement visible après le brouillage.
- S'il y a plus d'une région avec un nombre impair de pixels, un pixel de la deuxième région est réaffecté à la première, et ainsi de suite. Cela signifie qu'il ne peut jamais y avoir qu'une seule région avec un nombre impair de pixels et qu'un seul pixel restera non brouillé.
- Il existe un troisième argument optionnel qui limite le nombre de régions. Le réglage de cette valeur sur 2, par exemple, donnera des images codées à deux tons. Cela peut sembler meilleur ou pire selon les images impliquées. Si un numéro est spécifié ici, l'image ne peut être restaurée qu'avec le même numéro.
- Le nombre de couleurs distinctes dans l'image d'origine limite également le nombre de régions. Si l'image d'origine est à deux tons, quelle que soit l'image clé ou le troisième argument, il ne peut y avoir qu'un maximum de 2 régions.