Matplotlib の同次座標系は、回転した 3D ステレオを描画します

同次座標系は、剛体の座標系と位置を記述し、相対回転、相対移動、絶対回転、および絶対移動の一連のメソッドも提供します.回転した 3D ステレオを描画するための最良の選択です.

同次座標系

デカルト座標系の 3 つの軸を として表しxyz、任意の同次座標系を として表します。普通

このような行列を使用して普通xyz座標系と座標系の関係を説明します。

S_{noa} = \begin{bmatrix} R_{noa} & \vec{p}\\vec{0} & 1 \end{bmatrix} = \begin{bmatrix}n_x&o_x&a_x&x\\n_y & o_y & a_y & y\ \ n_z & o_z & a_z & z\\ 0 & 0 & 0 & 1 \end{bmatrix}

座標系における座標系の原点の絶対位置を表し、 座標  系におけるn軸の方向ベクトル(かつ単位ベクトル)を表し、o軸とa軸は同じ\vec{p}=\begin{bmatrix} x & y & z \end{bmatrix}^T普通xyz\vec{n} = \begin{bmatrix} n_x & n_y & n_z \end{bmatrix}xyz

R_x、R_y、R_zさらに、回転行列 ( ) と並進行列 ( T)を含む同次変換行列を使用して、位置合わせされたサブ座標系の変換を完了することができます。

R_x= \begin{bmatrix} 1&&&\\&\cos{\theta}&- \sin{\theta}&\\&\sin{\theta}&\cos{\theta}&\\&& & 1 \end {bmatrix}, R_y= \begin{bmatrix} \cos{\theta} & & \sin{\theta} & \\ & 1 & & \\ - \sin{\theta} & & \cos{ \theta}& \\&&&1\end{bmatrix}

R_z= \begin{bmatrix}\cos{\theta}& - \sin{\theta}&&\\\sin{\theta}&\cos{\theta}&&\\&&1&\\&& & 1 \end{bmatrix }, T=\begin{bmatrix} 1 & & & dx\\ & 1 & & dy\\ & & 1 & dz\\ & & & 1 \end{bmatrix}

回転行列を 処方箋 例にとると、 座標系が x 軸を中心に回転 (つまり絶対変換) し、 座標  系が n 軸を中心に回転 (つまり相対変換) することをR_x \times S_{noa} 意味します。 普通S_{noa} \times R_x普通

通常、描画するときに注意する必要があるのは、各グラフのさまざまなコンポーネント (メカニカル アームなど: https://hebitzj.blog.csdn.net/article/details/123810092 ) の相対的な位置関係です。同次変換行列が提供する同次座標系変換方法は明らかに私たちのニーズを満たすことができます

グラフィック変換の原則

既知の同次座標系の場合 普通、この座標系でグラフィックスを描画するにはどうすればよいですか?

S_{ノーマル} の部分配列は  、座標系の 3 つの 軸のコンポーネントR_{ノーマル} を記述します 。たとえば、次のように記述します。  x 軸の n 軸のコンポーネントを記述し、 x 軸の o 軸のコンポーネントを記述し、コンポーネントを記述します。 x軸上のa軸の普通xyzn_x牛斧

普通 座標系の点が 与えられた 場合\beta = \begin{bmatrix} n' & o' & a' \end{bmatrix}、 xyz 座標系の x 座標は次のようになります。

x' = n'n_x+o'o_x + a'a_x

同じことが点の y 座標と z 座標にも当てはまり、次の変換によって\beta = \begin{bmatrix} n' & o' & a' \end{bmatrix} 点が 普通 座標からxyz 座標に変換されます。

\begin{bmatrix} x' & y' & z' \end{bmatrix} =\beta \times R_{noa}^T + \vec{p}^T = \begin{bmatrix} n' & o' & a ' \end{bmatrix} \times \begin{bmatrix} n_x & n_y & n_z\\ o_x & o_y & o_z\\ a_x & a_y & a_z \end{bmatrix} + \begin{bmatrix} x & y & z \end {bmatrix}

matplotlib の plot_surface と voxels のパラメーターは、3 次元空間のポイント セットです。

最初に (座標系で描かれた) 座標の原点にグラフを描き、次に普通 同次座標系 普通 に対応する行列が与えられた後 、上記の式を使用してxyz グラフの各点の座標を解きます。座標系

もちろん、この結論は 2 次元同次座標系にも拡張できます。

コアコード

matplotlib 描画と同次座標系のシームレスな接続を実現するために、CoordSys_3d クラスを作成しました。各クラス メソッドの機能は次のとおりです。

  • trans: xyz 軸上のオフセットを指定して、並進変換行列を生成します。
  • rot: 回転角度と回転軸を指定して、回転変換行列を生成します
  • abs_tf: trans と rot で生成された変換行列を入力し、絶対変換を行う
  • rela_tf: trans と rot で生成された変換行列を入力し、相対変換を行う
  • 適用: サーフェス/ソリッドを記述する xyz マトリックスが与えられた場合、同次座標系マトリックスに従ってサーフェス/ソリッドを変換および回転します。
from typing import Union

import matplotlib.pyplot as plt
import numpy as np


class _CoordSys_nd:
    dtype = np.float16
    dim = None
    # 位置, 各个轴的方向向量
    position = property(fget=lambda self: self.s[:self.dim, -1])
    direction = property(fget=lambda self: self.s[:self.dim, :self.dim])

    def __init__(self, state: np.ndarray = None):
        size = self.dim + 1
        self.s = np.eye(size, dtype=self.dtype)
        # 使用非空形参
        if isinstance(state, np.ndarray):
            assert state.shape == self.s.shape
            self.s = state

    def abs_tf(self, tf):
        ''' 绝对变换'''
        return type(self)(tf @ self.s)

    def rela_tf(self, tf):
        ''' 相对变换'''
        return type(self)(self.s @ tf)

    def apply(self, *coords) -> tuple:
        ''' 局部坐标值 -> 全局坐标值'''
        xyz = np.stack(coords, axis=-1) @ self.direction.T + self.position
        return tuple(i[..., 0] for i in np.split(xyz, self.dim, axis=-1))

    def plot_coord_sys(self, length=.5, linewidth=None,
                       colors=['orangered', 'deepskyblue', 'greenyellow'], labels='xyz'):
        ''' 绘制局部坐标系'''
        pos = self.position
        axis = self.direction.T * length
        for i in range(self.dim):
            plt.plot(*zip(pos, pos + axis[i]), c=colors[i], label=labels[i], linewidth=linewidth)

    def __str__(self):
        return str(self.s) + '\n'

    __repr__ = __str__


class CoordSys_2d(_CoordSys_nd):
    dim = 2

    def apply(self, x: np.ndarray, y: np.ndarray) -> tuple:
        ''' 局部坐标值 -> 全局坐标值'''
        return super().apply(x, y)

    def transform(self, dx: float = 0., dy: float = 0.,
                  theta: float = 0, relative: bool = True):
        ''' dx,dy: 平移变换的参数
            theta: 旋转变换的参数
            relative: 是否使用相对变换'''
        # 绕 z 轴旋转, 并平移
        mat = np.concatenate((np.eye(3, 2, dtype=self.dtype),
                              np.array((dx, dy, 1))[:, None]), axis=-1)
        if theta:
            theta = np.deg2rad(theta)
            cos, sin = np.cos(theta), np.sin(theta)
            mat[:2, :2] = np.array([[cos, -sin], [sin, cos]])
        return (self.rela_tf if relative else self.abs_tf)(mat)


class CoordSys_3d(_CoordSys_nd):
    dim = 3

    def apply(self, x: np.ndarray, y: np.ndarray, z: np.ndarray) -> tuple:
        ''' 局部坐标值 -> 全局坐标值'''
        return super().apply(x, y, z)

    @classmethod
    def trans(cls, dx: float = 0., dy: float = 0., dz: float = 0.) -> np.ndarray:
        ''' 齐次变换矩阵: 平移'''
        return np.concatenate((np.eye(4, 3, dtype=cls.dtype),
                               np.array((dx, dy, dz, 1))[:, None]), axis=-1)

    @classmethod
    def rot(cls, theta: float, axis: Union[int, str]) -> np.ndarray:
        ''' 齐次变换矩阵: 旋转'''
        mat, theta = np.eye(4, dtype=cls.dtype), np.deg2rad(theta)
        cos, sin = np.cos(theta), np.sin(theta)
        axis = 'xyz'.index(axis) if isinstance(axis, str) else axis
        if axis == 0:
            mat[1: 3, 1: 3] = np.array([[cos, -sin], [sin, cos]])
        elif axis == 1:
            mat[:3, :3] = np.array([[cos, 0, sin], [0, 1, 0], [-sin, 0, cos]])
        elif axis == 2:
            mat[:2, :2] = np.array([[cos, -sin], [sin, cos]])
        else:
            raise AssertionError(f'axis {axis} is out of bounds for 3 dimensions')
        return mat


if __name__ == '__main__':
    rot = CoordSys_3d.rot
    trans = CoordSys_3d.trans

    state = CoordSys_3d()
    # 相对变换
    state = state.rela_tf(rot(30, 'y'))
    print(state)
    # 绝对变换
    state = state.abs_tf(trans(dx=2, dy=3, dz=4))
    print(state)

次に、サーフェスとソリッドをそれぞれ描画する 2 つの関数を定義し、メソッドを検証します。

  • シリンダー: 内外面、上下底面で構成される中空円柱を描画し、plot_surface 関数を 4 回呼び出して描画します。初期状態では、中空円柱の主軸と法線ベクトル2 つの底面は両方とも z 軸です
  • rubik_cube: 複数の立方体で構成される中空のルービック キューブを描画し、ボクセル関数を 1 回呼び出して描画します

これら 2 つの関数の共通点は、最初に原点を中心にして (これは非常に重要です。効果が期待に応えた後に効果を微調整できます)、表面と 3 次元を表す xyz 行列を生成し、次に apply を使用することです。 xyz 行列を変換して変換を完了する CoordTF オブジェクトの関数 3D 画像の平行移動と回転操作

import matplotlib.pyplot as plt
import numpy as np

from coord import CoordSys_3d

red = 'orangered'
orange = 'orange'
yellow = 'yellow'
green = 'greenyellow'
cyan = 'aqua'
blue = 'deepskyblue'
purple = 'mediumpurple'
pink = 'violet'

ROUND_EDGE = 30  # 圆等效多边形边数
DTYPE = np.float16  # 矩阵使用的数据类型


def figure3d():
    ''' 创建3d工作站'''
    figure = plt.subplot(projection='3d')
    tuple(getattr(figure, f'set_{i}label')(i) for i in 'xyz')
    return figure


def cylinder(figure, state: CoordSys_3d,
             R: float, h: float, r: float = 0,
             smooth: int = 2, **plot_kwd):
    ''' 以 state 的 z 轴为主轴绘制圆柱
        figure: 3D 工作站对象
        state: CoordSys_3d 齐次变换矩阵
        R: 圆柱底面外径
        r: 圆柱底面内径
        h: 圆柱高度
        smooth: 图像细致程度 (至少 2)'''
    theta = np.linspace(0, 2 * np.pi, ROUND_EDGE, dtype=DTYPE)
    z = np.linspace(-h / 2, h / 2, smooth, dtype=DTYPE)
    theta, z = np.meshgrid(theta, z)
    # 绘制圆柱内外曲面: 以 z 轴为主轴, 原点为中心
    x, y = np.cos(theta), np.sin(theta)
    figure.plot_surface(*state.apply(x * R, y * R, z), **plot_kwd)
    figure.plot_surface(*state.apply(x * r, y * r, z), **plot_kwd)

    phi = np.linspace(0, 2 * np.pi, ROUND_EDGE, dtype=DTYPE)
    radius = np.linspace(r, R, 2, dtype=DTYPE)
    phi, radius = np.meshgrid(phi, radius)
    # 绘制上下两底面: 法向量为 z 轴, 原点为中心, 在 z 轴上偏移得到两底面
    x, y = np.cos(phi) * radius, np.sin(phi) * radius
    z = np.zeros_like(x)
    for dz in (-h / 2, h / 2):
        s = state.rela_tf(CoordSys_3d.trans(dz=dz))
        figure.plot_surface(*s.apply(x, y, z), **plot_kwd)


def rubik_cube(figure, state: CoordSys_3d,
               length: float, hollow: float = 0.7, smooth: int = 10,
               colors: list = [red, orange, yellow, green, cyan, blue, purple, pink], **plot_kwd):
    ''' 绘制魔方
        length: 边长
        smooth: 魔方的细粒度'''
    x = np.linspace(-length / 2, length / 2, smooth + 1)
    filled = np.random.random([smooth] * 3) > hollow
    color = np.random.choice(colors, size=filled.shape)
    # 绘制各个通道
    figure.voxels(*state.apply(*np.meshgrid(x, x, x)), filled=filled,
                  facecolors=color, edgecolors='white', **plot_kwd)
    return figure


if __name__ == '__main__':
    plt.rcParams['figure.figsize'] = [6.4, 6.4]

    fig = figure3d()
    fig.set_xlim((-6, 4))
    fig.set_ylim((-3, 7))
    fig.set_zlim((-5, 5))

    rot = CoordSys_3d.rot
    trans = CoordSys_3d.trans

    # 绕 y 轴相对旋转 20°, 再绝对平移
    state = CoordSys_3d().rela_tf(rot(20, 'y')).abs_tf(trans(dx=-1, dy=2, dz=-2))
    print(state)
    # 以 z 轴为主轴, 绘制空心圆柱
    cylinder(fig, state=state, R=5, r=4, h=3, cmap='Set3', alpha=0.5)
    # 绘制局部坐标系
    state.plot_coord_sys(length=10, linewidth=5), plt.legend()

    # 在空心圆柱的 z 轴上平移
    state = state.rela_tf(trans(dz=5))
    print(state)
    # 绘制空心魔方
    rubik_cube(fig, state=state, length=6, hollow=0.8, smooth=10, alpha=0.6)
    plt.show()

最終的な描画効果は上の図に示されています。

おすすめ

転載: blog.csdn.net/qq_55745968/article/details/129912954