实例功能:
使用256个不同色彩位置透明度的三角形相互重叠构成给定图片
实现思路:
1.采用遗传算法的思想,以256个三角形为一组构成一个父代,开始时随机生成20个父代,并分别计算其环境适应度,最终选取适应度最高的作为初始父代;
2.从当前父代随机变异产生10个子代,计算该11个个体的环境适应度,选取最高的作为新的父代;
3.重复步骤2,直到给定的迭代次数,期间每100代输出一次当前环境适应度,并保存当前父代图片到本地。
初始定义:
class Color() # 颜色类,模式为RGBA色彩模式(RGB三色通道及A透明度通道)
class Triangle() # 三角形类,定义三角形三顶点,颜色
def Draw_Together(triangles) # 将所有三角形绘制重叠到一张图片中,triangles:列表,包含一个父代的所有三角形
def Mutate(rate, triangle) # 三角形变异, rate:变异率, triangle:单个三角形
def Cal_match_rate(gene_img, target_img) # 计算环境适应度(两图像的RGB通道值的差值平方), gene_img:生成的图像,target_im:目标图像
具体实现:
头文件 中,导入了os模块用来生成保存结果图片的路径;
导入了PIL模块下的Image和ImageDraw模块,用来打开或创建所需图片;
导入了numpy模块用来做科学计算,用来高效的计算环境适应度;
导入了random模块用来初始化相关三角形,及用于其变异;
导入了gc模块来进行垃圾回收。
具体代码如下:
1 # -*- coding: utf-8 -*- 2 import os 3 from PIL import Image, ImageDraw 4 import numpy as np 5 import random as r 6 import gc
1 class Color(): 2 """ 3 RGBA模式,RGB三色通道,A透明度通道 4 """ 5 def __init__(self): 6 #分别随机生成[0,255]之间的一个整数,作为RGB的值 7 #随机生成[95, 115]之间的一个整数,作为A的值 8 self.r = r.randint(0, 255) 9 self.g = r.randint(0, 255) 10 self.b = r.randint(0, 255) 11 self.a = r.randint(95, 115)
1 class Triangle(): 2 """ 3 三角形类,定义三角形三顶点,颜色 4 """ 5 def __init__(self): 6 self.color = Color() #随机初始化颜色值 7 self.ax = r.randint(0, 255) #随机初始化三顶点坐标 8 self.ay = r.randint(0, 255) 9 self.bx = r.randint(0, 255) 10 self.by = r.randint(0, 255) 11 self.cx = r.randint(0, 255) 12 self.cy = r.randint(0, 255)
1 def Draw_Together(triangles): 2 """ 3 该函数负责将输入的三角形列表合并绘制到一张图片中。 4 triangles: 一个父代列表,包含了一个父代包含的所有由三角形类定义的三角形。 5 """ 6 img = Image.new('RGBA', size=(256, 256)) # 新建一个画布 7 draw_img = ImageDraw.Draw(img) # 创建一个img图像上绘图的对象 8 draw_img.polygon([(0, 0), (0, 255), (255, 255), (255, 0)], fill=(255, 255, 255, 255)) # 绘制多边形,此处绘制了一个覆盖全画布大小的白色全不透明矩形,作为背景 9 for triangle in triangles: 10 triangle_img = Image.new('RGBA', size=(256, 256)) # 新建一个画单个三角形的画布 11 draw_triangle = ImageDraw.Draw(triangle_img) 12 draw_triangle.polygon([(triangle.ax, triangle.ay), 13 (triangle.bx, triangle.by), 14 (triangle.cx, triangle.cy)], 15 fill=(triangle.color.r, triangle.color.g, triangle.color.b, triangle.color.a)) # 在前面定义的画布triangle_img上绘制出指定三角形 16 img = Image.alpha_composite(img, triangle_img) # 将两个图片按各自透明度叠加到一张图中 17 return img
1 def Mutate(rate, triangle): 2 """ 3 在给定变异率下,对传入的三角形在一定幅度内随机变异 4 """ 5 if rate > r.random(): 6 child = Triangle() 7 child.ax = max(0, min(255, triangle.ax + r.randint(-20, 20))) 8 child.ay = max(0, min(255, triangle.ay + r.randint(-20, 20))) 9 child.bx = max(0, min(255, triangle.bx + r.randint(-20, 20))) 10 child.by = max(0, min(255, triangle.by + r.randint(-20, 20))) 11 child.cx = max(0, min(255, triangle.cx + r.randint(-20, 20))) 12 child.cy = max(0, min(255, triangle.cy + r.randint(-20, 20))) 13 14 child.color.r = max(0, min(triangle.color.r + r.randint(-10, 10), 255)) 15 child.color.g = max(0, min(triangle.color.g + r.randint(-10, 10), 255)) 16 child.color.b = max(0, min(triangle.color.b + r.randint(-10, 10), 255)) 17 child.color.a = max(90, min(triangle.color.a + r.randint(-10, 10), 120)) 18 return child 19 return triangle
1 def Cal_match_tate(gene_img ,target_img): 2 """ 3 计算传入的两图像的像素差值的平方和作为其生成图像的环境适应度, 4 其值大小越小,说明两图像相似度高,即环境适应度越高。 5 """ 6 # 将生成图像和目标图像的RGB值分别合并为一个一维向量,便于计算 7 gene_pixel = np.array([]) #生成图像的像素向量 8 for p in gene_img.split()[:-1]: # split获得一个元组,包含RGBA四个Image对象,[:-1]用来选取前三个结果,即对象的RGB值 9 gene_pixel = np.hstack((gene_pixel, np.hstack(np.array(p)))) 10 # np.array(p)将p对象转化为numpy类型的数组, np.hstack()可将矩阵按行合并为一维向量 11 12 target_pixel = np.array([]) 13 for p in target_img.split()[:-1]: 14 target_pixel = np.hstack((target_pixel, np.hstack(np.array(p)))) 15 16 return np.sum(np.square(np.subtract(gene_pixel, target_pixel))) # 计算环境适应度,并返回
1 def main(result_address): 2 """ 3 流程: 4 1. 随机初始化20个图像(每个图像由256个三角形绘成),挑选match_rate最小的当做父代 5 2. 从父代变异10个子代, 选出父代及十个子代中match_rate最小的当做新父代 6 3. 重复步骤2,30000次 7 4. 每100代输出一次match_rate,并保存图像到本地 8 """ 9 TARGET = input('enter the image fullname: ') 10 target_img = Image.open(TARGET).resize((256, 256)).convert("RGBA") #导入目标图像并改变大小为256*256,色彩模式RGBA 11 # 流程1 12 ## 初始化父代 13 parent_images = [] #储存每个父代的合并后的图像,便于环境适应度计算 14 parent_triangles = [] #储存每个父代所包含的所有三角形 15 for i in range(20): 16 print('正在初始化第%d个父代...' %(i+1)) 17 parent_triangles.append([]) 18 for j in range(256): 19 parent_triangles[i].append(Triangle()) # 随机生成一个父代中的一个三角形 20 parent_images.append(Draw_Together(parent_triangles[i])) #每当一个父代生成好后,就绘制其合并图像,并添加到parent_images中 21 22 ## 计算match_rate,并挑选最小的当父代 23 match_rates = [] 24 for i in range(20): 25 match_rates.append(Cal_match_tate(parent_images[i], target_img)) 26 27 parent = parent_triangles[match_rates.index(min(match_rates))] # 定义最终父代为match_rate最小的那个初始化父代 28 29 del parent_images # 删除保存所有初始化父代图像及三角形的列表,因为后面用不到了 30 del parent_triangles 31 gc.collect() 32 33 # 流程2,3 34 mutate_rate = 0.05 35 for cir in range(30000): 36 ## 从父代变异10个子代 37 child_images = [] 38 child_triangles = [] 39 for i in range(10): 40 child_triangles.append([]) 41 for j in range(256): 42 child_triangles[i].append(Mutate(mutate_rate, parent[j])) 43 child_images.append(Draw_Together(child_triangles[i])) 44 45 ## 计算match_rate,并挑选最小的当父代 46 match_rates = [] 47 for i in range(10): 48 match_rates.append(Cal_match_tate(child_images[i], target_img)) 49 50 if Cal_match_tate(Draw_Together(parent), target_img) > min(match_rates): 51 parent = child_triangles[match_rates.index(min(match_rates))] 52 53 del child_images 54 del child_triangles 55 gc.collect() 56 57 # 流程4 58 if cir % 100 == 0: # 每迭代100代就输出一次环境适应度值,并保存当前父代图像到本地 59 print('第%d代的match_rate:\t%d' %(cir, Cal_match_tate(Draw_Together(parent), target_img))) 60 save_img = Draw_Together(parent) 61 save_img.save(os.path.join(result_address, '%d.png' % (cir)))
最后的 程序启动 代码中,没什么说的,只是定义了保存位置。可自行调整。
1 result_address = r'C:\Users\HASEE\Desktop\results' # 设定生成的图片保存位置为桌面的results文件夹 2 if not os.path.isdir(result_address): os.makedirs(result_address) #为保证桌面一定有该文件夹,以免报错,此处判断了上述路径是否存在,若不存在就创建该路径 3 4 if __name__ == '__main__': # 运行主函数 5 main(result_address)
最后:
因为计算时间的原因,本程序中生成的是256*256的图片,图片太小,可能做头像都不够,不过网上有许多图像放大网站可以高清晰度方法图片,所以问题不大。
另一个问题也是算力问题,处于时间考虑,无法迭代足够多的次数,因此对于复杂图片的精确拟合会很困难,而对简单图形文件还算可以。不过使用复杂图片拟合出来的结果也别有另一番风味。
结果奉上:
下面依次为:原始图片、初始随机生成图片、1900代迭代图片、4050代迭代图片
其中1900代迭代图片经过网上工具四倍放大后,得到下面结果: