2021年の振り返り

1月

Anycubic Mega Proの運用を開始

第四級アマチュア無線技士試験合格

第一級アマチュア無線技士試験も受けるので、免許の申請は保留。(12月に結局申請)

2月

myCobotのグリッパーを入手

3月

RealSense F455とD415を入手。

第一級陸上特殊無線技士試験に合格し、免許証を入手。

4月

第一級アマチュア無線技士試験不合格。

法規パートが及ばなかった。モールス信号を始めて学び、古いが自分の中では新鮮さを感じた。

KickstarterでBackしていたPetoi Bittleを組み立てた。

5月

UnityとROSでカメラ画像から深層学習でつかむ対象の姿勢を予測してPick and Placeを行うチュートリアルを試す。

KickstarterでbackしていたLooking Glass Portraitを入手

6月

Unitree Go1が気になったが、予算の問題で購入できず。

7月

Maker Faire Tokyo 2021の出展が不可という連絡を受け取る。

来年再挑戦する予定。

xArm用のSuction Cupを入手

ROS 2の本を揃え、勉強をする決意をする。

8月

Kickstarterでbackして入手したxArm 5 をアップデートしてxArm 6にした。

休日会員だったDMM.make AKIBAが8月で終了ということで最後の加工を行った。

9月

OAK-D-LiteをKickstarterでback

ROS 2の入門として「ScamperとRaspberry Piで学ぶROS 2プログラミング入門」を読んだ。

Mini PupperをKickstarterでback

10月

ROS 2(Foxy)でカルタ取りロボットシステムを作る最初のステップとして、RealSense D435の画像、深度からカルタの位置推定・札認識を実装した。

UFACTORY Lite 6をKickstarterでback

11月

百人一首カルタ取りロボの音声認識部分もシステムに接続し、「読み上げ音声を認識→該当の札を取る」を自動でできるようになった。

12月

ZED Miniを入手

クオータニオン学習用のおもちゃを試作したがいまいちだった。

ロボセミで「百人一首カルタ取りロボでROS 2に入門」のタイトルで発表

OAK-D-Liteが届く

総括

  • 相変わらずガジェットにつぎ込んだ。特にKickstarter経由の比率が高かった。
  • 第一級アマチュア無線技士試験は不合格だったが、陸上特殊無線技士試験に合格して技適の例外申請などで役に立ちそうな期待をもった。
  • ROS 2の学習を始め、百人一首カルタ取りロボットシステムを試しに作ってみてROS 2への理解が深まった。

ROS 2 FoxyでRealSense D435の画像をsubscribeする(QoSの設定とPython、C++実装)

RealSenseをROS 2で利用するときに、少しつまずきがあったので、こちらに記録用にまとめておきます。

具体的には以下のrealsense-rosパッケージを使い、realsenseから得られるRGB画像及びDepth画像をImageトピックとしてpublishしたものを、自分でsubscribeするプログラムを 作成しましたが、ROS 2ではQoS(Quality of Service)の設定を適切にしないと画像がうまくsubscribeできないということを知りました。 重要なのはreliability settingをbest_effortに変更することなのですが、これを含めたrealsense画像をsubscribeするシンプルなプログラムをサンプルとして紹介します。

私の場合、普段はPythonを中心に使っていますが、画像などの重ためのデータを扱う際にノード間のデータのやり取りをプロセス内通信にして余計なメモリへのコピーをなくし、パフォーマンス低下を防げるcomponentの利用は現状C++が必須のようなのと、その他のパッケージでもサンプルがC++で記述されているものもありそうなので、念の為PythonC++の両方の実装を行いました。 なおパッケージ名や関数名などはできるだけ両言語で同じようになるようにしましたが、両言語のパッケージをそれぞれ試される場合は重複を避けるためパッケージ名を変更してください。

Componentに興味のある方はこちらを参考にしてください。 docs.ros.org

事前に以下のサイトの手順に従ってrealsense-rosパッケージをビルドしてください。

github.com

また今回つまずきのポイントになったQoSに関する説明はこちらにあります。やり取りすデータに欠損などが発生した際のデータの扱いに関するルールを事前に定義しておける機能と理解しましたが、今回はその中のReliabilityPolicyというものがRELIABLEになっていると、RealSenseの画像データがsubscribeされないという問題が発生したので設定を変更してBEST_EFFORTにしています。

docs.ros2.org

Python実装

パッケージを作成し、image_subscriber.pyを作成する。

$ cd ~/ros2_ws/src
$ ros2 pkg create --build-type ament_python realsense_subscriber
$ touch ~/ros2_ws/src/realsense_subscriber/src/realsense_subscriber/image_subscriber.py

image_subscriber.py

import rclpy
from rclpy.node import Node
from sensor_msgs.msg import Image
from cv_bridge import CvBridge
import cv2


class RealSenseSubscriber(Node):
    def __init__(self):
        super().__init__("realsense_subscriber_python")
        self.declare_parameter('image_topic_name', '/camera/color/image_raw')
        image_topic_name = self.get_parameter('image_topic_name').get_parameter_value().string_value

        video_qos = rclpy.qos.QoSProfile(depth=10)
        video_qos.reliability = rclpy.qos.QoSReliabilityPolicy.BEST_EFFORT

        self.sub_img = self.create_subscription(
            Image,
            image_topic_name,
            self.on_image_subscribed,
            video_qos
        )
        self.sub_img
    
    def on_image_subscribed(self, img):
        img_np = CvBridge().imgmsg_to_cv2(img)
        img_np = cv2.cvtColor(img_np, cv2.COLOR_BGR2RGB)
        cv2.imshow("Image", img_np)
        cv2.waitKey(1)


def main(args=None):
    try:
        rclpy.init(args=args)
        rclpy.spin(RealSenseSubscriber())
    
    except KeyboardInterrupt:
        pass

    rclpy.shutdown()
        

if __name__ == "__main__":
    main()

setup.pyを編集し、entry_pointsにrealsense_subscriberを追記する。

from setuptools import setup

package_name = 'realsense_subscriber'

setup(
    name=package_name,
    version='0.0.0',
    packages=[package_name],
    data_files=[
        ('share/ament_index/resource_index/packages',
            ['resource/' + package_name]),
        ('share/' + package_name, ['package.xml']),
    ],
    install_requires=['setuptools'],
    zip_safe=True,
    maintainer='root',
    maintainer_email='root@todo.todo',
    description='TODO: Package description',
    license='TODO: License declaration',
    tests_require=['pytest'],
    entry_points={
        'console_scripts': [
            "realsense_subscriber = realsense_subscriber.image_subscriber:main"
        ],
    },
)

package.xmlを編集し、rclpyとsensor_msgsを追記する。

<?xml version="1.0"?>
<?xml-model href="http://download.ros.org/schema/package_format3.xsd" schematypens="http://www.w3.org/2001/XMLSchema"?>
<package format="3">
  <name>realsense_subscriber</name>
  <version>0.0.0</version>
  <description>TODO: Package description</description>
  <maintainer email="root@todo.todo">root</maintainer>
  <license>TODO: License declaration</license>

  <test_depend>ament_copyright</test_depend>
  <test_depend>ament_flake8</test_depend>
  <test_depend>ament_pep257</test_depend>
  <test_depend>python3-pytest</test_depend>

  <export>
    <build_type>ament_python</build_type>
    <exec_depend>rclpy</exec_depend>
    <exec_depend>sensor_msgs</exec_depend>
  </export>
</package>

C++実装

パッケージを作成し、image_subscriber.cppを作成する。

$ cd ~/ros2_ws/src
$ ros2 pkg create --build-type ament_cmake realsense_subscriber
$ touch ~/ros2_ws/src/realsense_subscriber/src/image_subscriber.cpp

image_subscriber.cpp

#include <rclcpp/rclcpp.hpp>
#include <rclcpp/qos.hpp>
#include <sensor_msgs/msg/image.hpp>
#include <cv_bridge/cv_bridge.h>
#include <opencv2/opencv.hpp>


using sensor_msgs::msg::Image;

class RealSenseSubscriber: public rclcpp::Node
{
    public:
        RealSenseSubscriber(rclcpp::NodeOptions options = rclcpp::NodeOptions());
        ~RealSenseSubscriber(){}
    
    private:
        void onImageSubscribed(Image::SharedPtr img);
        rclcpp::Subscription<Image>::SharedPtr sub_img;
        std::string image_topic_name;
};

RealSenseSubscriber::RealSenseSubscriber(rclcpp::NodeOptions options) : Node("realsense_subscriber", options)
{
    image_topic_name = this->declare_parameter<std::string>("image_topic_name", "/camera/color/image_raw");
    rclcpp::QoS video_qos(10);
    video_qos.best_effort();
    video_qos.durability_volatile();
    sub_img = this->create_subscription<Image>(image_topic_name, video_qos, std::bind(&RealSenseSubscriber::onImageSubscribed, this, std::placeholders::_1));
}

void RealSenseSubscriber::onImageSubscribed(Image::SharedPtr img)
{
    auto cv_img = cv_bridge::toCvShare(img, img->encoding);
    cv::cvtColor(cv_img->image, cv_img->image, cv::COLOR_RGB2BGR);
    cv::imshow("Image", cv_img->image);
    cv::waitKey(1);
}

int main(int argc, char **argv)
{
    rclcpp::init(argc, argv);
    rclcpp::spin(std::make_shared<RealSenseSubscriber>());
    rclcpp::shutdown();
    return 0;
}

CMakeList.txtの記述を追加する。

cmake_minimum_required(VERSION 3.5)
project(realsense_subscriber)

# Default to C99
if(NOT CMAKE_C_STANDARD)
  set(CMAKE_C_STANDARD 99)
endif()

# Default to C++14
if(NOT CMAKE_CXX_STANDARD)
  set(CMAKE_CXX_STANDARD 14)
endif()

if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
  add_compile_options(-Wall -Wextra -Wpedantic)
endif()

set(ROS_LIBS rclcpp;cv_bridge;sensor_msgs)
find_package(ament_cmake REQUIRED)
foreach(lib IN LISTS ROS_LIBS)
  find_package(${lib} REQUIRED)
endforeach()

set(target image_subscriber)
add_executable(${target} src/${target}.cpp)
ament_target_dependencies(${target} rclcpp OpenCV cv_bridge sensor_msgs)
install(TARGETS ${target} DESTINATION lib/${PROJECT_NAME})

ament_package()

package.xmlに依存ライブラリとして、rclcppとlibopencv-devを追加する。

<?xml version="1.0"?>
<?xml-model href="http://download.ros.org/schema/package_format3.xsd" schematypens="http://www.w3.org/2001/XMLSchema"?>
<package format="3">
  <name>realsense_subscriber</name>
  <version>0.0.0</version>
  <description>TODO: Package description</description>
  <maintainer email="root@todo.todo">root</maintainer>
  <license>TODO: License declaration</license>

  <buildtool_depend>ament_cmake</buildtool_depend>
  <depend>rclcpp</depend>
  <depend>libopencv-dev</depend>

  <test_depend>ament_lint_auto</test_depend>
  <test_depend>ament_lint_common</test_depend>

  <export>
    <build_type>ament_cmake</build_type>
  </export>
</package>

PythonC++共通

こちらをコンパイルする。

$ colcon build --symlink-install --packages-select realsense_subscriber
$ source install/setup.bash

コンパイル後にまずrealsense-rosパッケージにあるrs_launch.pyを立ち上げます。

$ ros2 launch realsense2_camera rs_launch.py

その後、別のターミナルで作成したノードを実行します。

$ ros2 run realsense_subscriber image_subscriber

正しく実行できれば、ウィンドウが表示され、RealSenseから取得した画像が表示されます。(筆者環境ではDocker使用のため、表示にXephyrを使用していますが、マシンにROS 2を直接インストールしている場合はWindowが新規に表示されると思います)

f:id:hygradme:20210925234738p:plain

なおsubscribeするトピック名をパラメータで定義しているので、ノード起動時に引数を指定することでDepth画像を表示したり、別のWebカメラの画像などをpublishするノードを立ててそのtopicを指定すれば、その画像を表示することも可能です。 参考にdepth画像をsubscribeする際の引数の設定がこちらになります。

$ ros2 run realsense_subscriber image_subscriber  --ros-args -p image_topic_name:=/camera/depth/image_rect_raw

なおDepth画像のピクセル値はmmで計測された深度情報が入っているので、そのまま表示しても0~255の値の範囲しか通常の画像表示では行えないので、25.5mm程度の距離までしか適切に表示できません。ピクセル値をそれぞれ一定の値で割って(1000など)、0~255の範囲に計測範囲の距離が収まるようにするとわかりやすく画像を表示することができるようになります。

参考

  • ScamperとRaspberry Piで学ぶROS2プログラミング入門(オーム社)
  • ロボットプログラミングROS2の実装・実践(科学情報出版株式会社)

2020の振り返り

1月

RTX2080搭載のノートPC(PT515-51-A76Y8)を購入。外付けのNVMeSSD経由でUbuntuを起動する。

2月

xArmにステーキを焼いてもらう芸を仕込む。

ロボットに調理とかさせる展示等をすることを見越し、食品衛生責任者の講習を受け資格取得。

Universal Robots UR3を入手。Maker Faireへの出展きっかけとなる

3月

2d lidarをいくつか買う。

Spot Microに障害物回避歩行機能を実装。

Windows MRでUniversal Robotsを遠隔操作する。

Spot Microの頭にD435をつける。

4月

Jetson Xavier NXのModuleが届く。

5月

Jetson Xavier NX Developer Kitが届く。

6月

Realsense L515が届く。

Spot Microをブラウザで操作できる用にvue.js+Flaskでコントローラーを簡易Webアプリケーションとして実装。

驚異の1万円という低価格の二足歩行ロボットPLEN.Dを購入。

7月

KickstarterでバックしていたxArmのGripperがついに届く。

書籍「予算1万円でつくる二足歩行ロボット」の通りに二足歩行ロボットを作る。表面実装に初トライ。

8月

RealSense D455が届く。

Spot Microの改良版として自作四足歩行ロボットの足をカーボンで作る。

9月

dogotix-MIC-01Bを注文。

10月

MakerFaire出展、またその出展内容についてロボティクス勉強会で発表する。

docs.google.com

ソフトグリッパーに興味を持ち、3Dプリンターやシリコンゴムで制作する。

Pico Flexxを購入して試す。

Nintedo Switchのマリオカート ライブ ホームサーキット のレースにJetRacerを妨害キャラとして投入し吹っ飛ばす。

11月

M8 Lidarを入手する。

16Lineの3D Lidarがあきらめきれず、Velodyne Lidar VLP-16-HiResのジャンク品を修理して使用可能にする。

Velodyne Lidar VLP-16を入手する。

ROSでVLP-16で取った自前スキャンデータを使って3次元のSLAMを試す。

12月

myCobotを入手。

犬型ロボット dogotix-MIC-01Bが2か月以上の期間を経てついに届く。

youtu.be

youtu.be

youtu.be

その他

自作PCのグラフィックボードをGTX1080 からRTX3090に変更。

  • 暖かいが暖房器具としてはもう少しパワーが欲しい。計算資源としてはなかなか。

iPhone XからiPhone 12 Proに機種変更。

  • Velodyne Lidar繋いでiPhoneで点群を見るアプリを作って「Velodyne Lidar付スマホ」できないか少し調べたが、作るの大変そうなのでとりあえずは諦めた。

総括

  • 相変わらず色々ガジェットを買った。
  • 給付金を見越して Jetson Xavier NXを2種類購入したが、サイズはJetson Nanoとほぼ同じ割に性能がかなり高く今後もっと活用していきたいと思った。
  • CNCフライスでアルミとカーボン切削に挑戦。パラメータの調整がなかなか大変なことを知る。来年余裕あればCNCフライス購入したい。
  • MakerFaireに出展。展示の難しさを知ったがかなり面白かった。
  • 3D Lidar面白い。もう少し有効活用していきたい。
  • 購入した犬ロボは思ったより大きくパワーもあった。試すスペースがなかなかなく、扱いの難しさを感じたが大きな四足歩行ロボットの可能性を感じた。

2019の振り返り

1月

Looking Glass 到着

Cluster, VRChat初体験

  • VRChatのすごさに感動するが、すぐにVR酔いしてしまう。

例のグラボ買う

Windows MR購入

  • NorthStarMeetupで安いという情報を得たのでその場でポチる。

echo spot 導入

  • カメラがついていて、アプリから遠隔監視ができるので、3Dプリンタの印刷状態を遠隔監視する用途に多用するように。

2月

NorthStarMeetup参加で刺激を受け3Dプリンターを購入(CR-10S)

3月

HoloLens 2の実寸大モックを作成

  • 来日していたHoloLens 2の発表プレゼンをしていたMSの方からも賞賛の言葉を得る。

Project North Star作成

  • 3Dプリンターを使った初の大掛かりな製作作業。電源供給等でかなり手こずったが、なんとか動かせた。

Davinci Color Mini購入

  • 安くなってたのでカラープリンターに手を出してしまう。しかし狭い部屋に3Dプリンター二台はやりすぎだったことにあとで気づく(CNCフライスにしておけばよかったか)

4月

JetsonNano購入

xRTechで令和ことブルームと平成の手話で対応する文字を表示するアプリを展示

  • 平成最後のxR Tech Tokyo

Project North StarをT265で6DoF化

5月

Jetson Xavierを購入。現在はxArmの制御用に使用。

Oculus Questを購入

6月

Structure Coreを購入

7月

ZEDを購入

Xtion Pro Liveを購入

SR305を購入

8月

例のグラボで自作PCを作成、TensorflowをGPU使用で動かす

M5Stick VとCを購入

自作アイトラッキング装置を作成、xRTechで展示

第二種電気工事士に合格

Mediapipeのハンドトラッキングを試し性能の高さにびっくりする

JetRacerを作成

Coral Dev Boardを購入

9月

JetBotを作成するが壊れる

JetRacer作成記事を投稿

Spot MiniにインスパイアされたSpot Microというオープンソースの四足歩行ロボの作成に着手(色は青白)

10月

回路Cadに入門、作成した基盤を実際に海外に発注

四足歩行ロボ第二世代を作成(色は青黒)

11月

xArmをいじる。自動水やりを試しにやってみる。

ラズパイ4を購入

12月

AI Carのアドベントカレンダーに投稿。初アドベントカレンダー参加。

四足歩行ロボの骨折を機に足回りの改造、trot歩行に成功

総括

  • 相変わらず色々ガジェットを買った。
  • 3DプリンタでNorthStar, HoloLens 2のモック, JetRacer, JetBot, SpotMicroなどを作成し、3Dプリンターに大変お世話になった。
  • 第二種電気工事士試験に合格、また回路cadでサーボモーターの電源供給用の自作基盤を制作し総じて電気系の知識が結構増えた。
  • 去年まで継続して出していたアプリは今年作らなかったが、ハード制作系に初チャレンジ。色々難しさを知る。
  • xArmをいじり初のロボットハンド操作を体験。購入したセンサー類などと組み合わせ、今後様々なタスクにチャレンジしたい。
  • 四足歩行ロボットはやっと前進ができるようになった。今後深度カメラ or LidarとSLAM、深層学習等を組み合わせ自動で動くペットのような動きをさせたい。

JetRacerの自動走行の仕組み

JetRacerとは

Nvidiaが公開しているJetsonNanoを用いた自動走行カープロジェクトがあります。 github.com

前回の記事ではこちらの作り方を説明しました。

yhrscoding.hatenablog.com

作った当時はとりあえずサンプルのJupyter Notebookをただ実行するだけで動かしていただけでした。 今回こちらで自動走行ができる仕組みについて少し調べてみました。

基本的なラジコンの仕組み

まず自動走行する以前に、JetRacerの改造前のラジコンについて考えてみます。

車本体の位置を制御することに関して、ラジコンのコントローラー(プロポ)からステアリング(Steering、進行方向)・スロットル(Throttle、進行速度)の2つの信号が車側の受信機に届き、値に応じて車に取り付けられている2つのモーターの動きを制御しています。

f:id:hygradme:20191208031003j:plain:w300 f:id:hygradme:20191208031014j:plain:w300

ラジコンの自動走行

ラジコンを自動走行させるには基本的には人間に変わり、何らかのセンサーを使い状況を認識し、判断を行いステアリング・スロットルを適切に制御することで自動走行を実現します。

現在の標準のJetRacerではスロットルはあらかじめ指定した固定値を常に維持し、車正面に搭載のカメラから得られた情報を元にステアリングの値を変化させることでコースに沿った走行が実現しています。
ちなみにJetRacerでスロットルを状況に応じて自動で操作するということは可能で、方法はいくつか考えられます(前に参加した大会ではステアリング角に応じた速度の値を決めることでコーナーでは低速、直線では高速にする改良を行い、タイムを大幅に縮めることができました)

スロットル制御については以下の記事でもmasato-kaさんが触れられていますので、興味のある方は参考にしてください。 masato-ka.hatenablog.com

カメラ画像からからステアリング角を決める仕組み

上で述べたように、自動走行には人間の目に変わりコースの形状・車の位置などを認識し、それに応じた制御をする必要があります。
こちらは実際の自動車の自動運転では車両前方につけたカメラで白線認識を行ったり、奥行きまで認識できるセンサー(Lidar、ステレオカメラ、ミリ波レーダー)などを用い、車の空間における位置関係を把握しステアリング・スロットルの判断を行うということが行われていると思われます。白線認識を行ったり、LidarなどのセンサーをJetRacerに搭載することも可能ですが、システムが複雑になったり、重量・価格などがネックになってしまいます。

もっともシンプルに自動走行を実現するには通常のWebカメラのようなカメラ1つ(単眼カメラと呼ばれる)からステアリングを直接推定することです。 そんなこと可能なのかと思われるかもしれませんが、近年人気のディープラーニングを使って、「画像を入力->ステアリング角を出力」を行ったという論文をNvidiaが数年前に発表しました。

End to End Learning for Self-Driving Cars (2016) https://images.nvidia.com/content/tegra/automotive/images/2016/solutions/pdf/end-to-end-dl-using-px.pdf

詳細は省きますが、内容としては「道路を走行中の画像」と「その画像を撮影した時点での車のステアリング角」のペアのデータを大量に収集し、CNNというニューラルネットワークにその関係を学習させ、走行中にその時点での画像から直ちにその時点で取るべきステアリング角を推定し実際にステアリング角を変化させるということを行っています。

f:id:hygradme:20191208042121p:plain:w400
画像とステアリング角を使ってネットワークを学習し推論する

JetRacerの自動走行もこの論文と同様の原理で自動走行しています。 唯一異なる点は論文のように実際の走行時のステアリング角を学習のターゲットとせず、各画像に付与した車が向かうべきターゲットピクセル座標(x,y)を使って正しいステアリング角を擬似的に作り出している点です。

具体的に見ていきましょう。

上記のレポジトリ内の jetracer/notebooks/interactive_regression.ipynb を順に実行することでデータ収集、学習を行うことができますが、実行して画像を収集していく過程で、TASKという変数名で指定した名前から始まるフォルダが、jetracer/notebooks内に作成されています。 そのフォルダ内のapex内に画像ファイルが以下のようなファイル名で保存されていると思います。 99_109_xxxxxxxxxxxx.jpg

このファイル名先頭の2つの数字が画像を保存する際にクリックしたピクセル座標x,yに対応しています。

なおこのファイルの保存の処理はnotebooks/xy_dataset.py内のXYDatasetクラスのsave_entry関数で行われています。

class XYDataset(torch.utils.data.Dataset):
    (中略)
    def save_entry(self, category, image, x, y):
        category_dir = os.path.join(self.directory, category)
        if not os.path.exists(category_dir):
            subprocess.call(['mkdir', '-p', category_dir])
            
        filename = '%d_%d_%s.jpg' % (x, y, str(uuid.uuid1()))
        
        image_path = os.path.join(category_dir, filename)
        cv2.imwrite(image_path, image)
        self.refresh()

filenameを x_y_uuid.jpgという形式にしてフォルダに保存していることがわかります。 jupyter notebook上のカメラ画像の座標をクリックするごとにこの関数が呼ばれ、画像とターゲットのピクセル座標をファイル名内に入れて保持します。

ネットワークをトレーニングする際にこの画像とファイル名を読み込んで学習を行います。 なお実際にネットワークから出力するx,yの値はピクセル座標ではなく、画像サイズを元の値から割って得られる -1 から1の値です。

なおこの処理は上の画像を保存する処理が入っているnotebooks/xy_dataset.py内に記述されており、

class XYDataset(torch.utils.data.Dataset):
    (中略)
    def __getitem__(self, idx):
        ann = self.annotations[idx]
        image = cv2.imread(ann['image_path'], cv2.IMREAD_COLOR)
        image = PIL.Image.fromarray(image)
        width = image.width
        height = image.height
        if self.transform is not None:
            image = self.transform(image)
        
        x = 2.0 * (ann['x'] / width - 0.5) # -1 left, +1 right
        y = 2.0 * (ann['y'] / height - 0.5) # -1 top, +1 bottom
        
        if self.random_hflip and float(np.random.random(1)) > 0.5:
            image = torch.from_numpy(image.numpy()[..., ::-1].copy())
            x = -x
            
        return image, ann['category_index'], torch.Tensor([x, y])

の中の

 x = 2.0 * (ann['x'] / width - 0.5) # -1 left, +1 right
 y = 2.0 * (ann['y'] / height - 0.5) # -1 top, +1 bottom

でターゲットのピクセル座標ann['x']、ann['y'] がそれぞれ-1 ~ 1 に変換された x,yになっていることがわかるかと思います。

お気づきかもしれませんが、このターゲットのピクセル座標を予測しても、実際のステアリング角とは異なるので、通常は予測結果をそのままラジコンのステアリング角には使用できません。

JetRacerではこの予測したピクセル座標(-1~1に正規化)のxの値をそのままステアリング角とみなします。ステアリング角も-1~1の範囲に正規化されているため、例えばもっとも左にステアリング角を取るとき(steering = -1)はx=-1となり、もっとも右にステアリング角を取るとき(steering = 1)はx=1となります。

f:id:hygradme:20191208145724p:plain:w400
画像内のピクセル座標とステアリング角の対応付け

このxの値をそのままステアリング角とみなすというのはやや極端かと思いますが、カメラが車体進行方向に対してまっすぐに取り付けられているとするならば、画像の真ん中を境にマークの位置に応じて左右のステアリング値に直接変換するというのはもっとも簡単で手軽な方法の1つだと思います。

実際の画像を見ていただくとわかりますが、真ん中から左側をクリックするときはステアリングは左、右側をクリックするときはステアリングは右になっていそうなことがわかると思います。 f:id:hygradme:20191208152754j:plain:w350 f:id:hygradme:20191208152822j:plain:w350

ピクセル座標の値をステアリング角とみなして、実際にラジコンにステアリング指示を行う処理はjetracer/notebooks/road_following.ipynbの最後のセルに記述されています。

import numpy as np

STEERING_GAIN = 0.75
STEERING_BIAS = 0.00

car.throttle = 0.15

while True:
    image = camera.read()
    image = preprocess(image).half()
    output = model_trt(image).detach().cpu().numpy().flatten()
    x = float(output[0])
    car.steering = x * STEERING_GAIN + STEERING_BIAS

の中の

    x = float(output[0])
    car.steering = x * STEERING_GAIN + STEERING_BIAS

の部分が、学習済みモデルに画像を入れて出力されたピクセル座標のxの値を取り出し、ステアリング角(car.steering)に変換している部分です。 実際の処理ではSTEERING_GAINという値をかけることで、ステアリングの値を多少小さめに指示する操作を行っています。こちらの値は使用するラジコンの最大ステアリング角等を見て調整することでより性能の良い自動走行を実現できると思われます。 またSTEERING_BIASはステアリング値に一定値を常に加える処理ですので、カメラが車体本体から左右にずれていたり、重力の軸周りに回転してしまっている場合あるいはラジコンのステアリング機構自体の左右へのズレを補正するためのもので、そういうズレがなければ通常STEERING_BIAS=0.00で問題ないと思います。

ピクセル座標のyの値は標準のJetRacerでは使用しておりませんが、こちらは画像をクリックしてアノテーションを行うときのことを考えると、奥行き方向に関連する情報を持っている可能性があると思いますので(コース上をクリックするとするならば視界の先をどれだけ見渡せるかで速度を出せるかどうかの目安にできる?)、速度(throttle)に関する制御に使えるのではないかと考えられます。

以上で簡単ですが、JetRacerの自動走行の仕組みについて自分の理解の範囲で解説させていただきました。 勘違いや、JetRacerの自動走行の仕組みについてより詳しい背景等ご存知でしたら是非ご指摘願います。

JetRacerの組み立て

NVIDIAからJetson Nanoを使ったAI自動走行車JetBotに続く、JetRacerが発表されました。 github.com

こちら市販のラジコンカーにJetson Nanoとカメラを取り付け、カメラの画像からその時点でどちらに転舵すべきかを機械学習で学習させ、それを元にラジコンカーを自動走行させるというものです。

参考動画:

JetBotと異なり、ラジコンカーならではのスピード感ある自動走行を楽しむことができます。(もっと大きい場所でより複雑な形状のコース作って試してみたいが、なかなか東京付近で場所がない... どなたか場所の提供 or いい場所を教えてください...)

ラジコンカーに馴染みない方向けに簡単に補足しますと、今回取り扱うラジコンカーは二つのモーターから構成されており、

  • サーボーモータ

    • 角度を細かく調整できるモータ
    • 前輪の回転軸をサーボーモーターによってずらし、ラジコンカーの進行方向を左右に変更する。
  • DCモーター

    • 角度は細かく調整できないが高速で回転させられる
    • 前後輪を回転させ、ラジコンカーを前進・後進させる。

という役割を担っています。 デフォルトのJetRacerではDCモーターは常に一定速度で動かし、カメラから得られた情報からサーボーモータをどの程度動かすかを決め、サーボーモーターにそれを伝え、コースアウトしないように走行することで自動走行を実現しています。

今回こちらをパーツの仕入れから、実際に動かすまで一通りやってみたので、その過程をまとめました。なお車体はlatraxというものとtamiyaの2通りありますが、日本で入手しやすいtamiyaのシャーシのものを作成しました。

作成に関する注意事項

作成部品の入手難易度はやや高めかと思います。その理由は、

  1. パーツ合計がやや高額になる。(3Dプリントパーツ無しでも5万ぐらい?)
  2. 3Dプリントするパーツが3点ある。(ボード類取り付け用のベース板、シャーシへの穴あけ用の補助板、カメラモジュール取り付け台)
  3. まあまあ分厚いシャーシに穴あけするボール盤などの道具を利用する必要がある。(私はDMM.make AKIBAを利用しました。固定を接着剤とか別の方法でやるなら不要かもしれません)

一方で上の3つがクリアできれば組み立ては配線系が少しややこしいことを除けば、ラジコンの知識はほぼなくてもできます。

ただし注意事項として、ラジコン本体・プロポ(ラジコンのコントローラー)の調達はよく調べたほうがいいです。 私の場合、横着をして、シャーシ・プロポ・バッテリー・バッテリー充電用ACアダプタセットのものを買ってしまいました。(これと単三電池4本を買うだけで、ラジコンとして遊ぶことができる)

しかしプロポが2つの信号(進行・後退と左右転舵)しか送れない2chのものだったので、公式で推奨している3chのものとは異なり、プロポから自動走行or手動走行を切り替えることができませんでした。このままだと学習させる時、面倒だったので結局3chプロポを買って受信機を取り替えました。 ただし、マルチプレクサを経由せずモータードライバからの信号or受信機からの信号の配線を手で繋ぎ変えて手動・自動を変えることはできますので、予算厳しい方は私と同じく2chの車体・プロポセットを購入すればマルチプレクサなしで最低限手動自動共にJetRacerを動かすことはできます。

なお3chプロポを最初から使うと言う方は、シャーシ、プロポ、受信機、サーボモータ、ラジコン用バッテリー、ラジコン用ACアダプタを個別で調達すると安く上がると思います。

パーツ一覧(購入するもの)

パーツ 数量 価格 URL 備考
Jetson Nano 1 12300円 http://akizukidenshi.com/catalog/g/gM-14393/
MicroSDカード 1 1000~3000円 Amazonなど 私は手元にあったSanDiskのExtremeProに32GBにしました
USBバッテリー 1 2000円 https://www.amazon.co.jp/gp/product/B07L4WRCWJ/ref=ppx_yo_dt_b_asin_title_o00_s00?ie=UTF8&psc=1 最低でも5Vで2A供給可能なもの。2.4Aぐらい出せると良い?
USB A -> 外径5.5mm 内径2.1mm円形ジャックケーブル 1 500円 https://www.amazon.co.jp/gp/product/B071GTKDZJ/ref=ppx_yo_dt_b_asin_title_o04_s00?ie=UTF8&psc=1
ラジコン本体+プロポ+バッテリー 1 17000円ぐらい https://www.amazon.co.jp/gp/product/B01LY45KQF/ref=ppx_yo_dt_b_asin_title_o00_s01?ie=UTF8&psc=1 プロポが2ch
プロポ+受信機 1 11000円 https://www.amazon.co.jp/dp/B01CTK6UDY/ref=cm_sw_r_tw_dp_U_x_HPoEDbQZAEVZQ 3chプロポと受信機のセット
PWM サーボーモータードライバー 1 610円 https://www.amazon.co.jp/gp/product/B01D1D0CX2/ref=ppx_yo_dt_b_asin_title_o01_s00?ie=UTF8&psc=1
RC サーボマルチプレクサ 1 1231円 Pololu 4-チャンネル RC サーボ マルチプレクサー (組立て済み) :Pololu-2806:朱雀技研工房ストア - 通販 - Yahoo!ショッピング 操作をプロポ or Jetsonからのどちらかを切り替えるためのもの
3ピンジャンパーケーブルセット 1 899円 https://www.amazon.co.jp/gp/product/B07BMRY7BH/ref=ppx_yo_dt_b_asin_title_o05_s00?ie=UTF8&psc=1 Jetson、モータードライバー、マルチプレクサなどの配線に
メス-メス ジャンパーケーブル 1 680円 https://www.amazon.co.jp/dp/B01MTAD3GA/ref=cm_sw_r_tw_dp_U_x_7OoEDb1J1WX0Q
USB WiFiアダプタ 1 798円 https://www.yodobashi.com/product/100000001003540389/ WiFi通信用。M.2無線カードより安くて楽。
ラズパイカメラモジュールv2 1 4580円 https://www.amazon.co.jp/gp/product/B01EW2KDTE/ref=ppx_yo_dt_b_asin_title_o09_s00?ie=UTF8&psc=1 公式のものでないと認識しない可能性あり
IMX219 160度カメラ 1 2499円 https://www.amazon.co.jp/gp/product/B07HMXJ9Y1/ref=ppx_yo_dt_b_asin_title_o03_s00?ie=UTF8&psc=1 ラズパイカメラモジュールのカメラ部分をこちらに差し替え
M3x45mm スタンドオフ 1 1014円 https://www.amazon.co.jp/gp/product/B07NPKW74J/ref=ppx_yo_dt_b_asin_title_o00_s01?ie=UTF8&th=1 制御系のボード類をシャーシに取り付けるためのもの
M3x8mm ネジ 8個 617円 https://www.amazon.co.jp/dp/B01HZCYV3W/ref=cm_sw_r_tw_dp_U_x_CMoEDb6NMM54P スタンドオフを取り付ける
M2x8mm ネジ 18個 560円 https://www.amazon.co.jp/dp/B01HD3P98Y/ref=cm_sw_r_tw_dp_U_x_AOoEDbZNC63JA ボード類の取り付け用
バッテリー保持用ストラップ 1 999円 https://www.amazon.co.jp/gp/product/B0744DNC1K/ref=ppx_yo_dt_b_asin_title_o02_s01?ie=UTF8&psc=1 Jetson用バッテリーを車体に取り付けるためのもの

3Dプリントが必要なパーツ

  1. ボード類を取り付けるベース板
  2. シャーシに穴あけするときに穴あけ位置を特定するための補助板
  3. カメラモジュールを取り付ける台

組み立て

基本的にはこちらに従って組み立てます。 jetracer/hardware_setup.md at master · NVIDIA-AI-IOT/jetracer · GitHub

フェーズ1 (シャーシの穴あけ、モーターカバーの除去、スタンドオフの取り付け、受信機の取り付け)

  1. シャーシに穴あけするため、上で3dプリントした2番の補助板をシャーシの裏側(地面側)にテープなどで固定します。この際3dプリントした板の突起がある部分がシャーシ裏側のネジ部分と一致するはずです。(一部ネジを取り外さないとはみ出るので適宜ネジを外す)

  2. 補助板に空いている3mmの穴4箇所をターゲットとして3mmドリルで穴あけ、シャーシまで貫通させます。

  3. (オプション) ネジの皿部分がシャーシからはみ出ないように上の4つの穴に対して、皿穴加工をします。私は適当に6,7mmぐらいのドリルで数ミリ穴あけしました。(公式のStep2 Countersink)

  4. シャーシ表側のDCモーター付近のカバーを外します。(大きなモーターの方) これがあると、あとでベース台を固定するときに邪魔になるかと思います。

    f:id:hygradme:20190909191502j:plain:w400
    モーターカバーを外したシャーシ

  5. 先ほど開けた4つの穴を通じて、シャーシにM3x8mmのネジでスタンドオフを取り付けます。

    f:id:hygradme:20190909190352j:plain:w400
    シャーシ背面図

  6. (オプション)受信機を3chプロポ対応のものに取り替えるor取り付けます。 車体・プロポセットで買った方はシャーシに2ch用の受信機が付いていますが、こちらを取り外します。粘着力強めの両面テープで付いているだけですので頑張って外してください。

f:id:hygradme:20190911215331j:plain:w400
2chの受信機

f:id:hygradme:20190911215716j:plain:w400
2ch用の受信機を外した様子

2ch用の受信機を外した後、3ch用の受信機を取り付けます。

f:id:hygradme:20190911215946j:plain:w400
3ch用の受信機を取り付けた様子

フェーズ2(3Dプリントしたベース板に各種基盤を取り付け)

  1. マルチプレクサをM2x8mmのネジ2つでベース板に取り付けします。2箇所だけで少し不安なので、ネジのない側に何か台なり設置したい気はします...

  2. PWMサーボモータードライバーをM2x8mmのネジ4つでベース板に取り付けします。

  3. Jetson Nanoを M2x8mmのネジ4つでベース板に取り付けします。

  4. カメラモジュールのカメラ部分をIMX219のカメラに取り替え、カメラモジュールを3Dプリントしたカメラ台にM2x8mmネジ4つで取り付けます。カメラモジュールのコネクターをJetsonNanoに取り付けます。さらにカメラ台をベース板にM2x8mmネジ4つで取り付けます。最後にカメラモジュールとJetsonNanoを接続します。ラズパイ公式のカメラモジュール仕様の場合、ケーブルが半ねじりの状態になってしまいます。

  5. USB WiFiアダプタをJetson Nanoに取り付けます。

フェーズ3 結線

  1. Jetson Nanoとモータードライバの接続

これによって、JetsonNano側から来る前進・後進・右転舵・左転舵の指示をモーター側に伝えることができるようになります。1ピンのジャンパーケーブル1つと、3ピンのジャンパーケーブル1つを取り付けします。

  • Jetson側

    • 1ピンジャンパーはGNDに接続します。(図のJetsonNano上のGPIOピン上、右列側の黒線)
    • 3ピンジャンパーは3V3, 3番, 5番 に接続します。(図のJetsonNano上のGPIOピン上、左列側の黒線(3V3)・赤線(3番)・白線(5番) )
  • サーボモータードライバー側

    • 1ピンジャンパーはGNDに接続します(図の下側のモータードライバ上の一番上の黒線)
    • 3ピンジャンパーはVCC(黒線), SDA(赤線),SCL(白線)に接続します。

なお、JetsonNanoとモータードライバ間の3ピンの結線は以下のような対応になります。

  • 3V3 がVCCに(黒)
  • SDAが3番に(赤)
  • SCLが5番に(白)

f:id:hygradme:20190909192051j:plain:w400
JetsonNanoとモータードライバーの接続

  1. サーボモータドライバとマルチプレクサの接続

マルチプレクサとはA,Bという二つの信号のどちらかをCから出力するという時にDという別の信号によって電気的にA・Bを切り替えることができる装置です。今回、A・Bの信号はJetsonNano・プロポに対応し、マルチプレクサを使うことで、自動走行or手動走行を切り替えすることができるようになります。

具体的には図のようにマルチプレクサを上から見た図で、

  • M1~M4 (上の例でいう信号Aたち。Masterの略か)
  • S1~S4 (上の例でいう信号Bたち。Slaveの略か)
  • Out1~Out4(上の例でいう信号C。ここをモータに繋ぐことでM1~M4 or S1~S4のどちらかの信号がモーター側に伝わります。 )
  • SEL (上の例でいう信号D。M1~M4 or S1~S4の切り替えを担当)

となります。

f:id:hygradme:20190909202624j:plain:w400
マルチプレクサ

二箇所取り付けます。 この時向きはGND通しが繋がるように注意してください。

  • サーボモータドライバ上の0番(黒赤黄上でJetsonと接続したピンから最も近い方)をマルチプレクサのS1に接続。(転舵信号を担当)
  • サーボモータドライバ上の1番(黒赤黄上でJetsonと接続したピンから最も近い方)をマルチプレクサのS2に接続。(前進・後進の信号を担当)

f:id:hygradme:20190912005415j:plain:w400
モータードライバとマルチプレクサの接続

  1. 受信機とマルチプレクサの接続

マルチプレクサのM1,M2,SELを受信機の1番・2番・3番と接続します。この接続によりプロポからの操作信号(転舵・前進後進・手動自動運転の切り替え)をマルチプレクサに送ることができるようになります。マルチプレクサのGND側のピンと受信機の右側のピンが繋がるように注意してください(図では黒線)

f:id:hygradme:20190912010130j:plain:w400
マルチプレクサと受信機の接続の様子

  1. マルチプレクサとサーボモータ、ESCの接続

上の手順でマルチプレクサにJetsonNano(自動運転)とプロポ(手動運転)の信号が入るようになり、この二つはSELの信号によって切り替えられるようになっています。さらにこの信号をサーボモータとESCに送り実際にモーターを動かせるようにします。 なおESC(Electric Speed Controller)は前進後進を行うDCモーターに電流を供給する装置です。入力として駆動信号とバッテリーからの電流の二つをもち、駆動信号を増幅してモーターに駆動に十分な電流を供給します。放熱用のファンがついているものです。 以下の二つの接続を行います。 なおいずれも黒線がマルチプレクサのGNDに接続されるように気をつけてください。
- サーボモータ(受信機の下に付いている)とマルチプレクサのOut1の接続(図で左側の指あたりから出ているESCの赤白黒の線)
- ESCとマルチプレクサのOut2の接続(図で右側の指あたりから出ているサーボモータの赤白黒の線)

f:id:hygradme:20190912010710j:plain:w400
マルチプレクサとサーボモータ、ESCの接続の様子

フェーズ4 最終組み立て

JetsonNanoなどが取り付けてあるベース板をM3x8mmのネジ4つを使ってスタンドオフに取り付けます。これによってベース板がシャーシに固定されます。

f:id:hygradme:20190912011909j:plain:w400
JetRacer完成

以上でハードウェア部分は完成です。気が向いたら、別の記事でソフトウェア部分について書こうと思います。

Coin Roller Master Privacy Policy

Privacy Policy

hygradme built the Coin Roller Master app as a Free app. This SERVICE is provided by hygradme at no cost and is intended for use as is.

This page is used to inform visitors regarding my policies with the collection, use, and disclosure of Personal Information if anyone decided to use my Service.

If you choose to use my Service, then you agree to the collection and use of information in relation to this policy. The Personal Information that I collect is used for providing and improving the Service. I will not use or share your information with anyone except as described in this Privacy Policy.

The terms used in this Privacy Policy have the same meanings as in our Terms and Conditions, which is accessible at Coin Roller Master unless otherwise defined in this Privacy Policy.

Information Collection and Use

For a better experience, while using our Service, I may require you to provide us with certain personally identifiable information, including but not limited to None. The information that I request will be retained on your device and is not collected by me in any way.

The app does use third party services that may collect information used to identify you.

Link to privacy policy of third party service providers used by the app

Log Data

I want to inform you that whenever you use my Service, in a case of an error in the app I collect data and information (through third party products) on your phone called Log Data. This Log Data may include information such as your device Internet Protocol (“IP”) address, device name, operating system version, the configuration of the app when utilizing my Service, the time and date of your use of the Service, and other statistics.

Cookies

Cookies are files with a small amount of data that are commonly used as anonymous unique identifiers. These are sent to your browser from the websites that you visit and are stored on your device's internal memory.

This Service does not use these “cookies” explicitly. However, the app may use third party code and libraries that use “cookies” to collect information and improve their services. You have the option to either accept or refuse these cookies and know when a cookie is being sent to your device. If you choose to refuse our cookies, you may not be able to use some portions of this Service.

Service Providers

I may employ third-party companies and individuals due to the following reasons:

  • To facilitate our Service;
  • To provide the Service on our behalf;
  • To perform Service-related services; or
  • To assist us in analyzing how our Service is used.

I want to inform users of this Service that these third parties have access to your Personal Information. The reason is to perform the tasks assigned to them on our behalf. However, they are obligated not to disclose or use the information for any other purpose.

Security

I value your trust in providing us your Personal Information, thus we are striving to use commercially acceptable means of protecting it. But remember that no method of transmission over the internet, or method of electronic storage is 100% secure and reliable, and I cannot guarantee its absolute security.

Links to Other Sites

This Service may contain links to other sites. If you click on a third-party link, you will be directed to that site. Note that these external sites are not operated by me. Therefore, I strongly advise you to review the Privacy Policy of these websites. I have no control over and assume no responsibility for the content, privacy policies, or practices of any third-party sites or services.

Children’s Privacy

These Services do not address anyone under the age of 13. I do not knowingly collect personally identifiable information from children under 13. In the case I discover that a child under 13 has provided me with personal information, I immediately delete this from our servers. If you are a parent or guardian and you are aware that your child has provided us with personal information, please contact me so that I will be able to do necessary actions.

Changes to This Privacy Policy

I may update our Privacy Policy from time to time. Thus, you are advised to review this page periodically for any changes. I will notify you of any changes by posting the new Privacy Policy on this page. These changes are effective immediately after they are posted on this page.

Contact Us

If you have any questions or suggestions about my Privacy Policy, do not hesitate to contact me.

This privacy policy page was created at privacypolicytemplate.net and modified/generated by App Privacy Policy Generator