许可优化
许可优化
产品
产品
解决方案
解决方案
服务支持
服务支持
关于
关于
软件库
当前位置:服务支持 >  软件文章 >  LS-DYNA中如何设置SET中的NODE_LIST:Python Matplotlib画图教程(3)——如何绘制动图

LS-DYNA中如何设置SET中的NODE_LIST:Python Matplotlib画图教程(3)——如何绘制动图

阅读数 5
点赞 0
article_banner

6bf6247a60723299ffb66e2f6275701d.png

上一篇我们说好了,要给出整套源码,为了不占正文的篇幅,我会在文末贴出。放心,不是让你去关注公众号。因为,我并没有。。

之前我们聊了怎么画圆以及直线,我还是建议你能抽出10分钟把前面的文章先看了,因为这是个连续剧,如果你打开电视机看到的就是依萍在大雨中哭泣,那你肯定无法想象她竟然有一个司令爸爸。

2fbc8518b67af0041de05e30071cd85e.png



这次咱们主要来聊一聊,怎么让matplotlib绘制的图形动起来,一定程度上来说,一个跳动的图片总归会让人更有兴趣看下去,当然丑的除外。(阿多么痛的领悟)

不过为了体现出我对你们是真爱,我先拿一个丑的动图开场,后面我们再做一个有趣的。(一定要坚持看下去哦)

fd5a7766219c1ef1495097994eb367b6.gif

如果你已经熟读并背诵了上一期的内容,应该已经了解如何绘制出这样一个错综复杂的“蜘蛛网”图。

现在你可能会说,“呵,tui!请把你80年代的Disco灯关掉,我眼晕。”

82ddc494ef795bb39ffaaddea22eba81.png

别急,咱们一起来学习一下如何让平凡的图片动起来。



对于动图,或者视频而言,有几个非常重要的因素

  1. 这段动画是多少帧
  2. 一共有多少幅图

如果你是个男孩子(老男孩也是男孩子啊),玩游戏的时候对于帧数的要求一定会非常高,因为这一指标代表着是否会出现卡顿的现象。比如吃鸡,cs之类的游戏,0.1秒的卡顿也会决定鹿死谁手。而这个卡顿,就是因为在这一刻帧数低了(俗称掉帧)。

932325af662e3d91365f612ef0234f18.gif

帧也就是FPS(frames per second,不是first person shooting),代表每秒钟会播放多少张图。以电影来说,我国一般的标准是25帧,别觉得不可思议,即使是25帧的电影,也会让你觉得非常流畅,丝毫不卡。甚至对于有些影视作品,如果使用了50帧,60帧来制作,你会觉得太过流畅了,不像是电影。

但是如果一个游戏25帧,那么给人的感受就会非常差,卡顿感十足。(如果有兴趣,我可以写一篇相关的科普。)

所以当我们在制作动图的时候,如果需要表达一个连续性的动态效果,则需要50或者60帧,会有一个比较理想的结果。因为如果更高,比如90帧或120帧,从视觉而言,几乎没有变化,但是动图文件大小会翻倍。(因为画面多了一倍啊)

b8e70434c5f728fdab71e74cdcc904a2.png

如果你需要表达的是一些不连贯的效果,比如上方的Disco灯,其实只有2帧。

那么如何理解一共需要多少张图片呢?简单来说,就是如下的数学公式

对于这样的表达式应该没有什么必要去进行解释了,为了不侮辱各位的智商,咱们继续往后进行。

8e59983af48f2c83bf84e8b7b4a84f7a.png



matplotlib中同样也给出了制作动图的方法,使用起来非常方便。不信?咱们一起试试。

首先,请确保你已经了解了如何绘制出一个全连接图,也就是上一篇的内容。(实在写不出再去翻看答案。)

4e5e2843a3a0949f8610bfab9028f839.png

对比于文章开头那个丑丑的动图,其实只是动态的修改了圆的颜色,其他啥都没变

既然目标比较明确,那么我们首先去浏览一下官方的文档,看看能不能找到一些有用的东西。matplotlib.animation - Matplotlib 3.1.0 documentation​matplotlib.org
 

12714e5061f05ae58fd1ebed62093296.png

不知道你是何种感觉,我对于官方文档只想说,“花Q!”晦涩难懂就不说了,还没有点效果支持,差评!

002e9708fbe96560b06b8702d8acd314.png

当然对于学习而言,尤其是编码,如何在网上找答案显得格外重要。比如stackoverflow上的内容质量还是不错的,不过你得能接受全英文的环境。Matplotlib animation of a step​stackoverflow.com
 

887ba02a51996edc62af5c5d4c782584.png

比如上方的问题,虽然和我们当前要做的事情毫不沾边,但是整体的思路是类似的

如果需要制作动图需要两个重要的步骤

  1. 设置起始状态。
  2. 设置每一步如何更新/变动当前的画布。

简言之,我们需要定义两个方法,init以及animate来实现整个的动态效果。别急,咱们来看代码。


def init():    ax1.set_xlim(0, 10)    ax1.set_ylim(0, 10)    ax1.set_aspect('equal') #变成方形画布    ax1.axis('off') #将xy轴隐藏    draw_line(each_level) #画线算法    draw_level(each_level) #画圆算法

初始化的内容里比较简单,我们定义了整个画布的范围,属性,以及画出了首帧图片。(也就是上一次那个不会动的图)

a7cec57ab698f5436c82fcb1ac3809da.png

之后我们需要定义出,每一次图片更新时,都需要做些什么。从当前的效果而言,就是每次更新,随机变换圆形的颜色。


color_list=['r','g','b','c','y'] #随机颜色列表 def animate(i):    circles=ax1.patches #获取到所有的圆    for item in circles:        index = random.randint(0, len(color_list) - 1)        item.set_facecolor(color_list[ index])        item.set_edgecolor(color_list[ index])

这里我们会预先设定一个颜色列表,每次通过random函数来找出随机值,并通过set_facecolor以及set_edgecolor进行替换颜色。

如果觉得没看懂,并没有关系,这只是一个思想,首先需要想清楚,自己想要的动画效果是什么,以及每次的变动都在做什么。


import matplotlib.animation as animation anim=animation.FuncAnimation(fig, animate,                               init_func=init,                               frames=100,                               interval=600,                               blit=False)anim.save('a.gif',writer='pillow')

最后通过调用animation库中的生成方法,来合成一张动态图。注意看!重点都在下面呢!

  1. frames代表了总共有多少幅画面
  2. interval代表了图与图的间隔时间有多久,说白了就是
       
  3. init_func 和animate方法千变万化,随时根据自己的需求进行调整。
  4. anim.save('a.gif',writer='pillow'),如果储存的结果是gif格式,writer选择为pillow或ImageMagick(根据自己的实际配置选择)。如果是mp4格式,writer选择为‘ffmpeg’(一个你可能没听过但是每天都在用的东西。)

对,就是经过这一系列的操作,就可以绘制出动态更改颜色的动图了。看懵了?等下可以复制源码自己跑跑试试。

bcb8b5b08e32e3d63b5276f7edbfa826.png



我们怎么可能就画这么一个Low图就结束了呢?前面的内容,最多只能算作是一场热身啦~因为接下来,我们要画出来雨滴图(那是个啥?)。国际惯例,先给你看看成品。

我们还是仅仅应用绘制圆和做动图的方法,来实现这一过程。(多了我也不会啊。)

653a14e9e2d9ef0141855571f5c1271a.gif



简单的分析一波。整体来说,就是一张画布上,随机分布了一些圆,这些圆从半径是0开始慢慢扩大,直到达到某个数字时消失。为了体现出水波的减弱,我们用圆的颜色越来越浅来代表这一过程。

难么?难的。。

f2c2828d42733f0c605003ba6f1fec7c.png

首先,对于雨滴而言,不会在同一刻全都砸在水面上。即使是同一时间砸在水面的两滴雨滴,也不在同一时刻波纹消失。为了不给自己挖坑,咱们就不考虑波纹叠加之类的问题了。

通过这一波看似严谨的逻辑分析,我们把整个算法分为几个步骤

  1. 同一时刻画布上最多50个雨点。
  2. 半径是0的时候代表刚落在水面,半径超过1.5则认为这个雨点击出的水花消失。
  3. 对于水花的增长度也是随机变量,我们认为在接近1秒(0.8~1.2秒)的时间内,一个水花完全消失。
  4. 对于一个水花,随着半径的增大,颜色逐渐变浅,从纯黑到纯白。
  5. 为了显得不那么卡顿,我们使用50帧

分析的头头是道的,但是如何进行实践呢?现在只差一个码农了。

a3bfe373608560d552ab01cc13a2e3d1.png

等一下,我不就是的么?开码!


class drop():    def __init__(self,orig_point,cur_radius,max_radius,rate,index):        self.orig_point = orig_point        self.cur_radius = cur_radius        self.max_radius = max_radius        self.rate = rate        self.index=index    def draw_circle(self):        light=int((self.cur_radius/self.max_radius)*255)        color=str( '%x'%(light)).zfill(2)        cir=Circle(xy=(self.orig_point),radius=self.cur_radius ,ec ='#'+color*3,fc='w',zorder=self.index)        ax.add_patch(cir)

首先我们定义了一个class(类型),代表了一滴雨滴。对于原点,当前半径,最大半径,增大速率,以及index进行了设置,至于Index有啥意义,咱们后面看。

代码难度并不大,尝试用心去了解。至于Circle如何定义一个圆,上一篇我们也讲过啦,不会记得翻回去看哟~

插一句,我们如何挑选出代表了当前半径的灰色RGB值。

如果你懂RGB的色度值计算方法应该知道,一个颜色被分为了3个通道,每个通道都有0到255这样的256个选择,全是0即‘#000000’表示黑色,‘#FFFFFF’全是F则表示了白色。

那么灰色的色度值是什么呢?其实就是在RGB三个值都一样的时候,就代表了灰色(由浅到深),也是我们在代码中使用的方法。值得注意的是,这时候需要把10进制改为16进制


fig=plt.figure()ax=fig.add_subplot(111)ax.set_aspect(1)ax.set_xlim(0,10)ax.set_ylim(0,10)ax.axis('off')points=list() fps=50 #帧数start_count=5 #最初有多少雨滴start_rand=3 #最初雨滴的随机变量total=50 #最多雨滴术rate_low=1.8 #一个雨滴从有到无的速率,可以调整到自己喜欢的速度rate_range=0.3max_radius=1.5 #最大半径 def init():    global rate_low,rate_range    count=start_count+np.random.randint(0,start_rand+1)    for i in range(count):        rate=(rate_low+random.random()*rate_range)/fps        orig_x=random.random()*10        orig_y = random.random() * 10        point=drop((orig_x,orig_y),0,max_radius,rate,i)        points.append(point)        point.draw_circle() def animate(i):    plt.cla()    ax.set_aspect(1)    ax.set_xlim(0, 10)    ax.set_ylim(0, 10)    ax.axis('off')    for i,point in enumerate(points):        if(point.cur_radius<point.max_radius):            point.cur_radius+=point.rate            point.cur_radius=point.cur_radius if point.cur_radius<point.max_radius else point.max_radius            point.draw_circle()        else:            points.remove(point)     count=len(points)    diff=total-count    new_count=1 if diff>=1 else diff    for i in range(new_count):        rate = (rate_low + random.random() * rate_range)/fps        orig_x = random.random() * 10        orig_y = random.random() * 10        point = drop((orig_x, orig_y), 0, max_radius, rate, i)        points.append(point)        point.draw_circle()

上方代码代表了如何设置init方法以及每次动画在做什么,我们先卖个关子,各位有兴趣可以自己调整一下相关的参数或算法,自己动手远比我在这叨叨给你听效果好得多。

当然究其原因,其实是我写累了。哈哈哈哈。

e09b5724cf79ebdbb415853df2d86fc9.png

末尾贴出上一篇的源码,收!


import numpy as npimport matplotlib.pyplot as pltfrom matplotlib.patches import Circlefrom matplotlib.pyplot import Line2Dimport randomimport matplotlib.animation as animation  color_list=['r','g','b','c','y']def draw_level(each_level):    ylim=ax1.get_ylim()[1]    xlim=ax1.get_xlim()[1]    each_level=np.array(each_level)    each_col=xlim/((len(each_level)))    each_row=ylim/np.amax(each_level)    radius=0.4*each_row    for i,item in enumerate(each_level):        start_point=ylim-(ylim-item*each_row)/2        for j in range(item):            light=int(random.random()*255)            color =str( '%x' % light).zfill(2)            # if i==0 :            #     cir=Circle(xy=((i+0.5)*each_col,start_point-(j+0.5)*each_row),radius=radius,color='#'+color+color+color,zorder=999)            # else:            #     cir=Circle(xy=((i+0.5)*each_col,start_point-(j+0.5)*each_row),radius=radius,color='#000000',zorder=999)            index=random.randint(0,len(color_list)-1)            cir = Circle(xy=((i + 0.5) * each_col, start_point - (j + 0.5) * each_row), radius=radius,                         color=color_list[ index], zorder=999)            ax1.add_patch(cir) def draw_line(each_level):    ylim = ax1.get_ylim()[1]    xlim = ax1.get_xlim()[1]    each_level = np.array(each_level)    each_col = xlim / ((len(each_level)) )    each_row = ylim / np.amax(each_level)    result=list()    for i, item in enumerate(each_level):        start_point = ylim - (ylim - item * each_row) / 2        a = list()        for j in range(item):            a.append(start_point - (j + 0.5) * each_row)        result.append(a)    p1=result[0][1]    # for i_next in result[1]:    #         line=Line2D([(0.5)*each_col,(1.5)*each_col],[p1,i_next])    #         ax1.add_line(line)       for i in range(len(each_level)-1):        for item in result[i]:            for i_next in result[i+1]:                line=Line2D([(i+0.5)*each_col,(i+1.5)*each_col],[item,i_next])                ax1.add_line(line) def init():    ax1.set_xlim(0, 10)    ax1.set_ylim(0, 10)    ax1.set_aspect('equal')    ax1.axis('off')    draw_line(each_level)    draw_level(each_level) def animate(i):    circles=ax1.patches    for item in circles:        # light = int(random.random() * 255)        # color =str( '%x' % light).zfill(2)        # item.set_facecolor('#'+color+color+color)        index = random.randint(0, len(color_list) - 1)        item.set_facecolor(color_list[ index])        item.set_edgecolor(color_list[ index]) fig=plt.figure() ax1=fig.add_subplot(111) each_level=[3,5,6] # fig.savefig('t13.png')anim=animation.FuncAnimation(fig, animate,                               init_func=init,                               frames=100,                               interval=600,                               blit=False)anim.save('demo_6.gif',writer='pillow')

免责声明:本文系网络转载或改编,未找到原创作者,版权归原作者所有。如涉及版权,请联系删


相关文章
技术文档
QR Code
微信扫一扫,欢迎咨询~
customer

online

联系我们
武汉格发信息技术有限公司
湖北省武汉市经开区科技园西路6号103孵化器
电话:155-2731-8020 座机:027-59821821
邮件:tanzw@gofarlic.com
Copyright © 2023 Gofarsoft Co.,Ltd. 保留所有权利
遇到许可问题?该如何解决!?
评估许可证实际采购量? 
不清楚软件许可证使用数据? 
收到软件厂商律师函!?  
想要少购买点许可证,节省费用? 
收到软件厂商侵权通告!?  
有正版license,但许可证不够用,需要新购? 
联系方式 board-phone 155-2731-8020
close1
预留信息,一起解决您的问题
* 姓名:
* 手机:

* 公司名称:

姓名不为空

姓名不为空

姓名不为空
手机不正确

手机不正确

手机不正确
公司不为空

公司不为空

公司不为空