Created
September 4, 2015 21:20
-
-
Save ssarangi/7d6d85c52bdda35012b7 to your computer and use it in GitHub Desktop.
image evolution using genetic algorithm
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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