-
Notifications
You must be signed in to change notification settings - Fork 3
Home
This Rust program uses a genetic algorithm to evolve an image to resemble a specified target image, compiling the results of each generation into frames to output as a GIF. The images are handled at a fixed size (100×100 pixels), with a separate genetic algorithm (SimpleGA) executed for each pixel. Each pixel's color is represented by 8-bit genes (bit strings) for RGB, which are evolved to approximate the target image's pixels.
Constants define the image size and GA parameters, such as IMG_SIZE = 100 (image width/height), POPULATION_SIZE = 6 (population size), and ITERATION = 50 (number of generations).
The run_ga_with_output function loads "target.png". If loading fails, a sample gradient image is generated.
For each coordinate (i,j), SimpleGA::new((i,j)) is called to create a 2D array (ga_grid) of 100×100 GA instances. Each GA holds its pixel position and generates an initial population with random genes of the specified size.
The Chromosome struct contains a position (pos) and a gene bit string (gene: Vec<Vec<bool>>). The gene corresponds to three channels (R, G, B), each with GENE_LENGTH = 8 bits.
Chromosome::new(pos) initializes each bit with a 50% probability of being 0 or 1. For example, (0..RGB_CHANNELS).map(...) generates 8 bits for each of the three channels.
The get_val method converts each channel’s bit string into an 8-bit integer (0–255). It calculates RGB values through a left-shift and summation loop, returning a [u8;3] array.
Chromosome::get_fitness calculates the distance to the target pixel’s color using RMSE (Root Mean Square Error). It computes the sum of squared differences for each channel, divides by 3, and takes the square root (lines 119–122).
Fitness is calculated as exp(-rmse/50.0) using RMSE. A smaller RMSE yields a larger exponential value, indicating higher fitness. If the difference is nearly zero (RMSE < 1.0), fitness is doubled to reduce penalties.
In get_fitness, the chromosome’s position (self.pos) is used to retrieve the corresponding pixel from the target image. Care is taken with coordinate systems, using get_pixel(x, y) with (pos.1, pos.0).
The SimpleGA::tournament_selection method randomly selects candidates from the pool and returns the individual with the highest fitness. By default, three individuals are chosen, and the one with the highest fitness becomes a parent.
Crossover, implemented in uniform_crossover, generates two children from two parents with a probability of CROSSOVER_RATE = 0.8. For each bit, there’s a 50% chance of swapping bits between parents. If crossover doesn’t occur, parents are copied directly.
Chromosome::mutate flips each bit with a low probability (MUTATION_RATE = 0.05). Additionally, there’s a 10% chance of flipping a random bit in a random channel to maintain diversity.
Each generation preserves the top ELITE_SIZE = 2 individuals with the highest fitness. The pool is sorted by fitness, and the top two are copied to the next generation’s pool.
SimpleGA::step combines the above components to build a new pool. After copying elite individuals, the pool is filled by repeating tournament selection, crossover, and mutation until the population size is met. The pool is then trimmed to the set size, updating the GA for the next generation.
In run_ga_with_output, a for gen in 0..ITERATION loop runs the evolution for 50 generations. Each generation calls step on the GA for every pixel position to advance the evolution.
After each generation, a new 100×100 pixel image frame is created. For each pixel position (i,j), the best individual (get_best) from the corresponding GA is used, calculating RGB values from its genes to set the pixel. Additionally, fitness and "perfect match" counts (where the gene-derived color exactly matches the target) are computed.
Statistics like average fitness and match rate are displayed for generations 0, 25, and the final generation. For a sample pixel near the center, average, maximum, and minimum fitness values are also calculated and output.
Frames from all generations are stored in a frames vector and converted to a GIF using create_simple_gif_from_frames. This function creates a fixed 6×6×6 (216-color) palette, optionally skipping frames to keep around 50 frames total. Each pixel color is converted to a palette index, and gif::Encoder writes the GIF file. The genetic algorithm’s progress is visualized in the GIF, with the final generation’s result saved as result.png.