【Python与生活】怎么用python画出好看的分形图?

分形(Fractal)是具有自相似性的几何图形,小尺度下的形态与整体形态高度相似,典型代表有曼德博集合(Mandelbrot Set)、朱利亚集合(Julia Set)、科赫雪花、分形树等。Python结合matplotlibnumpy(高效数值计算)和numba(加速循环)可以轻松绘制出视觉效果惊艳的分形图,本文从基础原理到代码实战,教你画出高质量的分形图形。

一、前置准备

1.1 安装依赖

需要的核心库:

  • numpy:数值计算(矩阵运算、复数处理)
  • matplotlib:绘图与色彩渲染
  • numba:JIT编译加速(分形计算涉及大量循环,纯Python速度慢)
  • PIL(可选):保存高清图片

执行安装命令:

bash 复制代码
pip install numpy matplotlib numba pillow

1.2 核心优化思路

分形计算的核心是迭代判断点是否属于分形集合,循环次数多且计算密集:

  • numpy向量化运算替代纯Python循环;
  • numba.jit装饰器编译核心函数,提速10~100倍;
  • 合理设置色彩映射(colormap)提升视觉效果。

二、实战1:曼德博集合(Mandelbrot Set)

曼德博集合是最经典的分形,定义为复平面上满足 ( z_{n+1} = z_n^2 + c )(初始 ( z_0=0 ))迭代不发散的点 ( c=x+yi ) 的集合。

2.1 完整代码(高清+彩色)

python 复制代码
import numpy as np
import matplotlib.pyplot as plt
from numba import jit
from matplotlib.colors import LinearSegmentedColormap

# ===================== 1. 核心计算函数(Numba加速) =====================
@jit(nopython=True)  # JIT编译,大幅提速
def mandelbrot(c, max_iter):
    """判断单个点c是否属于曼德博集合,返回迭代次数(用于上色)"""
    z = 0
    n = 0
    while abs(z) <= 2 and n < max_iter:
        z = z * z + c
        n += 1
    # 平滑上色:避免迭代次数突变导致的色块
    if n == max_iter:
        return max_iter  # 属于集合,设为最大迭代次数
    return n + 1 - np.log(np.log2(abs(z)))  # 平滑后的迭代次数

@jit(nopython=True)
def mandelbrot_set(x_min, x_max, y_min, y_max, width, height, max_iter):
    """生成曼德博集合的迭代次数矩阵"""
    x = np.linspace(x_min, x_max, width)
    y = np.linspace(y_min, y_max, height)
    img = np.zeros((height, width))
    for i in range(height):
        for j in range(width):
            c = complex(x[j], y[i])
            img[i, j] = mandelbrot(c, max_iter)
    return img

# ===================== 2. 自定义色彩映射(更美观) =====================
def create_fractal_cmap():
    """创建自定义分形配色(深蓝→紫色→粉色→黄色→白色)"""
    colors = [
        (0.0, 0.0, 0.1),    # 深蓝(集合内部)
        (0.2, 0.0, 0.5),    # 深紫
        (0.5, 0.1, 0.8),    # 粉紫
        (0.8, 0.2, 1.0),    # 亮粉
        (1.0, 0.8, 0.2),    # 黄色
        (1.0, 1.0, 1.0)     # 白色(迭代次数最多)
    ]
    return LinearSegmentedColormap.from_list("fractal", colors, N=1024)

# ===================== 3. 绘制曼德博集合 =====================
def plot_mandelbrot():
    # 1. 配置参数(可调整视角,比如放大局部细节)
    # 全局视图:x∈[-2.0, 1.0], y∈[-1.5, 1.5]
    # 局部细节(比如"海马谷"):x∈[-0.8, -0.7], y∈[0.0, 0.1]
    x_min, x_max = -2.0, 1.0
    y_min, y_max = -1.5, 1.5
    width, height = 2000, 2000  # 分辨率(越高越清晰,计算越久)
    max_iter = 1000  # 迭代次数(越高细节越多)

    # 2. 计算分形数据
    print("开始计算曼德博集合...")
    img = mandelbrot_set(x_min, x_max, y_min, y_max, width, height, max_iter)
    print("计算完成!")

    # 3. 绘图配置
    plt.figure(figsize=(10, 10), dpi=200)  # dpi越高,图片越清晰
    cmap = create_fractal_cmap()  # 自定义配色
    # 绘制(用log缩放让色彩过渡更自然)
    plt.imshow(img, cmap=cmap, extent=(x_min, x_max, y_min, y_max), aspect="equal")
    
    # 4. 美化设置(无坐标轴、标题等)
    plt.axis("off")  # 隐藏坐标轴
    plt.tight_layout(pad=0)  # 去除边距
    plt.title("Mandelbrot Set", fontsize=16, color="white", pad=10)  # 可选标题

    # 5. 保存高清图片
    plt.savefig(
        "mandelbrot_set.png",
        dpi=300,  # 保存分辨率
        bbox_inches="tight",  # 去除白边
        facecolor="black"  # 背景色(分形背景用黑色更美观)
    )
    plt.show()

if __name__ == "__main__":
    plot_mandelbrot()

2.2 关键优化与美化说明

  1. Numba加速@jit(nopython=True) 编译核心迭代函数,2000×2000分辨率的计算时间从几十分钟缩短到几十秒;
  2. 平滑上色 :传统的"迭代次数上色"会出现明显色块,通过 n + 1 - np.log(np.log2(abs(z))) 让色彩过渡更自然;
  3. 自定义配色:避开matplotlib默认配色,用深蓝→紫色→黄色的渐变,贴合曼德博集合的视觉特征;
  4. 高清输出 :设置dpi=300保存,无坐标轴、无白边,符合壁纸级视觉效果。

2.3 效果调整技巧

  • 放大局部细节 :修改x_min/x_max/y_min/y_max,比如聚焦"海马谷"(x∈[-0.8, -0.7], y∈[0.0, 0.1]),能看到更精细的分形结构;
  • 调整迭代次数max_iter越大,细节越多(但计算越久),局部放大时建议设为2000+;
  • 更换配色 :修改create_fractal_cmap中的颜色值,比如换成"青→绿→橙"的暖色调。

三、实战2:朱利亚集合(Julia Set)

朱利亚集合与曼德博集合同源,区别是迭代公式中 ( c ) 为固定常数,( z_0 ) 为复平面上的点(( z_{n+1}=z_n^2 + c ))。不同的 ( c ) 会生成完全不同的分形形态,视觉效果同样惊艳。

3.1 完整代码

python 复制代码
import numpy as np
import matplotlib.pyplot as plt
from numba import jit
from matplotlib.colors import LinearSegmentedColormap

# ===================== 1. 核心计算函数 =====================
@jit(nopython=True)
def julia(z, c, max_iter):
    """判断点z是否属于朱利亚集合"""
    n = 0
    while abs(z) <= 2 and n < max_iter:
        z = z * z + c
        n += 1
    if n == max_iter:
        return max_iter
    return n + 1 - np.log(np.log2(abs(z)))

@jit(nopython=True)
def julia_set(c, x_min, x_max, y_min, y_max, width, height, max_iter):
    """生成朱利亚集合数据"""
    x = np.linspace(x_min, x_max, width)
    y = np.linspace(y_min, y_max, height)
    img = np.zeros((height, width))
    for i in range(height):
        for j in range(width):
            z = complex(x[j], y[i])
            img[i, j] = julia(z, c, max_iter)
    return img

# ===================== 2. 自定义配色(冷色调) =====================
def create_julia_cmap():
    colors = [
        (0.0, 0.1, 0.2),    # 深蓝黑
        (0.1, 0.3, 0.8),    # 蓝
        (0.2, 0.8, 1.0),    # 青
        (0.5, 1.0, 0.8),    # 浅青
        (1.0, 1.0, 1.0)     # 白
    ]
    return LinearSegmentedColormap.from_list("julia", colors, N=1024)

# ===================== 3. 绘制朱利亚集合 =====================
def plot_julia():
    # 1. 核心参数(不同的c对应不同形态,推荐几个经典值)
    # c = -0.8 + 0.156j (经典螺旋)
    # c = 0.285 + 0.01j (羽毛状)
    # c = -0.7269 + 0.1889j (蝴蝶状)
    c = complex(-0.8, 0.156)
    x_min, x_max = -1.5, 1.5
    y_min, y_max = -1.5, 1.5
    width, height = 2000, 2000
    max_iter = 1000

    # 2. 计算数据
    print("开始计算朱利亚集合...")
    img = julia_set(c, x_min, x_max, y_min, y_max, width, height, max_iter)
    print("计算完成!")

    # 3. 绘图
    plt.figure(figsize=(10, 10), dpi=200)
    cmap = create_julia_cmap()
    plt.imshow(img, cmap=cmap, extent=(x_min, x_max, y_min, y_max), aspect="equal")
    plt.axis("off")
    plt.tight_layout(pad=0)
    plt.savefig(
        "julia_set.png",
        dpi=300,
        bbox_inches="tight",
        facecolor="black"
    )
    plt.show()

if __name__ == "__main__":
    plot_julia()

3.2 经典c值推荐

不同的复数 ( c ) 会生成完全不同的朱利亚集合:

  • c = -0.8 + 0.156j:螺旋状结构,视觉冲击力强;
  • c = 0.285 + 0.01j:羽毛状分形,细节丰富;
  • c = -0.7269 + 0.1889j:蝴蝶状分形,对称美感;
  • c = 0.45 + 0.1428j:类似星系的结构。

四、实战3:分形树(递归实现)

分形树是递归分形的经典案例,通过"主干→分支→子分支"的自相似递归生成,代码更简单,适合入门。

4.1 完整代码

python 复制代码
import matplotlib.pyplot as plt
import numpy as np

# ===================== 1. 递归绘制分形树 =====================
def draw_fractal_tree(x, y, angle, length, depth, ax):
    """
    递归绘制分形树
    :param x/y: 当前起点坐标
    :param angle: 当前分支角度(弧度)
    :param length: 当前分支长度
    :param depth: 递归深度
    :param ax: 绘图轴
    """
    if depth == 0:
        return
    
    # 计算分支终点坐标
    dx = length * np.cos(angle)
    dy = length * np.sin(angle)
    x2 = x + dx
    y2 = y + dy
    
    # 绘制当前分支(深度越浅,颜色越绿,线条越粗)
    color = (0.1, 0.6 + 0.3*(depth/10), 0.1)  # 从深绿到浅绿
    ax.plot([x, x2], [y, y2], color=color, linewidth=depth/2, solid_capstyle="round")
    
    # 递归绘制左分支(角度偏转30°,长度缩短)
    draw_fractal_tree(x2, y2, angle + np.pi/6, length * 0.7, depth - 1, ax)
    # 递归绘制右分支(角度偏转30°,长度缩短)
    draw_fractal_tree(x2, y2, angle - np.pi/6, length * 0.7, depth - 1, ax)

# ===================== 2. 绘制分形树 =====================
def plot_fractal_tree():
    # 1. 初始化画布
    fig, ax = plt.subplots(figsize=(10, 12), dpi=200)
    ax.set_aspect("equal")
    ax.set_xlim(-20, 20)
    ax.set_ylim(0, 30)
    ax.axis("off")  # 隐藏坐标轴
    ax.set_facecolor("#f0f0f0")  # 浅灰色背景

    # 2. 绘制分形树(起点在底部中间,初始角度向上,递归深度10)
    draw_fractal_tree(0, 0, np.pi/2, 10, 10, ax)

    # 3. 保存图片
    plt.tight_layout(pad=0)
    plt.savefig(
        "fractal_tree.png",
        dpi=300,
        bbox_inches="tight",
        facecolor="#f0f0f0"
    )
    plt.show()

if __name__ == "__main__":
    plot_fractal_tree()

4.2 效果调整

  • 递归深度depth越大,树枝越多(建议10~12,太大易卡顿);
  • 分支角度 :修改np.pi/6(30°),角度越大,树越"蓬松";
  • 长度缩放 :修改0.7,值越小,分支越短,树越紧凑;
  • 颜色 :调整color的RGB值,比如换成"红→橙"的秋色调。

五、进阶美化技巧

5.1 色彩优化

  • 避免纯黑/纯白:用深灰(#101010)或浅灰(#f8f8f8)作为背景,更柔和;
  • 渐变配色:用LinearSegmentedColormap自定义渐变,贴合分形的层次;
  • 对数缩放:绘图时用np.log(img + 1)让低迭代次数的色彩过渡更自然。

5.2 高清输出

  • 设置dpi=300(打印级分辨率),bbox_inches="tight"去除白边;
  • 保存为PNG(无损)或SVG(矢量图,无限放大无锯齿);
  • 对于超大分辨率(4000×4000+),可分块计算避免内存溢出。

5.3 动态效果(可选)

结合matplotlib.animation制作分形演化动画(比如曼德博集合放大过程):

python 复制代码
# 示例:简单动画框架(需结合曼德博代码)
import matplotlib.animation as animation

fig, ax = plt.subplots(figsize=(10,10))
def update(frame):
    # 每次帧调整x/y范围(放大局部)
    x_min = -2.0 + frame*0.01
    x_max = 1.0 - frame*0.01
    img = mandelbrot_set(x_min, x_max, y_min, y_max, width, height, max_iter)
    ax.imshow(img, cmap=cmap, extent=(x_min, x_max, y_min, y_max))
    ax.axis("off")
    return [ax]

ani = animation.FuncAnimation(fig, update, frames=100, interval=50)
ani.save("mandelbrot_animation.mp4", writer="ffmpeg", dpi=150)

六、总结

  1. 核心工具numpy(数值)+ matplotlib(绘图)+ numba(加速)是绘制分形的黄金组合;
  2. 美化关键:自定义配色、平滑上色、高清无白边输出,避开默认样式;
  3. 分形类型
    • 曼德博/朱利亚集合:适合复杂精细的视觉效果,需Numba加速;
    • 分形树:递归实现,简单易上手,适合入门;
  4. 扩展方向:可尝试科赫雪花、谢尔宾斯基三角形,或结合OpenCV添加滤镜效果。

通过以上代码和技巧,你可以轻松画出壁纸级的分形图,无论是用于学习、可视化还是艺术创作,都能达到专业级效果。

相关推荐
陳10302 小时前
C++:继承
开发语言·c++
GSDjisidi2 小时前
正社員・個人事業主歓迎|GSD東京本社で働こう|業界トップクラスの福利厚生完備
开发语言·面试·职场和发展
xiaoye-duck2 小时前
C++ string 类使用超全攻略(下):修改、查找、获取及常见实用接口深度解析
开发语言·c++·stl
Tao____2 小时前
可以本地部署的物联网平台
java·开发语言·物联网·mqtt·低代码
码界奇点2 小时前
基于DDD与CQRS的Java企业级应用框架设计与实现
java·开发语言·c++·毕业设计·源代码管理
柏林以东_2 小时前
线程安全的数据集合
java·开发语言·安全
喵喵喵小鱼2 小时前
arcgis JavaScript api实现同时展示多个撒点气泡
开发语言·javascript·arcgis
fengfuyao9852 小时前
基于MATLAB的螺旋锥齿轮齿面接触分析(TCA)实现
开发语言·matlab