Skip to content

Instantly share code, notes, and snippets.

@ssarangi
Created September 4, 2015 21:20
Show Gist options
  • Save ssarangi/7d6d85c52bdda35012b7 to your computer and use it in GitHub Desktop.
Save ssarangi/7d6d85c52bdda35012b7 to your computer and use it in GitHub Desktop.
image evolution using genetic algorithm
# draw about 50 polygons with varying transparencies to recreate an image
from PIL import Image, ImageDraw, ImageChops
from random import random, randint
import numpy as np
def clamp(minimum, x, maximum):
return max(minimum, min(x, maximum))
def pil2array(img):
return np.array(img.getdata(),
np.uint8).reshape(img.size[1], img.size[0], 3)
def array2pil(arr, size):
mode = 'RGBA'
arr = arr.reshape(arr.shape[0]*arr.shape[1], arr.shape[2])
if len(arr[0]) == 3:
arr = np.c_[arr, 255*np.ones((len(arr),1), np.uint8)]
return Image.frombuffer(mode, size, arr.tostring(), 'raw', mode, 0, 1)
class Point:
def __init__(self, x, y):
self.__x = x
self.__y = y
@property
def x(self):
return self.__x
@property
def y(self):
return self.__y
class Individual:
"""
Biggest_bounding_box is a list of [min_x, max_x, min_y, max_y]
"""
def __init__(self, bounding_box, color, biggest_bounding_box):
self.__bounding_box = bounding_box
self.__color = color
self.__biggest_bounding_box = biggest_bounding_box
@property
def bounding_box(self):
return self.__bounding_box
@property
def color(self):
return self.__color
def mutate(self):
mutation_type = randint(0, 3)
if mutation_type == 0:
self.__bounding_box[0] = randint(self.__biggest_bounding_box[0], self.__biggest_bounding_box[1])
self.__bounding_box[2] = randint(self.__biggest_bounding_box[0], self.__biggest_bounding_box[1])
if mutation_type == 1:
self.__bounding_box[1] = randint(self.__biggest_bounding_box[0], self.__biggest_bounding_box[1])
self.__bounding_box[3] = randint(self.__biggest_bounding_box[0], self.__biggest_bounding_box[1])
if mutation_type == 2:
self.__color = (randint(0, 255), randint(0, 255), randint(0, 255), randint(0, 255))
def draw(self, draw):
draw.ellipse(self.__bounding_box, fill=self.__color)
def __str__(self):
output_str = str(self.__bounding_box) + " --> color: " + str(self.__color)
return output_str
__repr__ = __str__
class Evolve:
def __init__(self, img):
self.__target = Image.open(img)
self.__width = self.__target.width
self.__height = self.__target.width
print(self.__target.size)
self.__biggest_bounding_box = [0, self.__target.width, 0, self.__target.height]
def __get_bounding_box(self, center, radius):
x_top = clamp(0, center.x - radius, self.__width)
y_top = clamp(0, center.y - radius, self.__height)
x_bottom = clamp(0, center.x + radius, self.__width)
y_bottom = clamp(0, center.y + radius, self.__height)
bb = [x_top, y_top, x_bottom, y_bottom]
return bb
def individual(self, min_radius, max_radius, min_x, max_x, min_y, max_y):
center = Point(randint(min_x, max_x), randint(min_y, max_y))
radius = randint(min_radius, max_radius)
bounding_box = self.__get_bounding_box(center, radius)
color = (randint(0, 255), randint(0, 255), randint(0, 255), randint(0, 255))
return Individual(bounding_box, color, [min_x, max_x, min_y, max_y])
def population(self, individual_count, min_radius, max_radius):
min_x = 0
max_x = self.__target.width
min_y = 0
max_y = self.__target.height
return [self.individual(min_radius, max_radius, min_x, max_x, min_y, max_y) for i in range(0, individual_count)]
def fitness(self, individual):
nimg = Image.new(self.__target.mode, (self.__width, self.__height), color=(255, 255, 255, 255))
draw = ImageDraw.Draw(nimg)
draw.ellipse(individual.bounding_box, fill=individual.color)
diff = ImageChops.difference(self.__target, nimg)
arr = np.array(diff)
# arr = pil2array(diff)
return np.sum(np.absolute(arr))
def grade(self, pop):
"""
Find average fitness of the population
:return: returns the average fitness
"""
summed = np.sum([self.fitness(individual) for individual in pop])
return summed / (len(pop) * 1.0)
def evolve_population(self, pop, retain=0.2, random_select=0.5, mutate=0.1):
graded = [(self.fitness(individual), individual) for individual in pop]
graded = [x[1] for x in sorted(graded, key=lambda tup: tup[0])]
retain_length = int(len(graded) * retain)
parents = graded[:retain_length]
# randomly add other individuals to promote genetic diversity
for individual in graded[retain_length:]:
if random_select > random():
parents.append(individual)
# mutate some individuals
for individual in parents:
if mutate > random():
individual.mutate()
# Crossover the parents to create children
parents_length = len(parents)
desired_length = len(pop) - parents_length
children = []
while len(children) < desired_length:
# randomly decide whether we want to take the x,y from male &
# color from female or vice-versa
male_idx = randint(0, parents_length-1)
female_idx = randint(0, parents_length-1)
male = parents[male_idx]
female = parents[female_idx]
if male != female:
which_one = [male, female]
top_x = which_one[randint(0, 1)].bounding_box[0]
top_y = which_one[randint(0, 1)].bounding_box[1]
bottom_x = which_one[randint(0, 1)].bounding_box[2]
bottom_y = which_one[randint(0, 1)].bounding_box[3]
color_r = which_one[randint(0, 1)].color[0]
color_g = which_one[randint(0, 1)].color[1]
color_b = which_one[randint(0, 1)].color[2]
color_a = which_one[randint(0, 1)].color[3]
child = Individual([top_x, top_y, bottom_x, bottom_y], (color_r, color_g, color_b, color_a), self.__biggest_bounding_box)
children.append(child)
parents.extend(children)
return parents
def render_population(self, pop):
nimg = Image.new(self.__target.mode, (self.__width, self.__height))
drawobj = ImageDraw.Draw(nimg)
for individual in pop:
individual.draw(drawobj)
return nimg
def main():
target = 'monalisa.png'
ev = Evolve(target)
individual_count = 100
p = ev.population(individual_count, min_radius=20, max_radius=40)
fitness_history = [ev.grade(p)]
for i in range(25000):
p = ev.evolve_population(p)
grade = ev.grade(p)
print(grade)
fitness_history.append(grade)
nimg = ev.render_population(p)
if i == 5000 or i == 10000 or i == 15000 or i == 20000 or i == 25000:
nimg.save('iterations/%s.png' % i)
if __name__ == "__main__":
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment