-
Notifications
You must be signed in to change notification settings - Fork 5
/
Genetic.py
224 lines (167 loc) · 7.7 KB
/
Genetic.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
"""
Script for recreating image using genetic alghoritm.
"""
import string
import random
from skimage.metrics import peak_signal_noise_ratio as psns # For image similarity evaluation
from PIL import Image, ImageDraw, ImageFont
import cv2
import numpy as np
import imageio # For gif saving
#Load and show original image for tracking (convert to black and white)
original_image = Image.open("image.jpg").convert("L")
original_height, original_width = original_image.size
cv2.imshow("Original", np.array(original_image))
cv2.imwrite("original.jpg",np.array(original_image))
#Adjust hyperparameters
NUMBER_OF_GENERATIONS = 7500
POPULATION_NUMBER = 50 # How many images in 1 generation (without elitism)
MUTATION_CHANCE = 0.1 # Chance of mutating (adding random shapes)
MUTATION_STRENGTH = 1 # How many shapes to add in mutation
ELITISM = True # Turn on/off elitism (transfering best images to next generation without crossover)
ELITISM_NUMBER = 4 # How many best images transfer to next generation (elitism)
STARTING_SHAPE_NUMBER = 6 # How many shapes to draw on each image in first generation
PRINT_EVERY_GEN = 25 # Print fitness value every x generations
SAVE_FRAME_FOR_GIF_EVERY = 100 # Save best image every x generations for gif creation
# What to draw functions
def draw_rectangle(image, size=10):
"""Draw rectangle on image with given size."""
x = random.randint(0,original_width-1)
y = random.randint(0,original_height-1)
color = (random.randint(0,255))
image.rectangle([(y,x), (y+size,x+size)], fill=color)
def draw_line(image):
"""Draw random line on image."""
x1 = random.randint(0,original_width-1)
y1 = random.randint(0,original_height-1)
x2 = random.randint(0,original_width-1)
y2 = random.randint(0,original_height-1)
thickness_value = random.randint(1, 4)
color = (random.randint(0,255))
image.line([(y1,x1), (y2,x2)], fill=color, width=thickness_value)
def draw_text(image, size=20):
"""Draw random text on image with given size."""
font = ImageFont.truetype("arial.ttf", size)
text_length = random.randint(1,3)
text = "".join(random.choice(string.ascii_letters) for i in range(text_length))
x = random.randint(0,original_width-1)
y = random.randint(0,original_height-1)
color = (random.randint(0,255))
image.text((y,x), text, fill=color, font=font)
def add_random_shape_to_image(image, number_of_shapes):
"""Add shape with random proporties to image number_of_shapes times."""
image_filled = image.copy()
for _ in range(0, number_of_shapes):
draw = ImageDraw.Draw(image_filled)
draw_text(draw)
return image_filled
def create_random_population(size):
"""Create first generation with random population."""
first_population = []
for _ in range(0, size):
blank_image = Image.new("L", (original_height, original_width))
filled_image = add_random_shape_to_image(blank_image, MUTATION_STRENGTH)
first_population.append(filled_image)
return first_population
def evaluate_fitness(image):
"""Evaluate similarity of image with original."""
return psns(np.array(image), np.array(original_image))
# Crossover operations with alternatives and helpers
def images_to_arrays(image1, image2):
"""Represent images as arrays."""
img1_arr = np.array(image1)
img2_arr = np.array(image2)
return img1_arr ,img2_arr
def blending(image1, image2):
"""Blend to images together with 0.5 alpha."""
return Image.blend(image1, image2, alpha=0.5)
def random_horizontal_swap(image1, image2):
"""Swap random rows of two images."""
img1_arr, img2_arr = images_to_arrays(image1, image2)
horizontal_random_choice = np.random.choice(original_width,
int(original_width/2),
replace=False)
img1_arr[horizontal_random_choice] = img2_arr[horizontal_random_choice]
return Image.fromarray(img1_arr)
def random_vertical_swap(image1, image2):
"""Swap random columns of two images."""
img1_arr, img2_arr = images_to_arrays(image1, image2)
vertical_random_choice = np.random.choice(original_height,
int(original_height/2),
replace=False)
img1_arr[:,vertical_random_choice] = img2_arr[:,vertical_random_choice]
return Image.fromarray(img1_arr)
def half_vertical_swap(image1, image2):
"""Swap images halfs (verticaly)."""
img1_arr, img2_arr = images_to_arrays(image1, image2)
img1_half = img1_arr[0:int(original_height/2),]
img2_half = img2_arr[int(original_height/2):original_height,]
return np.vstack((img1_half, img2_half))
def half_horizontal_swap(image1, image2):
"""Swap images halfs (horizontaly)."""
img1_arr, img2_arr = images_to_arrays(image1, image2)
img1_half = img1_arr[:,0:int(original_width/2)]
img2_half = img2_arr[:,int(original_width/2):original_width]
return np.hstack((img1_half, img2_half))
def crossover(image1, image2):
"""Make crossover operation on two images."""
return random_horizontal_swap(image1, image2)
def mutate(image, number_of_times):
"""Mutate image adding random shape number_of_times."""
mutated = add_random_shape_to_image(image, number_of_times)
return mutated
def get_parents(local_population, local_fitnesses):
"""Connect parents in pairs based on fitnesses as weights using softmax."""
fitness_sum = sum(np.exp(local_fitnesses))
fitness_normalized = np.exp(local_fitnesses) / fitness_sum
local_parents_list = []
for _ in range(0, len(local_population)):
parents = random.choices(local_population, weights=fitness_normalized, k=2)
local_parents_list.append(parents)
return local_parents_list
def whole_pipeline():
"""Go through whole pipeline and execute it."""
save_gif = [] #Creating empty frames list for gif saving at the end
# Create first generation
population = create_random_population(POPULATION_NUMBER)
# Loop through generations
for generation in range(0, NUMBER_OF_GENERATIONS):
# Calculate similarity of each image in population to original image
fitnesses = []
for img in population:
actual_fitness = evaluate_fitness(img)
fitnesses.append(actual_fitness)
# Get ids of best images in population
top_population_ids = np.argsort(fitnesses)[-ELITISM_NUMBER:]
# Start creating new population for next generation
new_population = []
# Connect parent into pairs
parents_list = get_parents(population, fitnesses)
# Create childs
for i in range(0, POPULATION_NUMBER):
new_img = crossover(parents_list[i][0], parents_list[i][1])
#Mutate
if random.uniform(0.0, 1.0) < MUTATION_CHANCE:
new_img = mutate(new_img, MUTATION_STRENGTH)
new_population.append(new_img)
# Elitism transfer
if ELITISM:
for ids in top_population_ids:
new_population.append(population[ids])
# Print info every x generations
if generation % PRINT_EVERY_GEN == 0:
print(generation)
print(fitnesses[top_population_ids[0]])
# Get best actual image and show it
open_cv_image = np.array(population[top_population_ids[0]])
cv2.imshow("test", open_cv_image)
# Gif creation
if generation % SAVE_FRAME_FOR_GIF_EVERY == 0:
save_gif.append(open_cv_image)
cv2.waitKey(1)
population = new_population
# Save gif and best output
imageio.mimsave("output_gif.gif", save_gif)
cv2.imwrite("output_best.jpg", open_cv_image)
if __name__ == "__main__":
whole_pipeline()