漱石枕流

勉強、開発したことのメモ

JetsonTX2のセットアップの記録

JetsonTX2を搭載した移動ロボットを実験で使うので、ROSやOpenCV、ZEDステレオカメラ、rp-LiDARのセットアップの記録を残しておこうと思う。

- ホスト: Ubuntu16.04搭載のノートPC
- ターゲット: JetsonTX2

JetPack

JetPack-L4T-3.2.1を使った。ZEDのROS用ノードzed-ros-wrapperコンパイルする際に、SDK>=2.3を使う必要があるが、CUDA9.0に依存している。よってCUDAのバージョンが9.0以上であるかを確認する。

1回目のインストール

多分1回目ではOSしか入れないと思う。

リカバリーモード

一旦電源を切った後、再度電源を入れると同時に、リカバリーボタンとリセットボタンを同時に押す。2秒後にリセットボタンを外す。すると起動画面は現れないはず。その状態で付属のケーブルでJetsonのmini-USBとPCのUSBを接続し、lsusbNVIDIA corpのリストが表示されればOK。

パッケージ選択

ホスト側には何も入れる必要はない。必要最低限にしたいため(そしてROSのOpenCV3との互換性を憂慮して)Jetson側ではFlash OS Image、CUDA(9.0)、cuDNN(7.0)のみ選択した(OpenCV、VisionWorks、TensorRT、Multlmediaはno actionに)。ただし1回目のインストールではOSしか入れないので、他のパッケージはまだ関係なさそう。このまま続行すればインストールは10分ぐらいで終わった。

f:id:amazingsoblin:20181026174629p:plain
1回目のインストール

2回目のインストール

XTerm上のインストーラにfinishedと表示されたらCtr-Cで一旦終了する。Jetsonを起動して、WiFiに繋いで2回目に進む。

パッケージ選択

先ほどと同じ、必要なパッケージだけチェックを入れる。このときFlash OS Imageはno actionにしておく。

IPアドレスの確認

JetsonのIPアドレスを確認。ifconfigとかip addr showとか。このときJetson側でaptを使うコマンドがバックグランドで動いていて面倒なことがあったので、そのような場合は

sudo rm /var/lib/dpkg/lock

しておく。これで勝手に入るのでは。

f:id:amazingsoblin:20181026174732p:plain
2回目のインストール

自分がやった時に起きたエラー

インストールの最中にapt-get updateをやっているようなのだが、デフォルトのサーバーへの接続が遅すぎて、一旦アメリカのサーバーへと変更しようとした。だが何かエラーが起きたようで、インストールの途中でcudaが入っていないと怒られた(指定したはずなのに)。結局一旦中断して、手動(apt-get)でcuda-toolkit-9-0を入れるはめになった。するとスムーズに進んだ。

確認

以下のコマンドが出ればインストールはされていると思う。

which nvcc => /usr/local/cuda-9.0/bin/nvcc

ROSのインストール

ros-kinetic-ros-perceptionに含まれるパッケージの多くはOpenCVに依存しており、デフォルトではこれらをインストールしようとするとros-kinetic-opencv3が依存パッケージと入ってしまう。しかしその実体はOpenCV3のCUDA無しバージョンであるようで、自前でビルドしたOpenCVとの衝突を起こしてしまう。試行錯誤の結果、ros-kinetic-ros-perceptionは全て自前でローカル環境でビルドしたほうが良いと思う。なのでROSも必要最低限のパッケージのみ入れる。こちらスクリプトに従ってインストール。

sudo ./installROS.sh -p ros-kinetic-ros-base

ZEDのSDKのインストール

ここからSDKをダウンロード。自分はバージョン2.4を選んだ。次に以下のレポジトリをクローンしてzed_wrapperの部分だけcatkin_ws/src配下に移す。

github.com

CUDA>=9.0とSDKがきちんとインストールされていればcatkin_makeできるはず。

OpenCV3(.4)のインストール

現時点で最新のバージョン3.4をビルドする。ここの記事を参考にした。

contrib

opencvmodulesincludeと同じレベルのディレクトリに置いて、-DOPENCV_EXTRA_MODULES_PATH=../opencv_contrib/modulesを上の記事のCMakeコマンドに付け加えた。全体で1時間半程度でビルドできた。

ROSの画像処理関係(ros-perception)のビルド

CUDA有りでビルドしたOpenCV3を使ってimage_viewcv_bridgeをビルドしてしまう。

$ sudo apt-get install libyaml-cpp libgtk-2.0-dev ros-kinetic-eigen-conversion etc.

で依存関係を満たした後、以下のros-kinetic-image-commonros-kinetic-image-pipelineros-kinetic-vision-opencvをクローンしてビルドする。これでcv_bridgeimage_viewがローカルのOpenCV3を使ってくれるようになる。もしかしたらPoint Cloud LibraryもCUDA環境でビルドしたほうが速いのかも知れない。

github.com

github.com

github.com

ros-kinetic-opencv3をインストールするべきでない理由

cv_bridge(apt-getでインストールしたもの、ros-kinetic-opencv3を利用する)とcv::cuda::を両方使用するノードをリンクするときに、${catkin_LIBRARIES}${OpenCV_LIBRARIES}をtarget_link_librariesに指定した。またここの記事を参考にCMakeLists.txtでOpenCVを使い分けさせ、以下のようにきちんと2つのOpenCV両方にリンクされていることを確認した。

$cd ~/catkin_ws/devel/lib/HogeHogePackage/
$ ldd HogeHogeNode
=> /usr/local/libopencv3.・・・
=> /opt/ros/kinetic/lib/libopencv3.・・・

しかしどうやっても実行時にcv::cuda::へのリンクエラーが出てしまう。ros-kinetic-opencv3はCUDAに対応していないこと、cv_bridgeは(ローカル環境であれros-kinetic-opencv3であれ)OpenCVが使えればそれで良いことを考えて、今回の結論に至った。

ROSの画像処理とOpenCV3+CUDA9.0の連携の確認

以下のようなノードをJetson側に作って

  1. 動画読み込み
  2. GPUで処理
  3. 処理結果を可視化してpublish

ノートPC側でimage_viewで確認するというもの。

PCLのビルド

以下によるとPCLはBoost、Eigen、FLANN、VTK、QHULLに依存するようである。FLANNはまだインストールしていないのでapt-getしようかと思ったが、FLANNもCUDA有りでビルドできるようである。

Point Cloud Library 1.8.0 has been released – Summary?Blog

FLANNのビルド

以下のレポジトリからクローンしてきて、バージョンを1.9.1にする。

github.com

$ git checkout 1.9.1

CUDAを使うため、CMakeLists.txtを以下のように変更する。

option(BUILD_C_BINDINGS "Build C bindings" ON)
option(BUILD_PYTHON_BINDINGS "Build Python bindings" ON)
option(BUILD_MATLAB_BINDINGS "Build Matlab bindings" OFF) <===
option(BUILD_CUDA_LIB "Build CUDA library" ON) <===
option(BUILD_EXAMPLES "Build examples" ON)
option(BUILD_TESTS "Build tests" ON)
option(BUILD_DOC "Build documentation" OFF) <===
option(USE_OPENMP "Use OpenMP multi-threading" ON)
option(USE_MPI "Use MPI" ON) <===

謎のエラー

自分の環境ではこれでMakefileは生成された。もしかしたらこれは環境依存なのかもしれないが、ビルドの際に以下のようなエラーが出たので、

/home/nvidia/builds/flann/src/cpp/flann/algorithms/kdtree_cuda_3d_index.cu(764): error: namespace "thrust" has no member "gather"

1 error detected in the compilation of "/tmp/tmpxft_0000106b_00000000-6_kdtree_cuda_3d_index.cpp1.ii".
CMake Error at flann_cuda_s_generated_kdtree_cuda_3d_index.cu.o.cmake:266 (message):
  Error generating file
  /home/nvidia/builds/flann/build/src/cpp/CMakeFiles/flann_cuda_s.dir/flann/algorithms/./flann_cuda_s_generated_kdtree_cuda_3d_index.cu.o


src/cpp/CMakeFiles/flann_cuda_s.dir/build.make:63: recipe for target 'src/cpp/CMakeFiles/flann_cuda_s.dir/flann/algorithms/flann_cuda_s_generated_kdtree_cuda_3d_index.cu.o' failed
make[2]: *** [src/cpp/CMakeFiles/flann_cuda_s.dir/flann/algorithms/flann_cuda_s_generated_kdtree_cuda_3d_index.cu.o] Error 1
CMakeFiles/Makefile2:240: recipe for target 'src/cpp/CMakeFiles/flann_cuda_s.dir/all' failed
make[1]: *** [src/cpp/CMakeFiles/flann_cuda_s.dir/all] Error 2
make[1]: *** Waiting for unfinished jobs....
[ 64%] Linking CXX executable ../bin/flann_example_cpp
[ 64%] Built target flann_example_cpp
[ 70%] Linking CXX static library ../../lib/libflann_s.a
[ 70%] Built target flann_s
Makefile:149: recipe for target 'all' failed
make: *** [all] Error 2

問題のあったソースファイルsrc/cpp/flann/algorithms/kdtree_cuda_3d_index.cu#include <thurst/gather.h>を追加するとビルドできた。

PCLのビルド

$ sudo apt-get install ros-kinetic-pcl-msgs ros-kinetic-tf ros-kinetic-tf2-eigen etc.

TODO?

Point Cloud LibraryもCUDA有りでビルドして、点群処理パッケージもローカルのものを使わせる。

DCモーターのトルク定数と逆起電力定数は一致する

モーションコントロールで言うところのKtとKeの違いが気になってきたので、復習がてらまとめなおす。

定義

DCモーターではこの2つの値は一致するが、DCモーターによらない定義を始めに示す。

逆起電力定数Ke

逆起電力定数はその名の通りモーターの電気的な特性に由来し、モーターに流れた電流に対して反対向きに発生する電圧で、定義は

 \displaystyle V = K_{E} \omega

である。レンツの法則による発生する誘導起電力は

 \displaystyle V = -\frac{d\Phi}{dt} = -\frac{d\Phi}{d \theta} \frac{d \theta }{dt}

であるから、 K_{E} = \frac{d\Phi}{d \theta}とも表せる。

 \omega、すなわち機械的な運動に対してどれだけ電圧=電気特性が生まれるかを示す係数であると言える。

トルク定数Kt

モーターが発生するトルクはそれに流れる電流に比例し、発生したトルク \tauに対して

 \displaystyle \tau = K_{T} i

と定義される。これは電流=電気的特性からどれだけトルク=機械的特性が生まれるかを示すと言える。

DCモーター

DCモータの等価回路は、ブラシの電圧降下を無視すると以下のようになる。

f:id:amazingsoblin:20181007234143p:plain

電気的な方程式

角速度を \omegaとすると逆起電圧は K_{E} \omegaであるから、端子電圧 vは次のように表される。

 \displaystyle v = Ri + L \frac{di}{dt} + K_{E} \omega

機械的な方程式

出力トルクはトルク定数を K_{T} iとして \tau = K_{T} iであるから、

 \displaystyle J \frac{\omega}{dt} = \tau - \tau_{L}

ただし負荷側トルクを \tau_{L}とする。

ブロック線図

信号の流れを

  1. 電圧(入力)
  2. 電流
  3. トルク
  4. 角速度

の順番とするとブロック線図は以下のようになる。

f:id:amazingsoblin:20181008001202p:plain

DCモーターのブロック線図

電気的エネルギーと機械的エネルギー

電力に関する方程式は以下のようになる。

 \displaystyle vi = Ri^{2} + Li \frac{di}{dt} + K_{E}i \omega = Ri^{2} + \frac{d}{dt} \left( \frac{1}{2}Li^{2} \right) + K_{E}i \omega

それぞれ第一項はモーター内の銅損項、第二項はインダクタに蓄えられるエネルギーの変化、第三項は機械出力を表す。その機械出力は力学的な仕事率としては

 \displaystyle \tau \omega = K_{T}i \omega

と表されるはずであるから、それぞれを比べると  K_{T} = K_{E}が結論される。

電圧制御と電流制御

もしDCモーターに対して電圧制御を行う場合、電機子抵抗とインダクタンスの部分がLPFになっているため、立ち上がりが遅くなってしまう(速い変化、すなわち高周波成分を妨げるため)。一方図のようにオペアンプで駆動すると、(オペアンプが理想的である限り)コイルの周波数特性にかかわらず指定した電流を流せてしまう。理由はオペアンプの出力が電圧源であるためである。

DCモーターのトルク、速度、角度制御システム

先ほどのDCモーターのトルクや速度、角度の制御を行うことを考える。まず始めにトルクから始め、その後制御器を付け加えることで速度、角度の制御系を組む。

トルク制御

トルクの値を指令値に一致させることが目的であるが、残念ながらトルクの値を直接はフィードバックできない。代わりにフィードバックできるセンサ情報としてDCモーターに流れる電流値が挙げられる。そこでトルク定数の式 \tau = K_{T} iを思い出すと、入力 \tau^{*}をトルク定数の公称値 \hat{K_{T}}で割った電流指令 i^{*}を参照値にすれば良い。そしてその参照値とセンサ値の差に対して電圧指令を計算する制御器を設計し、その指令値の電圧を発生する電源をDCモーターに接続すれば良い。というわけでブロック図は以下のようになる。

f:id:amazingsoblin:20181008023023p:plain

トルク定数の公称値が実際のモデルと正しければ、このシステムの伝達関数はほぼ1とみなすことができる。

速度制御系

さらにPI制御器を頭に付け、角速度の差からトルクを出力するようにする。

f:id:amazingsoblin:20181008030830p:plain

角度制御系

角速度に積分器をつけ、フィードバック誤差に対してPID制御を行う。

f:id:amazingsoblin:20181008031826p:plain

差動増幅器のシミュレーション

古典制御について復習するがてら、周波数領域の設計を考えたりするアナログ回路を少し再勉強しようと思った。

オペアンプの基本

1年前講義で聞いた時は全然理解していなかったのだが、以下の3つのルールを使うといいらしい。

ひとつ目はいわゆる仮想接地の原理。2つ目は知識不足なのでよく分からない。

反転増幅回路

f:id:amazingsoblin:20181007172627p:plain 1つめのルールから+端子が接地されているため、-端子も電圧が0になる。2つめのルールから-端子には電流が流れないので、電源からの電流はVoutへ向かって流れる。その電流は V1/R0であるから、

 \displaystyle V_{out} = -\frac{R_{0}}{R_{1}} V_{1}

であり、入力電圧を反転して出力する。

非反転増幅回路

f:id:amazingsoblin:20181007172657p:plain

+端子に電源が直接接続されているので、1つめのルールから-端子にも電源電圧がかかる。したがって2つめルールより電流はVoutからグラウンドへと流れる。その電流値は V1/R1であるから、

 \displaystyle V_{out} = \frac{R_{0}}{R_{1}}V_{in}

となり、増幅率は正である。

差動増幅回路のシミュレーション

図のような差動増幅回路の計算をしてみる。

f:id:amazingsoblin:20181007155514p:plain オペアンプの両端子の電圧は等しいので Vとすると、上下について分圧を計算すると

 \displaystyle V = \frac{R_{0}V_{1} + R_{1}V_{3}}{R_{0} + R_{1}} = \frac{R_{0}V_{2} + R_{1}V_{out}}{R_{0} + R_{1}}

よって分子が等しいので

 \displaystyle V_{out} = V_{3} + \frac{R_{0}}{R_{1}}(V_{1} - V_{2})

なので図の回路は

  • V3だけオフセットをのせた上で
  • V2を基準として(V1 - V2)を増幅する
  • 増幅率はR0/R1

ということになる。

ここで次のような回路を設計したいとする。

  • 入力 :  0 \pm 1V
  • 出力 :  5 \pm 4V
  • 入力と出力は同相

図の差動増幅回路を使うとすると、V2=0としてV1に信号をのせ、R1=1kΩ、R2=4kΩとすれば入力を増幅できる。またオフセットは5VなのでV3=5Vとすれば良いと思われる。LTSPICEでシミュレーションした結果が以下。

f:id:amazingsoblin:20181007162512p:plain

波形をプロットすると以下のようになり、きちんと同位相でオフセットをのせた上で4倍できていることが分かる。

f:id:amazingsoblin:20181007164918p:plain

ROS開発のためのEmacs設定

ROSをC++で開発する際に、データタイプや名前空間をいちいち正確に書くのは正直しんどい。特にmsg、srv、actionファイルからどのようなデータ構造体が生成されたのかは確認しづらい(ConstPtr& を付け忘れて醜いコンパイルエラーが出るのはよくある)。また困ったことにflycheckのサーチパスに/opt/ros/kinetic/include~/catkin_ws/devel/includeを入れても、どういうわけかそれらを認識してくれない。ネットで探していたところironyを使ってROSパッケージのシンタックスチェックを行う方法を見つけた

Add include paths to flycheck and to company-irony? - Emacs Stack Exchange

のでメモ。これから説明する方法でROSパッケージの補完、コード・リーディングができるようになる。

手順1

自分がこれからビルドしようとしているパッケージに以下を追記

set(CMAKE_EXPORT_COMPILE_COMMANDS ON)

そのままcatkin_makeする。すると~/catkin_ws/buildcompile_commands.jsonが生成される。これはrtagsironyがソースファイルのパースやコード補完を行うために必要となる。後述のrtagsはCMakeLists.txtのあるディレクトリを自動で探してくれるが、compile_commands.json~/catkin_ws/build/に生成されるので、~/catkin_ws/jsonへのシンボリックリンクを貼っておくと自動で解析を行ってくれるようである。

(~/catkin_ws/)$ ln -sf build/compile_commands.json compile_commands.json

これをIronyに読み込ませていく。

手順2

melpaからironyyasnippetrtagsなどをインストール。自分は以下の記事を参照した。

C++11時代のEmacs C++コーディング環境

手順3

まずirony, yasnippetシンタックスチェック(静的解析)やコードの補完を行えるようにする。

$ cd catkin_ws/src
$ emacs my_pkg/***/src/node.cpp

Emacsを開いたら環境変数irony-cdb-search-directory-listを次のように打ち込んで編集

M-x irony-cdb-json-add-compile-commands-path

下のようにProject rootを聞かれるので~/catkin_ws/build/を指定する。次にCompile commandsを聞かれるので、同ディレクトリのcompile_commands.jsonを読みこませる。

Project root: ~/catkin_ws/build
Comiple commands: ~/catkin_ws/build/compile_commands.json

これで完了。さらにC-h v irony-cdb-search-directory-list環境変数irony-cdb-search-directory-listにbuildを付け加えておいた(念の為)。

f:id:amazingsoblin:20180702232007p:plain
ironyの設定

手順2の設定でEmacs起動時にironyが自動で起動するようになっているはずなので、再度.cppを開く。

$ emacs my_pkg/***/src/node.cpp

うまくいけばirony-serverが起動してシンタックスチェックを開始する。写真のようにros::の途中まで打ち込むと補完が行われるし、変数の型のチェックもリアルタイムで行われる。

f:id:amazingsoblin:20180702231150p:plain
補完

また自分で定義したmsgから自動生成される.hファイル(~/catkin_ws/devel/include/mypkg/内に置かれる)についても補完を行ってくれる。特にyasnippetを使えばテンプレートパラメータや関数の変数のリストも補完してくれるので、かなり便利である。

手順4

次にrtagsでコード・リーディングできるようにする。こうすると/opt/ros/kinetic/include内のファイルまで型や名前空間を追跡できるので、内部実装も覗けるようになる。手順2にしたがってrtagsとrtags-modeをインストールした後、rdmrcコマンドを使えることを確認する。

$ which rdm
/usr/bin/rdm
$ rdm --daemon

rtagsのタグを生成する。

$ 
$ cd ~/catkin_ws/build
$ rc -J .

rcコマンドがjsonの読み込みを開始するので、落ち着くまで待つ。Emacsでcppファイルを開き、タグジャンプしたい関数などにカーソルを合わせてM-.(Altを押しっぱなしにして-と.を順番に押す)すると定義されている箇所に飛べる。写真ではros::ServiceServer上にカーソルを合わせてジャンプしたところ/opt/ros/kinetic/include/rosに飛んだ。

f:id:amazingsoblin:20180702234252p:plain
ros::ServiceServerの定義にジャンプ

キーバインドは手順2の設定参照。

もちろん自分で作ったパッケージについてもCMAKE_EXPORT_COMPILE_COMMANDSをONにすれば、補完やタグジャンプを行えるようになる。

ROSのC++テンプレートメタプログラミングをふんだんに使っているので、ironyyasnippetで静的解析や補完を行いながら開発しないとどこでコンパイルエラーがどこで発生しているのか分かりづらい。

その他

.launch.urdf.xacroファイルを開くときにnxml-modeにするとインデントと閉じタグを勝手に入れてくれる。yamlファイルもyaml-modeでインデントできる。

(add-to-list 'auto-mode-alist '("\\.urdf" . nxml-mode))
(add-to-list 'auto-mode-alist '("\\.xacro" . nxml-mode))
(add-to-list 'auto-mode-alist '("\\.launch" . nxml-mode))
(add-to-list 'auto-mode-alist '("\\.yaml" . yaml-mode))

以上でかなり効率よく開発できる。

追記: clang-format

ROSにも以下のようなC++のスタイルガイドが存在するようである。

github.com