python-基于遗传算法的多三角形拟合图像实例

实例功能

    使用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模块,用来打开或创建所需图片;

扫描二维码关注公众号,回复: 8054715 查看本文章

  导入了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
  
  定义  颜色类 中,RGB颜色完全随机生成,而透明度选择了较为中间的[95,115],具体原因是在测试中发现,如果透明度值上限太小整体颜色会偏淡,下限太大整体颜色会很深,区间过大时也会出现颜色一边偏的情况,具体的区间选择可能还需要考虑实际被拟合图片的颜色深淡情况。
 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
  
  定义  计算环境适应度函数 中,因为图片涉及的像素量较为庞大,传统计算方法可能较慢,因此这里采用了numpy的科学计算方法,以提高计算速度。这里的处理过程不一定是最优的,有兴趣的可以想其它方法进行计算。
 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)))   # 计算环境适应度,并返回
  
  定义  主函数 中,按照开始时所构思的实现思路,顺次地实现了每一步的相关功能。其中整体迭代次数初定义了30000代,从实际测试中发现,其实到一两千代后图像的差别变化就很缓慢了,可能还是变异率的问题。不过代码可以随时终止,因为每100代就会自动保存一次。
 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代迭代图片经过网上工具四倍放大后,得到下面结果:

      

猜你喜欢

转载自www.cnblogs.com/yu-long/p/11974213.html
今日推荐