首頁 > 軟體

Python繪製分形圖案探索無限細節和奇妙之美

2023-09-07 18:00:24

分形是無限複雜的模式,在不同的尺度上具有自相似性。例如,一棵樹的樹幹會分裂成更小的樹枝。這些樹枝又分裂成更小的樹枝,以此類推。

通過程式設計的方式生成分形,可以將簡單的形狀變成複雜的重複圖案。

本文將探討如何利用一些簡單的幾何學基礎和程式設計知識,在Python中建立令人印象深刻的分形圖案。

分形在資料科學中發揮著重要作用。例如,在分形分析中,對資料集的分形特徵進行評估,以幫助理解基礎過程的結構。此外,處於分形生成中心的迴圈演演算法可以應用於廣泛的資料問題,例如從二進位制搜尋演演算法到遞迴神經網路。

一、目標

寫一個可以畫等邊三角形的程式,並且在三角形的每條邊上,它必須能夠繪製一個稍微小一點的向外的三角形。能夠根據人的意願多次重複此過程,從而建立一些有趣的模式。

二、表示影象

把影象表示為一個二維的畫素陣列。畫素陣列中的每個單元格將代表該畫素的顏色(RGB)。

為此,可以使用NumPy庫生成畫素陣列,並使用Pillow將其轉換為可以儲存的影象。

藍色畫素的x值為3,y值為4,可以通過一個二維陣列存取,如pixels[4][3]

三、畫一條線

現在開始編碼,首先,需要一個可以獲取兩組座標並在它們之間畫一條線的函數。

下面的程式碼通過在兩點之間插值來工作,每一步都向畫素陣列新增新的畫素。你可以把這個過程看作是在一條線上逐個畫素地進行著色。

可以在每個程式碼片段中使用連續字元“”來容納一些較長的程式碼行。

import numpy as np
from PIL import Image
import math
def plot_line(from_coordinates, to_coordinates, thickness, colour, pixels):
    # 找出畫素陣列的邊界
    max_x_coordinate = len(pixels[0])
    max_y_coordinate = len(pixels)
    # 兩點之間沿著x軸和y軸的距離
    horizontal_distance = to_coordinates[1] - from_coordinates[1]
    vertical_distance = to_coordinates[0] - from_coordinates[0]
    # 兩點之間的總距離
    distance =  math.sqrt((to_coordinates[1] - from_coordinates[1])**2 
                + (to_coordinates[0] - from_coordinates[0])**2)
    # 每次給一個新的畫素上色時,將向前走多遠
    horizontal_step = horizontal_distance/distance
    vertical_step = vertical_distance/distance
    # 此時,將進入迴圈以在畫素陣列中繪製線
    # 迴圈的每一次迭代都會沿著線新增一個新的點
    for i in range(round(distance)):
        # 這兩個座標是直線中心的座標
        current_x_coordinate = round(from_coordinates[1] + (horizontal_step*i))
        current_y_coordinate = round(from_coordinates[0] + (vertical_step*i))
        # 一旦得到了點的座標,
        # 就在座標周圍畫出尺寸為thickness的圖案
        for x in range (-thickness, thickness):
            for y in range (-thickness, thickness):
                x_value = current_x_coordinate + x
                y_value = current_y_coordinate + y
                if (x_value > 0 and x_value < max_x_coordinate and 
                    y_value > 0 and y_value < max_y_coordinate):
                    pixels[y_value][x_value] = colour
# 定義影象的大小
pixels = np.zeros( (500,500,3), dtype=np.uint8 )
# 畫一條線
plot_line([0,0], [499,499], 1, [255,200,0], pixels)
# 把畫素陣列變成一張真正的圖片
img = Image.fromarray(pixels)
# 顯示得到的圖片,並儲存它
img.show()
img.save('Line.png')

此函數在畫素陣列的每個角之間繪製一條黃線時的結果

四、畫三角形

現在有了一個可以在兩點之間畫線的函數,可以畫第一個等邊三角形了。

給定三角形的中心點和邊長,可以使用公式計算出高度:h = ½(√3a)。

現在利用這個高度、中心點和邊長,可以計算出三角形的每個角的位置。使用之前製作的plot_line函數,可以在每個角之間畫一條線。

def draw_triangle(center, side_length, thickness, colour, pixels):
    # 等邊三角形的高度是,h = ½(√3a)
    # 其中a是邊長
    triangle_height = round(side_length * math.sqrt(3)/2)
    # 頂角
    top = [center[0] - triangle_height/2, center[1]]
    # 左下角
    bottom_left = [center[0] + triangle_height/2, center[1] - side_length/2]
    # 右下角
    bottom_right = [center[0] + triangle_height/2, center[1] + side_length/2]
    # 在每個角之間畫一條線來完成三角形
    plot_line(top, bottom_left, thickness, colour, pixels)
    plot_line(top, bottom_right, thickness, colour, pixels)
    plot_line(bottom_left, bottom_right, thickness, colour, pixels)

在500x500畫素PNG的中心繪製三角形時的結果

五、生成分形

一切都已準備就緒,可以用Python建立第一個分形。

但是最後一步是最難完成的,三角形函數為它的每一邊呼叫自己,需要能夠計算每個新的較小三角形的中心點,並正確地旋轉它們,使它們垂直於它們所附著的一側。

通過從旋轉的座標中減去中心點的偏移量,然後應用公式來旋轉一對座標,可以用這個函數來旋轉三角形的每個角。

def rotate(coordinate, center_point, degrees):
    # 從座標中減去旋轉的點
    x = (coordinate[0] - center_point[0])
    y = (coordinate[1] - center_point[1])
    # Python的cos和sin函數採用弧度而不是度數
    radians = math.radians(degrees)
    # 計算旋轉點
    new_x = (x * math.cos(radians)) - (y * math.sin(radians))
    new_y = (y * math.cos(radians)) + (x * math.sin(radians))
    # 將在開始時減去的偏移量加回旋轉點上
    return [new_x + center_point[0], new_y + center_point[1]]

將每個座標旋轉35度的三角形

可以旋轉一個三角形後,思考如何在第一個三角形的每條邊上畫一個新的小三角形。

為了實現這一點,擴充套件draw_triangle函數,為每條邊計算一個新三角形的旋轉和中心點,其邊長被引數shrink_side_by減少。

一旦它計算出新三角形的中心點和旋轉,它就會呼叫draw_triangle(自身)來從當前線的中心畫出新的、更小的三角形。然後,這將反過來打擊同一個程式碼塊,為一個更小的三角形計算另一組中心點和旋轉。

這就是所謂的迴圈演演算法,因為draw_triangle函數現在會呼叫自己,直到達到希望繪製的三角形的最大深度。有這個跳脫句子是很重要的,因為理論上這個函數會一直迴圈下去(但實際上呼叫堆疊會變得太大,導致堆疊溢位錯誤)。

def draw_triangle(center, side_length, degrees_rotate, thickness, colour, 
                  pixels, shrink_side_by, iteration, max_depth):
    # 等邊三角形的高度是,h = ½(√3a)
    # 其中'a'是邊長
    triangle_height = side_length * math.sqrt(3)/2
    # 頂角
    top = [center[0] - triangle_height/2, center[1]]
    # 左下角
    bottom_left = [center[0] + triangle_height/2, center[1] - side_length/2]
    # 右下角
    bottom_right = [center[0] + triangle_height/2, center[1] + side_length/2]
    if (degrees_rotate != 0):
        top = rotate(top, center, degrees_rotate)
        bottom_left = rotate(bottom_left, center, degrees_rotate)
        bottom_right = rotate(bottom_right, center, degrees_rotate)
    # 三角形各邊之間的座標
    lines = [[top, bottom_left],[top, bottom_right],[bottom_left, bottom_right]]
    line_number = 0
    # 在每個角之間畫一條線來完成三角形
    for line in lines:
        line_number += 1
        plot_line(line[0], line[1], thickness, colour, pixels)
        # 如果還沒有達到max_depth,就畫一些新的三角形
        if (iteration < max_depth and (iteration < 1 or line_number < 3)):
            gradient = (line[1][0] - line[0][0]) / (line[1][1] - line[0][1])
            new_side_length = side_length*shrink_side_by
            # 正在繪製的三角形線的中心
            center_of_line = [(line[0][0] + line[1][0]) / 2, 
                              (line[0][1] + line[1][1]) / 2]
            new_center = []
            new_rotation = degrees_rotate
            # 需要旋轉traingle的數量
            if (line_number == 1):
                new_rotation += 60
            elif (line_number == 2):
                new_rotation -= 60
            else:
                new_rotation += 180
            # 在一個理想的世界裡,這將是gradient=0,
            # 但由於浮點除法的原因,無法
            # 確保永遠是這種情況
            if (gradient < 0.0001 and gradient > -0.0001):
                if (center_of_line[0] - center[0] > 0):
                    new_center = [center_of_line[0] + triangle_height * 
                                 (shrink_side_by/2), center_of_line[1]]
                else:
                    new_center = [center_of_line[0] - triangle_height * 
                                  (shrink_side_by/2), center_of_line[1]]
            else:
                # 計算直線梯度的法線
                difference_from_center = -1/gradient
                # 計算這條線距中心的距離
                # 到新三角形的中心
                distance_from_center = triangle_height * (shrink_side_by/2)
                # 計算 x 方向的長度,
                # 從線的中心到新三角形的中心
                x_length = math.sqrt((distance_from_center**2)/ 
                                     (1 + difference_from_center**2))
                # 計算出x方向需要走哪條路
                if (center_of_line[1] < center[1] and x_length > 0):
                    x_length *= -1
                # 現在計算Y方向的長度
                y_length = x_length * difference_from_center
                # 用新的x和y值來偏移線的中心
                new_center = [center_of_line[0] + y_length, 
                              center_of_line[1] + x_length]
            draw_triangle(new_center, new_side_length, new_rotation, 
                          thickness, colour, pixels, shrink_side_by, 
                          iteration+1, max_depth)

三角形分形,收縮邊=1/2,最大深度=2

六、結論

下面是通過修改輸入到draw_triangle函數的shrink_side_bymax_depth值生成的不同影象的一些範例。

有趣的是,這些多次重複的圖案往往能創造出更復雜的形狀,比如六邊形,但卻具有令人著迷的對稱性。

越來越複雜的形狀開始在重複三角形的對稱性中出現

另一個分形,每次迭代使用較小的尺寸減小

分形是非常有趣的玩法,可以創造出美麗的圖案。使用一些簡單的概念和豐富的創造力,可以產生非常令人印象深刻的結構。

在理解分形的核心屬性和應用迴圈演演算法的過程中打下的堅實基礎,可以幫助理解資料科學中更復雜的分形問題。

到此這篇關於Python繪製分形圖案探索無限細節和奇妙之美的文章就介紹到這了,更多相關Python繪製分形圖案內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!


IT145.com E-mail:sddin#qq.com