基于旋转矩阵的固定角度间隔旋转立方体GIF为何出现加速异常?
旋转立方体GIF出现加速现象的原因及解决方法
我用旋转矩阵编写了生成旋转立方体GIF的代码,按等间隔生成立方体图像后合成GIF,但发现立方体在GIF中有时会出现加速现象。代码如下:
import matplotlib.pyplot as plt import numpy as np from math import sin, cos, radians as rad import imageio import os class Point: def __init__(self, x, y, z): self.x = x self.y = y self.z = z def rotated(self, yaw, pitch, roll): x, y, z = self.x, self.y, self.z a, b, c = yaw, pitch, roll sa, sb, sc = sin(a), sin(b), sin(c) ca, cb, cc = cos(a), cos(b), cos(c) newx = (x*ca*cb + y*(ca*sb*sc-sa*cc) + z*(ca*sb*cc+sa*sc)) newy = (x*sa*cb + y*(sa*sb*sc+ca*cc) + z*(sa*sb*cc-ca*sc)) newz = (x*-sb + y*cb*sc + z*cb*cc) return Point(newx, newy, newz) def make_gif(fps): # creates images array and depth counter images, i = [], 1 # adds images to the images array while True: try: images.append(imageio.imread(f"imgs/{i}.png")) i += 1 except: break # saves the gif imageio.mimsave("plot.gif", images, fps=fps) # deletes all the images for i in range(1, len(images)+1): os.remove(f"imgs/{i}.png") def rotating_gif(frames, fps, pitch, roll, yaw): ax = plt.axes(projection="3d") points = [Point(x, y, z) for x in range(-5, 6) for y in range(-5, 6) for z in range(-5, 6)] i = 1 for p, r, y in zip(np.linspace(0, pitch, frames), np.linspace(0, roll, frames), np.linspace(0, yaw, frames)): rotated = [point.rotated(p, r, y) for point in points] x_vals = [point.x for point in rotated] y_vals = [point.y for point in rotated] z_vals = [point.z for point in rotated] ax.scatter3D(x_vals, y_vals, z_vals) ax.set_xlim(-7, 7) ax.set_ylim(-7, 7) ax.set_zlim(-7, 7) plt.grid(False) plt.axis('off') plt.savefig(f"imgs/{i}.png") plt.cla() i += 1 make_gif(fps) def main(): rotating_gif(240, 60, rad(-720), rad(720), rad(720)) if __name__ == "__main__": main()
依赖包版本:
matplotlib==3.7.1 numpy==1.24.3 imageio==2.9.0
运行前需创建imgs目录。
问题原因
- 3D轴比例不一致:matplotlib的3D轴默认采用自动比例,即使设置了
xlim/ylim/zlim,轴的实际显示比例仍可能变化,导致立方体在不同帧中视觉大小不一致,产生“加速”错觉。 - 欧拉角旋转的视觉非线性:多轴欧拉角组合旋转时,因旋转分量的视觉叠加或万向锁效应,可能导致部分时间段内旋转看起来速度不均。
- 帧读取逻辑不稳定:原代码通过异常捕获终止帧读取,若出现临时文件丢失会导致帧序列不完整,影响GIF播放节奏。
修正后的代码
import matplotlib.pyplot as plt import numpy as np from math import sin, cos, radians as rad import imageio import os class Point: def __init__(self, x, y, z): self.x = x self.y = y self.z = z def rotated(self, yaw, pitch, roll): x, y, z = self.x, self.y, self.z # 采用标准Z-Y-X旋转顺序(roll-yaw-pitch),拆分旋转步骤避免计算混乱 # 绕Z轴旋转roll cos_r, sin_r = cos(roll), sin(roll) x1 = x * cos_r - y * sin_r y1 = x * sin_r + y * cos_r z1 = z # 绕Y轴旋转yaw cos_y, sin_y = cos(yaw), sin(yaw) x2 = x1 * cos_y + z1 * sin_y y2 = y1 z2 = -x1 * sin_y + z1 * cos_y # 绕X轴旋转pitch cos_p, sin_p = cos(pitch), sin(pitch) x3 = x2 y3 = y2 * cos_p - z2 * sin_p z3 = y2 * sin_p + z2 * cos_p return Point(x3, y3, z3) def make_gif(fps, frame_count): # 按固定帧数量读取,避免异常捕获的不确定性 images = [] for i in range(1, frame_count + 1): img_path = f"imgs/{i}.png" if os.path.exists(img_path): images.append(imageio.imread(img_path)) # 显式设置帧延迟,确保播放速度稳定 duration = 1 / fps imageio.mimsave("plot.gif", images, fps=fps, duration=duration) # 清理临时文件 for i in range(1, frame_count + 1): os.remove(f"imgs/{i}.png") def rotating_gif(frames, fps, pitch, roll, yaw): # 创建固定尺寸的画布,避免帧大小变化 fig = plt.figure(figsize=(6,6)) ax = fig.add_subplot(projection="3d") # 设置X/Y/Z轴等比例显示,彻底解决视觉变形问题 ax.set_box_aspect([1,1,1]) # 仅生成立方体顶点(而非所有点),减少计算量并提升视觉清晰度 points = [ Point(-5, -5, -5), Point(-5, -5, 5), Point(-5, 5, -5), Point(-5, 5, 5), Point(5, -5, -5), Point(5, -5, 5), Point(5, 5, -5), Point(5, 5, 5) ] # 预计算所有帧的角度序列,确保每帧旋转增量完全一致 pitch_vals = np.linspace(0, pitch, frames) roll_vals = np.linspace(0, roll, frames) yaw_vals = np.linspace(0, yaw, frames) for idx, (p, r, y) in enumerate(zip(pitch_vals, roll_vals, yaw_vals), 1): rotated = [point.rotated(y, p, r) for point in points] x_vals = [pt.x for pt in rotated] y_vals = [pt.y for pt in rotated] z_vals = [pt.z for pt in rotated] # 绘制立方体的边而非散点,更直观展示旋转状态 edges = [ (0,1), (0,2), (0,4), (1,3), (1,5), (2,3), (2,6), (3,7), (4,5), (4,6), (5,7), (6,7) ] for edge in edges: ax.plot3D([x_vals[edge[0]], x_vals[edge[1]]], [y_vals[edge[0]], y_vals[edge[1]]], [z_vals[edge[0]], z_vals[edge[1]]], color='black') ax.set_xlim(-7, 7) ax.set_ylim(-7, 7) ax.set_zlim(-7, 7) plt.grid(False) plt.axis('off') # 固定保存参数,避免帧裁剪或尺寸变化 plt.savefig(f"imgs/{idx}.png", bbox_inches='tight', pad_inches=0) plt.cla() make_gif(fps, frames) def main(): rotating_gif(240, 60, rad(-720), rad(720), rad(720)) if __name__ == "__main__": main()
关键修改说明
- 等比例3D轴:添加
ax.set_box_aspect([1,1,1]),确保三个轴的显示比例完全一致,消除视觉变形导致的“加速”错觉。 - 标准化旋转矩阵:拆分旋转步骤并采用标准Z-Y-X旋转顺序,避免欧拉角计算混乱,确保旋转角速度均匀。
- 稳定帧处理:
make_gif按固定帧数量读取图像,显式设置帧延迟,保证GIF播放速度稳定。 - 优化视觉效果:用立方体边替代散点,减少视觉干扰,更清晰展示旋转状态。
内容的提问来源于stack exchange,提问作者vs07
相关产品推荐
相关产品推荐

