grbl1.1+Arduino CNCシールドV3.5+bCNCを使用中。
BluetoothモジュールおよびbCNCのPendant機能でスマホからもワイヤレス操作可能。
その他、電子工作・プログラミング、機械学習などもやっています。
MacとUbuntuを使用。

CNCマシン全般について:
国内レーザー加工機と中国製レーザー加工機の比較
中国製レーザーダイオードについて
CNCミリングマシンとCNCルーターマシンいろいろ
その他:
利用例や付加機能など:
CNCルーター関係:



*CNCマシンの制作記録は2016/04/10〜の投稿に書いてあります。


2021年3月25日木曜日

Inverse Kinematics 逆運動学:Backward Shift、FABRIK、CCD

 ロボットアーム(マニピュレータ)における逆運動学のアルゴリズム。

今回は以下の方法について試してみます。

  • Forward + Shift
  • FABRIK(Foward and Backward Reaching Inverse Kinematics)
  • CCD(Cyclic Coordinate Descent)
環境:
  • Python3.6
  • Jupyter Notebook


運動学の場合、ロボットアームの各関節の角度を入力するとアーム先端(End-Effector)の座標値を求めることができます。
逆運動学の場合、End-Effector(アーム先端部)の座標値を入力すると各関節の角度が求められます。
上記の方法以外にヤコビ行列を用いた方法がありますが、それは次回へ

Backward Shift:

この方法は、解析的に各関節の角度を求めるというより、図形的に回転と移動を繰り返すことで徐々に目標の座標に近似させていきます。Baseに近いほうからLink1、Link2、Link3という順番にしておき、Backwardの名前の通り、Link3から回転移動処理を始め、次にLink2、そしてLink1と操作を続けていきます。


Chart1:
    アーム先端(End-Effector)の根元の関節Joint3から目標座標Target3に線を引いて角度θ3を得る。
    Joint3を軸にLink3をθ3回転させる。
Chart2:
    回転させたのち、End-EffectorとTarget3が一致するようにLink3をd3だけ平行移動する。
Chart3:
    Link2とLink3の間に隙間ができるが、これでLink3の操作は一旦終了。

次はLink2の回転移動操作。目標座標はLink3下端(Target2)。

Chart4:
    Joint2からTarget2へ向けて線を引き角度θ2を得る。
    Joint2を軸にLink2をθ2回転させる。
Chart5:
    回転させたのち、Link2上端とTarget2が一致するようにLink2をd2だけ平行移動する。
Chart6:
    Link1とLink2の間に隙間ができるが、これでLink2の操作は一旦終了。

次のLink1についても同様の手順で動かす。目標座標はLink2の下端(Target1)。

Chart7:
    Joint1からTarget1へ向けて線を引き角度θ1を得る。
    Joint1を軸にLink1をθ1回転させる。
Chart8:
    回転させたのち、Link1上端とTarget1が一致するようにLink1をd1だけ平行移動する。
Chart9:
    Link1下端とBaseの間には隙間d0ができるので、Link1下端がBaseと一致するようにd0だけ全体的に平行移動する。
Chart10:
    全体的に平行移動すると、End-Effectorと目標座標Target3との間に隙間ができる。
    Joint3からTarget3へ線を引き、これ以降はChart1に戻って同じ処理を繰り返す。
    最終的にはEnd-Effectorが目標座標Target3に限りなく近づく。
    
プログラム上では、この繰り返し処理に回数制限を設けるか、End-Effectorと目標座標との誤差が0.00001以下になったらループ処理を抜け出すかなどの設定にします。

コード:

  • リンク(Arm)のclassを用意して、長さ、両端座標、角度などを定義しておきます。
  • ならびに回転移動処理に必要なbackwardとshiftというメソッドも定義しておきます。
  • 角度はnp.arctan2()で二点の座標から求めています。
  • 変数Nを変えれば、Link数を増やすことができます。
  • Jupyter Notebook上でインタラクティブ描画するためにバックエンドとして「#matplotlib notebook」を使用しています。
  • コード後半のmotion()関数で、ロボットアームがマウス座標を追従します(Jupyter上では動きはやや遅い)。



FABRIK:

この方法は、「Forward and Backward Reaching Kinematics」の略で、ForwardとBackwardの回転移動操作を繰り返して徐々に目標座標に近づいていきます。
前述したBackward ShiftのShiftさせた部分をForward(前方からの)の回転移動操作に置き換えたようなものです。
まず、End-Effectorのほうから目標座標に対して回転移動操作(Backward)し、Link1まで操作したら、今度はLink1、Link2、Link3という順番で回転移動操作(Forward)していきます。向きが変わるだけで基本的な手順は前述のChart1〜Chart8までの操作と同じです。

コード:

  • Linkのclassを用意して、長さ、両端座標、角度などを定義しておきます。
  • 回転移動処理に必要なbackwardとforwardのメソッドも定義しておきます。
  • 角度はnp.arctan2()で二点の座標から求めています。
  • 変数Nを変えれば、Link数を増やすことができます。
  • Jupyter Notebook上でインタラクティブ描画するためにバックエンドとして「#matplotlib notebook」を使用しています。
  • コード後半のmotion()関数で、ロボットアームがマウス座標を追従します(Jupyter上では動きはやや遅い)。




CCD:

この方法は、2つのベクトル(回転軸から目標座標までのベクトル、回転軸からEnd-Effectorまでのベクトル)の角度の差分だけ回転させますが、Linkを回転させるというよりも、回転軸以降にあるJointの座標を回転移動させると言ったほうがいいでしょう。回転軸はJoint3、Joint2、Joint1という順番で移行し、それを繰り返します。


Chart1:
    まずEnd-Effectorに近いJoint3を回転軸とする。
    Joint3からTargetまでのベクトル、Joint3からEnd-Effectorまでのベクトル間の角度θ3を求める。
    回転軸となるJoint3以降にあるJoint(この場合End-Effectorのみ)をθ3回転させる。
Chart2:
    次の回転軸はJoint2。
    Joint2からTargetまでのベクトルとJoint2からEnd-Effectorまでのベクトル間の角度θ2を求める。
    Joint2以降のJoint(この場合、Joint3とEnd-Effector)をJoint2を中心にθ2回転させる。
Chart3:
    次の回転軸はJoint1。
    Joint1からTargetまでのベクトルとJoint1からEnd-Effectorまでのベクトルの角度θ1を求める。
    Joint1以降のJoint(この場合、Joint2、Joint3、End-Effector)をJoint1を中心にθ1回転させる。
Chart4:
    再度、回転軸はJoint3に戻り、Chart1同様の手順で回転移動させる。
    あとはこの繰り返しで徐々にEnd-EffectorはTargetに近づいていく。

コード:

  • 各Linkごとに動かすというよりも、回転軸となるJointを中心にそれ以降にあるJointの座標を回転移動させています。
  • CCD()ファンクション内の二重のforループは、最初のforループが回転軸用、次のforループが回転移動させるJoint座標用になります。
  • Jupyter Notebook上でインタラクティブ描画するためにバックエンドとして「#matplotlib notebook」を使用しています。



3つの比較:

どれも比較的簡単なアルゴリズムなので軽快に動きますが、Forward & Backward系とCCDでは少し動きに違いが出てきます。
以下は、マウス(赤x印)の動きに追従するインタラクティブなプログラム。3つの方法を重ねて同時に動かしています。
BackwardとFABRIKは同じアルゴリズムを使っているので似たような動きになります。
FABRIKは全体的に張りのある動きをしています。Backwardのほうはやや柔らかめ。
CCDはやや反応が遅く、先端に近いほうの動きとBaseに近いほうの動きに差が出て蛇行することがあります。
リンク数を多くすると、その動き方の違いも顕著になってきます。
リンク数20の場合。
マウスの動かし方にもよりますが、CCDは2つに比べるとやや不自然な形になっています。
結果、BackwardとFABRIKが反応も早く、動きも自然という感じです。それぞれの回転移動方法やコードも比較的簡単。

3つ同時のコード:

  • Backward Shift、 FABRIK、CCDの3つ方法をマウスに追従させることでインタラクティブに試すことができます。
  • 変数Nを変えればリンク数を増やすことができます。
  • Jupyter Notebook上でインタラクティブ描画するためにバックエンドとして「#matplotlib notebook」を使用しています。

これらのアルゴリズムは、CGのキャラなどを動かすときに向いているようです。角度制限がないため実際のロボットアームに使うアルゴリズムには向いていないようです。

2020年10月14日水曜日

Ubuntu20.04へアップグレード(18.04から)

 Ubuntu20.04がリリースされてから約半年が経過したので、そろそろ安定しているかなということで18.04からアップグレードしてみました。アップグレード通知が来ていたので、今回はコマンドを使わずGUIでアップグレード。

アップグレードのボタンを押すと約1時間半ほどでアップグレード完了で、特に難しい部分はありませんでした。途中で数回ほど諸設定を更新するかそのままにするか聞かれるくらいで、すべてボタンクリックで済みました。

Ubuntuの設定などは18.04から引き継いでいるようなので、設定し直す項目もなくシームレスにアップグレードされたという感じです。

ただ、アップグレードすると非対応のアプリケーションなどもあるので慣れ親しんだ18.04とは少し使い勝手が変わってしまう部分もありました。


その他バージョンアップしたもの:

・Anaconda最新版へ

・Python3.6から3.8へ

・cuda9.0から10.1へ

Python関連はAnaconda最新版を入れることでTensorflowやKerasなども最新版へ移行(特に問題なし)。

GPU(nvidia)関連は、nvidia-driver-455、nvidia-cuda-toolkit(11.1)をインストール。nvidia-cuda-toolkitは、Anaconda環境内でもインストールしたため、TensorflowやKerasは10.1で動作しているようです。以前はcuda関連のインストールが大変でしたが、まったく問題なし。今回はconda install pipでpip自体もAnaconda経由でインストールし、pipとcondaが混在しないようにしました。


問題ありの部分:

Tweakで設定できる機能拡張がGnome3.36に未対応のものも多く、それらは使えませんでした。

挙動が変な機能拡張はアンインストールかオフにしたので(上画像)、ほぼデフォルトに近い状態に戻ってしまったという感じです。Argosが便利でしたが使えない。


Tweaksが不安定:

Tweaks>全般>アニメーションをオフにすると左側のサイドバーがちらついてクリックできなくなる。

また、Tweakのメニューバーのメニューが消えてしまうという不具合もあります。ウィンドウ最大化するか、ウィンドウ左端を広げると直ります。

こんな感じ↑でメニューバーには何も表示されない。

そこで、ウィンドウ左端を左側に引き伸ばすとサイドバーが現れてメニューも表示されるようになります(以下)。

ちらついてタブがクリックできないという不具合もウィンドウを左側に広げると一応元に戻ります。どうやら、初期画面のサイズが小さすぎるのかもしれません。


Tweaksちらつき解決方法:

ここに書いてありました。

/usr/lib/python3/dist-packages/gtweak/tweakview.pyの154行目にあるself.listbox.set_size_request(200, -1)を

self.listbox.set_size_request(300, -1)に書き換えれば(要管理者権限)、初期画面が少し横に広がるのでちらつき問題は発生しないようです。実際書き換えて見たら直りました。


Wifiドライバ(接続が途切れる):

この問題は相変わらずで、18.04のときと同様にドライバソフトを入れ替えました。その方法はこちら


まとめ:

AnacondaやPython、cuda関連もアップグレードしているため、そのついでに20.04へ移行してしまったという感じです。18.04LTSはまだサポートが続いているので、18.04に慣れ親しんでいるならアップグレードしなくてもよかったかもしれません。おかげでPython環境は良好になったのですが、Ubuntuそのものはまだ使いづらいし、今後も多少のカスタマイズや修正が必要そう。

Ubuntu16.10から18.04へ移行したときはクリーンインストールしたために全て設定し直しましたが、今回はクリーンインストールではないため設定し直す部分も少なくて楽でした。

2019年12月27日金曜日

クォータニオン / Quaternion:立方体の回転操作(その3)

前回までは単位球に対する一点の回転操作でしたが、今回はもう少し具体的にということで立方体の回転操作を試してみました(基本的なクォータニオン回転操作についてはこちらへ)。
立方体の8個の頂点を個別に計算して回転操作しています。
また、scale変数で立方体の縦横高さの比率を調整できるようにし、pos変数で位置の基準点を変えられるようにしています。

手順としては:
・8頂点(ベクトル)のそれぞれのスカラー値を求める
・8頂点(ベクトル)をスカラー値で正規化
・正規化した8頂点(ベクトル)を回転操作
・正規化されている8頂点(ベクトル)をスカラー倍してもとのスケールに戻す

ipywidgetsでインタラクティブに角度を変えられるようになっています。また立方体の縦横高さの比率も変更可。
オンライン上で実行するにはBinderで可能です(方法についてはこちらを参考に)。

以下がコード(Gist):
・回転角度と立方体の縦横高さ比が変更可能なバージョン
・回転角度と回転軸(単位ベクトル)が変更可能なバージョン
の二つがあります。

関連:
クォータニオン(四元数) / Quaternion / 回転制御(その1)
クォータニオン / 回転制御(その2):二点間から単位ベクトルと回転角度を求める


四元数
四元数
posted with amazlet at 19.12.22
今野 紀雄
森北出版
売り上げランキング: 67,351

Binder:オンライン上でのJupyter Notebook(ipynbファイル)の実行

ネット上で見かけるipynbファイルをブラウザに表示するにはnbviewerにアクセスして、
ipynbファイルのURLを入力してGo!ボタン。

そうするとJupyter Notebook形式でコードが表示されます。

このプログラムをこのままオンライン上で実行するには、ページ右上の「Execute on Binder」(以下の赤丸のアイコン)をクリック。

そうすると、Binderに移行し実行可能な環境ができあがり、オンライン上でJupyter Notebookが使用可能。


必要なモジュール/requirements.txt
しかし、このプログラムに必要なモジュール(numpy、matplotlibなど)がこの環境にインストールされていなければエラーがでます。必要なモジュールについては、ipynbファイルとは別にrequirements.txtがアップロードされており、その中に記述されています。
Binder内のディレクトリ。

requirements.txtを開くと必要なモジュール名が書いてあります。requirements.txtがipynbファイルと同じディレクトリ内にあれば自動でモジュールがインストールされますが、もしrequirements.txtがなければ自力で必要そうなモジュールをインストールする必要があります。

ターミナルでモジュールをインストール:

ディレクトリのページに戻り、右上の「New」から「Terminal」を選択。
ターミナル 画面が現れたら、pipなどで必要なモジュールをこの環境にインストール。
必要なモジュールが揃えば、プログラム実行可能なります。


直接「Binder」にアクセスして実行:
また、nbviewerを使わずに、直接「Binder」から実行するには、
Binderのトップページ。
プルダウンメニューからipynbファイルの場所の種類(GitHubやGistなど)を選択。ipynbファイルのURLを入力し「launch」ボタンをクリックでファイルが開きます。
requirements.txtがない場合はモジュールが自動インストールされないため、そのままプログラムを実行するとエラーがでるので、先ほど同様オンライン上のターミナルで自力で必要なモジュールをインストール。


PythonユーザのためのJupyter[実践]入門
池内 孝啓 片柳 薫子 岩尾 エマ はるか @driller
技術評論社
売り上げランキング: 37,985

2019年12月23日月曜日

クォータニオン / 回転制御(その2):二点間から単位ベクトルと回転角度を求める

前回に引き続きクォータニオン回転制御についてです。
今回は任意の二点(ベクトルAとB)だけが与られていて、AからBへ回転移動する際に必要な単位ベクトルと回転角度を求めてみます。

しかしながら、任意の二点(AとB)といっても、いくつかのケースがあります。

(1)AとBが単位球面上に存在する(|A|=|B|=1のとき)
(2)AとBのスカラー値は同じだけど、単位球面上に存在しない(|A|=|B|≠1のとき)
(3)AとBのスカラー値が異なり、単位球面上に存在しない(|A|≠|B|≠1)
4)そもそもAもBも単位ベクトルには無関係な任意の2点の場合
*(4)に関しては、AからBへの直線移動も含まれるし、単位ベクトル自体も任意に決定できるので今回は省略。このことから、任意の二点といってもあくまで単位球を設定しなければ逆算できないということがわかります。


(1)と(2)の場合(AとBのスカラー値が等しい場合):
(1)が一番簡単で、(2)も回転操作後に(1)の値を元のスカラー倍すればいいので、回転操作自体は同じ計算方法になります。以下はXZ平面において回転軸Yの場合。
単純にAが回転するとBになったという状況。回転操作のためには一旦ベクトルAを正規化してAnにしなければならないので、AnからBnへの回転計算をしたのち、正規化されているBnを元のスカラー倍してBとなります。

単位ベクトルを求める:
この場合、単位ベクトル(UV)は正規化されたAnとBnがあればクロス積(外積)で求めることができます(単位ベクトル(UV)は、ベクトルAnとベクトルBnに対して垂直/法線ベクトルとなるため)。ただし、Aのスカラー値とBのスカラー値は等しいという前提です(|A|=|B|)。
Pythonであれば、

A = [ax, ay, ax] # ベクトルA
B = [bx, by, bz] # ベクトルB

As = np.linalg.norm(A) # Aのスカラー値
Bs = np.linalg.norm(B) # Bのスカラー値 ただしAs=Bsという前提

An = np.array(A) / As  # 正規化
Bn = np.array(B) / Bs  # 正規化

UV = np.cross(An, Bn)  # 求められた単位ベクトル

回転角度を求める:
またAB間の回転角度(theta)はドット積(内積)とarccos()で求められ、

cos_AnBn = np.dot(An, Bn)
theta = np.arccos(cos_AnBn)

この二つの値(UV、theta)をクォータニオンの回転公式に代入し、Anを回転すれば計算結果がBnと一致します。そして正規化するまえの大きさに戻すため元のスカラー倍(Aのスカラー)します。

コード:
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
%matplotlib inline
# quaternion multiplication function
def QbyQ(q1, q2): # q = [w, x, y, z]
    w = q1[0]*q2[0] - q1[1]*q2[1] - q1[2]*q2[2] - q1[3]*q2[3]
    i = q1[0]*q2[1] + q1[1]*q2[0] + q1[2]*q2[3] - q1[3]*q2[2]
    j = q1[0]*q2[2] - q1[1]*q2[3] + q1[2]*q2[0] + q1[3]*q2[1]
    k = q1[0]*q2[3] + q1[1]*q2[2] - q1[2]*q2[1] + q1[3]*q2[0]
    return np.array([w, i, j, k])

# quaternion rotation function
def QPQc(P, UV, t): # P: quaternion, UV: unit vector, t: rotation angle theta
    q =  [np.cos(t/2),  UV[0] * np.sin(t/2),  UV[1] * np.sin(t/2),  UV[2] * np.sin(t/2)]
    qc = [np.cos(t/2), -UV[0] * np.sin(t/2), -UV[1] * np.sin(t/2), -UV[2] * np.sin(t/2)]
    qP = QbyQ(q, P)
    result = QbyQ(qP, qc)
    return result
# find unit vector and rotation angle for two vectors A and B
A = np.array([1, -1, 0.2])           # start point A
As = np.linalg.norm(A)               # scalar of A: |A|
An = A / As                          # normalization

B = np.array([0.4, -0.1, 1.1])       # end point B
Bs = np.linalg.norm(B)               # scalar of B: |B|
Bn = B / Bs                          # normalization

# find unit vector for An-Bn rotation
AnxBn = np.cross(An, Bn)             # orthogonal vector to An-Bn
UV = AnxBn / np.linalg.norm(AnxBn)   # unit vector (normalized): rotation axis 

# find rotation angle about An-Bn
# cos_t = (An dot Bb) / (|An|*|Bn|)  # (|An|*|Bn|) = An * Bn = 1 * 1 = 1
cos_AnBn = np.dot(An, Bn)
theta_AnBn = np.arccos(cos_AnBn)     # angle between An and Bn

# reproduct A-B rotation
Qa = np.insert(An, 0, 0)             # quaternion of An: [0, x, y, z]
Qb = QPQc(Qa, UV, theta_AnBn)        # quaternion rotation for An with UV and theta_AB
Bn_new = Qb[1:]                      # new Bn vector (normalized): [bx, by, bz]
B_new = Bn_new * Bs                  # new B vector

print('unit vector:', UV)
print('theta (degrees):', np.rad2deg(theta_AnBn))
print('initial vector B:', B)
print('new vector B    :', B_new) 


(3)の場合(AとBのスカラー値が異なる場合):
しかし(3)の場合は、単位球に対する任意の二点ABということから、Aを単純に回転させた結果がBになるとは限りません。以下のような状況。
Aを単純に回転させればA'(×印)にたどり着きますが、AとBのスカラー値(半径)が異なる場合はピンクの破線のような動きになります。Aは回転しつつ半径も縮まっていくという変化。
この場合、回転操作は(1)と同様に正規化されたベクトルAnで行い、最終的にAn(Bnと同じ)をBのスカラー倍(Bs倍)すればいいことになります。

回転軸となる単位ベクトルは(1)と同様にAnとBnのクロス積で求まります。

A = [ax, ay, ax] # ベクトルA
B = [bx, by, bz] # ベクトルB

As = np.linalg.norm(A) # Aのスカラー値
Bs = np.linalg.norm(B) # Bのスカラー値

An = np.array(A) / As
Bn = np.array(B) / Bs

UV = np.cross(An, Bn)  # 求められた単位ベクトル

AnとBnの回転角度に関しても(1)と同様にドット積とarccos()で求めます。

cos_AnBn = np.dot(An, Bn)
theta_AnBn = np.arccos(cos_AnBn)

求めた単位ベクトルUV(回転軸)とtheta_AnBn(回転角度)をクォータニオンの公式に代入すればBnが求まります。求めたBnに対して元のスカラー値Bsを掛ければBになります。
しかし、これでは移動前と移動後の二つの場面しかないので、途中の軌跡を描いて滑らかに移動しているかどうか確かめてみる必要がありそうです。つまり、先程の図のAからBへの移り変わり(ピンクの破線)を描いてみるというわけです。


AからBへの移動の中間補間:
まず移動のステップ数を決め、ステップ数に応じた回転角度とAからBへのスカラー値の変化量を決めます。

steps = 33
theta = np.linspace(0, theta_AnBn, steps)
ABs = np.lispace(As, Bs, steps)

これで、回転角度thetaは33ステップで0からtheta_AnBnまで変化します。同様にAB間のスカラー値ABsの変化も33ステップでAsからBsへ変化します。あとは公式に代入して33ステップ分の結果を得ます。

arw, arx, ary, arz = np.array([QPQc(Qa, UV, t) for t in theta]).T

上式で求まるのはAn-Bn間の回転結果(角度は違うけれどもスカラー値はどれも1)なので、各ステップに応じたスカラー値の変化量ABsを掛け合わせます。

ABx = arx * ABs
ABy = ary * ABs
ABz = arz * ABs

これで、各ステップごとに正規化されたベクトルに対して段階的にスカラー倍することになります。


ipywidgetsでインタラクティブ表示:
今回もipywidgetsでインタラクティブなグラフを表示してみます(ブログ上ではインタラクティブに動きませんが、mybinder.orgでこちらのgist URLを入力すればオンライン上でも動きます)。
関連:Binder:オンライン上でのJupyter Notebook(ipynbファイル)の実行
右端initial vector A(青)がinitialized vector B(緑)へ向かって移動する状態(ピンク)。unit vector:単位ベクトル(赤)が回転軸。
正規化されたAn-Bn間の回転移動は単位球面上にあります。AからBへの移動(ピンク破線)は単位球の同心円とずれているのがわかります。

クォータニオンの回転では、比較的簡単なパラメーターの調整だけで回転操作できることがわかりました。特にこのような移動途中の軌跡を描く際には便利なツールだと思います。

コード:
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
%matplotlib inline
# quaternion multiplication function
def QbyQ(q1, q2): # q = [w, x, y, z]
    w = q1[0]*q2[0] - q1[1]*q2[1] - q1[2]*q2[2] - q1[3]*q2[3]
    i = q1[0]*q2[1] + q1[1]*q2[0] + q1[2]*q2[3] - q1[3]*q2[2]
    j = q1[0]*q2[2] - q1[1]*q2[3] + q1[2]*q2[0] + q1[3]*q2[1]
    k = q1[0]*q2[3] + q1[1]*q2[2] - q1[2]*q2[1] + q1[3]*q2[0]
    return np.array([w, i, j, k])

# quaternion rotation function
def QPQc(P, UV, t): # P: quaternion, UV: unit vector, t: rotation angle theta
    q =  [np.cos(t/2),  UV[0] * np.sin(t/2),  UV[1] * np.sin(t/2),  UV[2] * np.sin(t/2)]
    qc = [np.cos(t/2), -UV[0] * np.sin(t/2), -UV[1] * np.sin(t/2), -UV[2] * np.sin(t/2)]
    qP = QbyQ(q, P)
    result = QbyQ(qP, qc)
    return result
def plot_base(elev=25, azim=-70):
    fig = plt.figure(figsize=(10, 10))
    ax = fig.add_subplot(111, projection='3d')
    ax.view_init(elev=elev, azim=azim)
    ax.set(xlim=(-1, 1), ylim=(-1 ,1), zlim=(-1, 1))
    ax.set(xlabel='X', ylabel='Y', zlabel='Z')
    ax.xaxis.pane.fill = ax.yaxis.pane.fill = ax.zaxis.pane.fill = False

    t = np.linspace(0, 2*np.pi, 128+1)
    alpha = 0.7
    ax.plot(np.cos(t), np.sin(t), [0]*len(t), linestyle=':', c='red', alpha=alpha)
    ax.plot(np.cos(t), [0]*len(t), np.sin(t), linestyle=':', c='red', alpha=alpha)
    ax.plot([0]*len(t), np.cos(t), np.sin(t), linestyle=':', c='red', alpha=alpha)
    ax.plot([-1, 1], [0, 0], [0, 0], linestyle=':', c='red', alpha=alpha)
    ax.plot([0, 0], [-1, 1], [0, 0], linestyle=':', c='red', alpha=alpha)
    ax.plot([0, 0], [0, 0], [-1, 1], linestyle=':', c='red', alpha=alpha)
    return ax
# find unit vector and rotation angle for two vectors A and B
A = np.array([1, -1, 0.2])           # start point A
As = np.linalg.norm(A)               # scalar of A: |A|
An = A / As                          # normalization

B = np.array([0.4, -0.1, 1.1])       # end point B
Bs = np.linalg.norm(B)               # scalar of B: |B|
Bn = B / Bs                          # normalization

# find unit vector for An-Bn rotation
AnxBn = np.cross(An, Bn)             # orthogonal vector to An-Bn
UV = AnxBn / np.linalg.norm(AnxBn)   # unit vector (normalized): rotation axis 

# find rotation angle about An-Bn
# cos_t = (An dot Bb) / (|An|*|Bn|)  # (|An|*|Bn|) = An * Bn = 1 * 1 = 1
cos_AnBn = np.dot(An, Bn)
theta_AnBn = np.arccos(cos_AnBn)     # angle between An and Bn

# reproduct A-B rotation
Qa = np.insert(An, 0, 0)             # quaternion of An: [0, x, y, z]
Qb = QPQc(Qa, UV, theta_AnBn)        # quaternion rotation for An with UV and theta_AB
Bn_new = Qb[1:]                      # new Bn vector (normalized): [bx, by, bz]
B_new = Bn_new * Bs                  # new B vector
# interactive plot with ipywidgets
from ipywidgets import interact

steps = 33
Arc = np.array([QPQc(Qa, UV, t) for t in np.linspace(0, theta_AnBn, steps)]).T # arc An-Bn
_, arx, ary, arz = Arc
ABs = np.linspace(As, Bs, steps) # scalar transition from A to B
ABx = arx * ABs                   # x term for arc A-B
ABy = ary * ABs                   # y term for arc A-B
ABz = arz * ABs                   # z term for arc A-B

@interact(idx=(0, steps-1, 1), elev=(0, 180, 1), azim=(-180, 180, 1))
def two_vec(idx=10, elev=25, azim=96):
    ax = plot_base(elev, -azim)

    ax.plot([0, A[0]], [0, A[1]], [0, A[2]], c='b', alpha=0.4)   # vector A
    ax.scatter(A[0], A[1], A[2], c='b', alpha=0.4)               # point A
    ax.text(A[0], A[1], A[2], s='  initial vector A')
    ax.plot([0, An[0]], [0, An[1]], [0, An[2]], c='b')           # vector An
    ax.scatter(An[0], An[1], An[2], c='b')                       # point An
    ax.text(An[0], An[1], An[2], s='normalized vector An  ', ha='right', va='top')

    ax.plot([0, UV[0]], [0, UV[1]], [0, UV[2]], c='r')           # unit vector
    ax.scatter(UV[0], UV[1], UV[2], c='r')
    ax.text(UV[0], UV[1], UV[2], s='unit vector  ', ha='right', va='top')

    ax.plot([0, B[0]], [0, B[1]], [0, B[2]], c='g', alpha=0.4)    # vector B
    ax.text(B[0], B[1], B[2], s='initialized vector B  ', ha='right', va='center')
    ax.scatter(B[0], B[1], B[2], c='g', alpha=0.4)                # vector B
    ax.plot([0, Bn[0]], [0, Bn[1]], [0, Bn[2]], c='g')            # vector Bn
    ax.scatter(Bn[0], Bn[1], Bn[2], c='g')                        # vector Bn
    ax.text(Bn[0], Bn[1], Bn[2], s='normalized vector Bn  ', ha='right', va='center')
    
    ax.plot([0, arx[idx]], [0, ary[idx]], [0, arz[idx]], color='magenta')           # vector An (move)
    ax.plot([0, arx[idx]*As], [0, ary[idx]*As], [0, arz[idx]*As], c='m', alpha=0.4) # move A    (move)
    ax.scatter(arx[idx]*As, ary[idx]*As, arz[idx]*As, c='m', alpha=0.4)             # point A   (move)
    ax.scatter(arx[idx], ary[idx], arz[idx], c='m')                                 # point An  (move)
    ax.plot(arx[:idx+1], ary[:idx+1], arz[:idx+1], linestyle='--', c='b', alpha=0.7)   # arc An-Bn
    ax.plot(arx[:idx+1]*As, ary[:idx+1]*As, arz[:idx+1]*As, ls='--', c='b', alpha=0.7) # arc A-A
    rate = As + (Bs - As) * idx / steps
    ax.scatter(arx[idx]*rate, ary[idx]*rate, arz[idx]*rate, c='m')                  # point A-B
    ax.plot(ABx[:idx+1], ABy[:idx+1], ABz[:idx+1], ls='--', c='m', alpha=0.9)       # arc A-B

関連:
クォータニオン(四元数) / Quaternion / 回転制御(その1)
クォータニオン / Quaternion:立方体の回転操作(その3)

四元数
四元数
posted with amazlet at 19.12.22
今野 紀雄
森北出版
売り上げランキング: 67,351

人気の投稿