Welcome to Mashykom WebSite




Jetson Nano Mouse ロボットのリモート操作:ROS1編


 Jetson nanoはアメリカの半導体メーカーであるNVIDIA社が製造・販売をするシングルボードコンピューターです。NVIDIA社のJetbotホームページでは、Jetson Nano を搭載したロボットをDIYで制作する手順が紹介されてます。このJetbotホームページでは、Jetson Nano を搭載したロボット群 JetBot AI Robot の完成品も紹介されており、実際に注文取り寄せもできます。日本国内で、「Jetson Nano」を使ったAI自律走行車「JetBot」を実際に作成して、操作した実例が 「JetBotを動かしてみよう 第1回 部品の調達から作成したモデルによる走行」に掲載されています。JetBot ロボットを自作することに興味のある方には参考になります。

 このページでは、完成したロボットを前提として、Jetson Nano Developer Kit B01 を搭載したRobotの操作について説明します。使用するRobotはRT社製の Jetson Nano Mouse です。Jetson Nano Mouse は、NVIDIA社の Jetson Nano を搭載したロボット群 JetBot AI Robot の中で紹介されているロボットの一つです。

 RT Corporation社の製品紹介では以下のように説明されています。「Jetson Nano MouseはAI関連技術とロボット制御技術を同時に学ぶことができる小型二輪移動ロボットです。広角カメラ2台を前方に搭載しており、Jetson Nanoの性能を活かして機械学習や画像処理技術の研究開発に利用できます。ロボット制御用のソフトウェアをGitHubで公開しているため、AGVや自動運転で用いる技術開発、研究のプロトタイピングにも活用できます。」

 Jetson Nanoは、Tegra X1のSoCに4GBのメモリを搭載したコンピューティングモジュールで、ピーク性能は472GFLOPSを実現しています。Maxwell世代のGPUを内蔵しているため、CUDAを使った推論を利用して、画像認識などの機能を実装できます。Jetson Nano自体は多種類のインターフェイス機器を内蔵していませんが、開発キットと呼ばれる追加の機能を持つベース基板に接続されています。この開発キットにはJetson Nanoのモジュールそのものと、I/Oを内蔵した基板が組み合わさって提供されており、Jetson Nano単体では用意できない各種I/O(USBやHDMIなど)や電源回路が搭載されています。

 Jetson Nano開発者キットの仕様は以下の通りです。

  別途用意が必要なものは

です。

 Jetson Nano Mouse の仕様は以下の通りです。

 このページでは、リモートPCとして Mac を使用した手順を説明しています。

jnmouse.png
RT社製 Jetson Nano Mouse ロボット

Last updated: 2022.3.20


カメラの映像をリモートPCのブラウザに表示


OpenCVのバージョンを確認します。バージョンが4系でも、モジュール名はcv2 です。


$ python3

>>> import cv2
>>> print cv2.__version__
'4.5.5'

と表示されるはずです。OpenCV4バージョンです。

 Jetson Nano Mouse のカメラを接続して下さい。カメラモジュールV2が認識されているか否かを確認するために以下のコマンドを打ちます。


$ ls -al /dev/video*

----

crw-rw----+ 1 root video 81, 0  3月 18 11:40 /dev/video0
crw-rw----+ 1 root video 81, 3  3月 18 11:40 /dev/video1
crw-rw----+ 1 root video 81, 6  3月 18 11:40 /dev/video2

 /dev/video0、/dev/video1 、/dev/video2として認識されました。キャプチャコマンドを実行します。


$ nvgstcapture

 Jetson Nano に接続したモニタのデスクトップにカメラ映像が表示されています。なお、リモートPCには表示されません。

 今後使用する予定のROSパッケージのインストールをします。まずはGitHubから必要なGitリポジトリをダウンロードしてきます。


 $   cd ~/catkin_ws/src
 $   git clone https://github.com/ryuichiueda/raspimouse_ros_2.git
 $   git clone https://github.com/rt-net/jnmouse_ros_examples.git
 $   git clone https://github.com/rt-net/jetson_nano_csi_cam_ros.git
 $   git clone https://github.com/rt-net/gscam.git
    
 

 このROSパッケージ「jetson_nano_csi_cam」はJetson Nano DevKit B01 + dual CSI cameraのROSドライバです。 Jetson Nano DevKit B01に取り付けたCSI cameraの画像をGStreamerまたはJetson Linux Multimedia API経由で取得し、ROSのsensor_msgs/Imageとして配信するためのものです。launchファイルでgscamを呼び出し、GStreamerまたはJetson Linux Multimedia APIを経由して、解像度とフレームレートの設定、カメラのキャリブレーション、複数のCSIカメラ画像の取得・配信を実現しています。gscam の詳細については、ROS wikiを参照ください。

 この節では、インストールしたROSパッケージのうち、jetson_nano_csi_cam_ros と gscam を利用しますが、raspimouse_ros_2 及び jnmouse_ros_examples は使用しません。今後利用予定があるので、インストールしておきます。

 次に依存関係にあるソフトウェアをインストールします。

  
$ rosdep install -r -y -i --from-paths .

 ここで、必要なソフトウェアはダウンロードできているので、catkin_makeを使って~/catkin_ws/src以下のROSパッケージをビルドします。


$ cd ~/catkin_ws
$ catkin_make

 以上でROSパッケージのインストールは完了です。念の為、「gscam」に必要な「gstreamer」の関連パッケージをインストールします。


$ sudo apt-get install gstreamer1.0-tools libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev libgstreamer-plugins-good1.0-dev

 CAM0として接続されたカメラストリームのデータを「/csi_cam_0/image_raw」のROSトピックとして配信するには以下のコマンドをターミナルで実行します。


$ roslaunch jetson_nano_csi_cam jetson_csi_cam.launch sensor_id:=0 width:=480 height:=640 fps:=20

 また、CAM0とCAM1に接続された二つのカメラストリームのデータをそれぞれ`/csi_cam_0/image_raw`と`/csi_cam_1/image_raw`のROSトピックとして同時に配信するには以下のコマンドを実行します。


$ roslaunch jetson_nano_csi_cam jetson_dual_csi_cam.launch width:=480 height:=640 fps:=20

 オプションの指定なしで、ROSトピックとしてカメラの映像を配信するには以下のコマンドを実行します。


$ roslaunch jetson_nano_csi_cam jetson_csi_cam.launch 

----

... logging to /home/koichi/.ros/log/414496b4-a668-11ec-b5a4-5ca6e68574bd/roslaunch-Jetson-8353.log
Checking log directory for disk usage. This may take a while.
Press Ctrl-C to interrupt
Done checking log file disk usage. Usage is <1GB.

started roslaunch server http://Jetson:41005/

SUMMARY
========

PARAMETERS
 * /csi_cam_0/camera_id: 0
 * /csi_cam_0/camera_info_url: package://jetson_...
 * /csi_cam_0/camera_name: csi_cam_0
 * /csi_cam_0/frame_id: /csi_cam_0_link
 * /csi_cam_0/gscam_config: nvarguscamerasrc ...
 * /csi_cam_0/image_height: 480
 * /csi_cam_0/image_width: 640
 * /csi_cam_0/sync_sink: True
 * /csi_cam_0/target_fps: 20
 * /rosdistro: melodic
 * /rosversion: 1.14.12

NODES
  /
    csi_cam_0 (gscam/gscam)

auto-starting new master
process[master]: started with pid [8366]
ROS_MASTER_URI=http://localhost:11311

----
中略
----

SUMMARY
========

PARAMETERS
 * /csi_cam_0/camera_id: 0
 * /csi_cam_0/camera_info_url: package://jetson_...
 * /csi_cam_0/camera_name: csi_cam_0
 * /csi_cam_0/frame_id: /csi_cam_0_link
 * /csi_cam_0/gscam_config: nvarguscamerasrc ...
 * /csi_cam_0/image_height: 480
 * /csi_cam_0/image_width: 640
 * /csi_cam_0/sync_sink: True
 * /csi_cam_0/target_fps: 20
 * /rosdistro: melodic
 * /rosversion: 1.14.12

NODES
  /
    csi_cam_0 (gscam/gscam)

auto-starting new master
process[master]: started with pid [8967]
ROS_MASTER_URI=http://localhost:11311

setting /run_id to 4966e8b8-a66f-11ec-be56-5ca6e68574bd
process[rosout-1]: started with pid [8983]
started core service [/rosout]
process[csi_cam_0-2]: started with pid [8990]

 このlaunchでは配信用のノードを起動するだけです。配信されている映像を確認するには何かしら別の手段を利用します。 映像が配信されているかを簡単に確認するには、端末を起動して`rostopic list`を実行し、配信中のROSトピック一覧から`/csi_cam_0/image_raw`という名前のトピックを探します。


$ rostopic list

----

/csi_cam_0/camera_info
/csi_cam_0/image_raw
/csi_cam_0/image_raw/compressed
/csi_cam_0/image_raw/compressed/parameter_descriptions
/csi_cam_0/image_raw/compressed/parameter_updates
/csi_cam_0/image_raw/compressedDepth
/csi_cam_0/image_raw/compressedDepth/parameter_descriptions
/csi_cam_0/image_raw/compressedDepth/parameter_updates
/csi_cam_0/image_raw/theora
/csi_cam_0/image_raw/theora/parameter_descriptions
/csi_cam_0/image_raw/theora/parameter_updates
/rosout
/rosout_agg

 Jetson Nanoにwebサーバーを立ち上げるために、以下のコマンドを順に打ちます。


$ sudo apt-get install ros-melodic-rosbridge-server
$ cd ~/catkin_ws/src
$ git clone https://github.com/GT-RAIL/async_web_server_cpp.git
$ git clone git@github.com:RobotWebTools/web_video_server.git
$ cd ~/catkin_ws
$ catkin_make

 catkin_makeでエラーが出ます。
CMake Error at /opt/ros/melodic/share/cv_bridge/cmake/cv_bridgeConfig.cmake:113 (message)
以下のように修正します。/opt/ros/melodic/share/cv_bridge/cmake/cv_bridgeConfig.cmake を開いて、


  set(_include_dirs "include;/usr/include;/usr/include/opencv")
この行の内容を以下のように修正
set(_include_dirs "include;/usr/include;/usr/include/opencv4")

 Jetson Nano Mouseに付属のカメラからの映像をリモートPCのブラウザで表示してみましょう。最初に、一つ目のターミナルで


$ roslaunch jetson_nano_csi_cam jetson_csi_cam.launch

と打って、カメラからの映像をトピックとして配信します。トピックのノード名は「csi_cam_0」です。二つ目のターミナルから


$ rosrun web_video_server web_video_server 

と打ちます。デフォルトでは、ポート番号は8080となっています。ここで、WiFi接続されたPCまたはスマホのブラウザから、ロボットのIPアドレスが192.168.10.10のとき
http://192.168.10.10:8080/stream?topic=/csi_cam_0/image_raw
とURLを指定します。ロボットのカメラからの映像がブラウザに表示されると、成功です。

 ターミナルを2個立ち上げるのは煩雑なので、launch ファイルを作成します。jnmouse_rosディレクトリの中に camstream.launch を作成します。以下の内容で作成して下さい。


<launch>
    <include file="$(find jetson_nano_csi_cam)/launch/jetson_csi_cam.launch" />
    <node pkg="web_video_server" name="web_video_server" type="web_video_server">
    </node>
</launch>

 以下のコマンドを打って、camstream.launch を起動します。


$ roslaunch jnmouse_ros camstream.launch

 上と同じく、リモートPCのブラウザからロボットの{IPアドレス}:8080/stream?topic=/csi_cam_0/image_raw にアクセスします。


リモートPCのブラウザからロボットの操作


 roswwwというROSパッケージをインストールします。これはwebサーバーを立ち上げるROSパッケージです。


$ cd ~/catkin_ws/src
$ git clone https://github.com/tork-a/roswww.git
$ cd ~/catkin_ws
$ catkin_make

 以下のコマンドを打って、roswwwを起動します。


$ roslaunch roswww roswww.launch

 roswwwを起動したらブラウザでhttp://localhost:8085というアドレスでアクセスします。リモートPCから, http://{Jetson NanoのIPアドレス}:8085/ にアクセスすると,インストール済みパッケージのリストを表示するページが開きます。http://{Jetson NanoのIPアドレス}:8085/roswww にアクセスすると、index.html のページが開きます。

 ここから、webアプリを作成して、スマホからロボットを操作するためのパッケージの作成に入ります。インストールする必要があるソフトは、rosbridgeとBootstrapです。まず、rosbridgeをインストールします。

 

$ sudo apt install ros-melodic-rosbridge-suite

 このソフトrosbridgeは、ROS以外のソフトからROSの機能を利用するためのプロトコル、rosbridge protocolを使用するためのものです。非ROSのソフトがrosbridgeでROSの世界と通信をするときは、JSON(Javascript object notation)でデータをやり取りします。ROSのJSON APIです。

 次に、roswww/www ディレクトリ内にBootstrapのモジュールを配置します。Bootstrapの公式ページhttps://getbootstrap.com/docs/3.3/getting-started#downloadから必要なモジュールをダウンロードします。以下のように、wgetを用いて、zip ファイルをダウンロードします。


$ roscd roswww/www
$ wget https://github.com/twbs/bootstrap/releases/download/v3.3.7/bootstrap-3.3.7-dist.zip
$ unzip bootstrap-3.3.7-dist.zip

これで、bootstrapのフォルダーが生成されますが、その内容物だけをwwwディレクトリ内に置きたいので


$ rm -rf css
$ mv bootstrap-3.3.7-dist/* ./
$ rmdir bootstrap-3.3.7-dist/
$ rm bootstrap-3.3.7-dist.zip

と入力して、整理します。Bootstrapの現在(2022年3月)のバージョンは5.1.3ですが、内容が複雑なっているので、最も簡易なバージョンを使用します。www/css ディレクトリにbootstrap.min.cssが配置され、www/js ディレクトリにbootstrap.min.jsがインストールされてます。

 次に、jQuery公式ホームページのダウンロードのリンクをコピーして、


$ wget https://code.jquery.com/jquery-3.3.1.min.js
$ mv jquery-3.3.1.min.js ./js/

とすると、バージョン(jquery-3.3.1.min.js)が www/js ディレクトリにダウンロードできます。minは容量を小さくしたファイルを意味します。

 webサーバーで表示するhtmlファイルを作成してみましょう。以下のスクリプトをindex1.htmlという名前にして、www ディレクトリに保存してください。

<!DOCTYPE html>
<html lang="ja">    
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>JestsonMouse Monitor</title>   

    <!-- Bootstrap -->
    <link href="css/bootstrap.min.css" rel="stylesheet">
  </head>
  <body>
    <nav class="navbar navbar-default navbar-fixed-top">
      <div class="container">
        <div class="navbar-header">
          <a class="navbar-brand" href="#">JetsonMouse Monitor</a>   
        </div>
      </div>
    </nav>
    <div class="container" style="padding-top:60px">
      <div class="row">
        <div class="col-md-4">.col-md-4</div>
        <div class="col-md-4">.col-md-4</div>
        <div class="col-md-4">.col-md-4</div>
      </div>
    </div>
    <!-- jQuery (necessary for Bootstrap's JavaScript plugins) -->
    <script src="js/jquery-3.3.1.min.js"></script>
    <!-- Include all compiled plugins (below), or include individual files as needed -->
    <script src="js/bootstrap.min.js"></script>
  </body>
</html>

<script src="js/jquery-3.3.1.min.js"></script>
の行は、インターネット上のCDNのURLを指定して、



<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>

と書くことの方が一般的です。ここでは安定性を重視して、ダウンロードしたjquery-3.3.1.min.jsのソースファイルをディレクトリjsに保存しています。

 index.htmlにシンボリック・リンクを貼ります。


$ ln -s index1.html index.html

と打ちます。

  最後に、launchファイルを作成します。


$ roscd roswww/launch
$ nano monitor1.launch

 以下のスクリプトにmonitor1.launchと名前をつけて、launchディレクトリに保存してください。このwebサーバーはjnmouse_rosパッケージを部分的に使用しますので、launchファイルの中に埋め込みます。rosbridgeも使用します。


<launch>
<include file="$(find rosbridge_server)/launch/rosbridge_websocket.launch" />
<include file="$(find roswww)/launch/roswww.launch" />
<include file="$(find jnmouse_ros)/launch/jnmouse.launch" />
</launch>

 launchファイルを立ち上げます。


$ roslaunch roswww monitor1.launch

 リモートPCのブラウザから、http://{Jetson NanoのIPアドレス}:8085/roswww/index.html にアクセスして下さい。webページ index.html がブラウザに表示されます。[Ctrl+c]でroslaunchを停止してください。

距離センサーの値をブラウザに表示するプログラムを作成します。index1.htmlにrosbridgeによる通信部分を書き加えたindex2.htmlファイルを作ります。

<!DOCTYPE html>
<html lang="ja">    
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>JetsonMouse Monitor</title>   

    <!-- Bootstrap -->
    <link href="css/bootstrap.min.css" rel="stylesheet">
  </head>
  <body>
    <nav class="navbar navbar-default navbar-fixed-top">
      <div class="container">
        <div class="navbar-header">
          <a class="navbar-brand" href="#">JetsonMouse Monitor</a>   
        </div>
      </div>
    </nav>
    <div class="container" style="padding-top:60px">
      <div class="row">
        <div class="col-md-4" id="lightsensors"><span id="lightsensors"></span></div>
        <div class="col-md-4">.col-md-4</div>
        <div class="col-md-4">.col-md-4</div>
      </div>
    </div>
    <!-- jQuery (necessary for Bootstrap's JavaScript plugins) -->
	<script src="js/jquery-3.3.1.min.js"></script>
    <!-- Include all compiled plugins (below), or include individual files as needed -->
    <script src="js/bootstrap.min.js"></script>
    <script src="js/eventemitter2.min.js"></script>
    <script src="js/roslib.min.js"></script>
    <script src="main.js"></script>
  </body>
</html>

id="lightsensors"という記述は、以下のmain.jsファイルの中で
document.getElementById("lightsensors").innerHTML = str;
を対応させるためのidで、ブラウザの表示に埋め込む場所を示しています。 jsファイルのeventemitter2.min.jsとroslib.min.jsはダウンロードして、ディレクトリjsに保存したJavascriptのソースファイルです。ROSの通信に必要なファルです。以下のリンク先からコピーできますが、ソースファイルを保存しないときは、インターネットのリンク先を


<script src ="https://static.robotwebtools.org/EventEmitter2/current/eventemitter2.min.js"></script>
<script src="https://static.robotwebtools.org/roslibjs/current/roslib.min.js"></script>


とプログラムの中で指定すると、利用できます。

  以下のスクリプトを作成し、名前をmain2.jsとしてwwwディレクトリに保存します。


var ros = new ROSLIB.Ros({ url : 'ws://' + location.hostname + ':9090' });
                                                   
ros.on('connection', function() {console.log('websocket: connected'); });
ros.on('error', function(error) {console.log('websocket error: ', error); });
ros.on('close', function() {console.log('websocket: closed');});

var ls = new ROSLIB.Topic({
        ros : ros,
        name : '/lightsensors',
        messageType : 'jnmouse_ros/LightSensorValues'
});

ls.subscribe(function(message) {
        str = JSON.stringify(message);
        document.getElementById("lightsensors").innerHTML = str;
        console.log(str);                                  
});

このJavascriptファイルは、index2.htmlの中で



<script src="main.js"></script>

から読み込まれると、ブラウザに距離センサーの値を表示します。第1行目のvar ros = new ROSLIB.Ros({ url : 'ws://' + location.hostname + ':9090' });は、{}内のオブジェクト・リテラルを引数としてROSLIB.Rosに渡して、オブジェクト変数rosを作成しています。{}内の記述はrosbridgeのサーバーのurlがロボットのIPアドレスで、ポート番号が9090であり、通信のプロトコルにWeb Socketを用いることを意味します。var ls = new ROSLIB.Topic({・・・{);は、ROSのトピックのオブジェクトを定義をしています。ls.subscribe(function(message) {・・・}は、トピックからのメッセージを処理するところです。その中で、str = JSON.stringify(message)はJSON形式で受け取ったメッセージを文字列に変換しています。

シンボリックリンクを


$ ln -s main2.js main.js

と貼ります。launchファイルを立ち上げ、


$ roslaunch roswww monitor1.launch

WiFi経由で接続されたPCやスマホのブラウザから、http://192.168.10.10:8085/roswww/index.html とURLを指定すると、ブラウザに距離センサーの値が表示されます。この表示の仕方では読みにくいので、もっとわかりやすい表示に改善する必要があります。

  htmlファイルを拡張しましょう。それに合わせて同時に、Javascriptファイルも修正します。距離センサーの値の表示を表形式して、それに対応するようにjsファイルを修正します。index2.htmlをコピーして、以下のように修正を加えて、index3.htmlとします。

<!DOCTYPE html>
<html lang="ja">   
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>JetsonMouse Monitor</title>   
    <link href="css/bootstrap.min.css" rel="stylesheet">
  </head>
  
  <body>
    <nav class="navbar navbar-default navbar-fixed-top">
      <div class="container">
        <div class="navbar-header">
          <a class="navbar-brand" href="#">JetsonMouse Monitor</a>   
        </div>
      </div>
    </nav>
    <div class="container" style="padding-top:60px">
      <div class="row">
        <div class="col-md-4" id="lightsensors">
          <h2>Light Sensors</h2>
          <table class="table table-striped">
            <tr><th>DIRECTION</th><th>VALUE</th></tr>
            <tr><td>left_forward</td><td id="left_forward"></td></tr>
            <tr><td>left_side</td><td id="left_side"></td></tr>
            <tr><td>right_side</td><td id="right_side"></td></tr>
            <tr><td>right_forward</td><td id="right_forward"></td></tr>
            <tr><td>sum_all</td><td id="sum_all"></td></tr>
            <tr><td>sum_forward</td><td id="sum_forward"></td></tr>
          </table>
        </div>
        <div class="col-md-4">.col-md-4</div>
        <div class="col-md-4">.col-md-4</div>
      </div>
    </div>
    <!-- jQuery (necessary for Bootstrap's JavaScript plugins) -->
    <script src="js/jquery-3.3.1.min.js"></script>
    <!-- Include all compiled plugins (below), or include individual files as needed -->
    <script src="js/bootstrap.min.js"></script>
    <script src="js/eventemitter2.min.js"></script>
    <script src="js/roslib.min.js"></script>
    <script src="main.js"></script>
  </body>
</html>

 main2.jsをコピーして、以下のように修正を加えて、main3.jsとします。


var ros = new ROSLIB.Ros({ url : 'ws://' + location.hostname + ':9090' });
                                                   
ros.on('connection', function() {console.log('websocket: connected'); });
ros.on('error', function(error) {console.log('websocket error: ', error); });
ros.on('close', function() {console.log('websocket: closed');});

var ls = new ROSLIB.Topic({
        ros : ros,
        name : '/lightsensors',
        messageType : 'jnmouse_ros/LightSensorValues'
});

ls.subscribe(function(message) {
        for( e in message ){
                document.getElementById(e).innerHTML = message[e];
        }
});

 

 シンボリックリンクを貼ります。


$ rm index.html
$ rm main.js
$ ln -s index3.html index.html
$ ln -s main3.js main.js

と貼ります。launchファイルを立ち上げます。


$ roslaunch roswww monitor1.launch

 WiFi経由で接続されたPCやスマホのブラウザから、http://192.168.10.10:8085/roswww/index.html とURLを指定すると、ブラウザに距離センサーの値が以下のような表形式で表示されます。

 
Light Sensors 
DIRECTION VALUE

left_forward 61
left_side -4
right_side -11
right_forward 80
sum_all 126
sum_forward 141

----

 次に、カメラの映像をブラウザに写しましょう。index3.htmlをコピーして、子画面のdiv要素に以下のように、object要素'camstream'を追加します。

 このファイルをindex4.htmlとして、保存します。これに対応するJSファイルを作成します。main3.jsをコピーして、main4.jsファイルを作成します。スクリプトの最下部に追加修正します。

var ros = new ROSLIB.Ros({ url : 'ws://' + location.hostname + ':9090' });

ros.on('connection', function() {console.log('websocket: connected'); });
ros.on('error', function(error) {console.log('websocket error: ', error); });
ros.on('close', function() {console.log('websocket: closed');});

var ls = new ROSLIB.Topic({
ros : ros,
name : '/lightsensors',
messageType : 'jnmouse_ros/LightSensorValues'
});

ls.subscribe(function(message) {
for( e in message ){
document.getElementById(e).innerHTML = message[e];
}
});

document.getElementById('camstream').data='http://' + location.hostname + ':8080/stream?topic=/csi_cam_0/image_raw';

このコードはカメラの映像をobject要素のdataという属性に埋め込むための処理をします。このコードが読み込まれると、ブラウザに映像が表示されます。 前と同じく、シンボリックリンクを貼り直します。


$ rm index.html
$ rm main.js
$ ln -s index4.html index.html
$ ln -s main4.js main.js

  カメラ映像を取り込むために、launchファイルを拡張します。元のlaunchファイルに、jetson_nano_csi_camとweb_video_serverのノードを加えて、以下のファイルを作成します。


<launch>
  <include file="$(find rosbridge_server)/launch/rosbridge_websocket.launch">
    <include file="$(find roswww)/launch/roswww.launch" />
    <include file="$(find jnmouse_ros)/launch/jnmouse.launch" />
    <include file="$(find jetson_nano_csi_cam)/launch/jetson_csi_cam.launch" />
    <node pkg="web_video_server" name="web_video_server" type="web_video_server">
    </node>
</launch>

このファイルをmonitor.launchと名前をつけて、保存してください。以下のコマンドを打って確認して下さい。


$ roslaunch roswww monitor.launch

  最後に、モーターのコントローラーをスマホのブラウザに表示させるためのプログラムを組みましょう。モーターの子画面を作るために、新しいhtmlファイルを以下の通り作成します。index5.htmlと名前をつけて保存してください。コピペがいいでしょう。

子画面用のdiv要素のMotors関係の部分が新しく追加されたコードです。この子画面には、モータースイッチのON/OFFボタンとタッチパッド仕様のモーターのコントローラーが配置されています。タッチパッドの上側(下側)をタッチすると、中心からの距離に応じてロボットが速度を変えて前進(後退)します。左側(右側)をタッチする、右(左)方向に回転します。 次に、JSファイルを作成します。以下のスクリプトをmain5.jsとして保存してください。

var ros = new ROSLIB.Ros({ url : 'ws://' + location.hostname + ':9090' });

ros.on('connection', function() {console.log('websocket: connected'); });
ros.on('error', function(error) {console.log('websocket error: ', error); });
ros.on('close', function() {console.log('websocket: closed');});

var ls = new ROSLIB.Topic({
ros : ros,
name : '/lightsensors',
messageType : 'jnmouse_ros/LightSensorValues'
});

ls.subscribe(function(message) {
for( e in message ){
document.getElementById(e).innerHTML = message[e];
}
});

var on = new ROSLIB.Service({
ros : ros,
name : '/motor_on',
messageType : 'std_srvs/Trigger'
});

var off = new ROSLIB.Service({
ros : ros,
name : '/motor_off',
messageType : 'std_srvs/Trigger'
});

$('#motor_on').on('click', function(e){
on.callService(ROSLIB.ServiceRequest(),function(result){
if(result.success){
$('#motor_on').attr('class','btn btn-danger');
$('#motor_off').attr('class','btn btn-default');
}
});

});

$('#motor_off').on('click', function(e){
off.callService(ROSLIB.ServiceRequest(),function(result){
if(result.success){
$('#motor_on').attr('class','btn btn-default');
$('#motor_off').attr('class','btn btn-primary');
}
});
});

var vel = new ROSLIB.Topic({
ros : ros,
name : '/cmd_vel',
messageType : 'geometry_msgs/Twist'
});

function pubMotorValues(){
fw = $('#vel_fw').html();
rot = $('#vel_rot').html();

fw = parseInt(fw)*0.001;
rot = 3.141592*parseInt(rot)/180;
v = new ROSLIB.Message({linear:{x:fw,y:0,z:0}, angular:{x:0,y:0,z:rot}});
vel.publish(v);
}

$('#touchmotion').on('click', function(e){
rect = $('#touchmotion')[0].getBoundingClientRect();
x = e.pageX - rect.left - window.pageXOffset;
y = e.pageY - rect.top - window.pageYOffset;

vel_fw = (rect.height/2 - y)*3;
vel_rot = rect.width/2 - x;
$('#vel_fw').html(parseInt(vel_fw));
$('#vel_rot').html(parseInt(vel_rot));
});

setInterval(pubMotorValues,100);

document.getElementById('camstream').data = 'http://'
+ location.hostname
+ ':8080/stream?topic=/csi_cam_0/image_raw';

このスクリプトはjQueryを使用することを前提しています。var on = new ROSLIB.Service 以下がmotor制御に関する新しい部分です。まず、サービス/motor_on、/motor_offに対応するオブジェクトonとoffを作っています。その後に、オン・オフのボタンがタッチされた時の処理が書かれています。ONがタッチされた('click')時の処理が$('#motor_on').on('click', function(e)以下の関数で記述され、OFFがタッチされた('click')時の処理が$('#motor_off').on('click', function(e)以下の記述文になります。ROSのサービスを使用するために、callserviceというメソッドを用いて、サービスから返ってきたデータがresultに入ります。モーターの電源が入っている場合、ボタンの色を変化させています。 前と同じく、シンボリックリンクを貼り直します。


$ rm index.html
$ rm main.js
$ ln -s index5.html index.html
$ ln -s main5.js main.js

roslaunchを立ち上げます。


$ roslaunch roswww monitor.launch

----(中略)

[INFO] [1647742520.159468]: Rosapi started
2022-03-20 11:15:20+0900 [-] WebSocketServerFactory starting on 9090
2022-03-20 11:15:20+0900 [-] Starting factory 
  2022-03-20 11:15:20+0900 [-] [INFO] [1647742520.529013]: Rosbridge WebSocket server started at ws://0.0.0.0:9090
  2022-03-20 11:15:20,568 - roswww - INFO - roswww : Configure webserver with cache:True, basic:False
  2022-03-20 11:15:20,571 - roswww - INFO - roswww : # of packages : 223
  2022-03-20 11:15:20,572 - roswww - INFO - roswww : Weg Page root : www
  2022-03-20 11:15:20,823 - roswww - INFO - roswww : Initialised
  2022-03-20 11:15:20,824 - roswww - INFO - roswww : Attempting to start webserver on port 8085
  2022-03-20 11:15:20,832 - roswww - INFO - roswww : Webserver successfully started on port 8085

  ----

jetson_monitor.png
Jetson Nano Mouse ロボットのwebコントロール用のカメラ画像

Motorsを「ON」にするとロボットが動きます。止めるときは、「OFF」をクリックします。


リアルタイム映像の物体検出とストリーミング配信


 Jetson Nano のカメラがとらえたリアルタイム映像をストリーミング映像として配信して、それをリモートPCで受信する方法を取り上げます。映像のストリーミング配信の方法について説明します。このgithub.com/dusty-nv/jetson-inferenceサイトに説明があります。Jetson Nano から Gstreamer を用いた映像のストリーミングは以下のようにします。「Jetson-inference」パッケージがインストールされているとします。

 利用するカメラは Raspberry Pi Camera と usb-Webcamera の2種類を想定します。Raspberry Pi Camera は MIPI CSI cameras に分類されます。webcamera はV4L2 cameras に分類されます。MIPI CSI cameraの利用するprotocol は csi://になり、V4L2 camera の利用するprotocolは v4l2:// なります。したがって、入力のデバイス指定はそれぞれ csi://0 、[v4l2://]/dev/video0 のようになります。

出力は RTP protocol で配信します。RTP ネットワークストリーミングは UDP/IP 上でホスト機器に配信されます。UDP/IP は TCP/IPと異なる配信方式です。rtp://{remote-ip}:1234 形式で配信先を指定します。したがって、csi カメラを使用するときは、ホストPCのIPアドレスが 192.168.10.10 のとき


  $ cd jetson-inference/build/aarch64/bin
  $ video-viewer --input-flip=rotate-180 csi://0 rtp://192.168.10.10:1234

と入力します。以下のような表示が出て、映像のストリーミングが開始されます。

 デフォルトではストリーミングされた映像が上下に反転しているので、 --input-flip=rotate-180 というオプションで反転を直しています。usbに接続されたWeb camera のケースでは必要ありませんので、以下のようになります。カメラモジュールを2個使用していると想定します。カメラモジュールを1個使用しているときは、/dev/video1 と設定します。カメラモジュールにカメラが接続されているときは、/dev/video0 の設定は間違いです。


$ video-viewer /dev/video2 rtp://192.168.10.10:1234

 Input Options と Output Options の詳細についてはgithub.com/dusty-nv/jetson-inferenceの解説を参照ください。

 Jetson Nano からの映像のストリーミングをリモートPCから受信する方法について説明します。VLC Playerを用いた映像の表示を取り上げます。各プラットフォームに対応したVLC Player を公式ページからダウンロードします。表示された手順に従ってインストールします。

 適当なディレクトリに以下の内容の SDP file (.sdp)を作成します。jetson.sdp と名前をつけて保存する。


v=0
c=IN IP4 192.168.10.10
m=video 1234 RTP/AVP 96
a=rtpmap:96 H264/90000

vlc_player.png
VLC player

 VLC プレイヤーを起動して、再生リストにjetson.sdp が表示されたら、再生のボタンをクリックする手順となります。実際には、Jetson Nano でストリーミングを開始したら、再生のボタンをクリックします。ストリーミングされた映像が再生されます。映像が再生されたら成功です。csi カメラの時、オプション --input-flip=rotate-180が指定されていないと、映像が上下に反転しています。

 ライブ映像の物体識別及び物体検出を行うことを考えます。csi カメラを用いるケースでは、

$ cd ~/jetson-inference/build/aarch64/bin
$ ./detectnet --input-flip=rotate-180 csi://0 rtp://192.168.10.10:1234
$ ./imagenet --input-flip=rotate-180 csi://0 rtp://192.168.10.10:1234

 とします。Web camera を使用するケースでは以下のようになります。


$ ./detectnet /dev/video2 rtp://192.168.10.10:1234
$ ./imagenet /dev/video2 rtp://192.168.10.10:1234

 GPUを使用しているので、検出スピードは速いです。当然ながら、映像の精度は、csi カメラよりも webcam の方が良いです。

 次に、ROSの中でJetson_inference を利用する方法を説明します。ros_deep_learningをダウンロードする前に、必要なpythonモジュールをダウンロードしておきます。そして、catkin_ws/src ディレクトリにros_deep_learningをダウンロードします。


$ sudo apt-get install ros-melodic-image-transport ros-melodic-vision-msgs
$ cd ~/catkin_ws/src
$ git clone https://github.com/dusty-nv/ros_deep_learning

 続いて、makeします。


$ cd ~/catkin_ws/
$ catkin_make
$ source ~/catkin_ws/devel/setup.bash

 csi camera を使用すると、映像が上下反対になっており、コマンドラインでの反転用のオプションがないので、usb-webcamを使用することにします。リアルタイム映像の物体検出は以下のように実行できます。


$ roslaunch ros_deep_learning detectnet.ros1.launch input:=/dev/video2 output:=rtp://192.168.10.10:1234

 以下のような表示が出て、映像のストリーミングが開始されます。

 この後、VLC player を起動して、ストリーミングされた映像を受信します。

 imagenet を使用すると、以下のようなエラーが出て、途中で停止します。理由は不明です。


$ roslaunch ros_deep_learning imagenet.ros1.launch input:=/dev/video2 output:=rtp://192.168.10.10:1234

----

[TRT] imageNet -- loaded 1000 class info entries
[TRT] imageNet -- networks/bvlc_googlenet.caffemodel initialized.
[ INFO] [1648265722.641508479]: model hash => 6171187992146253386
[ INFO] [1648265722.645900361]: hash string =>
/usr/local/bin/networks/bvlc_googlenet.caffemodelnetworks/ilsvrc12_synset_words.txt
[ERROR] [1648265722.650413702]: [setParam] Failed to contact master at [localhost:11311]. Retrying...

 以下の segent を用いた実行は正常に作動します。が、全体が暗い映像で、陰影と色彩が見にくい映像になっています。


  $ roslaunch ros_deep_learning segnet.ros1.launch input:=/dev/video2 output:=rtp://192.168.10.10:1234

 jetson-inference のみを使用すれば実行できるような物体識別や物体検出などをROSの中で行うことに意味はあるのでしょうか。Robot操作情報にフィードバックされて利用されるのであれば、ROSの中で物体推論などを行うことは有効です。しかし、この段階ではこのような有効利用はできていません。検討課題です。


ーーーーーーーー
** To be continued **

このページのトップへ
Jetson Nano で機械学習のページ

トップページに行く