Python繪制分形圖案探索無(wú)限細(xì)節(jié)和奇妙之美
分形是無(wú)限復(fù)雜的模式,在不同的尺度上具有自相似性。例如,一棵樹(shù)的樹(shù)干會(huì)分裂成更小的樹(shù)枝。這些樹(shù)枝又分裂成更小的樹(shù)枝,以此類(lèi)推。
通過(guò)編程的方式生成分形,可以將簡(jiǎn)單的形狀變成復(fù)雜的重復(fù)圖案。
本文將探討如何利用一些簡(jiǎn)單的幾何學(xué)基礎(chǔ)和編程知識(shí),在Python中建立令人印象深刻的分形圖案。
分形在數(shù)據(jù)科學(xué)中發(fā)揮著重要作用。例如,在分形分析中,對(duì)數(shù)據(jù)集的分形特征進(jìn)行評(píng)估,以幫助理解基礎(chǔ)過(guò)程的結(jié)構(gòu)。此外,處于分形生成中心的循環(huán)算法可以應(yīng)用于廣泛的數(shù)據(jù)問(wèn)題,例如從二進(jìn)制搜索算法到遞歸神經(jīng)網(wǎng)絡(luò)。
一、目標(biāo)
寫(xiě)一個(gè)可以畫(huà)等邊三角形的程序,并且在三角形的每條邊上,它必須能夠繪制一個(gè)稍微小一點(diǎn)的向外的三角形。能夠根據(jù)人的意愿多次重復(fù)此過(guò)程,從而創(chuàng)建一些有趣的模式。
二、表示圖像
把圖像表示為一個(gè)二維的像素陣列。像素陣列中的每個(gè)單元格將代表該像素的顏色(RGB)。
為此,可以使用NumPy庫(kù)生成像素?cái)?shù)組,并使用Pillow將其轉(zhuǎn)換為可以保存的圖像。
藍(lán)色像素的x值為3,y值為4,可以通過(guò)一個(gè)二維數(shù)組訪(fǎng)問(wèn),如pixels[4][3]
三、畫(huà)一條線(xiàn)
現(xiàn)在開(kāi)始編碼,首先,需要一個(gè)可以獲取兩組坐標(biāo)并在它們之間畫(huà)一條線(xiàn)的函數(shù)。
下面的代碼通過(guò)在兩點(diǎn)之間插值來(lái)工作,每一步都向像素陣列添加新的像素。你可以把這個(gè)過(guò)程看作是在一條線(xiàn)上逐個(gè)像素地進(jìn)行著色。
可以在每個(gè)代碼片段中使用連續(xù)字符“\”來(lái)容納一些較長(zhǎng)的代碼行。
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) # 兩點(diǎn)之間沿著x軸和y軸的距離 horizontal_distance = to_coordinates[1] - from_coordinates[1] vertical_distance = to_coordinates[0] - from_coordinates[0] # 兩點(diǎn)之間的總距離 distance = math.sqrt((to_coordinates[1] - from_coordinates[1])**2 \ + (to_coordinates[0] - from_coordinates[0])**2) # 每次給一個(gè)新的像素上色時(shí),將向前走多遠(yuǎn) horizontal_step = horizontal_distance/distance vertical_step = vertical_distance/distance # 此時(shí),將進(jìn)入循環(huán)以在像素?cái)?shù)組中繪制線(xiàn) # 循環(huán)的每一次迭代都會(huì)沿著線(xiàn)添加一個(gè)新的點(diǎn) for i in range(round(distance)): # 這兩個(gè)坐標(biāo)是直線(xiàn)中心的坐標(biāo) current_x_coordinate = round(from_coordinates[1] + (horizontal_step*i)) current_y_coordinate = round(from_coordinates[0] + (vertical_step*i)) # 一旦得到了點(diǎn)的坐標(biāo), # 就在坐標(biāo)周?chē)?huà)出尺寸為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 ) # 畫(huà)一條線(xiàn) plot_line([0,0], [499,499], 1, [255,200,0], pixels) # 把像素陣列變成一張真正的圖片 img = Image.fromarray(pixels) # 顯示得到的圖片,并保存它 img.show() img.save('Line.png')
此函數(shù)在像素陣列的每個(gè)角之間繪制一條黃線(xiàn)時(shí)的結(jié)果
四、畫(huà)三角形
現(xiàn)在有了一個(gè)可以在兩點(diǎn)之間畫(huà)線(xiàn)的函數(shù),可以畫(huà)第一個(gè)等邊三角形了。
給定三角形的中心點(diǎn)和邊長(zhǎng),可以使用公式計(jì)算出高度:h = ½(√3a)。
現(xiàn)在利用這個(gè)高度、中心點(diǎn)和邊長(zhǎng),可以計(jì)算出三角形的每個(gè)角的位置。使用之前制作的plot_line
函數(shù),可以在每個(gè)角之間畫(huà)一條線(xiàn)。
def draw_triangle(center, side_length, thickness, colour, pixels): # 等邊三角形的高度是,h = ?(√3a) # 其中a是邊長(zhǎng) 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] # 在每個(gè)角之間畫(huà)一條線(xiàn)來(lái)完成三角形 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的中心繪制三角形時(shí)的結(jié)果
五、生成分形
一切都已準(zhǔn)備就緒,可以用Python創(chuàng)建第一個(gè)分形。
但是最后一步是最難完成的,三角形函數(shù)為它的每一邊調(diào)用自己,需要能夠計(jì)算每個(gè)新的較小三角形的中心點(diǎn),并正確地旋轉(zhuǎn)它們,使它們垂直于它們所附著的一側(cè)。
通過(guò)從旋轉(zhuǎn)的坐標(biāo)中減去中心點(diǎn)的偏移量,然后應(yīng)用公式來(lái)旋轉(zhuǎn)一對(duì)坐標(biāo),可以用這個(gè)函數(shù)來(lái)旋轉(zhuǎn)三角形的每個(gè)角。
def rotate(coordinate, center_point, degrees): # 從坐標(biāo)中減去旋轉(zhuǎn)的點(diǎn) x = (coordinate[0] - center_point[0]) y = (coordinate[1] - center_point[1]) # Python的cos和sin函數(shù)采用弧度而不是度數(shù) radians = math.radians(degrees) # 計(jì)算旋轉(zhuǎn)點(diǎn) new_x = (x * math.cos(radians)) - (y * math.sin(radians)) new_y = (y * math.cos(radians)) + (x * math.sin(radians)) # 將在開(kāi)始時(shí)減去的偏移量加回旋轉(zhuǎn)點(diǎn)上 return [new_x + center_point[0], new_y + center_point[1]]
將每個(gè)坐標(biāo)旋轉(zhuǎn)35度的三角形
可以旋轉(zhuǎn)一個(gè)三角形后,思考如何在第一個(gè)三角形的每條邊上畫(huà)一個(gè)新的小三角形。
為了實(shí)現(xiàn)這一點(diǎn),擴(kuò)展draw_triangle
函數(shù),為每條邊計(jì)算一個(gè)新三角形的旋轉(zhuǎn)和中心點(diǎn),其邊長(zhǎng)被參數(shù)shrink_side_by
減少。
一旦它計(jì)算出新三角形的中心點(diǎn)和旋轉(zhuǎn),它就會(huì)調(diào)用draw_triangle
(自身)來(lái)從當(dāng)前線(xiàn)的中心畫(huà)出新的、更小的三角形。然后,這將反過(guò)來(lái)打擊同一個(gè)代碼塊,為一個(gè)更小的三角形計(jì)算另一組中心點(diǎn)和旋轉(zhuǎn)。
這就是所謂的循環(huán)算法,因?yàn)?code>draw_triangle函數(shù)現(xiàn)在會(huì)調(diào)用自己,直到達(dá)到希望繪制的三角形的最大深度。有這個(gè)轉(zhuǎn)義句子是很重要的,因?yàn)槔碚撋线@個(gè)函數(shù)會(huì)一直循環(huán)下去(但實(shí)際上調(diào)用堆棧會(huì)變得太大,導(dǎo)致堆棧溢出錯(cuò)誤)。
def draw_triangle(center, side_length, degrees_rotate, thickness, colour, \ pixels, shrink_side_by, iteration, max_depth): # 等邊三角形的高度是,h = ?(√3a) # 其中'a'是邊長(zhǎng) 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) # 三角形各邊之間的坐標(biāo) lines = [[top, bottom_left],[top, bottom_right],[bottom_left, bottom_right]] line_number = 0 # 在每個(gè)角之間畫(huà)一條線(xiàn)來(lái)完成三角形 for line in lines: line_number += 1 plot_line(line[0], line[1], thickness, colour, pixels) # 如果還沒(méi)有達(dá)到max_depth,就畫(huà)一些新的三角形 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 # 正在繪制的三角形線(xiàn)的中心 center_of_line = [(line[0][0] + line[1][0]) / 2, \ (line[0][1] + line[1][1]) / 2] new_center = [] new_rotation = degrees_rotate # 需要旋轉(zhuǎn)traingle的數(shù)量 if (line_number == 1): new_rotation += 60 elif (line_number == 2): new_rotation -= 60 else: new_rotation += 180 # 在一個(gè)理想的世界里,這將是gradient=0, # 但由于浮點(diǎn)除法的原因,無(wú)法 # 確保永遠(yuǎn)是這種情況 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: # 計(jì)算直線(xiàn)梯度的法線(xiàn) difference_from_center = -1/gradient # 計(jì)算這條線(xiàn)距中心的距離 # 到新三角形的中心 distance_from_center = triangle_height * (shrink_side_by/2) # 計(jì)算 x 方向的長(zhǎng)度, # 從線(xiàn)的中心到新三角形的中心 x_length = math.sqrt((distance_from_center**2)/ \ (1 + difference_from_center**2)) # 計(jì)算出x方向需要走哪條路 if (center_of_line[1] < center[1] and x_length > 0): x_length *= -1 # 現(xiàn)在計(jì)算Y方向的長(zhǎng)度 y_length = x_length * difference_from_center # 用新的x和y值來(lái)偏移線(xiàn)的中心 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
六、結(jié)論
下面是通過(guò)修改輸入到draw_triangle
函數(shù)的shrink_side_by
和max_depth
值生成的不同圖像的一些示例。
有趣的是,這些多次重復(fù)的圖案往往能創(chuàng)造出更復(fù)雜的形狀,比如六邊形,但卻具有令人著迷的對(duì)稱(chēng)性。
越來(lái)越復(fù)雜的形狀開(kāi)始在重復(fù)三角形的對(duì)稱(chēng)性中出現(xiàn)
另一個(gè)分形,每次迭代使用較小的尺寸減小
分形是非常有趣的玩法,可以創(chuàng)造出美麗的圖案。使用一些簡(jiǎn)單的概念和豐富的創(chuàng)造力,可以產(chǎn)生非常令人印象深刻的結(jié)構(gòu)。
在理解分形的核心屬性和應(yīng)用循環(huán)算法的過(guò)程中打下的堅(jiān)實(shí)基礎(chǔ),可以幫助理解數(shù)據(jù)科學(xué)中更復(fù)雜的分形問(wèn)題。
到此這篇關(guān)于Python繪制分形圖案探索無(wú)限細(xì)節(jié)和奇妙之美的文章就介紹到這了,更多相關(guān)Python繪制分形圖案內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
使用DataFrame實(shí)現(xiàn)兩表連接方式
這篇文章主要介紹了使用DataFrame實(shí)現(xiàn)兩表連接方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-08-08python使用OS模塊操作系統(tǒng)接口及常用功能詳解
os是?Python?標(biāo)準(zhǔn)庫(kù)中的一個(gè)模塊,提供了與操作系統(tǒng)交互的功能,在本節(jié)中,我們將介紹os模塊的一些常用功能,并通過(guò)實(shí)例代碼詳細(xì)講解每個(gè)知識(shí)點(diǎn)2023-06-06python如何基于redis實(shí)現(xiàn)ip代理池
這篇文章主要介紹了python如何基于redis實(shí)現(xiàn)ip代理池,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-01-01python神經(jīng)網(wǎng)絡(luò)Keras常用學(xué)習(xí)率衰減匯總
這篇文章主要為大家介紹了python神經(jīng)網(wǎng)絡(luò)Keras常用學(xué)習(xí)率衰減匯總,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-05-05python獲取系統(tǒng)內(nèi)存占用信息的實(shí)例方法
在本篇文章里小編給大家整理的是關(guān)于python獲取系統(tǒng)內(nèi)存占用信息的實(shí)例方法,有需要的朋友們可以參考學(xué)習(xí)下。2020-07-07Python類(lèi)的基礎(chǔ)入門(mén)知識(shí)
關(guān)于類(lèi)的定義2008-11-11使用GPT-3訓(xùn)練垃圾短信分類(lèi)器示例詳解
這篇文章主要為大家介紹了使用GPT-3訓(xùn)練垃圾短信分類(lèi)器示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-02-02python實(shí)現(xiàn)簡(jiǎn)單學(xué)生信息管理系統(tǒng)
這篇文章主要為大家詳細(xì)介紹了python簡(jiǎn)單的學(xué)生信息管理系統(tǒng),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-04-04Python制作動(dòng)態(tài)字符畫(huà)的源碼
python字符畫(huà)是一個(gè)簡(jiǎn)單有趣的圖畫(huà),它一般由程序制作而成,接下來(lái)通過(guò)本文給大家分享Python制作動(dòng)態(tài)字符畫(huà)的源碼,需要的朋友可以參考下2021-08-08