Skip to content

Commit 57b187a

Browse files
authored
Add files via upload
1 parent b44c4b7 commit 57b187a

File tree

3 files changed

+302
-0
lines changed

3 files changed

+302
-0
lines changed

GARI.py

+209
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,209 @@
1+
import numpy
2+
import matplotlib.pyplot
3+
import itertools
4+
import functools
5+
import operator
6+
import random
7+
8+
"""
9+
This work introduces a simple project called GARI (Genetic Algorithm for Reproducing Images).
10+
GARI reproduces a single image using Genetic Algorithm (GA) by evolving pixel values.
11+
12+
This project works with both color and gray images without any modifications.
13+
Just give the image path.
14+
Using three parameters, we can customize it to statisfy our need.
15+
The parameters are:
16+
1) Population size. I.e. number of individuals pepr population.
17+
2) Mating pool size. I.e. Number of selected parents in the mating pool.
18+
3) Mutation percentage. I.e. number of genes to change their values.
19+
20+
Value encoding used for representing the input.
21+
Crossover is applied by exchanging half of genes from two parents.
22+
Mutation is applied by randomly changing the values of randomly selected
23+
predefined percent of genes from the parents chromosome.
24+
25+
This project is implemented using Python 3.5 by Ahmed F. Gad.
26+
Contact info:
27+
28+
https://www.linkedin.com/in/ahmedfgad/
29+
"""
30+
31+
def img2chromosome(img_arr):
32+
"""
33+
First step in GA is to represent/encode the input as a sequence of characters.
34+
The encoding used is value encoding by giving each gene in the
35+
chromosome its actual value in the image.
36+
Image is converted into a chromosome by reshaping it as a single row vector.
37+
"""
38+
chromosome = numpy.reshape(a=img_arr,
39+
newshape=(functools.reduce(operator.mul,
40+
img_arr.shape)))
41+
return chromosome
42+
43+
def initial_population(img_shape, n_individuals=8):
44+
"""
45+
Creating an initial population randomly.
46+
"""
47+
# Empty population of chromosomes accoridng to the population size specified.
48+
init_population = numpy.empty(shape=(n_individuals,
49+
functools.reduce(operator.mul, img_shape)),
50+
dtype=numpy.uint8)
51+
for indv_num in range(n_individuals):
52+
# Randomly generating initial population chromosomes genes values.
53+
init_population[indv_num, :] = numpy.random.random(
54+
functools.reduce(operator.mul, img_shape))*256
55+
return init_population
56+
57+
def chromosome2img(chromosome, img_shape):
58+
"""
59+
First step in GA is to represent the input in a sequence of characters.
60+
The encoding used is value encoding by giving each gene in the chromosome
61+
its actual value.
62+
"""
63+
img_arr = numpy.reshape(a=chromosome, newshape=img_shape)
64+
return img_arr
65+
66+
def fitness_fun(target_chrom, indiv_chrom):
67+
"""
68+
Calculating the fitness of a single solution.
69+
The fitness is basicly calculated using the sum of absolute difference
70+
between genes values in the original and reproduced chromosomes.
71+
"""
72+
quality = numpy.mean(numpy.abs(target_chrom-indiv_chrom))
73+
"""
74+
Negating the fitness value to make it increasing rather than decreasing.
75+
Actually the next line adds nothing but it exists just because it is known
76+
that the fitness values are increasing not decreasing.
77+
"""
78+
quality = numpy.sum(target_chrom) - quality
79+
return quality
80+
81+
def cal_pop_fitness(target_chrom, pop):
82+
"""
83+
This method calculates the fitness of all solutions in the population.
84+
"""
85+
qualities = numpy.zeros(pop.shape[0])
86+
for indv_num in range(pop.shape[0]):
87+
# Calling fitness_fun(...) to get the fitness of the current solution.
88+
qualities[indv_num] = fitness_fun(target_chrom, pop[indv_num, :])
89+
return qualities
90+
91+
def select_mating_pool(pop, qualities, num_parents):
92+
"""
93+
Selects the best individuals in the current generation, according to the
94+
number of parents specified, for mating and generating a new better population.
95+
"""
96+
parents = numpy.empty((num_parents, pop.shape[1]), dtype=numpy.uint8)
97+
for parent_num in range(num_parents):
98+
# Retrieving the best unselected solution.
99+
max_qual_idx = numpy.where(qualities == numpy.max(qualities))
100+
max_qual_idx = max_qual_idx[0][0]
101+
# Appending the currently selected
102+
parents[parent_num, :] = pop[max_qual_idx, :]
103+
"""
104+
Set quality of selected individual to a negative value to not get
105+
selected again. Algorithm calcululations will just make qualities >= 0.
106+
"""
107+
qualities[max_qual_idx] = -1
108+
return parents
109+
110+
def crossover(parents, img_shape, n_individuals=8):
111+
"""
112+
Applying crossover operation to the set of currently selected parents to
113+
create a new generation.
114+
"""
115+
new_population = numpy.empty(shape=(n_individuals,
116+
functools.reduce(operator.mul, img_shape)),
117+
dtype=numpy.uint8)
118+
119+
"""
120+
Selecting the best previous parents to be individuals in the new generation.
121+
122+
**Question** Why using the previous parents in the new population?
123+
It is recommened to use the previous best solutions (parents) in the new
124+
generation in addition to the offspring generated from these parents and
125+
not use just the offspring.
126+
The reason is that the offspring may not produce the same fitness values
127+
generated by their parents. Offspring may be worse than their parents.
128+
As a result, if none of the offspring are better, the previous generations
129+
winners will be reselected until getting a better offspring.
130+
"""
131+
#Previous parents (best elements).
132+
new_population[0:parents.shape[0], :] = parents
133+
134+
135+
# Getting how many offspring to be generated. If the population size is 8 and number of parents mating is 4, then number of offspring to be generated is 4.
136+
num_newly_generated = n_individuals-parents.shape[0]
137+
# Getting all possible permutations of the selected parents.
138+
parents_permutations = list(itertools.permutations(iterable=numpy.arange(0, parents.shape[0]), r=2))
139+
# Randomly selecting the parents permutations to generate the offspring.
140+
selected_permutations = random.sample(range(len(parents_permutations)),
141+
num_newly_generated)
142+
143+
comb_idx = parents.shape[0]
144+
for comb in range(len(selected_permutations)):
145+
# Generating the offspring using the permutations previously selected randmly.
146+
selected_comb_idx = selected_permutations[comb]
147+
selected_comb = parents_permutations[selected_comb_idx]
148+
149+
# Applying crossover by exchanging half of the genes between two parents.
150+
half_size = numpy.int32(new_population.shape[1]/2)
151+
new_population[comb_idx+comb, 0:half_size] = parents[selected_comb[0],
152+
0:half_size]
153+
new_population[comb_idx+comb, half_size:] = parents[selected_comb[1],
154+
half_size:]
155+
156+
return new_population
157+
158+
def mutation(population, mut_percent):
159+
"""
160+
Applying mutation by selecting a predefined percent of genes randomly.
161+
Values of the randomly selected genes are changed randmly.
162+
"""
163+
for idx in range(population.shape[0]):
164+
# A predefined percent of genes are selected randomly.
165+
rand_idx = numpy.uint32(numpy.random.random(size=numpy.uint32(mut_percent/100*population.shape[1]))
166+
*population.shape[1])
167+
# Changing the values of the selected genes randomly.
168+
new_values = numpy.uint8(numpy.random.random(size=rand_idx.shape[0])*256)
169+
# Updating population after mutation.
170+
population[idx, rand_idx] = new_values
171+
return population
172+
173+
def save_images(curr_iteration, qualities, new_population, im_shape,
174+
save_point, save_dir):
175+
"""
176+
Saving best solution in a given generation as an image in the specified directory.
177+
Images are saved accoirding to stop points to avoid saving images from
178+
all generations as saving mang images will make the algorithm slow.
179+
"""
180+
if(numpy.mod(curr_iteration, save_point)==0):
181+
# Selecting best solution (chromosome) in the generation.
182+
best_solution_chrom = new_population[numpy.where(qualities ==
183+
numpy.max(qualities))[0][0], :]
184+
# Decoding the selected chromosome to return it back as an image.
185+
best_solution_img = chromosome2img(best_solution_chrom, im_shape)
186+
# Saving the image in the specified directory.
187+
matplotlib.pyplot.imsave(save_dir+'solution_'+str(curr_iteration)+'.png', best_solution_img)
188+
189+
def show_indivs(individuals, im_shape):
190+
"""
191+
Show all individuals as image in a single graph.
192+
"""
193+
num_ind = individuals.shape[0]
194+
fig_row_col = 1
195+
for k in range(1, numpy.uint16(individuals.shape[0]/2)):
196+
if numpy.floor(numpy.power(k, 2)/num_ind) == 1:
197+
fig_row_col = k
198+
break
199+
fig1, axis1 = matplotlib.pyplot.subplots(fig_row_col, fig_row_col)
200+
201+
curr_ind = 0
202+
for idx_r in range(fig_row_col):
203+
for idx_c in range(fig_row_col):
204+
if(curr_ind>=individuals.shape[0]):
205+
break
206+
else:
207+
curr_img = chromosome2img(individuals[curr_ind, :], im_shape)
208+
axis1[idx_r, idx_c].imshow(curr_img)
209+
curr_ind = curr_ind + 1

GeneticAlgorithm.py

+93
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
import os
2+
import sys
3+
import numpy
4+
import skimage.io, skimage.color, skimage.exposure
5+
import itertools
6+
import GARI
7+
8+
"""
9+
Reproduce a single image using Genetic Algorithm (GA) by evolving
10+
single pixel values.
11+
12+
This project works with both color and gray images without any modifications.
13+
Just give the image path.
14+
Using three parameters, we can customize it to statisfy our need.
15+
The parameters are:
16+
1) Population size. I.e. number of individuals pepr population.
17+
2) Mating pool size. I.e. Number of selected parents in the mating pool.
18+
3) Mutation percentage. I.e. number of genes to change their values.
19+
20+
Value encoding used for representing the input.
21+
Crossover is applied by exchanging half of genes from two parents.
22+
Mutation is applied by randomly changing the values of randomly selected
23+
predefined percent of genes from the parents chromosome.
24+
25+
This project is implemented using Python 3.5 by Ahmed F. Gad.
26+
Contact info:
27+
28+
https://www.linkedin.com/in/ahmedfgad/
29+
"""
30+
31+
# Reading target image to be reproduced using Genetic Algorithm (GA).
32+
target_im = skimage.io.imread(fname='fruit.jpg')
33+
# Target image after enconding. Value encoding is used.
34+
target_chromosome = GARI.img2chromosome(target_im)
35+
36+
# Population size
37+
sol_per_pop = 8
38+
# Mating pool size
39+
num_parents_mating = 4
40+
# Mutation percentage
41+
mutation_percent = .01
42+
43+
"""
44+
There might be inconsistency between the number of selected mating parents and
45+
number of selected individuals within the population.
46+
In some cases, the number of mating parents are not sufficient to
47+
reproduce a new generation. If that occurred, the program will stop.
48+
"""
49+
num_possible_permutations = len(list(itertools.permutations(iterable=numpy.arange(0,
50+
num_parents_mating), r=2)))
51+
num_required_permutations = sol_per_pop-num_possible_permutations
52+
if(num_required_permutations>num_possible_permutations):
53+
print(
54+
"\n*Inconsistency in the selected populatiton size or number of parents.*"
55+
"\nImpossible to meet these criteria.\n"
56+
)
57+
sys.exit(1)
58+
59+
60+
# Creating an initial population randomly.
61+
new_population = GARI.initial_population(img_shape=target_im.shape,
62+
n_individuals=sol_per_pop)
63+
64+
for iteration in range(200):
65+
# Measing the fitness of each chromosome in the population.
66+
qualities = GARI.cal_pop_fitness(target_chromosome, new_population)
67+
print('Quality : ', numpy.max(qualities), ', Iteration : ', iteration)
68+
69+
# Selecting the best parents in the population for mating.
70+
parents = GARI.select_mating_pool(new_population, qualities,
71+
num_parents_mating)
72+
73+
# Generating next generation using crossover.
74+
new_population = GARI.crossover(parents, target_im.shape,
75+
n_individuals=sol_per_pop)
76+
77+
"""
78+
Applying mutation for offspring.
79+
Mutation is important to avoid local maxima. Avoiding mutation makes
80+
the GA falls into local maxima.
81+
Also mutation is important as it adds some little changes to the offspring.
82+
If the previous parents have some common degaradation, mutation can fix it.
83+
Increasing mutation percentage will degarde next generations.
84+
"""
85+
new_population = GARI.mutation(new_population, mut_percent=mutation_percent)
86+
"""
87+
Save best individual in the generation as an image for later visualization.
88+
"""
89+
GARI.save_images(iteration, qualities, new_population, target_im.shape,
90+
save_point=500, save_dir=os.curdir+'//')
91+
92+
# Display the final generation
93+
GARI.show_indivs(new_population, target_im.shape)

fruit.jpg

14.5 KB
Loading

0 commit comments

Comments
 (0)