├── Genetic.py ├── LICENSE ├── README.md ├── image.jpg ├── images ├── 0.gif ├── 1.gif ├── 2.gif ├── 3.gif ├── 4.gif ├── 5.gif ├── 6.gif ├── 7.gif └── Original.jpg ├── original.jpg └── requirements.txt /Genetic.py: -------------------------------------------------------------------------------- 1 | """ 2 | Script for recreating image using genetic alghoritm. 3 | """ 4 | 5 | import string 6 | import random 7 | from skimage.metrics import peak_signal_noise_ratio as psns # For image similarity evaluation 8 | from PIL import Image, ImageDraw, ImageFont 9 | import cv2 10 | import numpy as np 11 | import imageio # For gif saving 12 | 13 | #Load and show original image for tracking (convert to black and white) 14 | original_image = Image.open("image.jpg").convert("L") 15 | original_height, original_width = original_image.size 16 | cv2.imshow("Original", np.array(original_image)) 17 | cv2.imwrite("original.jpg",np.array(original_image)) 18 | 19 | #Adjust hyperparameters 20 | NUMBER_OF_GENERATIONS = 7500 21 | POPULATION_NUMBER = 50 # How many images in 1 generation (without elitism) 22 | MUTATION_CHANCE = 0.1 # Chance of mutating (adding random shapes) 23 | MUTATION_STRENGTH = 1 # How many shapes to add in mutation 24 | ELITISM = True # Turn on/off elitism (transfering best images to next generation without crossover) 25 | ELITISM_NUMBER = 4 # How many best images transfer to next generation (elitism) 26 | STARTING_SHAPE_NUMBER = 6 # How many shapes to draw on each image in first generation 27 | 28 | PRINT_EVERY_GEN = 25 # Print fitness value every x generations 29 | SAVE_FRAME_FOR_GIF_EVERY = 100 # Save best image every x generations for gif creation 30 | 31 | # What to draw functions 32 | 33 | def draw_rectangle(image, size=10): 34 | """Draw rectangle on image with given size.""" 35 | x = random.randint(0,original_width-1) 36 | y = random.randint(0,original_height-1) 37 | 38 | color = (random.randint(0,255)) 39 | 40 | image.rectangle([(y,x), (y+size,x+size)], fill=color) 41 | 42 | 43 | def draw_line(image): 44 | """Draw random line on image.""" 45 | x1 = random.randint(0,original_width-1) 46 | y1 = random.randint(0,original_height-1) 47 | 48 | x2 = random.randint(0,original_width-1) 49 | y2 = random.randint(0,original_height-1) 50 | 51 | thickness_value = random.randint(1, 4) 52 | color = (random.randint(0,255)) 53 | 54 | image.line([(y1,x1), (y2,x2)], fill=color, width=thickness_value) 55 | 56 | 57 | def draw_text(image, size=20): 58 | """Draw random text on image with given size.""" 59 | font = ImageFont.truetype("arial.ttf", size) 60 | text_length = random.randint(1,3) 61 | text = "".join(random.choice(string.ascii_letters) for i in range(text_length)) 62 | 63 | x = random.randint(0,original_width-1) 64 | y = random.randint(0,original_height-1) 65 | 66 | color = (random.randint(0,255)) 67 | image.text((y,x), text, fill=color, font=font) 68 | 69 | 70 | def add_random_shape_to_image(image, number_of_shapes): 71 | """Add shape with random proporties to image number_of_shapes times.""" 72 | image_filled = image.copy() 73 | for _ in range(0, number_of_shapes): 74 | draw = ImageDraw.Draw(image_filled) 75 | draw_text(draw) 76 | return image_filled 77 | 78 | 79 | def create_random_population(size): 80 | """Create first generation with random population.""" 81 | first_population = [] 82 | for _ in range(0, size): 83 | blank_image = Image.new("L", (original_height, original_width)) 84 | filled_image = add_random_shape_to_image(blank_image, MUTATION_STRENGTH) 85 | first_population.append(filled_image) 86 | return first_population 87 | 88 | 89 | def evaluate_fitness(image): 90 | """Evaluate similarity of image with original.""" 91 | return psns(np.array(image), np.array(original_image)) 92 | 93 | 94 | # Crossover operations with alternatives and helpers 95 | 96 | def images_to_arrays(image1, image2): 97 | """Represent images as arrays.""" 98 | img1_arr = np.array(image1) 99 | img2_arr = np.array(image2) 100 | return img1_arr ,img2_arr 101 | 102 | 103 | def blending(image1, image2): 104 | """Blend to images together with 0.5 alpha.""" 105 | return Image.blend(image1, image2, alpha=0.5) 106 | 107 | 108 | def random_horizontal_swap(image1, image2): 109 | """Swap random rows of two images.""" 110 | img1_arr, img2_arr = images_to_arrays(image1, image2) 111 | horizontal_random_choice = np.random.choice(original_width, 112 | int(original_width/2), 113 | replace=False) 114 | img1_arr[horizontal_random_choice] = img2_arr[horizontal_random_choice] 115 | return Image.fromarray(img1_arr) 116 | 117 | 118 | def random_vertical_swap(image1, image2): 119 | """Swap random columns of two images.""" 120 | img1_arr, img2_arr = images_to_arrays(image1, image2) 121 | vertical_random_choice = np.random.choice(original_height, 122 | int(original_height/2), 123 | replace=False) 124 | img1_arr[:,vertical_random_choice] = img2_arr[:,vertical_random_choice] 125 | return Image.fromarray(img1_arr) 126 | 127 | 128 | def half_vertical_swap(image1, image2): 129 | """Swap images halfs (verticaly).""" 130 | img1_arr, img2_arr = images_to_arrays(image1, image2) 131 | img1_half = img1_arr[0:int(original_height/2),] 132 | img2_half = img2_arr[int(original_height/2):original_height,] 133 | return np.vstack((img1_half, img2_half)) 134 | 135 | 136 | def half_horizontal_swap(image1, image2): 137 | """Swap images halfs (horizontaly).""" 138 | img1_arr, img2_arr = images_to_arrays(image1, image2) 139 | img1_half = img1_arr[:,0:int(original_width/2)] 140 | img2_half = img2_arr[:,int(original_width/2):original_width] 141 | return np.hstack((img1_half, img2_half)) 142 | 143 | 144 | def crossover(image1, image2): 145 | """Make crossover operation on two images.""" 146 | return random_horizontal_swap(image1, image2) 147 | 148 | 149 | def mutate(image, number_of_times): 150 | """Mutate image adding random shape number_of_times.""" 151 | mutated = add_random_shape_to_image(image, number_of_times) 152 | return mutated 153 | 154 | 155 | def get_parents(local_population, local_fitnesses): 156 | """Connect parents in pairs based on fitnesses as weights using softmax.""" 157 | fitness_sum = sum(np.exp(local_fitnesses)) 158 | fitness_normalized = np.exp(local_fitnesses) / fitness_sum 159 | local_parents_list = [] 160 | for _ in range(0, len(local_population)): 161 | parents = random.choices(local_population, weights=fitness_normalized, k=2) 162 | local_parents_list.append(parents) 163 | return local_parents_list 164 | 165 | def whole_pipeline(): 166 | """Go through whole pipeline and execute it.""" 167 | save_gif = [] #Creating empty frames list for gif saving at the end 168 | 169 | # Create first generation 170 | population = create_random_population(POPULATION_NUMBER) 171 | 172 | # Loop through generations 173 | for generation in range(0, NUMBER_OF_GENERATIONS): 174 | 175 | # Calculate similarity of each image in population to original image 176 | fitnesses = [] 177 | for img in population: 178 | actual_fitness = evaluate_fitness(img) 179 | fitnesses.append(actual_fitness) 180 | 181 | # Get ids of best images in population 182 | top_population_ids = np.argsort(fitnesses)[-ELITISM_NUMBER:] 183 | 184 | # Start creating new population for next generation 185 | new_population = [] 186 | 187 | # Connect parent into pairs 188 | parents_list = get_parents(population, fitnesses) 189 | 190 | # Create childs 191 | for i in range(0, POPULATION_NUMBER): 192 | new_img = crossover(parents_list[i][0], parents_list[i][1]) 193 | #Mutate 194 | if random.uniform(0.0, 1.0) < MUTATION_CHANCE: 195 | new_img = mutate(new_img, MUTATION_STRENGTH) 196 | new_population.append(new_img) 197 | 198 | # Elitism transfer 199 | if ELITISM: 200 | for ids in top_population_ids: 201 | new_population.append(population[ids]) 202 | 203 | # Print info every x generations 204 | if generation % PRINT_EVERY_GEN == 0: 205 | print(generation) 206 | print(fitnesses[top_population_ids[0]]) 207 | 208 | # Get best actual image and show it 209 | open_cv_image = np.array(population[top_population_ids[0]]) 210 | cv2.imshow("test", open_cv_image) 211 | 212 | # Gif creation 213 | if generation % SAVE_FRAME_FOR_GIF_EVERY == 0: 214 | save_gif.append(open_cv_image) 215 | 216 | cv2.waitKey(1) 217 | population = new_population 218 | 219 | # Save gif and best output 220 | imageio.mimsave("output_gif.gif", save_gif) 221 | cv2.imwrite("output_best.jpg", open_cv_image) 222 | 223 | if __name__ == "__main__": 224 | whole_pipeline() 225 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Nikodem Pachala 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Genetic algorithm image reconstruction 2 | ## _Script for reconstructing B&W images using random shapes placement_ 3 | 4 | ## Installation 5 | ```sh 6 | pip install -r requirements.txt 7 | ``` 8 | 9 | ## Genetic alghorithm key functions / parameters 10 | 11 | ### Generation / population 12 | One generation consist of n (default: 50) B&W images containing various shapes/texts 13 | 14 | ### Crossover 15 | Various functions for crossover: 16 | - blending (with alpha channel 0.5) (recommended) 17 | - random rows / columns swapping 18 | - Concatenating two halves together 19 | 20 | ### Fitness function 21 | Peak signal-to-noise ratio (PSNR) used as function to evaluate similarity of two images 22 | 23 | ### Mutation 24 | Mutate by adding number of random shape/text to image 25 | 26 | ## Various art creation showcase 27 | Showcase uses default options + various small tweaks. 7500 Generations 28 | Original image is 201 x 300px. Presentation below shows resized gifs to (134x200) 29 | 30 | ![Original](https://raw.githubusercontent.com/Th3NiKo/Genetic-algorithm-image-reconstruction/main/images/Original.jpg) 31 | ![Original](https://raw.githubusercontent.com/Th3NiKo/Genetic-algorithm-image-reconstruction/main/images/1.gif) 32 | ![Original](https://raw.githubusercontent.com/Th3NiKo/Genetic-algorithm-image-reconstruction/main/images/2.gif) 33 | ![Original](https://raw.githubusercontent.com/Th3NiKo/Genetic-algorithm-image-reconstruction/main/images/3.gif) 34 | ![Original](https://raw.githubusercontent.com/Th3NiKo/Genetic-algorithm-image-reconstruction/main/images/4.gif) 35 | ![Original](https://raw.githubusercontent.com/Th3NiKo/Genetic-algorithm-image-reconstruction/main/images/5.gif) 36 | ![Original](https://raw.githubusercontent.com/Th3NiKo/Genetic-algorithm-image-reconstruction/main/images/6.gif) 37 | ![Original](https://raw.githubusercontent.com/Th3NiKo/Genetic-algorithm-image-reconstruction/main/images/7.gif) 38 | 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /image.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Th3NiKo/Genetic-algorithm-image-reconstruction/d526e24a8eb5e16bd8794aa18561034a8c2e613e/image.jpg -------------------------------------------------------------------------------- /images/0.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Th3NiKo/Genetic-algorithm-image-reconstruction/d526e24a8eb5e16bd8794aa18561034a8c2e613e/images/0.gif -------------------------------------------------------------------------------- /images/1.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Th3NiKo/Genetic-algorithm-image-reconstruction/d526e24a8eb5e16bd8794aa18561034a8c2e613e/images/1.gif -------------------------------------------------------------------------------- /images/2.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Th3NiKo/Genetic-algorithm-image-reconstruction/d526e24a8eb5e16bd8794aa18561034a8c2e613e/images/2.gif -------------------------------------------------------------------------------- /images/3.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Th3NiKo/Genetic-algorithm-image-reconstruction/d526e24a8eb5e16bd8794aa18561034a8c2e613e/images/3.gif -------------------------------------------------------------------------------- /images/4.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Th3NiKo/Genetic-algorithm-image-reconstruction/d526e24a8eb5e16bd8794aa18561034a8c2e613e/images/4.gif -------------------------------------------------------------------------------- /images/5.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Th3NiKo/Genetic-algorithm-image-reconstruction/d526e24a8eb5e16bd8794aa18561034a8c2e613e/images/5.gif -------------------------------------------------------------------------------- /images/6.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Th3NiKo/Genetic-algorithm-image-reconstruction/d526e24a8eb5e16bd8794aa18561034a8c2e613e/images/6.gif -------------------------------------------------------------------------------- /images/7.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Th3NiKo/Genetic-algorithm-image-reconstruction/d526e24a8eb5e16bd8794aa18561034a8c2e613e/images/7.gif -------------------------------------------------------------------------------- /images/Original.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Th3NiKo/Genetic-algorithm-image-reconstruction/d526e24a8eb5e16bd8794aa18561034a8c2e613e/images/Original.jpg -------------------------------------------------------------------------------- /original.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Th3NiKo/Genetic-algorithm-image-reconstruction/d526e24a8eb5e16bd8794aa18561034a8c2e613e/original.jpg -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | scikit-image==0.18.1 2 | imageio==2.9.0 3 | numpy==1.19.0 4 | opencv-python==4.2.0.34 5 | Pillow==8.2.0 6 | scikit-image==0.18.1 --------------------------------------------------------------------------------