/ LaTeX and ...  

MATLAB札记(一):让图像动起来

MATLAB札记(一)——让图像动起来

既然在LaTeX\LaTeX札记第一篇中谈到了在TeX\TeX文档中插入动图,我们就势必要先获取一组动图,当然格式可以有许多种,可以是有软件生成的GIF,也可以是录屏,或者是生成的视频。这篇文章主要讲的是如何在MATLAB中使用自带的库函数获取GIF。

引入

众所周知,一般来说,我们在MATLAB中获得的图片是静态的。MATLAB能生成动态的图片是怎么回事呢?MATLAB相信大家都很熟悉,但是MATLAB能生成动态的图片是怎么回事呢,下面就让小编带大家一起了解吧。

MATLAB能生成动态的图片,其实就是MATLAB能利用一些自带的库函数生成并保存为动态GIF图片,大家可能会很惊讶MATLAB怎么会能生成动态的图片呢?但事实就是这样,小编也感到非常惊讶。

这就是关于MATLAB能生成动态的图片的事情了,大家有什么想法呢,欢迎在评论区告诉小编一起讨论哦!(没有评论区)

在使用MATLAB这个功能的过程中,找到的比较好的一篇博客是参考文献1,这篇文章写的不是特别细,但是一贴就能上手,挺好用的。也是因为讲解不是特别细,所以我做出来的图有点小问题。这里我会把我使用过程中发现的一些小问题,放在一起说。按照这篇文章的说法MATLAB绘制动态图主要用两种作用:

  • 表现绘图过程,即图形的增长过程
  • 表示一个参数对图形的影响

我在电磁场实验中主要用到的是第一种,即表现绘图效果(图形增长过程的)。

  • Step 0:确定好绘图的函数
  • Step 1:让静态的变成动态的
  • Step 2:获得首帧画面
  • Step 3:绘图过程——循环
  • Step 4:将图像序列反向写入GIF文件中(可选)

前期准备——原理

在使用MATLAB制作动图的过程中,主要有两道工序:让图像动起来并让它延续不断、获取连续图像生成GIF文件。主要使用的函数有

  • 让图像动起来并让它延续不断
    • getframemoive两个函数
  • 获取连续图像生成GIF文件
    • frame2imimwrite

当然以上的四个函数是我在电磁场实验3中使用的。

Step 0:确定好绘图的函数

首先我们要确定好绘图函数,以及一些变量的初始化。

比如在电磁场实验1B——磁聚焦实验中,我们需要有磁聚焦的函数。

1
cos(theta).*t,sin(theta).*(cos(t-pi)+1),sin(theta).*sin(t-pi)

而在电磁场实验3——平面电磁波的反射和干涉实验中,我们先需要确定入射波和反射波的各项参数,并知晓入射波、反射波、合成波、透射波的方程。我们甚至可以画一个透明平面来表示介质分界面。下面是平面电磁波向理想介质垂直入射的相关前期准备。

1
2
3
4
5
6
7
8
%% 初始参数设定
E0=1; % The amplitude of the incident wave at z = 0
k1=pi/2; % phase

%% 波函数
Ei=E0.*cos( -k1.*z+(j./90).*pi); %入射波
Er=-E0.*cos(k1.*z+(j./90).*pi)./3; %反射波
Et=2*E0.*cos( -k2.*zt+(j./90).*pi)./3; %透射波

Step 1:让静态的变成动态的

首先,最关键的是我们要获得动图。获取动图我了解到的有两个办法:

法Ⅰ:不考虑生成GIF,只要求能在MATLAB中看到动效。

比如在电磁场实验1中使用的彗星图comet3函数。彗星图是动画图,其中一个圆(彗星头部)跟踪屏幕上的数据点。彗星主体是位于头部之后的尾部。尾巴是跟踪整个函数的实线。comet3(x,y,z) 显示经过点 [x(i),y(i),z(i)][x(i),y(i),z(i)] 曲线的彗星图。但这个comet3函数暂没有找到什么比较好的获取GIF办法。下图时使用comet3函数获得的一个磁聚焦实验的图像。其基本动画函数为

1
2
3
4
5
6
7
t=0:0.01:2*pi;
for theta=[-10:2:10]*pi/180;
grid on;
hold on;
comet3(cos(theta).*t,sin(theta).*(cos(t-pi)+1),sin(theta).*sin(t-pi)); % 获得动态效果
plot3(cos(theta).*t,sin(theta).*(cos(t-pi)+1),sin(theta).*sin(t-pi)); % 获得全图
end

lab01-2a

这种方法有利有弊,但弊大于利。主要问题就是他没法导出成比较有效果的GIF,导出成GIF需要获取动作每一帧的图像,而这个函数在绘制每一条线的时候的中间帧我们是无法获得的,硬性去套用获取GIF的函数,只能画出这样的图。

lab012

法Ⅱ:利用堆叠关系,或者有动态效果的图。

如果我们把曲线分成很多小段,让每个小段依次显示,我们是不是也能获得动态的图片呢?答案是肯定的,但是我们无法让图片一起显示之前显示过的小段当前我们所需要出现的小段,所以这里我们不妨采用堆叠的关系,这是什么意思呢?请看下面的图片我们就能大致知道意思了(第一次画这样的图,画的效果并不好

幻灯片1

❗值得注意的是:这里的小段只是我形象化的表现,而到对这些小段取极限,取到无穷小时,小段就变成了一个个的点。

幻灯片2

同理,我们的堆叠操作也是每次在前一张画面上生成一个点。

这里我们选用的就是法Ⅱ。但是很显然法Ⅱ也会有一定的问题,我们最终得到的线其实是这么多线堆叠而成的,我们要对线条进行操作就会变得异常复杂。**尤其当我们需要导出最终图像的矢量格式时,甚至会出现断点问题,这点在有两条重叠线的情况下最甚。**请看下图“平面电磁波向理想导体垂直入射的仿真结果”。

黄蓝

从图中我们显然能够发现,入射波和反射波的曲线重叠在了一起,而橙色的反射波点被掩盖在蓝色的入射波点下,但又有时会隐隐约约的透出来。

关键步骤——利用函数写进GIF中

Step 2:获得首帧画面

首先,根据需要,我们需要确定首幅图的样式,并指定标题,坐标轴标题等样式。(这步可以省略

接着,为确保图像在采集的过程中包括坐标轴及标题,并在在指定的范围内获得图像文件,我们需要下列代码。(这段代码可以直接使用,不需要改动

1
2
3
4
5
6
7
8
9
10
%确保图像在采集的过程中包括坐标轴及标题
ax = gca;
ax.Units = 'pixels';
pos = ax.Position;
ti = ax.TightInset;
rect = [-ti(1), -ti(2), pos(3)+ti(1)+ti(3), pos(4)+ti(2)+ti(4)];

%在指定的范围内获得图像文件
frame = getframe(ax,rect);
im=frame2im(frame);

然后,创建gif文件,指定其样式,写入首帧图像。这里的lab032.gif就是我们生成GIF图片的名称,注意下面和这个名称要对应哦

1
2
3
4
5
6
%创建gif文件,指定其样式,写入首帧图像
k = 1;
%用胞元存储采集到的图像,方便后面反转图像用
[I{k},map{k}]=rgb2ind(im,256);
imwrite(I{k},map{k,1},'lab032.gif','gif','Loopcount',Inf,'DelayTime',0.2);
k = k + 1;

❗值得注意的是

  • 一般的,如果你把Step 2写在循环中,就会循环往复的生成第一帧,而你的GIF就只剩两帧了。但是我们可以通过条件语句if去判定,当前处在哪一帧,if(j==1) 那么我们将运行绘制首帧的代码。同理,我们可以还可以在if(j==1) 中放置一些初始化的语句。
  • 如果我们将上述代码放置在循环外,我们则需要注意考虑图像的持续显示,即hold on函数的使用。

Step 3:绘图过程——循环

绘图过程我们可以把它放在一个for循环中。根据Step 1中所讲的,我们采用堆叠的方式去生成动态图像,,每一帧也就对应for循环中的一步,所以我们在for循环中每一步就需要生成从最原始点到目前帧需要出现画面的整条曲线。

在循环中,我们首先绘制我们的图像,例如平面电磁波向理想介质垂直入射一些绘图函数代码的书写。(程序的核心

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
% 绘图前预备
z=linspace ( -15 ,0 ,1000);
zt=linspace (0 ,15,1000);
y=zeros (1 ,1000);

% 电场的入射波

Ei=E0.*cos( -k1.*z+(j./90).*pi);
plot3(z,y,Ei ,'Color' ,[0.00,0.45,0.74],'LineWidth' ,1.5);
grid on

% 电场的反射波
Er=-E0.*cos(k1.*z+(j./90).*pi)./3;
plot3(z,y,Er ,'Color' ,[0.85,0.33,0.10],'LineWidth' ,1);

% 电场的透射波
Et=2*E0.*cos( -k2.*zt+(j./90).*pi)./3;;
plot3(zt ,y,Et ,'Color' ,[0.47,0.67,0.19],'LineWidth' ,1);
hold off

接着,我们再次获取该帧的GIF图像。(获取GIF的核心这段代码可以直接使用,不需要改动

1
2
3
4
5
6
7
8
9
10
11
12
%下面是制作gif的主要代码,除了调节间隔时间外,一般不需要改动 
ax = gca;
ax.Units = 'pixels';
pos = ax.Position;
ti = ax.TightInset;
rect = [-ti(1), -ti(2), pos(3)+ti(1)+ti(3), pos(4)+ti(2)+ti(4)];
frame = getframe(ax,rect);
im=frame2im(frame);
[I{k},map{k}]=rgb2ind(im,256);
%写入模式为“追加”模式
imwrite(I{k},map{k},'mygif.gif','gif','WriteMode','append','DelayTime',0.1);
k = k + 1;

最后,我们对for循环进行+1操作,进入下一次循环。

Step 4:将图像序列反向写入GIF文件中(可选)

这个就是坑到我的地方了。其实我的平面电磁波向理想介质垂直入射并不需要反向写入的过程。如果反向写入后,Step 2中法Ⅱ所对应GIF所表现出的时序就是下图这样的。

演示

有需要的读者可以使用下列代码。

1
2
3
4
%将图像按相反的顺序再写入到gif中
for i = (k-1):-1:1
imwrite(I{i},map{i},'mygif.gif','gif','WriteMode','append','DelayTime',0.1);
end

例子

〔案例1〕平面电磁波向理想介质垂直入射的仿真结果

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
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
%% Normal incidence to ideal medium
clear ,clc
E0=1; % The amplitude of the incident wave at z = 0
k1=pi/2;% phase
k2=pi;% phase


%% Draw the interface between lossless medium and conductor
for j=1:180
display(j);
z = linspace(-1.5,1.5,100);
y = linspace(-1.5,1.5,100);
x = 0.*repmat(z,100,1) + 10000.*repmat(y,100,1);
surf(z,y,x);
alpha(.3);
shading interp
hold on
plot3 ([-15 15],[0 0],[0 0],'k','LineWidth' ,1);
hold on
plot3 ([0 0],[-1.5 1.5],[0 0],'k','LineWidth' ,1);
hold on
plot3 ([0 0],[0 0],[-1.5 1.5],'k','LineWidth' ,1);
hold on
xlabel('$z$','FontSize',14,'Interpreter','latex');ylabel('$y$','FontSize',14,'Interpreter','latex');zlabel('$x$','FontSize',14,'Interpreter','latex');
grid on
set(gca ,'XLim' ,[-15 15]);
set(gca ,'YLim',[-1.5 1.5]);
set(gca ,'ZLim',[-1.5 1.5]);
if j==1
annotation('textbox',...
[0.433214285714286 0.278523812884377 0.17979066144921 0.0733333299727666],...
'String',{'$\varepsilon_1,\mu_1,\sigma_1=0$'},...
'Interpreter','latex',...
'FontSize',13,...
'EdgeColor','none');
annotation('textbox',...
[0.70 0.393761908122476 0.195266855927426 0.0733333299727666],...
'String',{'$\varepsilon_2,\mu_2,\sigma_2=0$'},...
'Interpreter','latex',...
'FontSize',13,...
'EdgeColor','none');

annotation('textbox',[0.45 0.65 0.07 0.09],...
'Color',[0.00,0.45,0.74],...
'String',{'${\vec E_i}$'},...
'Interpreter','latex',...
'FontSize',15,...
'FitBoxToText','off',...
'EdgeColor','none');
annotation('textbox',[0.23 0.43 0.07 0.09],...
'Color',[0.85,0.33,0.10],...
'String','${\vec E_r}$',...
'Interpreter','latex',...
'FontSize',15,...
'FitBoxToText','off',...
'EdgeColor','none');
annotation('textbox',[0.73 0.45 0.07 0.09],...
'Color',[0.47,0.67,0.19],...
'String','${\vec E_t}$',....
'Interpreter','latex',...
'FontSize',15,...
'FitBoxToText','off',...
'EdgeColor','none');

% Preperation of gif
ax = gca;
ax.Units = 'pixels';
pos = ax.Position;
ti = ax.TightInset;
rect = [-ti(1), -ti(2), pos(3)+ti(1)+ti(3), pos(4)+ti(2)+ti(4)];
frame = getframe(ax,rect);
im=frame2im(frame);
k = 1;
[I{k},map{k}]=rgb2ind(im,256);
imwrite(I{k},map{k,1},'lab032.gif','gif','Loopcount',Inf,'DelayTime',0.1);
k = k + 1;
end

% Preperation of drawing
z=linspace ( -15 ,0 ,1000);
zt=linspace (0 ,15,1000);
y=zeros (1 ,1000);

% Electric field of incident wave

Ei=E0.*cos( -k1.*z+(j./90).*pi);
plot3(z,y,Ei ,'Color' ,[0.00,0.45,0.74],'LineWidth' ,1.5);
grid on

% Electric field of reflected waves
Er=-E0.*cos(k1.*z+(j./90).*pi)./3;
plot3(z,y,Er ,'Color' ,[0.85,0.33,0.10],'LineWidth' ,1);

% Electric field of transmitted waves
Et=2*E0.*cos( -k2.*zt+(j./90).*pi)./3;;
plot3(zt ,y,Et ,'Color' ,[0.47,0.67,0.19],'LineWidth' ,1);
hold off
% N(j) = getframe;

% Get GIF
ax = gca;
ax.Units = 'pixels';
pos = ax.Position;
ti = ax.TightInset;
rect = [-ti(1), -ti(2), pos(3)+ti(1)+ti(3), pos(4)+ti(2)+ti(4)];
frame = getframe(ax,rect);
im=frame2im(frame);
[I{k},map{k}]=rgb2ind(im,256);
imwrite(I{k},map{k},'lab032.gif','gif','WriteMode','append','DelayTime',0.1);
k = k + 1;
end

%% Write the image to the GIF in reverse order
for i = (k-1):-1:1
imwrite(I{i},map{i},'lab032.gif','gif','WriteMode','append','DelayTime',0.1);
end

%% Function to play animation
% for i=1:2
% movie(N,1,30);
% end

lab032

❗值得注意的是

如果我们将制作GIF的代码注释掉转而保留

1
2
3
4
5
N(j) = getframe;
%% Function to play animation
for i=1:10
movie(N,1,30);
end

我们就可让这个动画效果播放10遍。利用的就是getframe获取帧函数和moive播放函数了。

〔案例2〕eit=cost+isinte^{it}=\cos t+i\sin t

来自参考文献1

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
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
%适用于在一幅图中表现给图过程,即图形的增长过程

%主要用来绘制三维的欧拉公式
clc;clear;clf;close all;

%获得数据
tmax = 4*pi;
t = 0:0.01:tmax;
tmp = exp(1i*t);
x = real(tmp);
y = imag(tmp);
[a,b] = size(x);
y1 = zeros(a,b) ;
x1 = zeros(a,b);

%确定首幅图的样式,并指定标题,坐标轴标题等样式
plot3(x(1,1),t(1,1),y(1,1),'black');
hold on
plot3(x(1,1),t(1,1),y1(1,1),'blue');
plot3(x1(1,1),t(1,1),y(1,1),'r');
axis([-1,1,0,tmax,-1,1])
str = ['$${e^{it}} = \cos t + i\sin t $$',char(13,10)','created by Lijunjie!'];
title({str},'interpreter','latex')
xlabel('实轴');
ylabel('时间轴');
zlabel('虚轴');
grid on;
set(gcf,'Position',[0,0,600,600], 'color','w');
set(gca,'ydir','reverse') %反转坐标轴

%确保图像在采集的过程中包括坐标轴及标题
ax = gca;
ax.Units = 'pixels';
pos = ax.Position;
ti = ax.TightInset;
rect = [-ti(1), -ti(2), pos(3)+ti(1)+ti(3), pos(4)+ti(2)+ti(4)];

%在指定的范围内获得图像文件
frame = getframe(ax,rect);
im=frame2im(frame);

%创建gif文件,指定其样式,写入首帧图像
k = 1;
%用胞元存储采集到的图像,方便后面反转图像用
[I{k},map{k}]=rgb2ind(im,256);
imwrite(I{k},map{k,1},'mygif.gif','gif','Loopcount',Inf,'DelayTime',0.2);
k = k + 1;

%画图并采集到gif中
steptmp = 20; %每幅图要画的点数
i = steptmp;
while i < (b-1)
x_1 = x(1,(i-steptmp+1):i+1);
t_1 = t(1,(i-steptmp+1):i+1);
y_1 = y(1,(i-steptmp+1):i+1);
y1_1 = y1(1,(i-steptmp+1):i+1);
x1_1 = x1(1,(i-steptmp+1):i+1);
plot3(x_1,t_1,y_1,'black');
hold on
plot3(x_1,t_1,y1_1,'blue');
plot3(x1_1,t_1,y_1,'r');

%下面是制作gif的主要代码,除了调节间隔时间外,一般不需要改动
ax = gca;
ax.Units = 'pixels';
pos = ax.Position;
ti = ax.TightInset;
rect = [-ti(1), -ti(2), pos(3)+ti(1)+ti(3), pos(4)+ti(2)+ti(4)];
frame = getframe(ax,rect);
im=frame2im(frame);
[I{k},map{k}]=rgb2ind(im,256);
%写入模式为“追加”模式
imwrite(I{k},map{k},'mygif.gif','gif','WriteMode','append','DelayTime',0.1);
k = k + 1;

i = i + steptmp;
end

%将图像按相反的顺序再写入到gif中
for i = (k-1):-1:1
imwrite(I{i},map{i},'mygif.gif','gif','WriteMode','append','DelayTime',0.1);
end

mygif

〔案例3〕二阶系统的极点、频率特性

来自参考文献1

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
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
function second_order_system_gif_1()
%此种情况用来表示某个参数变化时,对相关图形的影响
%此函数用来表示当二阶系统的共轭极点的虚部变化时对系统的
%频率响应和阶跃响应的影响
%后期可以通过Ulead GIF Animator软件将这4张gif合并在一起
%问题:图像大小设置为某些值时,可能会出错,需要重新调整

clc;clear;close all;
%初始化数据
b = 2;
a = 0:0.5:20;
[~,size_a] = size(a);
num = b.^2 + a.^2;
for i = 1:size_a
den{i} = conv([1 b + a(i) * 1i],[1 b - a(i) * 1i]);
end
w = 0:0.01:30;
k = 1;
%有多幅图,用胞元数组来指定文件名,从而方便在循环中使用
fieldnames = {'1.gif','2.gif','3.gif','4.gif'};

%画图,并制作gif
%由于每次画图都要擦掉上一次画的图,所以图形不能一直用hold on
for i = 1:1:size_a
%完成图像的绘制,为了保证效果,要保证图像大小以及
%坐标轴的范围不变
figure(1);
set(gcf,'Position',[0,0,300,400], 'color','w');

[hz,hp,ht] = zplane(num(i),den{i});
hold on;
x_data = [0 hp.XData 0];
y_data = [0 hp.YData 0];
plot(x_data,y_data,'--');
ylim([-22,22]);
xlim([-6,6]);
title(['二阶系统的极点',char(10,13)',...
'Created by Lijunjie']);
set(gca,'XTick',[-6:2:6]);
hold off

%采集绘制频率响应的数据
h = freqs(num(i), den{i},w);
mag = abs(h);
phase = angle(h);
phasedeg = phase*180/pi;

figure(2)
picture_positon;
plot(w,mag);
grid on;
xlabel 'Frequency (rad/s)', ylabel Magnitude
ylim([0 5.5]);
xlim([0 30]);
title(['二阶系统的幅频特性',char(10,13)',...
'Created by Lijunjie']);
figure(3);
picture_positon;
plot(w,phasedeg);
xlabel 'Frequency (rad/s)', ylabel 'Phase (degrees)';
ylim([-200,0]);
xlim([0 30]);
title(['二阶系统的相频特性',char(10,13)',...
'Created by Lijunjie']);

sys = tf(num(i),den{i});
figure(4)
[y_tmp,t_tmp] = step(sys,3.5);
plot(t_tmp,y_tmp);
picture_positon;
title(['二阶系统的阶跃响应',char(10,13)',...
'Created by Lijunjie']);
xlabel('Time(seconds)');
ylabel('Amplitude');
axis([0 3.5 0 2]);

%制作pdf
if i == 1
%采集到首帧,需要设置gif的样式,以及确定图像的大小
for j = 1:4
figure(j)
frame = getframe(gcf); % 获取整个窗口内容的图像
im=frame2im(frame);
[I{j,k},map{j,k}]=rgb2ind(im,256);
imwrite(I{j,k},map{j,k},fieldnames{j},'gif','Loopcount',Inf,'DelayTime',0.2);
end
else
for j = 1:4
figure(j)
frame = getframe(gcf);% 获取整个窗口内容的图像
im=frame2im(frame);
[I{j,k},map{j,k}]=rgb2ind(im,256);
%追加模式
imwrite(I{j,k},map{j,k},fieldnames{j},'gif','WriteMode','append','DelayTime',0.1);
end
end
k = k + 1;
end

%将采集到的图像以相反的顺序写入
for i = (k-1):-1:1
for j = 1:4
imwrite(I{j,i},map{j,i},fieldnames{j},'gif','WriteMode','append','DelayTime',0.1);
end
end

function picture_positon
%设置图像的大小
set(gcf,'Position',[0,0,600,400], 'color','w');
end
end

12

34

一些注意事项

  1. 这个功能搭配LaTeX\LaTeX札记第一篇,即可在LaTeX\LaTeX制作的PDF中播放动画效果了,还是很炫酷。
  2. 这样制作的图片清晰度还可以,但是还是图片质量存在一定的损失的,但是看上去还可以。
  3. 这样制作的图像最后获得矢量图,可能会存在一定的小问题:尤其当我们需要导出最终图像的矢量格式时,甚至会出现断点问题,这点在有两条重叠线的情况下最甚。
  4. 代码基本可以直接套用,需要更改的地方不是很多,建议看懂Step 4,再去使用反向写入功能,其实大部分情况下,我们是不需要反向写入的。

参考文献

  1. 木子识时务.Matlab绘制动态 .gif 图[EB/OL].https://www.jianshu.com/p/cd9501bc810a.2017-08-20/2020-06-27.

本文标题:MATLAB札记(一):让图像动起来

文章作者:Levitate_

发布时间:2020年06月27日 - 09:25:30

原始链接:https://levitate-qian.github.io/2020/06/27/matlab-note-01/

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。