Matplotlib Animation

  • 정지된 그림으로는 볼 수 없는 것들이 있습니다.
  • 시간에 따른 변화나 입체 도형의 뒷면이 그것입니다.
  • 애니메이션을 활용해 이를 보완합니다.

1. Matplotlib animation

matplotlib.animation

  • Matplotlib에서 사용할 수 있는 애니메이션은 두 가지가 있습니다.
  • Artist 객체 변화를 저장하는 ArtistAnimation,
  • Figure 전체의 변화를 저장하는 FuncAnimation이 그 것입니다.

1.1. base figure

  • 간단한 그림을 그려서 애니메이션으로 만듭니다.
  • 가운데 동그라미를 하나 그리고, 이 동그라미가 점점 커지는 모습을 구현합니다.
  • 기본 명령어를 사용해 화면 한가운데 동그라미를 그립니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 기본 설정
%matplotlib inline

import matplotlib.pyplot as plt
import seaborn as sns
import numpy as np
import pandas as pd

sns.set_context("talk")
sns.set_style("ticks")
sns.set_palette("colorblind")

# 가운데 동그라미 점 하나
fig, ax = plt.subplots(figsize=(5, 5), constrained_layout=True)
ax.set(xlim=(-1, 1), ylim=(-1, 1), xticks=[], yticks=[])

circle = ax.scatter(0, 0, s=10, lw=1, ec="b", fc="none")


  • 객체 지향 방식을 사용해 이 객체의 크기를 바꾸겠습니다.
  • circle이라는 이름으로 저장한 marker 하나는 collections로 다루어집니다.
  • .set_sizes()에 list 형태로 새로운 size를 전달하여 크기를 변경합니다.
  • marker가 하나밖에 없으므로 원소가 하나뿐인 list를 입력합니다.
  • 이를 update()라는 이름의 함수로 만들어 적용합니다.
1
2
3
4
5
6
7
8
9
# marker 크기 변경 함수
def update(frame_number):
circle.set_sizes([frame_number*30])

# marker 크기
update(10)

# 화면 출력
display(fig)


  • 화면 가운데 있는 원이 커졌습니다.
  • 이제 연속적으로 적용하고 .gif 파일로 적용하면 애니메이션이 됩니다.

1.2. FuncAnimation

matplotlib.animation.FuncAnimation

  • frame마다 변하는 Figure 차례로 저장해 애니메이션으로 만듭니다.
  • Figure 객체에 위에서 만든 update()같은 함수를 연속적으로 적용합니다.
  • 이 때 사용하는 함수가 FuncAnimation이고, Figure 객체, 함수와 함께 frames에 총 프레임을 넣고,
  • intervals에 frame 사이 시간 간격을 ms단위로 입력합니다.
  • 위 두 코드 뒤에 FuncAnimation 한 줄을 추가하고, .save()로 파일로 저장합니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
from matplotlib.animation import FuncAnimation

fig, ax = plt.subplots(figsize=(5, 5), constrained_layout=True)

# Axes 주변부 요소 삭제
ax.set(xlim=(-1, 1), ylim=(-1, 1), xticks=[], yticks=[])

# scatter marker 생성
circle = ax.scatter(0, 0, s=10, lw=3, ec="b", fc="none")

# animation frame마다 적용되는 변화
def update(frame_number):
# size 변경
circle.set_sizes([frame_number*500])

# animation 객체 생성
anim = FuncAnimation(fig, update, frames=120, interval=5)

# animation을 gif로 저장
anim.save("FuncAni0.gif", fps=24)


1.3. ArtistAnimation

matplotlib.animation.ArtistAnimation

  • 조금 다른 방식으로 artist 객체의 변화를 저장해 animation을 만들 수 있습니다.
  • Artist에 변화를 준 내용을 list로 저장해서 ArtistAnimation()에 전달하는 방식입니다.
  • FuncAnimation()에는 함수를 전달했던 것과 다른 방식입니다.
  • 함수로 표현하기 어려운 급격한 변화도 담을 수 있지만 리스트가 담길 메모리는 다소 부담이 됩니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
from matplotlib.animation import ArtistAnimation

fig, ax = plt.subplots(figsize=(5, 5), constrained_layout=True)

# Axes 주변부 요소 삭제
ax.set(xlim=(-1, 1), ylim=(-1, 1), xticks=[], yticks=[])

# animation frame마다 적용되는 변화
circles = []
for frame_number in range(120):
# scatter marker 생성
circle = ax.scatter(0, 0, s=10, lw=3, ec="b", fc="none")

# size 변경
circle.set_sizes([frame_number*500])

# artist list 추가
circles.append([circle])

# animation 객체 생성
anim = ArtistAnimation(fig, circles, interval=5)

# animation을 gif로 저장
anim.save("ArtistAni0.gif", fps=24)


  • 같은 애니메이션을 구현했습니다.

2. 3D 도형 시각화 적용

2.1. z axis 주변 회전

  • 원래의 목적인 3D 도형의 뒷면을 보여주는 시각화를 수행합니다.
  • Matplotlib 공식 홈페이지에 있는 구조를 가져와 다른 방식으로 그립니다.
  • Axes.contourf()를 3D Axes에 표현해보고,
  • 다른 공간에는 Axes.plot_surface()와 함께 아래 면을 사용합니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 2D mesh grid
xx, yy = np.mgrid[-2:2:20j, -2:2:20j]
zz = xx*np.exp(-(xx)**2-yy**2)

# 3D visualization
fig, axs = plt.subplots(ncols=2, figsize=(10, 7), constrained_layout=True,
subplot_kw={"projection":"3d"})

# (A): filled 3D contour
axs[0].contourf(xx, yy, zz, cmap="inferno", levels=10)

# (B): 3D surface plot + 2D contour
axs[1].plot_surface(xx, yy, zz, cmap="inferno", ec="k", linewidths=0.5)
axs[1].contourf(xx, yy, zz, zdir="z", offset=-0.6)
axs[1].contour(xx, yy, zz, zdir="z", offset=-0.6, linewidths=2, colors=["w"])

titles = ["(A): Axes.contourf()", "(B): Axes.plot_surface() + 2D contours"]
for ax, title in zip(axs, titles):
ax.view_init(azim=225, elev=20)
ax.set_zlim(-0.6, 0.5)
ax.set_title(title, pad=0)


  • 제법 멋진 그림이 그려졌지만 뒷부분이 보이지 않습니다.
  • 한 frame에 2도씩, 두 그림 모두 회전시킵니다. 180 frame을 적용해 한바퀴를 돌립니다.
1
2
3
4
5
6
7
8
9
10
# animation frame마다 적용되는 변화
def update(frame_number):
axs[0].view_init(azim=225 + frame_number*2)
axs[1].view_init(azim=225 + frame_number*2)

# animation 객체 생성
anim = FuncAnimation(fig, update, frames=180, interval=5)

# animation을 gif로 저장
anim.save("FuncAni1.gif", fps=24)


  • 빙글빙글 돌아가는 모습이 표현됩니다.

2.2. 3D 회전

Pega Devlog: 3D curved surfaces

  • 이번에는 조금 더 자유롭게 회전시켜보겠습니다.

  • Axes.view_init()에 들어가는 두 개의 인자, elevazim에 랜덤으로 만든 array를 입력하면 됩니다.

  • 돌려보는 재미가 있는 3D 객체를 생성합니다. 오른쪽 Ni 그림을 사용합니다.

  • 과거 글에 있는 그림을 사용합니다.

  • 핵심 코드만 가져옵니다.

  • 잘려진 부분 없이 온전한 모습으로 그립니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
from itertools import product
from matplotlib.colors import LightSource

# angles
polars = np.linspace(0, 179, 37)
azimuths = np.linspace(0, 360, 73)

df = pd.DataFrame(product(polars, azimuths), columns=["azi", "polar"])
df["x"] = df.apply(lambda x: np.cos(np.deg2rad(x[1]))*np.sin(np.deg2rad(x[0])), axis=1)
df["y"] = df.apply(lambda x: np.sin(np.deg2rad(x[1]))*np.sin(np.deg2rad(x[0])), axis=1)
df["z"] = df.apply(lambda x: np.cos(np.deg2rad(x[0])), axis=1)

# 3D 객체 생성
K1_Ni = -0.5
K2_Ni = -0.2
def calc_uni(K1, K2, df):
return K1*(df["x"]**2 + df["y"]**2)

def calc_cubic(K1, K2, df):
return K1*(df["x"]**2 * df["y"]**2 + \
df["y"]**2 * df["z"]**2 + \
df["z"]**2 * df["x"]**2) + \
K2*(df["x"]**2 * df["y"]**2 * df["z"]**2)

df["E_Ni"] = df.apply(lambda x: calc_cubic(K1_Ni, K2_Ni, x), axis=1)*3 +1

# 극좌표계를 직교좌표계로 변환
df["x_Ni"] = df["E_Ni"] * df["x"]
df["y_Ni"] = df["E_Ni"] * df["y"]
df["z_Ni"] = df["E_Ni"] * df["z"]

fig, ax = plt.subplots(figsize=(5, 5), constrained_layout=True,
subplot_kw={"projection": "3d"})
ax.plot_surface(df[f"x_Ni"].values.reshape((37, 73)),
df[f"y_Ni"].values.reshape((37, 73)),
df[f"z_Ni"].values.reshape((37, 73)),
ec="k", lw=0.2,
color="w", lightsource=LightSource(0, 10))

ax.set_box_aspect((1, 1, 1))
elev0, azim0 = 20, -60
ax.view_init(elev0, azim0)
ax.axis(False)


  • elevazim을 미리 array로 만들어 두고,
  • frame_number를 index로 사용해 하나씩 꺼내는 함수를 만듭니다.
  • 그리고, FuncAnimation()에서 이들을 호출해 애니메이션을 생성합니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
frames = 360
elev = elev0 + np.random.normal(loc=3, scale=1, size=frames).cumsum()
azim = azim0 + np.random.normal(loc=3, scale=1, size=frames).cumsum()

# animation frame마다 적용되는 변화
def update(frame_number):
ax.view_init(elev[frame_number],
azim[frame_number])

# animation 객체 생성
anim = FuncAnimation(fig, update, frames=360, interval=5)

# animation을 gif로 저장
anim.save("FuncAni2.gif", fps=24)


  • 3차원 공간을 자유롭게 회전하는 영상이 되었습니다.


도움이 되셨나요? 카페인을 투입하시면 다음 포스팅으로 변환됩니다

Share