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模塊,用來打開或創建所需圖片;

  導入了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代迭代圖片經過網上工具四倍放大后,得到下面結果:

      


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM