You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于旋转矩阵的固定角度间隔旋转立方体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目录。


问题原因

  1. 3D轴比例不一致:matplotlib的3D轴默认采用自动比例,即使设置了xlim/ylim/zlim,轴的实际显示比例仍可能变化,导致立方体在不同帧中视觉大小不一致,产生“加速”错觉。
  2. 欧拉角旋转的视觉非线性:多轴欧拉角组合旋转时,因旋转分量的视觉叠加或万向锁效应,可能导致部分时间段内旋转看起来速度不均。
  3. 帧读取逻辑不稳定:原代码通过异常捕获终止帧读取,若出现临时文件丢失会导致帧序列不完整,影响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()

关键修改说明

  1. 等比例3D轴:添加ax.set_box_aspect([1,1,1]),确保三个轴的显示比例完全一致,消除视觉变形导致的“加速”错觉。
  2. 标准化旋转矩阵:拆分旋转步骤并采用标准Z-Y-X旋转顺序,避免欧拉角计算混乱,确保旋转角速度均匀。
  3. 稳定帧处理:make_gif按固定帧数量读取图像,显式设置帧延迟,保证GIF播放速度稳定。
  4. 优化视觉效果:用立方体边替代散点,减少视觉干扰,更清晰展示旋转状态。

内容的提问来源于stack exchange,提问作者vs07

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.21 20:14:56