基于Python和MoviePy库实现数据的动态展示

基于Python和MoviePy库实现数据的动态展示

(翻译:以马内利) 

原文链接:Data Animations With Python and MoviePy  


Python拥有很多实现数据可视化的库,但是很少可以展示GIFs的动态视图。 这篇博客主要介绍怎样使用MoviePy库作为一个其他可视化库的通用插件。

MoviePy 提供函数 make_frame(t) 来创建相对于时间 t(s) 动画视频框。

from moviepy.editor import VideoClip

def make_frame(t):
    """ returns an image of the frame at time t """
    # ... create the frame with any library
    return frame_for_time_t # (Height x Width x 3) Numpy array

animation = VideoClip(make_frame, duration=3) # 3-second clip

# For the export, many options/formats/optimizations are supported
animation.write_videofile("my_animation.mp4", fps=24) # export as video
animation.write_gif("my_animation.gif", fps=24) # export as GIF (slow)

在之前的博客中,曾把这个函数用于向量动画(animate vector graphics, 基于Gizh库), 还有光纤追踪场景动画(ray-traced 3D scenes, 基于POV-Ray). 这篇博客主要介绍科学计算先关库, Mayavi, Vispy, Matplotlib还有 Scikit-image.


使用Mayavi制作动画

Mayavi 是一个python的模块,具有一个简单的用户交互界面,用来制作和交互的3D数据展示。 第一个例子讲展示,表面高度随着时间(t)而变化。 

import numpy as np
import mayavi.mlab as mlab
import  moviepy.editor as mpy

duration= 2 # duration of the animation in seconds (it will loop)

# 使用MAYAVI先创建一个图片

fig_myv = mlab.figure(size=(220,220), bgcolor=(1,1,1))
X, Y = np.linspace(-2,2,200), np.linspace(-2,2,200)
XX, YY = np.meshgrid(X,Y)
ZZ = lambda d: np.sinc(XX**2+YY**2)+np.sin(XX+d)

# 使用MoviePy把这个图片创建为一个动画,并保存

def make_frame(t):
    mlab.clf() # clear the figure (to reset the colors)
    mlab.mesh(YY,XX,ZZ(2*np.pi*t/duration), figure=fig_myv)
    return mlab.screenshot(antialiased=True)

animation = mpy.VideoClip(make_frame, duration=duration)
animation.write_gif("sinc.gif", fps=20)

基于Python和MoviePy库实现数据的动态展示

另一个例子将展示网眼线框的坐标和视角随着时间的变化而变化。 

import numpy as np
import mayavi.mlab as mlab
import  moviepy.editor as mpy

duration = 2 # duration of the animation in seconds (it will loop)

# 先创建一个图片

fig = mlab.figure(size=(500, 500), bgcolor=(1,1,1))

u = np.linspace(0,2*np.pi,100)
xx,yy,zz = np.cos(u), np.sin(3*u), np.sin(u) # Points
l = mlab.plot3d(xx,yy,zz, representation="wireframe", tube_sides=5,
                line_width=.5, tube_radius=0.2, figure=fig)

# 使用MoviePy把这个图片创建为一个动画,并保存为GIF格式

def make_frame(t):
    """ Generates and returns the frame for time t. """
    y = np.sin(3*u)*(0.2+0.5*np.cos(2*np.pi*t/duration))
    l.mlab_source.set(y = y) # change y-coordinates of the mesh
    mlab.view(azimuth= 360*t/duration, distance=9) # camera angle
    return mlab.screenshot(antialiased=True) # return a RGB image

animation = mpy.VideoClip(make_frame, duration=duration).resize(0.5)
# Video generation takes 10 seconds, GIF generation takes 25s
animation.write_videofile("wireframe.mp4", fps=20)
animation.write_gif("wireframe.gif", fps=20)

基于Python和MoviePy库实现数据的动态展示

因为Mayavi依赖于强大的ITK可视化引擎,因此它可以处理更加复杂的数据集。 

下面这个动画来是一个Mayavi 的事例。 

代码链接

EJZELfi.gif


基于Vispy的动画

Vispy是另一个可交互动态3D动画可视化库,基于OpenGL. 对于Mayavi, 我们首先创建图和网格,然后使用MoviePy使它们动起来。 

from moviepy.editor import VideoClip
import numpy as np
from vispy import app, scene
from vispy.gloo.util import _screenshot

canvas = scene.SceneCanvas(keys='interactive')
view = canvas.central_widget.add_view()
view.set_camera('turntable', mode='perspective', up='z', distance=2,
                azimuth=30., elevation=65.)

xx, yy = np.arange(-1,1,.02),np.arange(-1,1,.02)
X,Y = np.meshgrid(xx,yy)
R = np.sqrt(X**2+Y**2)
Z = lambda t : 0.1*np.sin(10*R-2*np.pi*t)
surface = scene.visuals.SurfacePlot(x= xx-0.1, y=yy+0.2, z= Z(0),
                        shading='smooth', color=(0.5, 0.5, 1, 1))
view.add(surface)
canvas.show()

# ANIMATE WITH MOVIEPY

def make_frame(t):
    surface.set_data(z = Z(t)) # Update the mathematical surface
    canvas.on_draw(None) # Update the image on Vispy's canvas
    return _screenshot((0,0,canvas.size[0],canvas.size[1]))[:,:,:3]

animation = VideoClip(make_frame, duration=1).resize(width=350)
animation.write_gif('sinc_vispy.gif', fps=20, opt='OptimizePlus')

基于Python和MoviePy库实现数据的动态展示

下面是一个来源于Vispy gallery的更加高级的例子,C嵌入到python里,用于调整优化3D的着色

代码链接

6PNEYB9.gif

代码链接

基于Python和MoviePy库实现数据的动态展示


基于Matplotlib库创建动画

3D/3D作图库Matplotlib已经包含了动画模块, 但是我认为MoviePy可以制作更加高质量的动画,而且更加迅速,虽然不知道为啥,详情请看这里。 一下就是怎么使用 MoviePy 制作 Matplotlib制作动画: 

import matplotlib.pyplot as plt
import numpy as np
from moviepy.video.io.bindings import mplfig_to_npimage
import moviepy.editor as mpy

# 使用MATPLOTLIB画一个图

duration = 2

fig_mpl, ax = plt.subplots(1,figsize=(5,3), facecolor='white')
xx = np.linspace(-2,2,200) # the x vector
zz = lambda d: np.sinc(xx**2)+np.sin(xx+d) # the (changing) z vector
ax.set_title("Elevation in y=0")
ax.set_ylim(-1.5,2.5)
line, = ax.plot(xx, zz(0), lw=3)

# 使用MOVIEPY让图动起来(根据时间t来更新图). 保存为GIF.

def make_frame_mpl(t):
    line.set_ydata( zz(2*np.pi*t/duration))  # <= Update the curve
    return mplfig_to_npimage(fig_mpl) # RGB image of the figure

animation =mpy.VideoClip(make_frame_mpl, duration=duration)
animation.write_gif("sinc_mpl.gif", fps=20)

基于Python和MoviePy库实现数据的动态展示

import numpy as np
import matplotlib.pyplot as plt
from sklearn import svm # sklearn = scikit-learn
from sklearn.datasets import make_moons
from moviepy.editor import VideoClip
from moviepy.video.io.bindings import mplfig_to_npimage

X, Y = make_moons(50, noise=0.1, random_state=2) # semi-random data

fig, ax = plt.subplots(1, figsize=(4, 4), facecolor=(1,1,1))
fig.subplots_adjust(left=0, right=1, bottom=0)
xx, yy = np.meshgrid(np.linspace(-2,3,500), np.linspace(-1,2,500))

def make_frame(t):
    ax.clear()
    ax.axis('off')
    ax.set_title("SVC classification", fontsize=16)

    classifier = svm.SVC(gamma=2, C=1)
    # the varying weights make the points appear one after the other
    weights = np.minimum(1, np.maximum(0, t**2+10-np.arange(50)))
    classifier.fit(X, Y, sample_weight=weights)
    Z = classifier.decision_function(np.c_[xx.ravel(), yy.ravel()])
    Z = Z.reshape(xx.shape)
    ax.contourf(xx, yy, Z, cmap=plt.cm.bone, alpha=0.8,
                vmin=-2.5, vmax=2.5, levels=np.linspace(-2,2,20))
    ax.scatter(X[:,0], X[:,1], c=Y, s=50*weights, cmap=plt.cm.bone)

    return mplfig_to_npimage(fig)

animation = VideoClip(make_frame, duration = 7)
animation.write_gif("svm.gif", fps=15)

Matplotlib 拥有很多漂亮的框架,而且和很多数量模块例如 Pandas 和 Scikit-Learn 兼容性良. 让我们欣赏一下当训练数据集增长的时候SVM分类器逐渐提高对于地图分布的理解。 

基于Python和MoviePy库实现数据的动态展示

简单来说,背景颜色告诉我们黑点和白点属于的类别。刚开始的时候没有清晰地线索,随着越来越多的数据加入以后,分类器逐渐理解数据呈月牙形分布状态。  


使用Numpy制作动画

如果经常处理Numpy数据阵列 (Numpy 是python的一个核心数字计算库), 无需任何其他拓展作图库就可以把数据阵列直接输入MoviePy.

下面的动画展示了法国丧尸大爆发的仿真动画(借鉴 Max Berggren的播客).  法国地图由网格建模(使用Numpy array)。丧尸病毒传播和感染都在这个模型上展示。 通过Numpy的一些简单操作后,这些网格被直接转化为

RGB图片,并输入到MoviePy. 

代码链接

基于Python和MoviePy库实现数据的动态展示


叠合动画 

啥比一个动画更炫酷呢? 哥们你答对了,两个动画!得益于MoviePy的合并功能,我们可以把两个不同库制作的动画合并起来。 

import moviepy.editor as mpy
# We use the GIFs generated earlier to avoid recomputing the animations.
clip_mayavi = mpy.VideoFileClip("sinc.gif")
clip_mpl = mpy.VideoFileClip("sinc_mpl.gif").resize(height=clip_mayavi.h)
animation = mpy.clips_array([[clip_mpl, clip_mayavi]])
animation.write_gif("sinc_plot.gif", fps=20)

基于Python和MoviePy库实现数据的动态展示

或者来点更加文艺的

# 在clip_mayavi中使得白色变成透明色
clip_mayavi2 = (clip_mayavi.fx( mpy.vfx.mask_color, [255,255,255])
                .set_opacity(.4) # whole clip is semi-transparent
                .resize(height=0.85*clip_mpl.h)
                .set_pos('center'))

animation = mpy.CompositeVideoClip([clip_mpl, clip_mayavi2])
animation.write_gif("sinc_plot2.gif", fps=20)

基于Python和MoviePy库实现数据的动态展示

是不是有点太酷炫了,不过有时候观众们需要点重口味的东西才好。 

同样我们也可以注释动画,从而实现比较不同过滤器或者算法。让我们看一个Scikit-image库的图像转换例子。 

import moviepy.editor as mpy
import skimage.exposure as ske # rescaling, histogram eq.
import skimage.filter as skf # gaussian blur

clip = mpy.VideoFileClip("sinc.gif")
gray = clip.fx(mpy.vfx.blackwhite).to_mask()

def apply_effect(effect, title, **kw):
    """ Returns a clip with the effect applied and a title"""
    filtr = lambda im: effect(im, **kw)
    new_clip = gray.fl_image(filtr).to_RGB()
    txt = (mpy.TextClip(title, font="Purisa-Bold", fontsize=15)
           .set_position(("center","top"))
           .set_duration(clip.duration))
    return mpy.CompositeVideoClip([new_clip,txt])

# 在原动画上实现四种效果
equalized = apply_effect(ske.equalize_hist, "Equalized")
rescaled  = apply_effect(ske.rescale_intensity, "Rescaled")
adjusted  = apply_effect(ske.adjust_log, "Adjusted")
blurred   = apply_effect(skf.gaussian_filter, "Blurred", sigma=4)

# 把得到的四种效果图合并在2x2分布的网格上,并写入文件
finalclip = mpy.clips_array([[ equalized, adjusted ],
                             [ blurred,   rescaled ]])
final_clip.write_gif("test2x2.gif", fps=20)

cMoPY1d.gif

如果我们使用concatenate_videoclips来代替CompositeVideo 和 Clipandclips_array, 我们将获得标题式动画,例子如下:

import moviepy.editor as mpy
import skimage.exposure as ske
import skimage.filter as skf

clip = mpy.VideoFileClip("sinc.gif")
gray = clip.fx(mpy.vfx.blackwhite).to_mask()

def apply_effect(effect, label, **kw):
    """ Returns a clip with the effect applied and a top label"""
    filtr = lambda im: effect(im, **kw)
    new_clip = gray.fl_image(filtr).to_RGB()
    txt = (mpy.TextClip(label, font="Amiri-Bold", fontsize=25,
                        bg_color='white', size=new_clip.size)
           .set_position(("center"))
           .set_duration(1))
    return mpy.concatenate_videoclips([txt, new_clip])

equalized = apply_effect(ske.equalize_hist, "Equalized")
rescaled  = apply_effect(ske.rescale_intensity, "Rescaled")
adjusted  = apply_effect(ske.adjust_log, "Adjusted")
blurred   = apply_effect(skf.gaussian_filter, "Blurred", sigma=4)

clips = [equalized, adjusted, blurred, rescaled]
animation = mpy.concatenate_videoclips(clips)
animation.write_gif("sinc_cat.gif", fps=15)

基于Python和MoviePy库实现数据的动态展示

最后,像第一个例子一样,MoviePy在处理视频数据的时候非常实用。 最后一个例子为细菌种群在视频范围内的增长和在黑白像素下的计数。第三个动画是细菌种群大小和时间的指数增长关系

代码链接

uoITKiA.gif


一个库就可以搞定所有动画吗? 

我希望我已经提供足够多屌炸天的模板供你用来在组会报告的时候惊吓你的同事了。任何库只要输出结果可以转换成为Numpy阵列,就可以使用MoviePy做成动画。 

一些作图库虽然已经提供了动画模块,但是修改和维护是件虐心虐身的事情。多亏了广大用户在不同环境下的测试工作,MoviePy已经逐渐趋于稳定(也许用户已经懒得报告bug了), 可以适用于很多场景。虽然要走的路还很长,但是用户逐渐青睐MoviePy用于展示视频或者GIF动画是一件乐事。就像用户青睐Matplotlib为Pandas和Scikit-learn做可视化一样。 

作为结尾福利,也是为了更好地符合你们的需要,我要向大家提一提另一个库,ImageIO。 这同样是个具有视频展示的python库,主要用于小界面任何图像,视频和体数据的读写。比如说你可以使用  imwrite()来读写图片,minwrite()用于任何视频和GIF, volwrite()用于体数据, write()用于流数据。

原创文章,作者:以马内利,如若转载,请注明出处:http://www.178linux.com/1333

(4)
以马内利以马内利
上一篇 2015-03-26 11:52
下一篇 2015-03-26

相关推荐

  • 马哥教育网络班21期+第一周课程练习

    一、计算机的组成及其功能 自上个世纪40年代开始截止到目前,我们所有的计算机包括手持的智能终端设备,它们整个组织体系设备都是遵循冯诺依曼体系结构。 现代计算机设备的组成部分: 运算器、控制器、存储器、输入设备、输出设备 控制器:控制器是整个计算机的枢纽,一般是控制计算机整个部件之间协调的,比如运算器要想运算的话,首先得从存储器中取出数值。或者输入设备输入数。…

    Linux干货 2016-07-07
  • 马哥教育网络班20期-第三周课程作业

    Table of Contents 1、列出当前系统上所有已经登录的用户的用户名,注意:同一个用户登录多次,则只显示一次即可。 2、取出最后登录到当前系统的用户的相关信息。 3、取出当前系统上被用户当作其默认shell的最多的那个shell。 4、将/etc/passwd中的第三个字段数值最大的后10个用户的信息全部改为大写后保存至/tmp/maxusers…

    Linux干货 2016-06-26
  • 集群-基础知识(1)

    背景 随着互联网访问量的急剧增加,单台服务器的能力已严重不能满足需求。则需要从两个方面考虑提高服务能力:1、向上扩展,2、向外扩展 向上扩展的缺点: 1、造价高 2、随着性能的提高,会在某个临界点遇到瓶颈,导致性能随后降低。 向外扩展的优点: 1、造价低 2、提供高并发能力和高可用性 3、可扩展性好。 分类 负载均衡集群(Load Balance) 高可用集…

    Linux干货 2015-11-26
  • Linux-系统启动的基本过程 以及相关破环修复实验。

    这章简单描述下系统的启动流程,主要以破环修复实验为主: 系统启动基本过程:       Linux系统启动过程大致按照如下步骤进行(这是一个简述):        第一阶段:BIOS启动引导阶段;       …

    2017-07-10
  • 高级文件系统管理之mdadm与lvm的创建

    ※配置配额系统 磁盘配额就是管理员可以为用户所能使用的磁盘空间进行配额限制,每一用户只能使用最大配额范围内的磁盘空间,比如一些网盘就是基于这个功能来做的,分配给用户固定的空间,花钱买,可以将空间设置的更大。 功能作用 磁盘配额可以限制指定账户能够使用的磁盘空间,这样可以避免因某个用户的过度的使用磁盘空间造成其它用户无法运行或工作甚至影响系统运行。这个功能不止…

    Linux干货 2016-08-27
  • 计算机网络基础

    计算机网络 指使用一定的通信线路,把地理位置上相对分散的、具有独立自制能力的计算机系统等连接起来,在一定通信协议的约束与控制下,实现数据交换的目的! 计算机网络体系结构 计算机网络体系结构是一个分层次的模块式结构,每一层完成一个功能,这种流式化的结构能提高生产力!网络分层的好处 促进了标准化 各层次相互独立,技术升级和扩展灵活性好 便于方案的设计 开放系统互…

    Linux干货 2016-09-06