アプリとサービスのすすめ

アプリやIT系のサービスを中心に書いていきます。たまに副業やビジネス関係の情報なども気ままにつづります

お国とサラリーマン(この話はノンフィクションです。実在の組織団体個人とは関係大ありです)

*この話はノンフィクションです。実在の組織団体個人とは関係大ありです


今は今、とあるところにルールをせっせと作るお国とサラリーマンたちがいました。
お国さんは天国か地獄に一番近い、老ぼれたちがはびこる組織のようなものです。サラリーマンは働き盛りの年齢の人たちです。
お国さんはルールを作り、サラリーマンたちはそのルールに従って頑張って働いています。

サラリーマンたちはお国さんから、子供の頃からしっかり教育を受け、大学を卒業してから働いて幸せになるように教育でしっかり洗脳されてきました。

とあるサラリーマンは大学を卒業した後、証券マンとして海外で働き、海外の開発部長まで登りつめ、ネンシュウ1200万円まで稼ぎエリートコースを突き進みました。ストレスもハンパないながらも、時間や労力を会社に、税金をお国さんに搾取されながら、ベンツや時計など高級なものを買って仕事に粉骨砕身の日々でした。

40代には働きすぎがたたり、健康を崩しながら少し落ち着きながら働き定年を迎えました。稼いだお金は老後の楽しみに取っておこうとせっせっと貯めてきましたが、働きすぎで肝臓を痛め長期の入院をするハメになりました。そのため、稼いだお金は病気の治療のため、どんどん使わなければならなくなりました。

お国さんは死に損ないの集まりながらも、サラリーマンに若い時せっせと働いてもらって、税金を納めてもらい、アコギな生活を送って計画通りに進みハッピーな思いをしながらあの世に旅立っていきました。

サラリーマンはボロ雑巾のように搾取され捨てられたあと、死ぬ前にこんなに働かなきゃよかったと口にして死んでいきましたとさ。

PCDをPCLで前処理して3DPointCloudの機械学習データセットを作るまで

前回はLidarからPCD(Point Cloud Data)までを作成した。

今回はそのPCDPCL(Point Cloud Library)で前処理して、Meshlabという 3d PointCloud用アノテーションソフトでアノテーションしてから、機械学習のデータセット(例えばpointnetとかsemantic 3dとかのDNN)として使えるまでの手順をまとめた。

長いのでやったこと淡々とまとめるだけの備忘録。

全体の流れ
f:id:trafalbad:20220401101016p:plain

全体のネットワーク図
f:id:trafalbad:20220401102929p:plain


目次
1.PCLでPCDの前処理
2.PCDをPLYで保存
3.PLYをMeshに変換
4.MeshLabでMeshをアノテーション
5.機械学習データセットとして読み込む




1. PCLでPCDの前処理

今回は都合上「table_scene_lms400.pcd」という参考用のPCDgithubから拝借して使う。

PCLにはいろんな処理があるけど、とりあえず参考になりそうなものを順番に適用して前処理してみた。

上から順番に処理しているので、だんだん良くなってる。

table_scene_lms400.pcd
f:id:trafalbad:20220401101931p:plain

filter
f:id:trafalbad:20220401101945p:plain

planar_segmenter
f:id:trafalbad:20220401102005p:plain


downsampler
f:id:trafalbad:20220401102024p:plain


clusterer
f:id:trafalbad:20220401102038p:plain



下の順番で前処理したGIF


1 load「table_scene_lms400.pcd」=>
2 filter =>
3 planar_segmenter =>
4 downsampler =>
5 clusterer

f:id:trafalbad:20220401102110g:plain


PCLはまだまだたくさんあるけど、Voxel化してdownsampleする方法はかなり使われているようだった。

参考:Downsampling a PointCloud using a VoxelGrid filter



2. PCDをPLYで保存

PCLで前処理したPCDはMeshLabで読み込むためにPLY形式で保存する必要がある。
よく変換に使うのは
・PCL
・open3d
の2つがある。



PCLの変換コード(pcd2ply.cpp)

#include <pcl/io/pcd_io.h>
#include <pcl/io/ply_io.h>
#include <pcl/console/print.h>
#include <pcl/console/parse.h>
#include <pcl/console/time.h>

using namespace pcl;
using namespace pcl::io;
using namespace pcl::console;

void printHelp (int, char **argv)
{
  print_error ("Syntax is: %s [-format 0|1] [-use_camera 0|1] input.pcd output.ply\n", argv[0]);
}

bool loadCloud (const std::string &filename, pcl::PCLPointCloud2 &cloud)
{
  TicToc tt;
  print_highlight ("Loading "); print_value ("%s ", filename.c_str ());

  tt.tic ();
  if (loadPCDFile (filename, cloud) < 0)
    return (false);
  print_info ("[done, "); print_value ("%g", tt.toc ()); print_info (" ms : "); print_value ("%d", cloud.width * cloud.height); print_info (" points]\n");
  print_info ("Available dimensions: "); print_value ("%s\n", pcl::getFieldsList (cloud).c_str ());

  return (true);
}

void saveCloud (const std::string &filename, const pcl::PCLPointCloud2 &cloud, bool binary, bool use_camera)
{
  TicToc tt;
  tt.tic ();

  print_highlight ("Saving "); print_value ("%s ", filename.c_str ());
  
  pcl::PLYWriter writer;
  writer.write (filename, cloud, Eigen::Vector4f::Zero (), Eigen::Quaternionf::Identity (), binary, use_camera);
  
  print_info ("[done, "); print_value ("%g", tt.toc ()); print_info (" ms : "); print_value ("%d", cloud.width * cloud.height); print_info (" points]\n");
}

/* ---[ */
int main (int argc, char** argv)
{
  print_info ("Convert a PCD file to PLY format. For more information, use: %s -h\n", argv[0]);

  if (argc < 3)
  {
    printHelp (argc, argv);
    return (-1);
  }

  // Parse the command line arguments for .pcd and .ply files
  std::vector<int> pcd_file_indices = parse_file_extension_argument (argc, argv, ".pcd");
  std::vector<int> ply_file_indices = parse_file_extension_argument (argc, argv, ".ply");
  if (pcd_file_indices.size () != 1 || ply_file_indices.size () != 1)
  {
    print_error ("Need one input PCD file and one output PLY file.\n");
    return (-1);
  }

  // Command line parsing
  bool format = true;
  bool use_camera = true;
  parse_argument (argc, argv, "-format", format);
  parse_argument (argc, argv, "-use_camera", use_camera);
  print_info ("PLY output format: "); print_value ("%s, ", (format ? "binary" : "ascii"));
  print_value ("%s\n", (use_camera ? "using camera" : "no camera"));

  // Load the first file
  pcl::PCLPointCloud2 cloud;
  if (!loadCloud (argv[pcd_file_indices[0]], cloud)) 
    return (-1);

  // Convert to PLY and save
  saveCloud (argv[ply_file_indices[0]], cloud, format, use_camera);

  return (0);
}


3. PLYをMeshに変換

PLYで保存したらMesh化する必要がある。
詳しくは知らないけど、ポリゴン化してxyz座標以外に先頭に「face elements」をつける必要があるらしい。

ShapeNet用のデータセット「PASCAL3D+」のoffファイルの中身

$ cat 06.off
3 20365 20364 20357
3 20355 20349 20357
3 20355 20349 20353
3 20357 20349 20355
3 20357 20349 20363
3 20363 20349 20357
3 20349 20348 20353
3 20348 20349 20346
3 20344 20342 20346

先頭の3が「face elements」

とにかくPLYをMesh化する(face elementsをつける)必要があって、主にopen3dtrimeshを使った。

ply2mesh.py

import os, sys
import numpy as np
import open3d as o3d
import trimesh

def o3d_create_mesh(pcd_path):
    pcd = o3d.io.read_point_cloud(pcd_path)
    pcd.estimate_normals()
    # estimate radius for rolling ball
    distances = pcd.compute_nearest_neighbor_distance()
    avg_dist = np.mean(distances)
    radius = 1.5 * avg_dist   

    mesh = o3d.geometry.TriangleMesh.create_from_point_cloud_ball_pivoting(
            pcd,
            o3d.utility.DoubleVector([radius, radius * 2]))

    # create the triangular mesh with the vertices and faces from open3d
    tri_mesh = trimesh.Trimesh(np.asarray(mesh.vertices), np.asarray(mesh.triangles),
                            vertex_normals=np.asarray(mesh.vertex_normals))
    trimesh.convex.is_convex(tri_mesh)
    return tri_mesh

def main(pcd_path, ply_path):
    polygon_mesh = o3d_create_mesh(pcd_path)
    polygon_mesh_ = trimesh.exchange.ply.export_ply(polygon_mesh, encoding='ascii')
    output_file = open(ply_path, "wb+")
    output_file.write(polygon_mesh_)
    output_file.close()

if __name__=='__main__':
    if len(sys.argv) < 2:
        print('python3 ~.py <pcd_path> <ply_path>')
    else:
        pcd_path, ply_path=str(sys.argv[1]), str(sys.argv[2])
        main(pcd_path, ply_path)


4. MeshLabでMeshをアノテーション

MeshLabはCADと同じような3Dデータの加工ソフト。
読み込めるファイル形式は多いが、PCDは無理なのでPLYで読み込む。
PLYでMesh化した「table_scene_lms400.ply」を読み込んでみる

f:id:trafalbad:20220401102521p:plain

終わったらOFFファイル形式でExport。

MeshLabの機能はPCLと同じことができるようなので、次記事でまとめたいと思う。

ちなみにShapeNet用のデータセットPASCAL3D+

のOFFファイル(人工データ)を参考にMeshLabで読み込んでみた。
f:id:trafalbad:20220401102602p:plain



5. 機械学習データセットとして読み込む

吐き出したOFFファイルをnumpyとkerasでloadしてみようと思う。
ここからPointNetとかSemantic3DみたいなPoint Cloud用DNNのデータセットとして使える。



numpy

def read_off(filename):
    num_select = 1024
    f = open(filename)
    f.readline()  # ignore the 'OFF' at the first line
    f.readline()  # ignore the second line
    All_points = []
    selected_points = []
    while True:
        new_line = f.readline()
        x = new_line.split(' ')
        if x[0] != '3':
            A = np.array(x[0:3], dtype='float32')
            All_points.append(A)
        else:
            break
    # if the numbers of points are less than 2000, extent the point set
    if len(All_points) < (num_select + 3):
        return None
    # take and shuffle points
    index = np.random.choice(len(All_points), num_select, replace=False)
    for i in range(len(index)):
        selected_points.append(All_points[index[i]])
    return selected_points  # return N*3 array


Keras

import os, sys
import trimesh
import numpy as np

def point_plot(points):
    fig = plt.figure(figsize=(5, 5))
    ax = fig.add_subplot(111, projection="3d")
    ax.scatter(points[:, 0], points[:, 1], points[:, 2])
    ax.set_axis_off()
    plt.show()

def main(path, plot=False):
    mesh = trimesh.load(path)
    mesh.show()
    points = mesh.sample(2048)
    if plot:
        point_plot(points)

if __name__=='__main__':
    path = sys.argv[1]
    main(path)


trimeshでの表示結果
f:id:trafalbad:20220401103728p:plain

出来は別として、

lidar=>PCD=>PCL=>meshlab=>機械学習データセット

の順で一から作るシステムパイプライン的なものが無事作れた。

参考サイト

Kinect等の色距離センサを用いた 点群処理と3D物体認識
Point Cloud Library (PCL) Class Reference
Point cloud classification with PointNet
How do I convert a 3D point cloud (.ply) into a mesh (with faces and vertices)?
trimesh references
How to determine output path directory for trimesh.exchange.ply.export_ply?

Parallels Desktopにホスト側からのログイン設定

仮想環境Parallels Desktopにホスト側からログインするための設定。

Virtual-Boxが使えなくなったので、代わりの仮想環境として使ってるので、その備忘録。

環境:ホストOS


環境ゲストOS

f:id:trafalbad:20220326183224j:plain
目次
1. セットアップ
2. sshでログイン
3. マウントフォルダの確認



1. 環境設定

$ sudo apt install openssh-server
$ sudo apt install net-tools

# ifconfigでIPアドレスを確認
$ ifconfig
>>>
# enp0s5: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
#         inet 10.211.55.8  netmask 255.255.255.0  broadcast 10.211.55.255
#         inet6 fdb2:2c26:f4e4:0:cf09:c8c5:3fdb:11cb  prefixlen 64  scopeid 0x0<global>
#       〜〜

enp0s5の部分が必要なネットワーク情報。


2. sshでログイン


ユーザーネームをcheck

$ echo $USER
>>>
# parallels


sshでログイン

# ssh [usr-name]@ip-address
$ ssh parallels@10.211.55.8
>>>
# Welcome to Ubuntu 20.04.4 LTS (GNU/Linux 5.13.0-35-generic x86_64)

#  * Documentation:  https://help.ubuntu.com
#  * Management:     https://landscape.canonical.com
#  * Support:        https://ubuntu.com/advantage

# 0 updates can be applied immediately.

# Your Hardware Enablement Stack (HWE) is supported until April 2025.
# Cannot open display "default display"


ログインできた。
めんどいのでMakefileでコマンドを簡略化

mkdir -p /Users/usr/downloads/mount
cd /Users/usr/downloads/mount
sudo vi Makefile

# makeでログイン
make in
>>># Your Hardware Enablement Stack (HWE) is supported until April 2025.
# Cannot open display "default display"

Makefile

in:
	ssh parallels@10.211.55.8 


3. マウントフォルダの確認

マントフォルダは
/media/psf/Home

# 試しにコピー
$ sudo cp d.txt /media/psf/Home/Downloads/

がホストに繋がってる。


参考

ParallelsのUbuntu Linuxにssh接続する

Depth画像から点群データ(Point Cloud Data:pcd)を作る方法

本来は、lidarで取得するpcd(点群データ)をdepth画像から作成する手順。
理由はlidarからだと綺麗なpcdが撮影できない感じがしたから。

なのでdepth画像とrgb画像を合わせてpcdを作ることにした。

目次
1. lidarから直接pcdを作成する
2. depth画像とrgb 画像からpcdを作成する
3. PCLで両方のpcdを比較してみる


1. lidarから直接pcdを作成する

pcd(点群データ)のtopic名は/camera/depth/color/points
まず、lidarで表示。
pcdをlidarに表示させるのは普通にlaunchする時に、filters:=pointcloudを引数にとる。

# 方法1:引数にfilters:=pointcloudをつける
$ roslaunch realsense2_camera rs_camera.launch filters:=pointcloud

# 方法2: lidarの点群データ表示用 launchファイル
$ roslaunch realsense2_camera demo_pointcloud.launch
# 画像で確認しながらbagファイルを作成
$ rosrun image_view image_view image:=/camera/color/image_raw


# rosbagに点群データを保存 ($ rosbag record -O [保存ファイル名] [topic名1, 2])
$ rosbag record -O /home/parallels/place/pcd.bag /camera/depth/color/points
### [ INFO] [1647787126.439056879]: Subscribing to /camera/depth/color/points
### [ INFO] [1647787126.441952051]: Recording to '/home/parallels/place/pcd.bag'

# rosbagの情報
$ rosbag info pcd.bag

### 〜
### types:       sensor_msgs/PointCloud2 [1158d486dd51d683ce2f1be655c3c181]
### topics:      /camera/depth/color/points   255 msgs    : sensor_msgs/PointCloud2

bagからpcdを作成

# rosrun pcl_ros bag_to_pcd <input_file.bag> <topic> <output_directory>
$ roscore
$ rosrun pcl_ros bag_to_pcd pcd.bag /camera/depth/color/points /home/parallels/place/pcds

### /color/points with the following fields: x y z rgb
### Data saved to /home/parallels/place/pcds/1647787135.031567812.pcd

pcdファイルが保存先フォルダに山のように保存される。

$ du -k 1647787135.031567812.pcd
### 1556	1647787135.031567812.pcd


1ファイル1MBを超えるのでなかなか重い


2. depth画像とrgb 画像からpcdを作成する

depth画像とrgb画像の作成は過去記事参照。

trafalbad.hatenadiary.jp


depth画像とrgb画像をペアで一つのpcdファイルを作成する。
ライブラリはopen3Dというのを使う。


セットアップ

$ pip3 install open3d
$ catkin_create_pkg pcd_create roscpp rospy std_msgs cv_bridge image_transport message_generation
$ sudo vi CMakeLists.txt
$ cd ~/catkin_ws && catkin_make

CMakeLists.txt(以下を追記)

find_package(PCL 1.8 REQUIRED)
include_directories(
  include
  ${catkin_INCLUDE_DIRS}
  ${PCL_INCLUDE_DIRS}
)
add_executable(vccs
  src/vccs.cpp
)
target_link_libraries(vccs
  ${catkin_LIBRARIES}
  ${PCL_LIBRARIES}
)


depth画像とrgb画像からpcdの作成

$ roslaunch realsense2_camera rs_camera.launch filters:=pointcloud

# 画像で確認しながらbagファイルを作成
$ rosrun image_view image_view image:=/camera/color/image_raw
# depthとRGBのtopic名を指定
$ rosbag record -O /home/parallels/place/depth.bag /camera/color/image_raw /camera/depth/image_rect_raw

### [ INFO] [1647786652.575961747]: Subscribing to /camera/color/image_raw
### [ INFO] [1647786652.582693138]: Subscribing to /camera/depth/image_rect_raw
### [ INFO] [1647786652.586763715]: Recording to '/home/parallels/place/depth.bag'.
# bagファイルをdepth画像とrgb画像に変換
$ python3 rosbag2depth.py

# depthとrgbからpcdの作成
$ python3 depth2pcd.py


結果(RGB/depth)
f:id:trafalbad:20220321010351p:plain

作成したPCD
f:id:trafalbad:20220321010431p:plain


depth2pcd.py(pcd作成スクリプト)

import open3d as o3d
import matplotlib.pyplot as plt
import os

def main(out_dir, plot=False):
    color_raw = o3d.io.read_image(os.path.join(out_dir, "rgb/frame000001.jpg"))
    depth_raw = o3d.io.read_image(os.path.join(out_dir, "depth/frame000000.png"))
    rgbd_image = o3d.geometry.RGBDImage.create_from_color_and_depth(
        color_raw, depth_raw)
    if plot:
        plt.subplot(1, 2, 1)
        plt.title('Redwood grayscale image')
        plt.imshow(rgbd_image.color)
        plt.subplot(1, 2, 2)
        plt.title('Redwood depth image')
        plt.imshow(rgbd_image.depth)
        plt.show()

    pcd = o3d.geometry.PointCloud.create_from_rgbd_image(
        rgbd_image,
        o3d.camera.PinholeCameraIntrinsic(
            o3d.camera.PinholeCameraIntrinsicParameters.PrimeSenseDefault))
    # Flip it, otherwise the pointcloud will be upside down
    pcd.transform([[1, 0, 0, 0], [0, -1, 0, 0], [0, 0, -1, 0], [0, 0, 0, 1]])
    o3d.visualization.draw_geometries([pcd])
    o3d.io.write_point_cloud(os.path.join(out_dir, "open3d.pcd"), pcd)

if __name__ == "__main__":
    out_dir = '/home/parallels/place'
    main(out_dir, plot=True)


3. PCLで両方のpcdを比較してみる

pclチュートリアルにあるクラスタリング(SupervoxelClustering)でpcdの出来を確認してみる。

pcl::SupervoxelClusteringを使用するとpointcloudを複数のsupervoxel clustersに分割できる

XYZ座標系の情報

                                             
       
       
       

コード説明
PointXYZ
    
点群3D位置情報のみ取得
  
PointXYZRGB
    
点群3Dの位置情報にRGBの色をつけて取得
PointXYZRGBA
    
点群3D位置情報にRGBAの色情報をつけて取得(Aは透明度Alpha)
  
PointXYZI
    
点群3D位置情報に輝度の色情報をつけて取得
  


PCLで確認してみる


Lidarから取得したPCD

$ rosrun pcd_create vccs /home/usr/pcd/1647790156.662197590.pcd

f:id:trafalbad:20220321012107g:plain


depthとrgbから作成したPCD

$ rosrun pcd_create vccs /home/usr/open3d.pcd

f:id:trafalbad:20220321012125g:plain


直接Lidarから取得した方出来がいいようだ。
実例でもdepthから作ってるの聞いたことないし、直接撮影した方が綺麗にできそう。PCDのテクはまだ不明なことが多い。



 確認用スクリプト(src/vccs.cpp)

#include <pcl/console/parse.h>
#include <pcl/point_cloud.h>
#include <pcl/point_types.h>
#include <pcl/io/pcd_io.h>
#include <pcl/visualization/pcl_visualizer.h>
#include <pcl/segmentation/supervoxel_clustering.h>

//VTK include needed for drawing graph lines
#include <vtkPolyLine.h>

// Types
typedef pcl::PointXYZRGBA PointT;
typedef pcl::PointCloud<PointT> PointCloudT;
typedef pcl::PointNormal PointNT;
typedef pcl::PointCloud<PointNT> PointNCloudT;
typedef pcl::PointXYZL PointLT;
typedef pcl::PointCloud<PointLT> PointLCloudT;

void addSupervoxelConnectionsToViewer (PointT &supervoxel_center,
                                       PointCloudT &adjacent_supervoxel_centers,
                                       std::string supervoxel_name,
                                       pcl::visualization::PCLVisualizer::Ptr & viewer);


int
main (int argc, char ** argv)
{
  if (argc < 2)
  {
    pcl::console::print_error ("Syntax is: %s <pcd-file> \n "
                                "--NT Dsables the single cloud transform \n"
                                "-v <voxel resolution>\n-s <seed resolution>\n"
                                "-c <color weight> \n-z <spatial weight> \n"
                                "-n <normal_weight>\n", argv[0]);
    return (1);
  }

 //////////// pcd input as process 1 ////////////
  PointCloudT::Ptr cloud (new PointCloudT);
  pcl::console::print_highlight ("Loading point cloud...\n");
  if (pcl::io::loadPCDFile<PointT> (argv[1], *cloud))
  {
    pcl::console::print_error ("Error loading cloud file!\n");
    return (1);
  }


  bool disable_transform = pcl::console::find_switch (argc, argv, "--NT");

  float voxel_resolution = 0.008f;
  bool voxel_res_specified = pcl::console::find_switch (argc, argv, "-v");
  if (voxel_res_specified)
    pcl::console::parse (argc, argv, "-v", voxel_resolution);

  float seed_resolution = 0.1f;
  bool seed_res_specified = pcl::console::find_switch (argc, argv, "-s");
  if (seed_res_specified)
    pcl::console::parse (argc, argv, "-s", seed_resolution);

  float color_importance = 0.2f;
  if (pcl::console::find_switch (argc, argv, "-c"))
    pcl::console::parse (argc, argv, "-c", color_importance);

  float spatial_importance = 0.4f;
  if (pcl::console::find_switch (argc, argv, "-z"))
    pcl::console::parse (argc, argv, "-z", spatial_importance);

  float normal_importance = 1.0f;
  if (pcl::console::find_switch (argc, argv, "-n"))
    pcl::console::parse (argc, argv, "-n", normal_importance);

  //////////////////////////////  //////////////////////////////
  ////// This is how to use supervoxels
  //////////////////////////////  //////////////////////////////
  //////////// create instance as process 2////////////
  pcl::SupervoxelClustering<PointT> super (voxel_resolution, seed_resolution);
  if (disable_transform)
//////////// set param as process 3 ////////////
    super.setUseSingleCameraTransform (false);
  super.setInputCloud (cloud);
  super.setColorImportance (color_importance);
  super.setSpatialImportance (spatial_importance);
  super.setNormalImportance (normal_importance);
  //////////// pcd output as process 1 ////////////
  std::map <std::uint32_t, pcl::Supervoxel<PointT>::Ptr > supervoxel_clusters;

  pcl::console::print_highlight ("Extracting supervoxels!\n");

//////////// execute as process 4 ////////////
  super.extract (supervoxel_clusters);
  pcl::console::print_info ("Found %d supervoxels\n", supervoxel_clusters.size ());

  pcl::visualization::PCLVisualizer::Ptr viewer (new pcl::visualization::PCLVisualizer ("3D Viewer"));
  viewer->setBackgroundColor (0, 0, 0);

  PointCloudT::Ptr voxel_centroid_cloud = super.getVoxelCentroidCloud ();
  viewer->addPointCloud (voxel_centroid_cloud, "voxel centroids");
  viewer->setPointCloudRenderingProperties (pcl::visualization::PCL_VISUALIZER_POINT_SIZE,2.0, "voxel centroids");
  viewer->setPointCloudRenderingProperties (pcl::visualization::PCL_VISUALIZER_OPACITY,0.95, "voxel centroids");

  PointLCloudT::Ptr labeled_voxel_cloud = super.getLabeledVoxelCloud ();
  viewer->addPointCloud (labeled_voxel_cloud, "labeled voxels");
  viewer->setPointCloudRenderingProperties (pcl::visualization::PCL_VISUALIZER_OPACITY,0.8, "labeled voxels");

  PointNCloudT::Ptr sv_normal_cloud = super.makeSupervoxelNormalCloud (supervoxel_clusters);
  //We have this disabled so graph is easy to see, uncomment to see supervoxel normals
  //viewer->addPointCloudNormals<PointNormal> (sv_normal_cloud,1,0.05f, "supervoxel_normals");

  pcl::console::print_highlight ("Getting supervoxel adjacency\n");
  std::multimap<std::uint32_t, std::uint32_t> supervoxel_adjacency;
  super.getSupervoxelAdjacency (supervoxel_adjacency);
  //To make a graph of the supervoxel adjacency, we need to iterate through the supervoxel adjacency multimap
  for (auto label_itr = supervoxel_adjacency.cbegin (); label_itr != supervoxel_adjacency.cend (); )
  {
    //First get the label
    std::uint32_t supervoxel_label = label_itr->first;
    //Now get the supervoxel corresponding to the label
    pcl::Supervoxel<PointT>::Ptr supervoxel = supervoxel_clusters.at (supervoxel_label);

    //Now we need to iterate through the adjacent supervoxels and make a point cloud of them
    PointCloudT adjacent_supervoxel_centers;
    for (auto adjacent_itr = supervoxel_adjacency.equal_range (supervoxel_label).first; adjacent_itr!=supervoxel_adjacency.equal_range (supervoxel_label).second; ++adjacent_itr)
    {
      pcl::Supervoxel<PointT>::Ptr neighbor_supervoxel = supervoxel_clusters.at (adjacent_itr->second);
      adjacent_supervoxel_centers.push_back (neighbor_supervoxel->centroid_);
    }
    //Now we make a name for this polygon
    std::stringstream ss;
    ss << "supervoxel_" << supervoxel_label;
    //This function is shown below, but is beyond the scope of this tutorial - basically it just generates a "star" polygon mesh from the points given
    addSupervoxelConnectionsToViewer (supervoxel->centroid_, adjacent_supervoxel_centers, ss.str (), viewer);
    //Move iterator forward to next label
    label_itr = supervoxel_adjacency.upper_bound (supervoxel_label);
  }

  while (!viewer->wasStopped ())
  {
    viewer->spinOnce (100);
  }
  return (0);
}

void
addSupervoxelConnectionsToViewer (PointT &supervoxel_center,
                                  PointCloudT &adjacent_supervoxel_centers,
                                  std::string supervoxel_name,
                                  pcl::visualization::PCLVisualizer::Ptr & viewer)
{
  vtkSmartPointer<vtkPoints> points = vtkSmartPointer<vtkPoints>::New ();
  vtkSmartPointer<vtkCellArray> cells = vtkSmartPointer<vtkCellArray>::New ();
  vtkSmartPointer<vtkPolyLine> polyLine = vtkSmartPointer<vtkPolyLine>::New ();

  //Iterate through all adjacent points, and add a center point to adjacent point pair
  for (auto adjacent_itr = adjacent_supervoxel_centers.begin (); adjacent_itr != adjacent_supervoxel_centers.end (); ++adjacent_itr)
  {
    points->InsertNextPoint (supervoxel_center.data);
    points->InsertNextPoint (adjacent_itr->data);
  }
  // Create a polydata to store everything in
  vtkSmartPointer<vtkPolyData> polyData = vtkSmartPointer<vtkPolyData>::New ();
  // Add the points to the dataset
  polyData->SetPoints (points);
  polyLine->GetPointIds  ()->SetNumberOfIds(points->GetNumberOfPoints ());
  for(unsigned int i = 0; i < points->GetNumberOfPoints (); i++)
    polyLine->GetPointIds ()->SetId (i,i);
  cells->InsertNextCell (polyLine);
  // Add the lines to the dataset
  polyData->SetLines (cells);
  viewer->addModelFromPolyData (polyData,supervoxel_name);
}


参考サイト

Open3D
Open3D+ROS+Pythonで3次元画像処理を楽々プロトタイピング
ROSでPCLのサンプルを動かす - supervoxel clustering

Jetbotをgazeboで動かすまで その2

前回の続きでgazeboで動かすのに必要なところをまとめてくだけ。
シュミレーション環境にはturtlebot3を使った。


目次
1. gazeboの環境設定
2. JetbotのXacroファイルをgazeboに対応させる
3. gazeboのコントロール用のdiff_drive_controllerのパラメーターの設定
4. Jetbotをシュミレーションで動かす




1. gazeboの環境設定

まずgazeboで動かせるようにする。
今回はturtlebot3の環境を使うので、簡単なinstallから。

# gazeboコントローラー
$ sudo apt-get install ros-noetic-ros-control
$ sudo apt-get install ros-noetic-ros-controllers

# turtlebot3 プラグインのinstall
$ git clone https://github.com/ROBOTIS-GIT/turtlebot3_msgs.git
$ git clone https://github.com/ROBOTIS-GIT/turtlebot3.git
$ git clone https://github.com/ROBOTIS-GIT/turtlebot3_simulations.git
$ git clone https://github.com/ROBOTIS-GIT/turtlebot3_gazebo_plugin git

# ~/.bashrcに記入 
export TURTLEBOT3_MODEL=burger
# catkin_makeして再起動
cd ~/catkin_ws && cakin_make
source ~/.bashrc
sudo reboot

2. JetbotのXacroファイルをgazeboに対応させる

wheel(車輪)にtransmissionタグの記述

<transmission name="${prefix}_trans" type="SimpleTransmission">
    <type>transmission_interface/SimpleTransmission</type>
    <actuator name="${prefix}_motor">
      <mechanicalReduction>1</mechanicalReduction>
    </actuator>
    <joint name="${prefix}_joint">
      <hardwareInterface>hardware_interface/VelocityJointInterface</hardwareInterface>
    </joint>
</transmission>

重要な点は1つで指定したjointをどの指令値で操作するかだけ。jointの指令のタイプには以下の3種類があります。

・hardware_interface/EffortJointInterface : 力指令で動く
・hardware_interface/VelocityJointInterface : 速度指令で動く
・hardware_interface/PositionJointInterface : 位置指令で動く

基本的にはEffortJointInterfaceが一番gazeboと相性が良いけど、今回使う差動2輪用のドライバでは右輪と左輪の両方ともにVelocityJointInterfaceを使う必要がある。


ros_controllの追加などのgazebo pluginの追加

<!-- ros_control plugin -->
<gazebo>
  <plugin name="gazebo_ros_control" filename="libgazebo_ros_control.so">
    <robotNamespace>/jetbot</robotNamespace>
    <robotSimType>gazebo_ros_control/DefaultRobotHWSim</robotSimType>
    <legacyModeNS>true</legacyModeNS>
  </plugin>
</gazebo>


下にはOdometry(移動距離を求めるプラグイン)なので必須じゃないです。

<!-- Odometry plugin -->
<gazebo>
  <plugin name="ground_truth" filename="libgazebo_ros_p3d.so">
    <frameName>world</frameName>
    <bodyName>base_link</bodyName>
    <topicName>/tracker</topicName>
    <updateRate>10.0</updateRate>
  </plugin>
</gazebo>


link関連のgazebo要素の追加
ここでは摩擦係数とgazebo上での色を設定。

<gazebo reference="base_link">
    <mu1>0.2</mu1>
    <mu2>0.2</mu2>
    <material>Gazebo/white</material>
</gazebo>

3. gazeboのコントロール用のdiff_drive_controllerのパラメーターの設定

これはyamlファイルに書いて、起動用のlaunchファイルで読み込む。

jetbot_controller.yaml

joint_state_controller:
  type: "joint_state_controller/JointStateController"
  publish_rate: 50

diff_drive_controller:
type        : "diff_drive_controller/DiffDriveController"
left_wheel  : 'left_wheel_joint'
right_wheel : 'right_wheel_joint'
publish_rate: 50.0
pose_covariance_diagonal : [0.001, 0.001, 1000000.0, 1000000.0, 1000000.0, 1000.0]
twist_covariance_diagonal: [0.001, 0.001, 1000000.0, 1000000.0, 1000000.0, 1000.0]

# Velocity commands timeout [s], default 0.5
cmd_vel_timeout: 1.0
# Wheel separation and diameter. These are both optional.
# diff_drive_controller will attempt to read either one or both from the
# URDF if not specified as a parameter
wheel_separation : 0.43515
wheel_radius : 0.193125

# Wheel separation and radius multipliers
wheel_separation_multiplier: 1.0 # default: 1.0
wheel_radius_multiplier    : 1.0 # default: 1.0 
# tf
# enable_odom_tf: true
base_frame_id: base_link
odom_frame_id: odom

# limits
linear:
  x:
    has_velocity_limits    : true
    max_velocity           :  0.825 # m/s
    min_velocity           : -0.825 # m/s
    has_acceleration_limits: true
    max_acceleration       :  1.0 # m/s^2
    min_acceleration       : -1.0 # m/s^2
angular:
  z:
    has_velocity_limits    : true
    max_velocity           :  3.14 # rad/s
    min_velocity           : -3.14 # rad/s
    has_acceleration_limits: true
    max_acceleration       :  1.0  # rad/s^2
    min_acceleration       : -1.0 # rad/s^2
left_wheel : 'left_wheel_joint' 
right_wheel : 'right_wheel_joint'

ではそれぞれ左右輪に相当するjointを指定

wheel_separation : 0.20 # タイヤの半径
wheel_radius : 0.05   # 2つのタイヤの間の距離を設定


逆向きに走らせたいとき
URDF(Xacro)ファイルの車輪(wheel)のjointのaxisタグのy軸マイナスにすればOK。
yamlファイルでは指定できない。

<!-- 対象部分 -->
<axis xyz="0 -1 0"/>

<!-- Joint本体 -->
<joint name="${prefix}_joint" type="continuous">
      <parent link="${parent}"/>
      <child link="${prefix}_link"/>
      <origin xyz="${xyz}" rpy="0 0 0"/>
      <axis xyz="0 -1 0"/>
</joint>

起動用launchファイル


jetbot.launch

<?xml version="1.0" encoding="UTF-8"?>
<launch>
  <arg name="model" default="$(find jetbot_description)/urdf/jetbot.xacro" />
  <param name="robot_description" command="$(find xacro)/xacro $(arg model) --inorder"/>
  <include file="$(find gazebo_ros)/launch/empty_world.launch">
    <arg name="world_name" value="$(find jetbot_description)/worlds/turtlebot3_world.world"/>
    <arg name="paused" value="false"/>
    <arg name="use_sim_time" value="true"/>
    <arg name="gui" value="true"/>
    <arg name="headless" value="false"/>
    <arg name="debug" value="false"/>
  </include>

  <!-- Load the URDF into the ROS Parameter Server -->
  <param name="robot_description" command="$(find xacro)/xacro $(arg model) --inorder"/>
  <!-- Run a python script to the send a service call to gazebo_ros to spawn a URDF robot -->
  <node name="spawn_urdf" pkg="gazebo_ros" type="spawn_model" args="-param robot_description -urdf -model jetbot" />

  <!-- ros_control motoman launch file -->
  <include file="$(find jetbot_control)/launch/jetbot_control.launch"/>
</launch>

jetbot_control.launch

<launch>

  <!-- Load joint controller configurations from YAML file to parameter server -->
  <rosparam command="load" file="$(find jetbot_control)/config/jetbot_controller.yaml" ns="/jetbot" />

  <!-- load the controllers -->
  <node name="controller_spawner" pkg="controller_manager" type="spawner" respawn="false" output="screen" ns="/jetbot" 
      args="joint_state_controller diff_drive_controller" />

  <!-- running of robot_state_publisher -->
  <node name="robot_state_publisher" pkg="robot_state_publisher" type="robot_state_publisher" />
</launch>

4. Jetbotをシュミレーションで動かす

動かしてみる。

$ roslaunch jetbot_simulation jetbot.launch

$ roslaunch jetbot_simulation wheel_simulation.launch

wheel_simulation.launch

<launch>
  <arg name="cmd_vel" default="/jetbot/diff_drive_controller/cmd_vel"/>
  <arg name="name" default="jetbot_drive"/>
  <param name="cmd_vel_topic_name" value="$(arg cmd_vel)"/>
  <node name="jetbot_drive" pkg="jetbot_simulation" type="jetbot_drive" required="true" output="screen"/>
</launch>


f:id:trafalbad:20220314234430g:plain
動かせた。



参考

ROS講座39 車輪ロボットを作る3(diff_drive_controllerで動かす)
【ROS】 Turtlebot3 のシミュレーション環境を構築してみた
Gazebo + ROS で自分だけのロボットをつくる 7. 実際にlaunchしてみる
Gazebo Components
ROSの勉強 第31弾:移動ロボットの作成(4)

Jetbotをgazeboで動かすまで その1


Jetbotという車輪型ロボットのRvizのみ対応のコードをgazeboに対応させて、シミュレーション上で動かせるようにした。


Rviz用のJetbotファイルをgazeboで動かすまでをざっとまとめた備忘録その1

目次
1. gazeboに対応させる手順
2. 今回の概要
3. gazeboプラグインについて
4. gazeboに対応させたJetbotのXacroファイル



1. gazeboに対応させる手順

gazeboでオリジナルロボットを動かすには次の手順が必要。

1. ロボットのSTL/COLLADAファイルの作成
ロボットの見た目を記述したファイル。
STLとかはCADで簡単に作れる。けどめんどいのでgithubのソースから拾ってくるのが入門としては一番。

2. ロボットのURDF(Xacro)ファイルの作成
ロボットをURDF(Xacro)で記述。


3. URDFにHardwareInterfaceを記述
とは動かすときに命令を与えるようのタグでJointタグの下に書く。

<transmission name="${prefix}_trans" type="SimpleTransmission">
     <type>transmission_interface/SimpleTransmission</type>
     <actuator name="${prefix}_motor">
       <mechanicalReduction>1</mechanicalReduction>
     </actuator>
     <joint name="${prefix}_joint">
       <hardwareInterface>hardware_interface/VelocityJointInterface</hardwareInterface>
     </joint>
</transmission>


4. URDFにgazeboプラグインの追加
これは次の記事でまとめて記述しますが、ROSとgazeboを連携したりするための記述。

5 その他
必要に応じてセンサープラグインや、ros_controllerのyamlファイルを記述。
これも詳細は次記事で。

6. launchファイルで起動
launchファイルは複数の処理を同時に実行できるので、gazeboは基本いろいろな機能が必要なのでlaunchファイルで起動する。


ROSとgazeboの連携の全体像
f:id:trafalbad:20220314202221p:plain
gazeboエレメントを追加することでrvizだけじゃなく、gazeboでもURDF(xacro)が読めるようになり、rvizとgazeboの両方を使えるようになり多彩なシミュレーションが可能に

2. 今回の概要

今回はRvizでは表示できるけど、gazeboでは動かせないJetbotを動かせるようにします。
ちなみにjetbotは車輪型ロボット。

・まずURDFにロボットの各軸の動軸、自由軸の設定を記述。

・そこにros_controlというgazeboプラグインを使ってROSからの指令でシミュレーター上で動くようにする。

・2つの動輪がROSからTwistを受け取って動くようにする。


そのためには以下の図のように.urdfに記述を加えてHardwareInterfaceを記述することとLaunchでControllerManagerの設定をすることが必要。


f:id:trafalbad:20220314201424p:plain


3. gazeboプラグイン

gazenoはもともとはROSとは別のソフト。

ROSからgazeboを動かそうとしたらROSとgazeboを接続する橋渡し的なプラグインの記述が必要でこれが「gazeboプラグイン」。


今回使うROSコントロールもgazeboプラグインの1つで、ROSトピックをsubscribeしてgazeboシミュレーター中の関節を動かします。またhokuyoセンサー(Lidar)やcameraなどのプラグインも接続させてシュミレーションを実行します。

逆にgazenoシミュレーター中の関節の角度をROSトピックにpublishするプラグインもあります。これ以外にも多くのgazeboプラグインが多く公開されてます。


hokuyoセンサーのプラグインの例(hokuyo.urdf.xacro)

<?xml version="1.0"?>
<robot xmlns:xacro="http://www.ros.org/wiki/xacro" name="hokuyo">
<material name="yellow" >
    <color rgba="1.0 1.0 0.0784313725490196 1.0" />
</material>	
<xacro:macro name="hokuyo_laser" params="xyz rpy parent length radius">
    
    <joint name="hokuyo_laser_joint" type="fixed">
        <axis xyz="0 0 1" />
        <origin xyz="${xyz}" rpy="${rpy}"/>
        <parent link="${parent}"/>
        <child link="hokuyo_laser_link"/>
    </joint>
     
    <link name="hokuyo_laser_link">
        <collision>
            <origin xyz="0 0 0" rpy="0 0 0"/>
            <geometry>
		    <cylinder length="${length}" radius="${radius}"/>
	    </geometry>
        </collision>
        <visual>
            <origin xyz="0 0 0" rpy="0 0 0"/>
            <geometry>
                <cylinder length="${length}" radius="${radius}"/>
	    </geometry>
	    <material name="yellow"/>
        </visual>
        <inertial>
            <mass value="1e-5" />
            <origin xyz="0 0 0" rpy="0 0 0"/>
            <inertia ixx="1e-6" ixy="0" ixz="0" iyy="1e-6" iyz="0" izz="1e-6" />
        </inertial>
    </link>
    <gazebo reference="hokuyo_laser_link">
        <material>Gazebo/Yellow</material>
    </gazebo>
    <gazebo reference="hokuyo_laser_link">
        <gravity>true</gravity>
        〜略〜
                <frameName>hokuyo_laser_link</frameName>
            </plugin>      
        </sensor>
    </gazebo>
 
</xacro:macro>
</robot>


Jetbotの取り付けるURDFファイルの記述

<xacro:include filename="$(find jetson_description)/meshes/sensor/hokuyo.urdf.xacro"/>

<!-- Laser link and plugin -->
<xacro:hokuyo_laser xyz="0 0 ${0.04 + 0.02}" 
	  rpy="0 0 0"
	  parent="base_link" length="0.01" radius="0.015">
</xacro:hokuyo_laser>

4. gazeboに対応させたJetbotのXacroファイル

Rvizで表示させるとこんな感じ。

f:id:trafalbad:20220314202919p:plain

これをgazeboで動かせるようにしたXacroファイル

<?xml version="1.0"?>
<robot name="jetbot" xmlns:xacro="http://www.ros.org/wiki/xacro">

  <!-- Included URDF/XACRO Files -->
  <xacro:include filename="$(find jetson_description)/urdf/common.xacro" />
  <!-- Import all Gazebo-customization elements, including Gazebo colors -->
  <xacro:include filename="$(find jetson_description)/urdf/jetbot.gazebo" />
  <xacro:include filename="$(find jetson_description)/meshes/sensor/hokuyo.urdf.xacro"/>

  <!-- chassis dimensions -->
  <xacro:property name="PI" value="3.1415926535897931"/>
  <xacro:property name="mass" value="1.0" /> <!-- mass body-->
  <xacro:property name="width" value="0.05" /> <!-- Link 1 -->
  <xacro:property name="height" value="0.03" /> <!-- Link 1 -->
  <xacro:property name="length" value="0.10" /> <!-- Link 1 -->

  <!-- wheel dimensions -->
  <xacro:property name="wheel_mass" value="0.1" /> <!-- mass wheel -->
  <xacro:property name="wheel_radius" value="0.03" /> <!-- radius -->
  <xacro:property name="wheel_width" value="0.01" /> <!-- wheel --> 

  <xacro:property name="camera_link" value="0.01" /> <!-- Size of square 'camera' box -->
  <xacro:property name="axel_offset" value="0.05" /> <!-- Space btw top of beam and the each joint -->

  <!-- Default wheel joint-->>
  <xacro:macro name="wheel_macro" params="parent prefix xyz color">
    <joint name="${prefix}_joint" type="continuous">
      <parent link="${parent}"/>
      <child link="${prefix}_link"/>
      <origin xyz="${xyz}" rpy="0 0 0"/>
      <axis xyz="0 -1 0"/>
    </joint>
    <transmission name="${prefix}_trans" type="SimpleTransmission">
      <type>transmission_interface/SimpleTransmission</type>
      <actuator name="${prefix}_motor">
        <mechanicalReduction>1</mechanicalReduction>
      </actuator>
      <joint name="${prefix}_joint">
        <hardwareInterface>hardware_interface/VelocityJointInterface</hardwareInterface>
      </joint>
    </transmission>
    <link name="${prefix}_link">
    <visual>
      <origin xyz="0 0 0" rpy="0 0 1.57079632679"/>
      <geometry>
	      <mesh filename="package://jetson_description/meshes/DAE/JetBot-v3-Wheel222.dae"/>
      </geometry>
      <material name="${color}"/>
    </visual>

    <collision>
      <origin xyz="0 0 0" rpy="0 0 1.57079632679"/>
      <geometry>
	      <mesh filename="package://jetson_description/meshes/DAE/JetBot-v3-Wheel222.dae"/>
      </geometry>
    </collision>

      <inertial>
        <origin xyz="0 0 0" rpy="0 1.5707 1.5707"/>
        <mass value="${wheel_mass}" />
        <xacro:cylinder_inertia  m="${wheel_mass}"
          r="${wheel_radius}" h="${wheel_width}" />
      </inertial>
    </link>
    <gazebo reference="${prefix}_link">
      <mu1>0.2</mu1>
      <mu2>0.2</mu2>
      <kp value="50000" />
      <kd value="10" />
      <material>Gazebo/white</material>
    </gazebo>
  </xacro:macro>

  <!-- Used for fixing robot to Gazebo 'base_link' -->
  <!-- base_footprint Definition -->
  <link name="odom"/>

  <joint name="odom_footprint" type="fixed">
    <parent link="odom" />
    <child  link="base_footprint" />
  </joint>

  <link name="base_footprint" />

  <joint name="footprint_base" type="fixed">
    <parent link="base_footprint" />
    <child  link="base_link" />
    <origin xyz="0.0 0.0 0.030"/>
  </joint>

  <!-- Base Link -->
  <link name="base_link">
    <visual>
      <origin xyz="0 0 0" rpy="0 0 0"/>
      <geometry>
	      <mesh filename="package://jetson_description/meshes/DAE/JetBot-v3-Chassis444.dae"/>
      </geometry>
      <material name="white"/>
    </visual>

    <collision>
      <origin xyz="-0.03 0 0.03" rpy="0 0 0"/>
      <geometry>
	<box size="${length} ${width} ${height}"/>
      </geometry>
    </collision>

    <inertial>
      <mass value="${mass}" />
      <origin xyz="-0.06 0 0.04" rpy="0 0 0" />
      <xacro:box_inertia m="${mass}" x="${length}" y="${width}" z="${height}" />
    </inertial>

  </link>

  <!-- right_wheel and joint-->
  <xacro:wheel_macro parent="base_link" prefix="right_wheel" xyz="0 -0.053 0" color="black"/>

  <!-- left_wheel and joint-->
  <xacro:wheel_macro parent="base_link" prefix="left_wheel" xyz="0 0.047 0" color="orange"/>

  <!-- caster joint-->
  <joint name="caster_0_joint" type="fixed">
    <parent link="base_link"/>
    <child link="caster_0"/>
    <origin rpy="0 0 0" xyz="-0.08 0 -0.018"/>
  </joint>

  <!-- caster -->
  <link name="caster_0">
    <visual>
      <origin rpy="0 0 0" xyz="0 0 0"/>
      <geometry>
        <sphere radius="0.012"/>
      </geometry>
      <material name="white"/>
    </visual>
    <collision>
      <origin rpy="0 0 0" xyz="0 0 0"/>
      <geometry>
        <sphere radius="0.012"/>
      </geometry>
    </collision>

      <inertial>
        <origin rpy="0 0 0" xyz="0 0 0"/>
        <mass value="0.1" />
        <xacro:sphere_inertia  m="0.1" r="0.012" />
      </inertial>

  </link>

  <gazebo reference="caster_0">
    <turnGravityOff>false</turnGravityOff>
    <material>Gazebo/White</material>
    <mu1 value="0.0"/>
    <mu2 value="0.0"/>
  </gazebo>

  <!-- Camera joint-->
  <joint name="camera_joint" type="fixed">
    <axis xyz="0 0 1" />
    <origin xyz="0.025 0 0.06" rpy="0 0 0"/>
    <parent link="base_link"/>
    <child link="camera_link"/>
  </joint>

  <!-- Camera -->
  <link name="camera_link">
    <visual>
      <origin xyz="0 0 0" rpy="0 0 0"/>
      <geometry>
	<box size="${camera_link} ${camera_link} ${camera_link}"/>
      </geometry>
      <material name="red"/>
    </visual>

    <collision>
      <origin xyz="0 0 0" rpy="0 0 0"/>
      <geometry>
	<box size="${camera_link} ${camera_link} ${camera_link}"/>
      </geometry>
    </collision>

    <inertial>
      <mass value="0.1" />
      <origin xyz="0 0 0" rpy="0 0 0" />
      <xacro:box_inertia m="0.1" x="${camera_link}" y="${camera_link}" z="${camera_link}" />
    </inertial>

  </link>

  <!-- Laser link and plugin -->
  <xacro:hokuyo_laser xyz="0 0 ${0.04 + 0.02}" 
	  rpy="0 0 0"
	  parent="base_link" length="0.01" radius="0.015">
  </xacro:hokuyo_laser>

  <!-- gazebo plugins  -->
  <!-- ros_control plugin -->
  <gazebo>
    <plugin name="gazebo_ros_control" filename="libgazebo_ros_control.so">
      <robotNamespace>/jetbot</robotNamespace>
      <robotSimType>gazebo_ros_control/DefaultRobotHWSim</robotSimType>
      <legacyModeNS>true</legacyModeNS>
    </plugin>
  </gazebo>

  <!-- Odometry plugin-->
  <gazebo>
    <plugin name="ground_truth" filename="libgazebo_ros_p3d.so">
      <frameName>world</frameName>
      <bodyName>base_link</bodyName>
      <topicName>/tracker</topicName>
      <updateRate>10.0</updateRate>
    </plugin>
  </gazebo>

</robot>


参考

ROS講座39 車輪ロボットを作る3(diff_drive_controllerで動かす)

PS3のコントローラーでROSのシュミレーションロボットを動かしてみた

ps3のコントローラーでROSのロボットを動かす。
今回はRvizの機能だけを使ったシュミレーション用だけど、実際に遠隔での実物ロボットの操作も可能。
gazeboと連携すれば本格的なシュミレーション環境ができる。

目次
1. PS3コントローラの動作確認
2. ps3用の独自のMsgを作る
3. Turtlesimをps3で動かす
4. Rvizで少しリアルなロボットをシュミレーションで動かす


1.PS3コントローラの動作確認

まずPS3(プレーステーション3)のコントローラーを有線で接続。

# 必要パッケージのインストール
$ sudo apt-get install ros-noetic-joy 
$ sudo apt-get install ros-noetic-teleop-twist-joy

# 認識してるか確認
$ ls -l /dev/input/js*
## crw-rw-r--+ 1 root input 13, 0  3月  7 13:53 /dev/input/js0
# PS3の動作確認(PSボタンを押してから)
$ roscore
$ rosrun joy joy_node
## [ WARN] [1646630772.708410626]: Couldn't set gain on joystick force feedback: Bad file descriptor
## [ INFO] [1646630772.710396915]: Opened joystick: /dev/input/js0 (Sony PLAYSTATION(R)3 Controller). deadzone_: 0.050000
$ rostopic echo /joy


ボタンを押せば、それに応じた値が出力される。



PS3のコントロールのボタンについて
PS3のコントロールのボタンの詳細は下を参考にした。
wiki.ros.org
qiita.com

f:id:trafalbad:20220307205435j:plainf:id:trafalbad:20220307205438j:plainf:id:trafalbad:20220307205442j:plain


2. ps3用の独自のMsgを作る

ps3のcontrollerのコードはあちこちに落ちてるので、参考に専用の「ps3_joy_msgs」というMsgを作った。

$ tree ps3_joy_msgs
ps3_joy_msgs
├── CMakeLists.txt
├── action
│   └── Task.action
├── cfg
│   └── Sample1.cfg
├── msg
│   └── Custom.msg
└── package.xml

CMakeLists.txt(下の部分を追記)

cmake_minimum_required(VERSION 3.0.2)
project(ps3_joy_msgs)

find_package(catkin REQUIRED COMPONENTS
  roscpp
  rospy
  std_msgs
  message_generation
  dynamic_reconfigure
  actionlib_msgs)

## Generate messages in the 'msg' folder
add_message_files(
  FILES
  Custom.msg)

## Generate actions in the 'action' folder
add_action_files(
  FILES
  Task.action)

## Generate added messages and services with any dependencies listed here
generate_messages(
  DEPENDENCIES
  std_msgs
  actionlib_msgs)
generate_dynamic_reconfigure_options(
  cfg/Sample1.cfg)

package.xml

<!--下をコメントアウト-->
<build_export_depend>message_generation</build_export_depend>
<exec_depend>message_runtime</exec_depend> 


あとは参考にしたgithubとほぼ同じなので割愛。

# 全コード構成
├── ps3_joy_basic
│   ├── CMakeLists.txt
│   ├── launch
│   │   └── turtle.launch
│   ├── package.xml
│   ├── run.sh
│   └── src
│       ├── basic_twist_publisher.cpp
│       ├── jmsg_talker.cpp
│       └── joy_twist_publisher.cpp
├── ps3_joy_msgs
│   ├── CMakeLists.txt
│   ├── action
│   │   └── Task.action
│   ├── cfg
│   │   └── Sample1.cfg
│   ├── msg
│   │   └── Custom.msg
│   └── package.xml
└── ps3_rviz_move
    ├── CMakeLists.txt
    ├── launch
    │   └── movement_robot.launch
    ├── package.xml
    ├── run.sh
    ├── rviz
    │   └── movement_robot.rviz
    ├── src
    │   └── robot_sim.cpp
    ├── stl
    │   └── stl_part.stl
    └── urdf
        └── movement_robot.urdf

3. Turtlesimをps3で動かす

ps3_joy_basicを使う。

launch/turtle.launch

<launch>
  <node name="joy_node" pkg="joy" type="joy_node" />
  <node name="joy_twist_publisher" pkg="ps3_joy_basic" type="joy_twist_publisher">
    <remap from="cmd_vel" to="turtle1/cmd_vel"/>
  </node>
  <node name="turtlesim_node" pkg="turtlesim" type="turtlesim_node" />
</launch>
# 動作
$ roslaunch ps3_joy_basic turtle.launch


f:id:trafalbad:20220307210908p:plain

動いた

4. Rvizで少しリアルなロボットをシュミレーションで動かす


ps3_rviz_moveを使う。
movement_robot.launch

<launch>
  <arg name="model" default="$(find ps3_rviz_move)/urdf/movement_robot.urdf"/>
  <arg name="rvizconfig" default="$(find ps3_rviz_move)/rviz/movement_robot.rviz"/>
  <arg name="joy" default="false" />
  <param name="robot_description" command="$(find xacro)/xacro $(arg model)" />

  <group if="$(arg joy)">
    <node name="joy_node"   pkg="joy" type="joy_node" />
    <node name="joy_twist_publisher" pkg="ps3_joy_basic" type="joy_twist_publisher"/>
  </group>
  <node name="robot_sim" pkg="ps3_rviz_move" type="robot_sim"/>

  <node name="robot_state_publisher" pkg="robot_state_publisher" type="robot_state_publisher" />
  <node name="rviz" pkg="rviz" type="rviz" args="-d $(arg rvizconfig)" required="true" />
</launch> 
# 動作
$ roslaunch ps3_rviz_move movement_robot.launch joy:=true

f:id:trafalbad:20220307210957p:plain

これで、ROSとgazeboを繋げば本格的なシュミレーション環境が作れるし、実際にロボットを遠隔操作できる。

参考サイト

ROS講座10 カスタムROSメッセージ
ps3joy-ros.org
ROS講座07 joyプログラム
GitHub-ros_lecture