I'm programming a Python program using a genetic algorithm to generate images with circles that resemble a target image.
I used cv2 (opencv)
But it does not evolve, keep getting simple(it's fitness isn't getting higher.)
Also the circles are keep going to left-top and getting smaller (or overlaps)
Can anyone tell me why it happens and how to solve?
This is the GitHub code I referenced https://github.com/kairess/genetic_image/blob/master/main.py
import cv2, random, os
import numpy as np
from skimage.metrics import structural_similarity
filepath = "here/goes/the/img/path"
# (i wrote the exact path while running code)
filename, ext = os.path.splitext(os.path.basename(filepath))
Target = cv2.imread(filepath)
height, width, channels = Target.shape
# hyperparameters
Gene_num = 150
Painting_num = 49
Parents_num = 6
Lucky_num = 1
Gene_cut = 1
Mutate = 0.04
Mutate_radius = 0.2
Mutate_xy = 0.2
Mutate_color = 0.2
Mutate_range = 0.2
Mutate_xyrange = 5
add = 0.15
remove = 0.7
Min_radius = 10
Max_radius = 20
Save = 100
Paintings_list = []
Parents_list = []
# Gene
class Gene():
def __init__(self):
self.center = np.array([random.randint(0, width), random.randint(0, height)])
self.radius = random.randint(Min_radius, Max_radius)
self.color = np.array([random.randint(0, 255), random.randint(0, 255), random.randint(0, 255)])
def mutate(self):
r = random.uniform(0, 1)
if r <= Mutate_radius: # radius
mutation_size = random.uniform(-Mutate_range, Mutate_range)
self.radius = int(max(Min_radius, self.radius + self.radius * mutation_size))
r = random.uniform(0, 1)
if r < Mutate_xy: # center
mutation_x = random.uniform(-Mutate_range, Mutate_range)
mutation_y = random.uniform(-Mutate_range, Mutate_range)
self.center[0] = int(max(0, min(width - 1, self.center[0] + mutation_x)))
self.center[1] = int(max(0, min(height - 1, self.center[1] + mutation_y)))
r = random.uniform(0, 1)
if r < Mutate_color: # color
mutation_size = random.uniform(-Mutate_range, Mutate_range)
self.color = self.color + self.color * mutation_size
class Picture():
def __init__(self):
self.DNA = []
for _ in range(Gene_num):
self.DNA.append(Gene())
self.Fitness = 0
self.img = 0
def mutate(self):
for i in self.DNA:
randnum = random.uniform(0, 1)
if randnum <= Mutate:
i.mutate()
def Crossover(self, p1, p2):
self.DNA = []
try:
for i in range(int(Gene_num / Gene_cut)):
if i%2 == 0:
for k in range(Gene_cut):
if i*Gene_cut + k < Gene_num:
self.DNA.append(p1[i*Gene_cut + k])
else:
for k in range(Gene_cut):
if i*Gene_cut + k < Gene_num:
self.DNA.append(p2[i*Gene_cut + k])
except IndexError:
pass
def Get_Fitness(self):
self.img = np.ones((height, width, channels), dtype=np.uint8) * 255
for gene in self.DNA:
cv2.circle(self.img, center=tuple(gene.center), radius=gene.radius, color=(int(gene.color[0]), int(gene.color[1]), int(gene.color[2])), thickness=-1)
self.Fitness = structural_similarity (Target, self.img, channel_axis = 2)
def Add_Remove(self):
randnum = random.uniform(0, 1)
if randnum <= add:
self.DNA.append(Gene())
elif randnum <= add + remove:
del_target = random.randint(0, len(self.DNA)-1)
del self.DNA[del_target]
def Get_LuckyParent():
global Parents_list
randnum = random.randint(0, len(Paintings_list)-1)
if randnum <= Parents_num:
Get_LuckyParent()
else:
Parents_list.append(Paintings_list[randnum].DNA.copy())
def Get_Parents():
global Parents_list
Parents_list = []
for i in range(Parents_num):
Parents_list.append(Paintings_list[i].DNA.copy())
for i in range(Lucky_num):
Get_LuckyParent()
def Make_Descendants():
num = 0
for i in Parents_list:
for k in Parents_list:
Paintings_list[num].Crossover(i, k)
num += 1
def mutate():
for i in Paintings_list:
i.mutate()
i.Add_Remove()
# 1st gene
for i in range(Painting_num):
Paintings_list.append(Picture())
n_gen = 0
while True:
for i in Paintings_list:
i.Get_Fitness()
Paintings_list.sort(key=lambda Picture: Picture.Fitness, reverse=True)
Best_img = Paintings_list[0].img
Best_score = Paintings_list[0].Fitness
print('Generation #%s, Fitness %s' % (n_gen, Best_score))
cv2.imshow('Best_img', Best_img)
cv2.waitKey(1)
if n_gen % Save == 0:
cv2.imwrite('result/%s_%s.jpg' % (filename, n_gen), Best_img)
n_gen += 1
Get_Parents()
Make_Descendants()
mutate()
cv2.imshow('best out', best_out)
cv2.waitKey(0)
This is how my code works
class Gene: it is data of each circles
class Picture: it is data of img made of circle
First, it makes 49 Picture object in list Paintings_list
- each object has 150
Geneobject in its listself.DNA
Second, calculates each Picture object's Fitness
- it uses function
cv2.circle()atself.img - than, calculates fitness using
structural_similarity()fromskimage.metrics
Third, sort and get parents for next generation
- sort list
Paintings_listby fitness - add 6 best
Pictureobject'sself.DNA.copy()atParents_list(Parents_num) - add 1 lucky
Pictureobject'sself.DNA.copy()atParents_list(Lucky_num)
Fourth, make next generations
- crossover
self.DNAofPicture(Gene) - crossover interval is
Gene_cut - the parents chosen at Third step are maintained
Fifth, mutate each Picture object
- each
Gene's mutate happens in percentMutate Gene's color, radius, center is mutated in different percent- also, delete or add
Geneby percentaddandremove
--> Repeat Second, Third, Fourth Fifth step
I tried to make it evolve by googling, asking to chatgpt and changed mutate code but failed It's not making error, but fitness is not changing consistently.
should i use different way to mutate or get fitness?
ps. it takes too long time. how can i use multiprocessing?