OGIMOノート~家族のためのモノづくり~

OGIMOテックノート ~家族のためのモノづくり~

重度障害の息子を持つ父親エンジニアの備忘録。自作の電子工作おもちゃ/リハビリ器具/ロボット関係の製作記録、思った事を残していきます

【アーム制御】CRANE+を使ったROSアーム制御①

はじめに

Turtlebot用ロボットアームを期間限定でレンタルできたので、勉強がてら遊んでみたいと思う。
うまく使えそうならば、ロボットアームを別途自作して、息子の食事介護支援などに応用できればよいなぁ、と企んでいます。

最初の目標 : グリッパー位置を任意の三次元位置に持っていく事。

使用ハードウェア

RT社製CRANE+
www.rt-shop.jp

アーム自由度4軸+ハンド部1軸だそうです。サーボモータはDynamixel AX-12A。電源は12V。
USBシリアル通信で制御きるので、HostはROSが動く環境であれば何でもよさそう。

HW動作確認は、以下のサイトが詳しいのでこちらを見ながら進める。
個別のサーボモータを動かすことができればOK
www.rt-shop.jp

ソフトウェア環境構築

5つのサーボモータの角度をそれぞれアームの期待動作に応じて、個別計算&更新していく手段がまず考えられるが、
せっかくROSを使うのでROS既存パッケージを使った動作を試してみたいと思う。
そこで、マニュピュレータ制御用パッケージ moveit! を使ったアーム制御環境を構築する
参考にしたのは、以下のサイト
ROSを使用した画像処理とマニピュレータ制御 by gbiggs
このサイトに従って進めていく。
まずはインストール。

sudo apt-get install ros-kinetic-dynamixel-motor
sudo apt-get install ros-kinetic-moveit-*

上記で一旦インストールを完了させた後、CRANE+のROSパッケージをダウンロードしコンパイルする。

cd ~/catkin_ws/src/
git clone https://github.com/gbiggs/crane_plus_arm.git
cd ../
catkin_make

CRANE+のパッケージの詳細説明は引用サイトに記載されていますので、ここでは割愛。
ちなみに、私が途中でハマったのは、USB制御ポートが異なった場合の対応方法。
defaultでは、/dev/ttyUSB0 で制御する想定なのだが、Host PCのUSB構成次第ではここ以外を使わざるを得ない場合がある。
その場合は、crane_plus_arm/crane_plus_hardware/config/servo_controller_manager.yaml 内のport_name以下を変更すればよい。

moveitを使って任意位置にアームを移動

まず第一段階として、ユーザー操作で任意の位置にアームを動かす制御プログラムを作成する。
アーム位置を設定する手段として、PS4用コントローラを使用する。
PS4コントローラのアナログスティックを動かして、アーム位置をマニュアルで変更できる様にする。

制御ノートの追加

ワークスペース内に制御パッケージを追加する。

cd ~/catkin_ws/src/
catkin_create_pkg arm_control roscpp moveit_core moveit_ros_planning_interface moveit_visual_tools moveit_msgs moveit_commander tf actionlib control_msgs geometry_msgs shape_msgs trajectory_msgs

パッケージ内のCMakeLists.txtで以下を追加する

①find_packageに記載追加
find_package(Boost REQUIRED
  system
  filesystem
  date_time
  thread
)
②include_directoriesにBoostのディレクトリを追加。
include_directories(
  ${catkin_INCLUDE_DIRS}
  ${Boost_INCLUDE_DIR}
)
③コンパイル情報を追加
add_executable(ArmCtrlTrack src/arm_ctrl_track.cpp)
target_link_libraries(ArmCtrlTrack
 ${catkin_LIBRARIES}
 ${Boost_LIBRARIES}
)
制御プログラムの作成

C++で実際に実装する。ポイントとしては、
 ・createTimer関数で定期的に実行関数をCallbackして、その関数内でarm座標をpublish(moveit関数を使用)
 ・PS4コントローラ(joy)のSubscribe関数からアナログスティック情報を取得して座標情報(private変数)を都度更新
である。

アーム移動は、参照サイト同様に、geometry_msgs::PoseStamped型で座標定義してMoveIt!関数を使って移動先を指示&実行する。この時の座標系はbase_link、初期座標は arm.setNamedTarget("resting")に移動した後は、奥行きX = 0.18[m]、水平Y=0[m]、高さZ=0.10[m]からスタートする。この座標設定はCRANE+が移動可能な場所を実験的に求めている。

グリッパー開閉は、参照サイト同様に、actionlibを利用し、control_msgs/GripperCommandActionアクションを使います。control_msgs/GripperCommandGoal.positionにグリッパー幅を指示する。初期値は0.10[m]でOpen状態とする。

#include <ros/ros.h>
#include <ros/wall_timer.h>
#include <math.h>
#include <sensor_msgs/Joy.h>
#include <actionlib/client/simple_action_client.h>
#include <moveit/move_group_interface/move_group.h>
#include <control_msgs/GripperCommandAction.h>
#include <geometry_msgs/PoseStamped.h>

class ArmCtrlTrack{
public:
	ArmCtrlTrack();
	~ArmCtrlTrack();
	void SetInitialPose();
	void timerCallback(const ros::TimerEvent&);
	void JoyConCallback(const sensor_msgs::Joy &joy_msg);
private:
	ros::Timer timer;
	ros::NodeHandle nh;
	ros::Subscriber joy_sub;
	geometry_msgs::PoseStamped arm_pose;
	geometry_msgs::Point pos_delta;			// 現在位置からのposition差分値
	control_msgs::GripperCommandGoal g_goal;
	float g_delta;

	moveit::planning_interface::MoveGroup arm_{"arm"};
	actionlib::SimpleActionClient<control_msgs::GripperCommandAction> gripper_{"/crane_plus_gripper/gripper_command"};
};

ArmCtrlTrack::ArmCtrlTrack(){
	timer   = nh.createTimer(ros::Duration(0.1), &ArmCtrlTrack::timerCallback, this);
	joy_sub = nh.subscribe("joy", 10, &ArmCtrlTrack::JoyConCallback, this);

	arm_pose.header.frame_id = "base_link";
	arm_pose.pose.position.x = 0.18;
	arm_pose.pose.position.y = 0.0;
	arm_pose.pose.position.z = 0.10;
	arm_pose.pose.orientation.x = 0.0;
	arm_pose.pose.orientation.y = 0.707106;
	arm_pose.pose.orientation.z = 0.0;
	arm_pose.pose.orientation.w = 0.707106;
	pos_delta.x = 0;
	pos_delta.y = 0;
	pos_delta.z = 0;
	g_delta = 0;
	gripper_.waitForServer();
	SetInitialPose();
}

ArmCtrlTrack::~ArmCtrlTrack(){
}

void ArmCtrlTrack::SetInitialPose(){
	// Planning interface for the arm
	arm_.setPoseReferenceFrame("base_link");
	arm_.setNamedTarget("resting");
	arm_.asyncMove();

	g_goal.command.position = 0.1;
	gripper_.sendGoal(g_goal);
}

void ArmCtrlTrack::JoyConCallback(const sensor_msgs::Joy &joy_msg){
	// 左スティック 垂直方向 : ARM y軸移動, 水平方向: ARM x軸方向、
	// 右スティック 垂直方向 : ARM Z軸移動, 水平方向, Griper Open/close
	pos_delta.x = joy_msg.axes[5] / 100 * ARM_UPDATE_TS * 2;
	pos_delta.y = joy_msg.axes[0] /100 * ARM_UPDATE_TS  *2;
	pos_delta.z = joy_msg.axes[1] / 100 * ARM_UPDATE_TS *2;
	g_delta     = joy_msg.axes[JOY_AXES_RIGHT_HORIZONTAL] / 100 * ARM_UPDATE_TS * 5;
}

void ArmCtrlTrack::timerCallback(const ros::TimerEvent&){
	// JOYCONからの操作情報をアップデート(ARM)
	if(pos_delta.x !=0 || pos_delta.y != 0 || pos_delta.z != 0){
		arm_pose.pose.position.x += pos_delta.x;
		arm_pose.pose.position.y += pos_delta.y;
		arm_pose.pose.position.z += pos_delta.z;
		ROS_WARN("[MOVEIT]Move Arm_position[%f, %f, %f]", arm_pose.pose.position.x, arm_pose.pose.position.y, arm_pose.pose.position.z);

		// ARM更新
		arm_.setPoseReferenceFrame("base_link");
		arm_.setPoseTarget(arm_pose);
		arm_.setGoalTolerance(0.02);
		arm_.asyncMove();
	}

	// JOYCONからの操作情報をアップデート(GRIPPER)
	if(g_delta != 0){
		g_goal.command.position += g_delta;
		if(g_goal.command.position < 0)	g_goal.command.position = 0;

		ROS_WARN("[MOVEIT]Move Gripper_position[%f]", g_goal.command.position);
		gripper_.sendGoal(g_goal);
	}
}

int main(int argc, char** argv){
	ros::init(argc, argv, "ArmCtrlTrack");
	ArmCtrlTrack client;
	ros::spin();
	return 0;
}

実装していく中でハマったポイントは以下の二点。(参照サイトに記載されたサンプルコードとの差分)
①move実行関数を arm_.Move() で実装した場合、動作完了するまでMove()関数から抜けられずそのまま座標更新ができなかった。そこで、非同期処理のarm_.asyncMove()に置き換える事で座標更新ができる様になった
②最初はmoveit::planning_interface::MoveGroup arm_("_arm")を:timerCallback関数内で都度定義していたが、その処理時間が想定以上にかかっていてtimerCallback処理時間溢れが発生していた。そこで、Classのprivate変数にコンストラクタ処理を含めて用意する方法に変更した。 (C++でClass変数設定時にコンストラクタを記載する手順を知らなくて苦戦した。。{}で記載できるんですね)
③コンストラクタ処理でgripper_.waitForServer()は必要。これがないと、ActionServerが準備できていない状態でClient側からコマンドが投げられてしまいエラーとなってしまう。

動作させるためのLaunchファイルも作成。制御用パッケージ内にlaunchフォルダを作成して、その中に以下の記載を追加。

[arm_ctrl_tracking.launch]
<?xml version="1.0"?>
    <include file="$(find crane_plus_hardware)/launch/start_arm_standalone.launch" />
    <include file="$(find crane_plus_moveit_config)/launch/move_group.launch" />
    <node pkg="joy" name="joycon" type="joy_node"/>
    <node pkg="arm_control" name="ArmCtrlTrack" type="ArmCtrlTrack"  output="screen"/>
</launch>
動作確認

動画撮影中。

意図した動作イメージを実現する事ができ、当初の目標は達成できた気がする。
しかし、応答速度が非常に遅い事 & 移動できない座標位置が非常に多い。。。。どうやらMoveit!自体は6軸アーム前提のソフトウェアのため、CRANE+の様な4軸アームに対しては逆運動学の式が最適に解けないのでは??
なので、次回はMoveit!を使用せず、CRANE+のハードウェア仕様に特化して逆運動学を自力で解くプログラム作成にチャレンジしてみようと思う。果たして、レンタル期間中に間に合うか!?

【組込みDeep Learning】Movidius + raspberry pi+ROS環境構築

Movidius を使ってみる

f:id:motokiinfinity8:20180209212816j:plain

Caffe/Tensorflowに対応した組み込み向けVPUスティックデバイス Movidius。 Intelに買収される前から目をつけていたのですが、いつの間にやらここまで有名になるとは。
ラズパイの様なGPUを持たないマシンでもDeep Learningをエッジ側で使えるので、使いこなせれば非常に便利であろう。

自作ROSロボットで、子供の顔を認識させて追従させる様なロボットを作りたかったので、ここはDeep Learning環境の構築に前向きにトライしてみます!

まずは、SDKを立ち上げてみた。
ここのサイトを参考にました。

【Movidius™NCS&RaspberryPi】リアルタイム物体認識【TensorFlow】 - Qiita

Tensorflowのインストール

ARM用Tensorflowはビルドでトラブりがちなので、コンパイル済を取ってくる。

wget https://github.com/lhelontra/tensorflow-on-arm/releases/download/v1.3.1/ten
sorflow-1.3.1-cp35-none-linux_armv7l.whl
sudo pip3 install tensorflow-1.3.1-cp35-none-linux_armv7l.whl 
SDKのインストール

公式サイトにしたがってインストール。バージョンは1.11.00

git clone http://github.com/Movidius/ncsdk && cd ncsdk && make install && make examples

OpenCVもビルドしようとするがこれは失敗するはず。前投稿の手順にしたがって事前にインストールしておくこと。

サンプル動作をさせてみる

SDK内にCaffe,Tensorflowの学習済モデルをMovidius用graphファイルに変換したものが入っているので、これを利用する。とりあえず参考サイト通り、GoogleのInceptionV3モデルを使ってみた。

cd ncsdk/examples/tensorflow/inception_v3
python3 run.py

で実行。例えば、下記のルリコシボタンインコの写真の場合、認識結果は以下。
f:id:motokiinfinity8:20180209223407j:plain

                                                                                                          • -

91 lorikeet 0.52588
93 bee eater 0.11914
89 macaw 0.023178
12 goldfinch, Carduelis carduelis 0.0059319
137 European gallinule, Porphyrio porphyrio 0.0055695

                                                                                                            • -

lorikeet (インコ)なので正解!
なお、認識時間は 3200×2400の写真で 0.53秒。ざっくり計算でVGA(640×480)ならば30fpsは出そうな雰囲気(実際は画像処理のオーバーヘッドの方が大きそう) なかなか優秀だと思う。

なお、run.py のコードは以下

from mvnc import mvncapi as mvnc
import sys
import numpy
import cv2
import time

path_to_networks = './'
path_to_images = '../../data/images/'
graph_filename = 'graph'
image_filename = path_to_images + 'bird.jpg'

start = time.time()

#mvnc.SetGlobalOption(mvnc.GlobalOption.LOGLEVEL, 2)
devices = mvnc.EnumerateDevices()
if len(devices) == 0:
    print('No devices found')
    quit()

device = mvnc.Device(devices[0])
device.OpenDevice()

print("Open device : {0}".format(time.time()-start) + "[sec]")

#Load graph
with open(path_to_networks + graph_filename, mode='rb') as f:
    graphfile = f.read()

#Load preprocessing data
mean = 128
std = 1/128

#Load categories
categories = []
with open(path_to_networks + 'categories.txt', 'r') as f:
    for line in f:
        cat = line.split('\n')[0]
        if cat != 'classes':
            categories.append(cat)
    f.close()
    print('Number of categories:', len(categories))

#Load image size
with open(path_to_networks + 'inputsize.txt', 'r') as f:
    reqsize = int(f.readline().split('\n')[0])

graph = device.AllocateGraph(graphfile)

print("Open graph : {0}".format(time.time()-start) + "[sec]")

img = cv2.imread(image_filename).astype(numpy.float32)
dx,dy,dz= img.shape
delta=float(abs(dy-dx))
if dx > dy: #crop the x dimension
    img=img[int(0.5*delta):dx-int(0.5*delta),0:dy]
else:
    img=img[0:dx,int(0.5*delta):dy-int(0.5*delta)]
img = cv2.resize(img, (reqsize, reqsize))

img=cv2.cvtColor(img,cv2.COLOR_BGR2RGB)

for i in range(3):
    img[:,:,i] = (img[:,:,i] - mean) * std

print('Start download to NCS...')
graph.LoadTensor(img.astype(numpy.float16), 'user object')
output, userobj = graph.GetResult()

print("get tensor result: {0}".format(time.time()-start) + "[sec]")

top_inds = output.argsort()[::-1][:5]

print(''.join(['*' for i in range(79)]))
print('inception-v3 on NCS')
print(''.join(['*' for i in range(79)]))
for i in range(5):
    print(top_inds[i], categories[top_inds[i]], output[top_inds[i]])

print(''.join(['*' for i in range(79)]))
graph.DeallocateGraph()
device.CloseDevice()
print('Finished')

MovidiusのROS対応

上記のMovidius SDKでは、Python3を使っていた。残念ながら、ROSは、Python2.7系がdefaultのため、Python3系を使うのはなかなかハードルが高そう。どうしようか悩んでいたところ、以下のサイトを発見。
github.com

どうやら、公式にROS対応している様だ。これはありがたい。

Install NCSDK v1.11.00
Install NC APP Zoo

が必要とのことで、GitHub - movidius/ncappzoo: Contains examples for the Movidius Neural Compute Stick.から NC APPをインストール

git clone https://github.com/movidius/ncappzoo
mv ncappzoo /opt/movidius/ncappzoo
ROSパッケージをインストール

参考サイトの手順にしたがって進める。

# Building
cd ~/catkin_ws/src
git clone https://github.com/intel/ros_intel_movidius_ncs.git
cd ros_intel_movidius_ncs
git checkout master
cd ~/catkin_ws
catkin_make
# Installation
catkin_make install
source install/setup.bash
# Copy label files from this project to the installation location of NCSDK
cp ~/catkin_ws/src/ros_intel_movidius_ncs/data/labels/* /opt/movidius/ncappzoo/data/ilsvrc12/

ただし、x86ベースのため、catkine_make時に sse4 エラーが発生する。そのため、
ros_intel_movidius_ncs/src/CMakeLists.txt にある下記コマンドオプションをコメントアウトしておく

  # Add x86 intrinsic compiler support
  #set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -msse4.1")
  #set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mf16c")
  #set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -march=native")

先ほどと同じ様に、、GoogleのInceptionV3モデルを使ってみる。

cd /opt/movidius/ncappzoo/tensorflow/inception_v3
make

RuntimeError: module compiled against API version 0xc but this version of numpy is 0xb
Segmentation fault (core dumped)
Makefile:41: recipe for target 'weights' failed
make: *** [weights] Error 139

でコケた。どうやら、numpyが1.13.0と1.11.0の2つが多重インストールされている様なので、pip3 uninstall numpy を2回かけた後、pip3 install numpy で 1.14.0をインストールしてみる。これで、ビルドが進んだ!

ちなみに、AlexNetを使ってみる場合は以下のコマンド。

cd /opt/movidius/ncappzoo/caffe/AlexNet
make

その後、USBカメラからの映像を確認する

roslaunch movidius_ncs_launch ncs_stream_classification_example.launch camera_topic:="/usb_cam/image_raw"

これで行けそうな見込み! と思いきや、

process[movidius_ncs_example_stream_classification-1]: started with pid [15135]

から動かず映像でず。解析はもう少しかかりそう。

ラズパイ(Ubuntu16.04)にOpenCV 3.3.0をインストール

さて、ROS(kinetic)環境を構築した訳だが、本格的なロボット開発をする前に、環境構築で苦戦が予想される画像処理&Deep Learning環境を先に構築しておきたいと思う。
まずは、ラズパイ(ubuntu16.04)環境にOpenCVを入れる。将来的にトラッキングなども行いたいので、3系を入れることとする。

Open CV 3.3.0 インストール

qiita.com
を参考に。

ただし、このままだとmake時にコケるため、以下の二点は注意が必要

 ① .bashrcから ROS向けの下記設定をコメントアウトしておく必要あり
(build時に ROSのOpenCV関連パッケージを見に行ってしまうらしい)

#source /opt/ros/kinetic/setup.bash
 
 ② cmake時のオプションは変更が必要

/usr/include/GL/glext.h:466:19: error: conflicting declaration ‘typedef std::ptrdiff_t GLsizeiptr’
typedef ptrdiff_t GLsizeiptr;

どうやらQT4 のパッケージが OpenGL ESベースでコンパイルされているのに、Open GLを指定していたため、定義の競合が起きた様子。そのため、OPENGL=ON → OPENGL-ES=ONに変更すれば解決!


念の為、手順メモを残す。

sudo apt-get install cmake libeigen3-dev libgtk-3-dev qt5-default freeglut3-dev \
libvtk6-qt-dev libtbb-dev ffmpeg libdc1394-22-dev libavcodec-dev libavformat-dev \
libswscale-dev libjpeg-dev libjasper-dev libpng++-dev libtiff5-dev \
libopenexr-dev libwebp-dev libhdf5-dev libpython3.5-dev python3-numpy \
python3-scipy python3-matplotlib libopenblas-dev liblapacke-dev

wget https://github.com/opencv/opencv/archive/3.3.0.tar.gz

tar xvf 3.3.0.tar.gz

mkdir opencv-3.3.0/build && cd opencv-3.3.0/build
cmake -G "Unix Makefiles" --build . -D BUILD_CUDA_STUBS=OFF -D BUILD_DOCS=OFF \
-D BUILD_EXAMPLES=OFF -D BUILD_JASPER=OFF -D BUILD_JPEG=OFF -D BUILD_OPENEXR=OFF \
-D BUILD_PACKAGE=ON -D BUILD_PERF_TESTS=OFF -D BUILD_PNG=OFF -D BUILD_SHARED_LIBS=ON \
-D BUILD_TBB=OFF -D BUILD_TESTS=OFF -D BUILD_TIFF=OFF -D BUILD_WITH_DEBUG_INFO=ON \
-D BUILD_ZLIB=OFF -D BUILD_WEBP=OFF -D BUILD_opencv_apps=ON -D BUILD_opencv_calib3d=ON \
-D BUILD_opencv_core=ON -D BUILD_opencv_cudaarithm=OFF -D BUILD_opencv_cudabgsegm=OFF \
-D BUILD_opencv_cudacodec=OFF -D BUILD_opencv_cudafeatures2d=OFF -D BUILD_opencv_cudafilters=OFF \
-D BUILD_opencv_cudaimgproc=OFF -D BUILD_opencv_cudalegacy=OFF -D BUILD_opencv_cudaobjdetect=OFF \
-D BUILD_opencv_cudaoptflow=OFF -D BUILD_opencv_cudastereo=OFF -D BUILD_opencv_cudawarping=OFF \
-D BUILD_opencv_cudev=OFF -D BUILD_opencv_features2d=ON -D BUILD_opencv_flann=ON \
-D BUILD_opencv_highgui=ON -D BUILD_opencv_imgcodecs=ON -D BUILD_opencv_imgproc=ON \
-D BUILD_opencv_java=OFF -D BUILD_opencv_ml=ON -D BUILD_opencv_objdetect=ON \
-D BUILD_opencv_photo=ON -D BUILD_opencv_python2=ON -D BUILD_opencv_python3=ON \
-D BUILD_opencv_shape=ON -D BUILD_opencv_stitching=ON -D BUILD_opencv_superres=ON \
-D BUILD_opencv_ts=ON -D BUILD_opencv_video=ON -D BUILD_opencv_videoio=ON \
-D BUILD_opencv_videostab=ON -D BUILD_opencv_viz=OFF -D BUILD_opencv_world=OFF \
-D CMAKE_BUILD_TYPE=RELEASE -D WITH_1394=ON -D WITH_CUBLAS=OFF -D WITH_CUDA=OFF \
-D WITH_CUFFT=OFF -D WITH_EIGEN=ON -D WITH_FFMPEG=ON -D WITH_GDAL=OFF -D WITH_GPHOTO2=OFF \
-D WITH_GIGEAPI=ON -D WITH_GSTREAMER=OFF -D WITH_GTK=ON -D WITH_INTELPERC=OFF -D WITH_IPP=ON \
-D WITH_IPP_A=OFF -D WITH_JASPER=ON -D WITH_JPEG=ON -D WITH_LIBV4L=ON -D WITH_OPENCL=ON \
-D WITH_OPENCLAMDBLAS=OFF -D WITH_OPENCLAMDFFT=OFF -D WITH_OPENCL_SVM=OFF -D WITH_OPENEXR=ON \
-D WITH_OPENGL-ES=ON -D WITH_OPENMP=OFF -D WITH_OPENNI=OFF -D WITH_PNG=ON -D WITH_PTHREADS_PF=OFF \
-D WITH_PVAPI=OFF -D WITH_QT=ON -D WITH_TBB=ON -D WITH_TIFF=ON -D WITH_UNICAP=OFF \
-D WITH_V4L=ON -D WITH_VTK=OFF -D WITH_WEBP=ON -D WITH_XIMEA=OFF -D WITH_XINE=OFF \
-D WITH_LAPACKE=ON -D WITH_MATLAB=OFF ..

make -j4

sudo make install


これでOK! ちゃんとインストールされたか確認するためには、
python3 で

import cv2
cv2.__version__  → '3.3.0'

と表示されれば問題なし!

次は、いよいよ画像認識環境の立ち上げです!

【ROS】ラズパイで始めるROS Bot入門② ~Ubuntu ServerへROSインストール / カメラ認識~

ROS(Kinetic)インストール

ようやくROS入門スタート。ROSをRaspberry Pi3@Ubuntu16.04LTS に入れる。手順は以下

$ sudo -s
# echo "deb http://packages.ros.org/ros/ubuntu $(lsb_release -sc) main” > /etc/apt/sources.list.d/ros-latest.list
# Ctrl-D
$ sudo apt-key adv --keyserver hkp://ha.pool.sks-keyservers.net:80 --recv-key 421C365BD9FF1F717815A3895523BAEEB01FA116
$ sudo apt-get update
$ sudo apt-get install ros-kinetic-desktop

ここでしばらく時間かかる。
完了したら、~/.bashrcにsource /opt/ros/kinetic/setup.bashを追加する。
その後、source .bashrcをして、roscd などの ROSコマンドが見える事を確認する。

rosdepも忘れず処理。

sudo rosdep init
rosdep update

最後に roscore と入力して、無事にstart すればOK!

ROSワークスペース作成

ワークスペース catkin_ws を作成しておく

$ mdkir catkin_ws
$ cd catkin_ws
$ mkdir src
$ cd src
$ catkin_init_workspace

この後、~/catkin_ws ディレクト上で "catkin_make" をしてエラーがでない事を確認しておく。

純正ラズパイカメラを使う

Ubuntuでラズパイカメラを使える様にしてみる。(有りものの有効活用)
こちらのサイトを参考にさせて頂いた

karaage.hatenadiary.jp

純正ラズパイカメラをROSで使えるraspicam_nodeというパッケージがあるそうです。
https://github.com/UbiquityRobotics/raspicam_node/tree/indigo_safe

ここのステップに従ってすすめる。

$ cd ~
$ git clone https://github.com/raspberrypi/userland.git
$ cd ~/userland
$ ./buildme

$ sudo apt-get install ros-kinetic-compressed-image-transport
$ sudo apt-get install ros-kinetic-camera-info-manager

$ cd ~/catkin_ws/src
$ git clone https://github.com/UbiquityRobotics/raspicam_node.git
$ git clone https://github.com/UbiquityRobotics/ubiquity_launches.git

$ cd ../
$ catkin_make

エラーなくcmake完了する事を確認する。
次に video groupに/dev/vchiq を追加する。

$ sudo -s
$ echo 'SUBSYSTEM=="vchiq",GROUP="video",MODE="0660"' > /etc/udev/rules.d/10-vchiq-permissions.rules
$ usermod -a -G video `whoami`
$ sudo shutdown -r now

その後、下記コマンドを追加する

$ cd ~
$ sudo apt-get install -y curl
$ sudo curl -L --output /usr/bin/rpi-update https://raw.githubusercontent.com/Hexxeh/rpi-update/master/rpi-update && sudo chmod +x /usr/bin/rpi-update
$ sudo rpi-update
$ sudo reboot

ついでにBootカメラを有効 & GPUのメモリ割り当てを変更するため、/boot/config.txtを書き換えます。(gpu_memのdefaultは64MB)

start_x=1
gpu_mem=128

ここで再起動すれば設定完了、のはず。

動かし方

最初のターミナルで

roscore

を起動。その後、別のコンソールで以下を実行

 source ~/catkin_src/devel/setup.bash
rosrun raspicam raspicam_node

を実行。これでカメラノードが立ち上がる。正しく動いていそうか簡易確認をするため、以下を実行する
camera.yamlがないとか怒られたので、camera1_128x720.yaml をcamera.yamlにコピー。

しかし、
mmal: mmal_component_create_core: could not find component 'vc.ril.camera'
mmal: Failed to create camera component
[INFO] [1444257925.655477127]: init_cam: Failed to create camera component
Segmentation fault

で停止。困った。。。一旦保留。
カメラ設定はやっぱ大変だ。。。


USBカメラを使う

仕方ないので、汎用のUSBカメラを使う方向に変更。 手持ちのWebカメラ(Logicool製)を使用。
いわゆる、UVC(USB Video Class)に準拠していれば問題ないと思われます。

まずは、

$ lsusb
$ ls /dev/video*

で、カメラが認識されているか調査。/dev/video0 が表示されていればOK!

その後に、ROSパッケージをインストール

$ sudo apt-get install ros-kinetic-usb-cam
$ sudo apt-get install ros-kinetic-compressed-image-transport
$ sudo apt-get install ros-kinetic-image-view

完了したら、別のターミナルで roscoreした状態で下記を設定。

$ rosrun  usb_cam usb_cam_node
$ rostopic list
  ここで /usb_cam/image_raw , /usb_cam/image_raw/compressed
が表示されればOK

画面上では"No accelerated ……"などのエラーログが表示されるが一旦無視。
別ターミナルで

$ rosrun image_view image_view image:=/usb_cam/image_raw

としてカメラ映像が表示できていればミッション完了!


次回は、いよいよロボット制御!の前に、もう少し画像認識の環境を掘り下げてみます!(こっちのプラットホーム構築の方が大変なので。。。)

【ROS】ラズパイで始めるROS Bot入門① ~Ubuntu Server インストール~

はじめに

ロボット制御のデファクトスタンダードになっているROS(Robot OS)を勉強している。
Kobukiみたいな既にROSインストール済のロボットでROSの基礎を学習中だが、自前で一から勉強してみたくなったので、お手軽なRaspberry piを使って自作ロボットを作ってみたいと思う。

Hostマシン : Raspberry Pi 3
使用Soft : Ubuntu 16.04 LTS / ROS Kinetic
使用ロボットHW : 市販二輪走行ロボット(次回に説明)

パッケージサポートが整っている Ubuntu 14.04 / ROS Indigo を選択したかったが、その場合 Raspberry Pi3では動かずRaspberry Pi 2 になると聞いたので、今回は Ubuntu 16.04 LTS / ROS Kinetic を選択した。

Ubuntu Server 16.04 のインストール

基本的には、下記サイトの参考にした
gihyo.jp

母艦Ubuntu PCで
https://wiki.ubuntu.com/ARM/RaspberryPi
から ubuntu-16.04-preinstalled-server-armhf+raspi3.img.xz をダウンロード。

Ubuntu PCの場合、 xzcat コマンドでSDカードに書き込む。この際、SDカードの有無時の/dev/のファイル増減から指定する/dev/ファイルを見つける。私の環境では/dev/sde1 であった。(/dev/sd** or /dec/mmcblk***のどれかになる模様)一旦、SDカードがmountしている場合はumountしておいた方がよい

$ xzcat ubuntu-16.04-preinstalled-server-armhf+rasPi 3.img.xz | sudo dd of=/dev/sde1

時間がまぁまぁかかる。もし途中経過が分からず不安ならば、別ターミナルで以下のコマンドを入力する

$ sudo killall -USR1 dd

Windows PCの場合、7zipで解凍して、出てきたimgファイルをDDforWindowsなどでSDカードに書き込む。(この際、管理者権限で実行しないとうまくSDスロットを認識してくれないので注意)



ラズパイでのUbuntu立ち上げ

 続いて、書込み後のSDをラズパイに挿入して、電源ON。
コンソール画面が出るのでログイン/パスワードを入力(デフォルトは"ubuntu/ubuntu")。

次に設定ファイルの更新をします。この修正をしないと、ソフトアップデートした際にOSが起動しなくなるとの事。
/boot/firmware/config.txtを以下の様に編集します。

#device_tree_address=0x100     ←コメントアウトする
#device_tree_end=0x8000      ← コメントアウトする
device_tree_address=0x02008000 ←追記する

この修正後に、"sudo apt update"→"sudo apt upgrade" を実施する。その後、rebootを実施して、立ち上がる事を確認する。

各種設定

sshで入るため、下記設定を行う

sudo apt-get install avahi-daemon

続いて、PPAを設定する。

sudo add-apt-repository ppa:ubuntu-raspi

/etc/apt/sources.listを次のように書き換える

deb http://jp.archive.ubuntu.com/ports/ xenial main restricted universe multiverse
deb-src http://jp.archive.ubuntu.com/ubuntu/ xenial main restricted universe multiverse
    
deb http://jp.archive.ubuntu.com/ports/ xenial-security main restricted universe multiverse
deb-src http://jp.archive.ubuntu.com/ubuntu/ xenial-security main restricted universe multiverse
    
deb http://jp.archive.ubuntu.com/ports/ xenial-updates restricted main multiverse universe
deb-src http://jp.archive.ubuntu.com/ubuntu/ xenial-updates restricted main multiverse universe
    
deb http://jp.archive.ubuntu.com/ports/ xenial-backports restricted main multiverse universe
deb-src http://jp.archive.ubuntu.com/ubuntu/ xenial-backports restricted main multiverse universe

続いて、ロケールタイムゾーンの設定をする

$ sudo vi /etc/hosts
    → 2行目に127.0.0.1 ubuntuがあるか確認し,ない場合は追加
$ sudo locale-gen ja_JP.UTF-8
$ echo "LANG=ja_JP.UTF-8" | sudo tee /etc/default/locale
$ sudo dpkg-reconfigure -f noninteractive locales
$ echo "Asia/Tokyo" | sudo tee /etc/timezone
$ sudo dpkg-reconfigure -f noninteractive tzdata
$ sudo vi  /etc/default/keyboard
    → XKBLAYOUT="us"をXKBLAYOUT="jp" に変更
$ sudo dpkg-reconfigure -f noninteractive keyboard-configuration

ここまで来たらいったん再起動。

GUIログイン設定

パッケージをインストール

$ sudo apt install ubuntu-desktop gnome-session-flashback xserver-xorg-video-fbturbo dphys-swapfile
$ sudo  vi /etc/X11/xorg.conf
  →  以下の記載を追加(新規作成)
Section "Device"
    Identifier "Raspberry Pi FBDEV"
    Driver "fbturbo"
    Option "fbdev" "/dev/fb0"
    Option "SwapbuffersWait" "true"
EndSection
$ sudo vi  /etc/network/interfaces
   → 以下をコメントアウト
# Source interfaces
# Please check /etc/network/interfaces.d before changing this file
# as interfaces may have been defined in /etc/network/interfaces.d
# See LP: #1262951
# source /etc/network/interfaces.d/*.cfg

ここまで来たらいったん再起動。その後、Ubuntuロゴをクリックするとセッションが選択できるので,[GNOME Flashback (Metacity)]を選択してログインできれば成功。

無線の設定

ここははまりかけたので注意。

まずは、電波帯の国設定

$ echo "JP" > sudo tee /etc/wpa_supplicant/wpa_supplicant.conf

続いて、/etc/network/interfacesの修正をする。

# source /etc/network/interfaces.d/*.cfg   ←コメントアウトされている事を確認

以下の4行を追加
auto wlan0
iface wlan0 inet dhcp
wpa-conf /etc/wpa_supplicant/[SSID名].conf
wireless-power off

その後、[SSID名前\],confを作成する。

$ sudo -s
# apt install wpasupplicant
# wpa_passphrase [SSID名] [パスワード] > /etc/wpa_supplicant/[SSID名].conf
# less  /etc/wpa_supplicant/[SSID名].conf
network={
   ssid="[SSID名]"
   #psk="[パスワード]"
  psk = xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
}
# apt install wireless-tools

これで、"ifup wlan0" をすれば wlan0 が見える!と思いきや、、、見えない…

何故だ!?とWebを調べていくと以下のサイトを発見
github.com

どうやら2017/4/13以降のソフトアップデートが原因の様で、以下のコマンドで回避できるとのこと。

$ cd /lib/firmware/brcm/
$ sudo wget https://github.com/RPi-Distro/firmware-nonfree/raw/master/brcm80211/brcm/brcmfmac43430-sdio.bin -O brcmfmac43430-sdio.bin
$ sudo wget https://github.com/RPi-Distro/firmware-nonfree/raw/master/brcm80211/brcm/brcmfmac43430-sdio.txt -O brcmfmac43430-sdio.txt
$ sudo reboot

これで無事にWifiが見えた! よかった

次回

いよいよ ROS インストールに行きます。(先は長いなぁ)

【電子工作(Arduino)】歩くと効果音の鳴るメロディ靴

製作動機

肢体不自由な息子、いつか歩ける事を夢見てリハビリなどで歩行練習を行うも、歩く事になかなか興味を持ってくれず停滞中。成長加速のため、何とか歩く行為に興味を持ってもらえないかという事で、何か歩く楽しみを見いだせるものを作ってやろうというのがきっかけ。
アイディアとしては、歩くとピコピコ音が鳴る靴。しかし、あの音だと息子は興味なさそうなので、息子が好きそうな音に変えたい!
例えば、マリオのジャンプの音とか、びよーんっていうコミカル音とか。

製作ターゲット(要求仕様的なもの)

足踏みに合わせて楽しい効果音が鳴る子供靴

  • 靴裏にしっかり体重が乗らないと音が鳴らない事
  • 効果音は自由な音源を選べる。
  • 右足と左足で効果音は変える。
  • うまく左右交互に歩ければ成功音を鳴せるとBetter

完成品

 f:id:motokiinfinity8:20180108011804j:plain:w400

完成品の使用動画
youtu.be

材料

Arduino uno or  Adafruit Pro Trinket - 5V 16MHz

f:id:motokiinfinity8:20180108002050j:plain:w200
まずラズパイかArduinoマイコンかで迷うところですが、
AC入り後の起動の速さと足踏み時の圧力閾値を微調整したかった理由でArduinoマイコンを選択。
ラピッドプログラム時にはUno、筐体に組み込む際は小型版のAdafruit Pro Trinket を使用します
(ピン互換性ありのため、我が家では重宝しています)

感圧センサ円形  SFE-SEN-09375× 2

f:id:motokiinfinity8:20180107233759j:plain:w200
靴に体重が乗っている事を検知するための感圧センサー。スイッチサイエンスで購入可。
100g~10kgまでの測量が可能。下記サイトによれば、無負荷で100kΩ、1kg過重で約1kΩ との事なので、1kΩ抵抗との分圧比でアナログ電圧値を検知すればよさそう
Force Sensitive Resistor Hookup Guide - learn.sparkfun.com

SDカードスロット HiLetgo Micro SD TFカードメモリシールドモジュール

f:id:motokiinfinity8:20180108003229j:plain:w200
音源(wavファイル)格納用のストレージ(Arduinoマイコンの内蔵RAMが32kBしかないため)
ArduinoマイコンとはSPIで接続。モジュールは上記メーカーに限らなくてもよいが、使うならば5V/3.3Vレベルシフタ回路を搭載している方がラク

 

スピーカーアンプ PAM8403 + スピーカ

f:id:motokiinfinity8:20180108003943j:plain:w200
Arduino出力端子を直接スピーカにつなげるだけでは音量不足のため、子供に気づかれる程度のボリューム確保のためのアンプ。
スピーカは壊れたおもちゃのスピーカを使用。再生する音声ファイルのゲインさえ大きくしておけば
それなりの音量で出力可能。ただし、スピーカ後ろに響かせるための空間確保は必要。

  

ハードウェア部

ざっくりした回路図は以下の通り。
f:id:motokiinfinity8:20180108004951j:plain
子供用靴は別途購入して感圧センサーを靴裏(かかと側)に埋め込む。

f:id:motokiinfinity8:20180108011320j:plainf:id:motokiinfinity8:20180108011243j:plain
靴(感圧センサー)以外はBOXタイプの外装に収めておく。スピーカ音はBOX側から鳴るイメージ。
靴とBOXとは有線接続(これが一番簡単なので)。
この線材は100均ショップで売ってるモノラルイヤホン(3m)を切って使用しました。
モノラルジャックで靴とBOX接続を取り外しできるアイディアは持ち運び/交換の観点で便利。

 f:id:motokiinfinity8:20180108011804j:plain:w400

f:id:motokiinfinity8:20180108011954j:plainf:id:motokiinfinity8:20180108012033j:plain
最終的な完成イメージ。

 

ソフトウェア部

・SDカードからwavファイル読み込み

こちらのサイトを参考にさせて頂きました。
hello-world.blog.so-net.ne.jp
1bit Readの低速度による遅延回避のためダブルバッファにして、片側を再生中に、もう片側をSD Readするという技を使っていて大変勉強になりました。
ただし、SDカード読込みの遅さが上記サイトと異なり異音が発生したため、バッファサイズは256Byteにしています。
wavファイルの形式は、32kHz / 8 bit / Stereo にしています。

音源の作り方としては、

  1. 無料サイト/Youtubeなどか効果音を抜き出してwav形式で保存
  2. Sound Engine などの音声波形編集ツールを使用して形式変換をする。

その際に、ファイル内の音量ゲインを確認して、小さければゲインを上げておく

 ソースは以下

#include <SPI.h>
#include <SD.h>
#define BUF_SIZE 256 // バッファ・サイズ

File dataFile;
const int speaker_pin = 3;

volatile uint8_t // グローバル変数
 buf[2][BUF_SIZE], // バッファ
 buf_page, // バッファ・ページ
 buf_flg; // バッファ読み込みフラグ
volatile uint16_t buf_index; // バッファ位置
volatile uint16_t read_size[2]; // バッファ読み込みサイズ
 
void play() {
 pinMode(speaker_pin, OUTPUT);
 DDRD |= B00001000; // PD3 (OC2B) (Arduino D3)
 TCCR2A = _BV(COM2B1) | _BV(WGM21) | _BV(WGM20); // 8bit高速PWM
 TCCR2B = _BV(CS20); // 分周なし
 OCR2A = 255;

 // *** 周期
 // *** 1count=0.0625us、 0.0625*256 -> 16us -> 62.5kHz
 buf_index = 44; buf_page = 0; buf_flg = 1; // パラメータ設定 (44byteはヘッダ) 
 read_size[buf_page] = dataFile.read( (uint8_t*)buf[buf_page], BUF_SIZE );
 TIMSK2 |= _BV(TOIE2);
 while(TIMSK2 & _BV(TOIE2)) {
  if(buf_flg) { // データ読み込み指令のフラグが立ったら読み込む
   read_size[buf_page ^ 1] = dataFile.read( (uint8_t*)buf[buf_page ^ 1], BUF_SIZE );
   buf_flg = 0; // 読み込んだらフラグを下ろす
  }
 }
 OCR2B = 0;
 dataFile.close();
}

ISR(TIMER2_OVF_vect) {
 OCR2B = buf[buf_page][buf_index++]; // データをPWMとして出力
 if(buf_index == read_size[buf_page]) { // 現在のバッファの最後まで来たら...
  if(buf_index != BUF_SIZE) TIMSK2 &= ~_BV(TOIE2); // ファイルの最後なら,TOIE2をクリア
  buf_index = 0; buf_page ^= 1; buf_flg = 1; // バッファを切り替え
 }
}


・感圧センサーからの踏み込み検知

シンプルにAruduinoマイコンのAnalogreadを使用。
チャタリング防止のため、足圧ON→OFF、OFF→ONの判定閾値を変えたくらいの工夫。
(閾値は子供の体重や姿勢に応じて微調整が必要)

const int l_sense_pin = 0;
const int r_sense_pin = 1
int l_foot = 0;       // l:左足で地面を踏んでいる
int r_foot = 0;       // l:右足で地面を踏んでいる
int sound_flag = 0;   // 1: 左足踏んだ際の音声、2:右足踏んだ際の音声
int l_foot_cnt = 0;
int r_foot_cnt = 0; 

#define FOOT_ON  180   // 足で地面を踏んでいると判断する閾値
#define FOOT_OFF  64    // 足を地面から話していると判断する閾値

void loop() {
  // put your main code here, to run repeatedly:
  l_sense_val = analogRead(l_sense_pin);
  r_sense_val = analogRead(r_sense_pin);

  // 左足踏み時に閾値を超えた場合
  if(l_foot==0 && l_sense_val>FOOT_ON ){
      l_foot = 1;
      sound_flag = 1;
      l_foot_cnt++;
  }
  if(l_foot==1 && l_sense_val<FOOT_OFF){
       l_foot = 0;
  }
  // 右足踏み時に閾値を超えた場合
  if(r_foot==0 && r_sense_val>FOOT_ON ){
      r_foot = 1;
      sound_flag = 2;
      r_foot_cnt++;
  }
  if(r_foot==1 && r_sense_val<FOOT_OFF){
    r_foot = 0;
  }

  if( sound_flag >0){
       // L5回+R5回の場合、成功音
      if(l_foot_cnt==5 && r_foot_cnt == 5){
        dataFile = SD.open("succeed.wav");
        l_foot_cnt=0;
        r_foot_cnt=0;
      // 左足踏み音
      }else if(sound_flag == 1){
        dataFile = SD.open("left.wav");
      // 右足踏み音
      }else if(sound_flag == 2){
        dataFile = SD.open("right.wav");
      }
      play();
      sound_flag = 0;
   }
  delay(50);
}


 

使用結果

動画は割愛。
最初は靴だけを鳴らせて様子を見せると「んっ?なんやなんや?」って興味を持って
「お、成功か!?」と思ったのですが、
いざ装着してもらうと、、、今いち反応なし。
今いち「歩く=音が鳴る」が繋がらないみたいで、頭にクエスチョンマークが飛んでいる模様。
しまいには、うじゃり出して終了。。。
撃沈。。。

…これに懲りず、引き続き頑張ります。。。