第一篇博客 | 使用Python绘制Mandelbrot集的图像
2019/5/8 星期三 16:37
先放上结果图:

1. 什么是Mandelbrot集?
曼德博集合(Mandelbrot set,或译为曼德布洛特复数集合)是一种在复平面上组成分形的点的集合,以数学家本华·曼德博的名字命名。
定义
摘自维基百科:
曼德博集合可以用复二次多项式来定义:
$ {\displaystyle f_{c}(z)=z^{2}+c\,} $, 其中$ {\displaystyle c} $是一个复数参数。
从$ {\displaystyle z=0} $开始对$ {\displaystyle f_{c}(z)} $进行迭代:
$ {\displaystyle z_{n+1}=z_{n}^{2}+c,n=0,1,2,…} $
$ {\displaystyle z_{0}=0\,} $
$ {\displaystyle z_{1}=z_{0}^{2}+c=c\,} $
$ {\displaystyle z_{2}=z_{1}^{2}+c=c^{2}+c\,} $
每次迭代的值依序如以下序列所示:
$ {\displaystyle (0,f_{c}(0),f_{c}(f_{c}(0)),f_{c}(f_{c}(f_{c}(0))),\ldots )} $
不同的参数$ {\displaystyle c} $可能使序列的绝对值逐渐发散到无限大,也可能收敛在有限的区域内。
曼德博集合$ {\displaystyle M} $就是使序列不延伸至无限大的所有复数$ {\displaystyle c} $的集合。
2.通过Python伪代码来实现Mandelbrot集的范例
def In_Mandelbrot(c: complex) -> bool:
z = complex(0)
for i in range(ITER_DEPTH):
z = z*z + c
if z.real ** 2 + z.imag ** 2 > 4:
return False
return True
在这段代码中,ITER_DEPTH表示迭代深度。
3. 实战
首先,加载数学库和用于输出图像的Pillow库:
import math
from PIL import Image, ImageDraw
然后,定义之后要设置的常量:
# 清晰度,坐标轴转换成像素的比例值。
SCALE = 256
# 迭代深度
ITER_DEPTH = 64
# 图像显示用,控制着色的参数,建议设置成 ITER_DEPTH / 16。
ITER_PER_COLOR = 4
加上创建和保存图像的代码:
# 图像背景为黑色。
im = Image.new('RGB', (4 * SCALE,) * 2, 0)
g = ImageDraw.Draw(im)
im.save("mandelbrot.jpg", format='JPEG')
这里4 * SCALE中4 = 2(正负坐标轴)*2(坐标范围为2, -2)
然后在创建图像和保存图像的代码中加上绘制M集的代码:
for i in range(SCALE * 4):
for j in range(SCALE * 4):
# 将像素位置转换成坐标轴上的位置。
x = i / SCALE - 2.0
y = j / SCALE - 2.0
# 初始化迭代的参数
c = complex(x, y)
z = complex(0, 0)
# 迭代,迭代终止时使用迭代的次数作色。
for k in range(ITER_DEPTH):
z = z ** 2
z += c
if z.real ** 2 + z.imag ** 2 > 4:
# 着色,使用的公式可以让输出的图片变得更清晰
g.point((i, j), (0, int((k // ITER_PER_COLOR) * ((256 - 1) / (ITER_DEPTH // ITER_PER_COLOR2))), 0))
break
首先开头的循环
for i in range(SCALE * 4):
for j in range(SCALE * 4):
遍历每一个像素,然后在初始化坐标的公式中
x = i / SCALE - 2.0
y = j / SCALE - 2.0
像素值在除与SCALE之后在[0, 4]的范围内,再减去二就是对应的坐标值。
c = complex(x, y)
z = complex(0, 0)
这段已经在之前有解释,不再赘述。
for k in range(ITER_DEPTH):
z = z ** 2
z += c
if z.real ** 2 + z.imag ** 2 > 4:
# 着色,使用的公式可以让输出的图片变得更清晰
g.point((i, j), (0, int((k // ITER_PER_COLOR) * ((256 - 1) / (ITER_DEPTH // ITER_PER_COLOR))), 0))
break
迭代公式中其他已经在之前有解释,使用ImageDraw.ImageDraw.point函数来绘制对应的像素,将输出颜色的公式分离出来:
(
0, # Red
int((k // 2) * ((256 - 1) / (ITER_DEPTH // ITER_PER_COLOR))), # Green
0 # Blue
)
使用绿色主要是为了显眼。
int((k // ITER_PER_COLOR) * ((256 - 1) / (ITER_DEPTH // ITER_PER_COLOR)))
在for循环中 k是迭代次数,在迭代的过程中发散时迭代次数越多颜色越亮,因此就有了开头那张图的效果。
最后附上完整代码(开始写博客才发现我这是有多水):
from PIL import Image, ImageDraw
from math import sqrt
# 清晰度,坐标轴转换成像素的比例值。
SCALE = 256
# 迭代深度
ITER_DEPTH = 64
# 控制着色的参数,建议设置成 ITER_DEPTH / 16
ITER_PER_COLOR = 4
im = Image.new('RGB', (4 * SCALE,) * 2, 0)
g = ImageDraw.Draw(im)
for i in range(SCALE * 4):
for j in range(SCALE * 4):
# 将像素位置转换成坐标轴上的位置。
x = i / SCALE - 2.0
y = j / SCALE - 2.0
# 初始化迭代的参数
c = complex(x, y)
z = complex(0, 0)
# 迭代,迭代终止时使用迭代的次数作色。
for k in range(ITER_DEPTH):
z = z ** 2
z += c
if z.real ** 2 + z.imag ** 2 > 4:
# 着色,使用的公式可以让输出的图片变得更清晰
g.point((i, j), (0, int((k // ITER_PER_COLOR) * ((256 - 1) / (ITER_DEPTH // ITER_PER_COLOR))), 0))
break
im.save("mandelbrot.jpg", format='JPEG')
(完)