3D坐标轴下画一个姿态、位置可调整的圆柱体

matplotlib中3D坐标轴下圆柱体的绘制

介绍下需求:使用matplotlib在一个3D坐标轴下绘制任意位置、任意姿态的圆柱体。
在这里插入图片描述

分析:可以看出这里主要解决的问题有两个,一个是绘制圆柱体,另一个是圆柱体的姿态控制。其中对于姿态的控制,需要用到欧拉旋转矩阵。

核心思想:

  • 首先绘制中心坐标为(0,0,0)的半径为1、高为1的圆柱面(相当于是单位1的圆柱面),注意此时绘制出来的没有上底和下底,圆柱面的绘制主要通过将高度坐标1分为两份(-0.5,0.5)即底边和顶边的高度对应z坐标,将2*pi的角度u平分成若干份,sin(u)对应x坐标,cos(u)对应y坐标(此处利用圆心为(0,0)的极坐标公式)
  • 将每个高度z与x,y分别对应,此时即得到圆柱体上底与下底的三维坐标,此时对x,y,z坐标进行相应调整即可构造出不同位置,不同姿态,不同大小的圆柱体的上底和下底的三维坐标
  • 利用matplotlib中的plot_surface方法即可绘制出圆柱面,此时绘制的仅仅为圆柱曲面,另外需要把两个底面补上,利用上面得到的上底与下底的3维坐标,利用add_collection3d方法即可将底面和顶面补上。

关于圆柱体的绘制

plot_surface函数的定义如下:

def plot_surface(self, X, Y, Z, *args, norm=None, vmin=None,
                 vmax=None, lightsource=None, **kwargs):
    """
    Create a surface plot.
    Parameters
    ----------
    X, Y, Z : 2d arrays

    color : color-like
    Color of the surface patches.可以设置16进制的rgb颜色

    rstride=1,  # rstride(row)指定行的跨度
    cstride=1,  # cstride(column)指定列的跨度

add_collection3d函数的定义

def add_collection3d(self, col, zs=0, zdir='z'):
    """
    Add a 3D collection object to the plot.

    2D collection types are converted to a 3D version by
    modifying the object and adding z coordinate information.

    Supported are:
        - PolyCollection
        - LineCollection
        - PatchCollection
    """

PolyCollection表示3维多边形的集合类,可以通过该类对平面多边形给出正确的填充外观,该类的定义如下:

def __init__(self, verts, *args, zsort='average', **kwargs):

    """
    Parameters
    ----------
    verts : list of array-like Nx3
        Each element describes a polygon as a sequence of ``N_i`` points
        ``(x, y, z)``.
    facecolors:可以为平面设置16进制的rgb颜色
    """

Notes:
关于圆柱面的绘制可以参考https://blog.csdn.net/weixin_41494909/article/details/86257870

PolyCollection类中的verts参数为包含表示多边形的坐标的元组的list,进而通过add_collection3d函数在3D坐标轴下绘制填充颜色的平面。多边形填充这部分参考:https://stackoverflow.com/questions/64029288/plotting-poly3dcollection-using-add-collection3d

圆柱的姿态问题

解释一下欧拉角:
在这里插入图片描述
如上图所示欧拉角Yaw,Pitch以及Roll,这三个角度的可以决定物体的姿态,由这3个角度可计算欧拉旋转矩阵,而相对于观察坐标系,物体的姿态发生变化时,只需要通过原位置坐标乘以欧拉旋转矩阵即可得到姿态变化后的物体在观测坐标系下的坐标。
更详细的内容可以参考:https://www.cnblogs.com/flyinggod/p/8144100.html
由上面的图,可以看出来,对于圆柱体而言,无论Yaw角度如何变化,物体的姿态是不变化的,所以对于圆柱体而言,只有pitch和Roll角度控制着物体的姿态。
欧拉旋转矩阵的实现代码如下:

def RotationMatrix(theta, phi, psi):
    '''将观测坐标系中的向量v转换成物体坐标系中向量v'或者将向量(坐标)绕原点旋转.

    Notes
    -----
    此程序旋转顺序(z->y->x),内旋.
    .每种特定顺序的外旋等价于其相反顺序的内旋,反之亦然.
    .坐标系绕原点旋转的旋转矩阵与向量(坐标)绕原点旋转的旋转矩阵互为转置.
    .世界坐标系向目标坐标系旋转的旋转矩阵与目标坐标系向世界坐标系旋转的旋转矩阵互为转置.
    '''

    theta, phi, psi = theta * pi / 180, phi * pi / 180, psi * pi / 180

    Rz = np.mat([[cos(psi), sin(psi), 0],
                 [-sin(psi), cos(psi), 0],
                 [0, 0, 1]])

    Ry = np.mat([[cos(theta), 0, -sin(theta)],
                 [0, 1, 0],
                 [sin(theta), 0, cos(theta)]])

    Rx = np.mat([[1, 0, 0],
                 [0, cos(phi), sin(phi)],
                 [0, -sin(phi), cos(phi)]])

    return Rx * Ry * Rz

代码示例

示例:

import numpy as np
from math import *

from mpl_toolkits.mplot3d.art3d import Poly3DCollection
import matplotlib.pyplot as plt


class Target(object):
    """Refer in particular to cylinder targets.

    Attributes
    ----------

    radius: float
        The radius of the base of a cylinder
    pitch: float
        the pitch angle of the cylinder
    roll: float
        the roll angle of the cylinder
    yaw:float
    	the roll angle of the cylinder
    length: float
        the length of the target
    position: list
        the position of the target, [x, y, z]

    """

    def __init__(self, radius, pitch, roll, yaw, length, position_x, position_y, position_z, **kwargs):
        self.radius = radius
        self.pitch = pitch
        self.roll = roll
        self.yaw = yaw
        self.length = length
        self.position = [position_x, position_y, position_z]


def RotationMatrix(theta, phi, psi):
    '''将观测坐标系中的向量v转换成物体坐标系中向量v'或者将向量(坐标)绕原点旋转.

    Notes
    -----
    此程序旋转顺序(z->y->x),内旋.
    .每种特定顺序的外旋等价于其相反顺序的内旋,反之亦然.
    .坐标系绕原点旋转的旋转矩阵与向量(坐标)绕原点旋转的旋转矩阵互为转置.
    .世界坐标系向目标坐标系旋转的旋转矩阵与目标坐标系向世界坐标系旋转的旋转矩阵互为转置.
    '''

    theta, phi, psi = theta * pi / 180, phi * pi / 180, psi * pi / 180

    Rz = np.mat([[cos(psi), sin(psi), 0],
                 [-sin(psi), cos(psi), 0],
                 [0, 0, 1]])

    Ry = np.mat([[cos(theta), 0, -sin(theta)],
                 [0, 1, 0],
                 [sin(theta), 0, cos(theta)]])

    Rx = np.mat([[1, 0, 0],
                 [0, cos(phi), sin(phi)],
                 [0, -sin(phi), cos(phi)]])

    return Rx * Ry * Rz


def show_cylinder(fig, target):
    """在3D坐标轴下展示一个任意位置,任意姿态的圆柱体

    Parameters
    ----------
    fig : matplotlib.figure.Figure
        传入一个空白的fig
    target : class Target
        一个圆柱体

    """

    fig.clf()  # Clear the figure in different detection scene
    # show the metal cylinder

    u = np.linspace(0, 2 * np.pi, 50)  # 把圆划分50等份
    h = np.linspace(-0.5, 0.5, 2)  # 把高(1m)划分两等份,对应上底和下底

    x = target.radius * np.sin(u)
    y = target.radius * np.cos(u)

    x = np.outer(x, np.ones(len(h)))  # 20*2
    y = np.outer(y, np.ones(len(h)))  # 20*2
    z = np.outer(np.ones(len(u)), h)  # 20*2
    z = z * target.length

    x_rotation = np.ones(x.shape)  # 旋转后的坐标 20*2
    y_rotation = np.ones(y.shape)
    z_rotation = np.ones(z.shape)

    th1 = target.pitch
    th2 = target.roll
    th3 = target.yaw
    a = np.array(RotationMatrix(th1, th2, th3))  # 3*3 pitch,roll
    for i in range(2):
        r = np.c_[x[:, i], y[:, i], z[:, i]]  # 20*3
        rT = r @ a  # 20*3
        x_rotation[:, i] = rT[:, 0]
        y_rotation[:, i] = rT[:, 1]
        z_rotation[:, i] = rT[:, 2]
    ax = fig.add_subplot(projection='3d')
    ax.view_init(30, 45)

    ax.plot_surface(x_rotation + target.position[0], y_rotation + target.position[1], z_rotation + target.position[2],
                    color='#E7C261', alpha=1, antialiased=False)

    verts = [list(zip(x_rotation[:, 0] + target.position[0], y_rotation[:, 0] + target.position[1],
                      z_rotation[:, 0] + target.position[2]))]

    ax.add_collection3d(Poly3DCollection(verts, facecolors='#E7C261'))
    verts = [list(zip(x_rotation[:, 1] + target.position[0], y_rotation[:, 1] + target.position[1],
                      z_rotation[:, 1] + target.position[2]))]

    ax.add_collection3d(Poly3DCollection(verts, facecolors='#E7C261'))

    ax.set_xticks(np.arange(-2, 3, 1))
    ax.set_yticks(np.arange(-2, 3, 1))
    ax.set_xlabel('X/m')
    ax.set_ylabel('Y/m')
    ax.set_zlabel('Z/m')
    ax.set_xlim(-2, 2)
    ax.set_ylim(-2, 2)
    ax.set_zlim(target.position[2] - 2, 0.1)

    ax.grid(None)  # delete the background grid


target = Target(0.2, 0, 0, 0, 1, 0, 0, -5)
fig = plt.figure()
show_cylinder(fig, target)
plt.show()

运行结果:
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

爱学习的贝塔

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值