# 6장 ROS 응용하기

## ROS framework으로 젯봇 프로그래밍

\
여기서는 기존의 젯봇 프로그래밍을 ROS Framework으로 하는 방법을 생각해 본다.본 장은 주로 [jetbot\_ros](https://github.com/dusty-nv/jetbot_ros) 을 참고했다.앞서 글에서 진행한 기본 SD card를 통해 CUDA, cuDNN, TensorRT 가 설치되어 있음을 가정한다.바로 ROS Melodic을 설치해 보자<br>

### ROS Melodic 설치

아래 설명을 따라하는 것으로 충분하지만 자세한 설치 방법은 [ROS Wiki](http://wiki.ros.org/melodic/Installation/Ubuntu)를 참조한다.

```
# 먼저 ROS 패키지를 받아올 저장소(repository)를 Ubuntu의 패키지 매니저인 apt 설정화일로 추가등록한다.
```

```
$ sudo sh -c 'echo "deb http://packages.ros.org/ros/ubuntu $(lsb_release -sc) main" > /etc/apt/sources.list.d/ros-latest.list'
```

```
# 이후 패키지의 무결성 검증을 위한 key를 키서버로부터 받아온다.
```

```
# 만약 proxy 뒤에서 진행하여 key를 가져오는 데 문제가 있다면
```

```
# curl을 사용하여 일단 key를 화일로 가져오고 apt-key로 등록하는 방법도 있다.
```

```
$ sudo apt-key adv --keyserver 'hkp://keyserver.ubuntu.com:80' --recv-key C1CF6E31E6BADE8868B172B4F42ED6FBAB17C654
```

```
```

```
# 기본 ROS 패키지를 설치한다.
```

```
$ sudo apt update
```

```
$ sudo apt install ros-melodic-ros-base
```

```
```

```
# 터미널을 열때마다 ROS framework을 사용하기 위한 환경변수가 설정되도록 bash 쉘 스크립트에 source 문을 추가한다.
```

```
sudo sh -c 'echo "source /opt/ros/melodic/setup.bash" >> ~/.bashrc'
```

```
```

```
# 본 장에서 설치하고자 하는 ROS 패키지가 의존하는 라이브러리 추가
```

```
$ sudo apt-get install ros-melodic-image-transport
```

```
$ sudo apt-get install ros-melodic-image-publisher
```

```
$ sudo apt-get install ros-melodic-vision-msgs
```

### <br>

### jetson-inference 엔진 설치

이미지 추론 엔진을 설치한다. 다양한 기훈련 모델의 C++ API를 제공된다. 나중에 ROS 노드에서 활용된다.아래를 따라하는 것으로 충분하지만 자세한 설치 방법은 [dusty-nv/jetson-inference](https://github.com/dusty-nv/jetson-inference) 을 참고한다.

```
# 본 엔진을 빌딩하기 위해 필요한 git, cmake를 설치한다.
```

```
# 대부분 기본으로 이미 설치되어 있을 것이다.
```

```
sudo apt-get install git cmake
```

```
```

```
# 추론 엔진의 소스코드를 내려받는다.
```

```
cd ~
```

```
git clone -b onnx https://github.com/dusty-nv/jetson-inference
```

```
cd jetson-inference
```

```
git submodule update --init
```

```
```

```
# 소스코드를 빌딩한다.
```

```
# jetson nano에서 실행하면 상당한 시간이 필요하다.
```

```
mkdir build
```

```
cd build
```

```
cmake ../
```

```
make
```

```
```

```
# 빌딩한 추론 라이브러리를 표준 디렉토리로 이동시킨다.
```

```
sudo make install
```

적절한 JetPack 버전을 설치하지 않을 시 위 작업중 cmake ../  가 완료되지 않고 다음과 같은 오류메시지를 볼 수 있다.

```
-- jetson-utils:  building as submodule, /home/jetbot/jetson-inference
```

```
qmake: could not exec '/usr/lib/aarch64-linux-gnu/qt4/bin/qmake': No such file or directory
```

```
CMake Error at /usr/share/cmake-3.10/Modules/FindQt4.cmake:1320 (message):
```

```
  Found unsuitable Qt version "" from NOTFOUND, this code requires Qt 4.x
```

```
Call Stack (most recent call first):
```

```
  utils/CMakeLists.txt:12 (find_package)
```

이 경우 다음과 같이 필요한 라이브러리를 설치하고 다시 cmake ../ 를 진행한다.

```
sudo apt install libqt4-dev
```

make 를 실행하는 중 다음과 같은 오류 메시지가 나오면

```
/home/jetbot/jetson-inference/utils/display/glUtility.h:27:10:
```

```
 fatal error: GL/glew.h: No such file or directory
```

```
 #include <GL/glew.h>
```

```
          ^~~~~~~~~~~
```

```
compilation terminated.
```

다음과 같이 필요한 라이브러리를 설치하고 다시 make 를 실행한다.

```
sudo apt install libglew-dev
```

### catkin 작업공간 생성

젯봇에 설치할 패키지를 위한 작업공간을 만든다. 여기서는 다음의 2개의 패키지를 설치할 것이다.

* ros\_deep\_learning 패키지
* jetbot\_ros 패키지

```
$ mkdir -p ~/catkin_ws/src
```

```
$ cd ~/catkin_ws
```

```
$ catkin_make
```

```
```

```
# 방금 만든 catkin 작업공간을 ROS에서 사용하기 위한 환경변수가
```

```
# 터미널을 열때마다 자동 설정하도록 .bashrc 화일에 source 실행문을 추가
```

```
$ sudo sh -c 'echo "source ~/catkin_ws/devel/setup.bash" >> ~/.bashrc'
```

#### ros\_deep\_learning 패키지 설치

이 패키지는 앞서 설치한 추론 엔진을 사용하며 다음의 3개의 노드를 제공한다.

* imagenet 노드: 이미지 메시지를 구독하여 이미지의 종류를 구별한다.
* detectnet 노드: 이미지 메시지를 구독하여 이미지 내의 여러 사물과 그 위치를 식별한다.
* segnet 노드: 이미지 메시지를 구독하여 이미지 안의 여러 사물 사이의 경계를 식별한다.

다음과 같이 이 패키지를 설치한다. 본 패키지에 대한 자세한 설명은 [dusty-nv/ros\_deep\_learning](https://github.com/dusty-nv/ros_deep_learning) 을 참고한다.

```
$ cd ~/catkin_ws/src
```

```
$ git clone https://github.com/dusty-nv/ros_deep_learning
```

```
$ cd ../
```

```
$ catkin_make
```

\
**imagenet 노드 테스트**본 패키지에서 제공하는 imagenet 노드를 다음과 같이 테스트해본다.테스트 입력으로 orange\_0.jpg 그림화일을 사용했다.

```
$ roscore
```

```
$ rosrun image_publisher image_publisher __name:=image_publisher ~/jetson-inference/data/images/orange_0.jpg &
```

```
$ rosrun ros_deep_learning imagenet /imagenet/image_in:=/image_publisher/image_raw _model_name:=googlenet
```

잠시 기다려면 다음과 같은 결과를 얻을 수 있다. (결과가 계속 스크롤되는 것을 멈추려면 Ctrl-C를 누루면 된다.)

```
...
```

```
[ INFO] [1570620596.934852418]: converting 1920x1920 bgr8 image
```

```
class 0950 - 0.978909  (orange)
```

```
class 0951 - 0.020962  (lemon)
```

```
[ INFO] [1570620597.009793914]: classified image, 0.978909 orange (class=950)
```

즉 아래의 orange\_0.jpg 그림을 97.8%의 확률로 class 0950 (orange) 으로 인식한다는 의미이다.차순위 확률로 class 0951 (lemon) 을 2.09%의 확률로 인식했다.![orange\_0.jpg](https://coda.io/contentProxy/VhxJbAh6af/blobs/bl-v_rp2rmnFK/00283e2489868d0b19707bfb55bc87d24f9ee1af568d6d8d5d027cafcb1283d5322c82dc8fa68ceeee64d391872e14358ac8cbc3968d45d59bc056924c1973b15b3180540bab9f41b3a4d3cb5634814bbcc3febc0d8d3b15ded530a62588c8455807492a)이번에는 입력 이미지로 granny\_smith\_0.jpg 화일을 입력하여 실행해 보았다.\
![granny\_smith\_0.jpg](https://coda.io/contentProxy/VhxJbAh6af/blobs/bl-GQz1uX8gHg/e3dc0af8f7842f4377ac8540404d607eba55088143fa3f949db0be149733d5c8f6f9021d1eea8f4dbc5e49031cecb33bdc7deb44e8d3ecc99b92a5d7d9a0e98167d43305769d0e747eda19eab318f01360148aa524952b2181c044103c630f6bfec91f2a)결과는 다음과 같다.

```
[ INFO] [1570621425.684989610]: converting 400x400 bgr8 image
```

```
class 0948 - 0.999713  (Granny Smith)
```

```
[ INFO] [1570621425.717892303]: classified image, 0.999713 Granny Smith (class=948)
```

\
**detectnet 노드의 테스트**이번에는 detectnet 노드를 다음과 같이 테스트해 본다.

```
$ rosrun image_publisher image_publisher __name:=image_publisher ~/jetson-inference/data/images/peds-004.jpg 
```

```
$ rosrun ros_deep_learning detectnet /detectnet/image_in:=/image_publisher/image_raw _model_name:=pednet
```

발행한 이미지는 peds-004.jpg로 다음과 같다.![peds-003.jpg](https://coda.io/contentProxy/VhxJbAh6af/blobs/bl-ALbJKvJwuF/1688cc7038911dab4c8a0ee267c6a2d49b9733d541ec001aaf847c7a555aad9435c78f158cd79ce6c92485dba58246217541b50debfd7f949b3a776bc7f93c815a5d9ebc0032dc4435bf25400ed7e42ac8b500b28ceb12feade761f71d4e74d0e5203750)결과는 아래와 같다. 위 결과는 5명의 Object가 탐지되었고 모두 class #0로 사람(person)으로 판정된다. 위 그림에서 서있는 5명의 사람에 해당한다.안타깝게도 github에서 [detectnet 노드의 코드](https://github.com/dusty-nv/ros_deep_learning/blob/master/src/node_detectnet.cpp)를 살펴보니 인식된 object를 boxing하여 합성된 이미지를 출력하는 코드는 구현되어 있지 않다.

```
[ INFO] [1570623639.552472411]: converting 1024x611 bgr8 image
```

```
[ INFO] [1570623639.733503133]: detected 5 objects in 1024x611 image
```

```
object 0 class #0 (person)  confidence=0.860840
```

```
object 0 bounding box (692.125000, 43.632202)  (841.000000, 460.040039)  w=148.875000  h=416.407837
```

```
object 1 class #0 (person)  confidence=0.901855
```

```
object 1 bounding box (851.312500, 59.966309)  (1013.937500, 490.619873)  w=162.625000  h=430.653564
```

```
object 2 class #0 (person)  confidence=1.095703
```

```
object 2 bounding box (16.562500, 13.425293)  (227.250000, 559.238037)  w=210.687500  h=545.812744
```

```
object 3 class #0 (person)  confidence=0.683105
```

```
object 3 bounding box (374.218750, 34.756592)  (619.234375, 598.618896)  w=245.015625  h=563.862305
```

```
object 4 class #0 (person)  confidence=0.908203
```

```
object 4 bounding box (549.078125, 129.889709)  (617.781250, 318.999878)  w=68.703125  h=189.110168
```

\
ros\_graph로 현재의 발행과 구독 상황을 보면 다음과 같다. 즉 image\_publisher 노드가 /Image\_raw 토픽으로 메시지를 발행하고 이를 detectnet 노드가 구독하는 상황이다.\
![rosgraph.png](https://coda.io/contentProxy/VhxJbAh6af/blobs/bl-7Zie10vFV-/8b772cff0ef8236871dcfc737d8d4c961d8025c3f94d2a550aa3824387f307880f3b4702f60fd8ca9e8954df3d226b0140a8b0114a3eda3b2f214f407e9d2558b3ce2ccc7313f5f73f3a8473e98b5d883d787ce34e0bf92ce3e1fd4520f77e3f2a03bb46)\
**ROS 노드에서 deep learning 추론 API 사용 방법**deep learning 추론 라이브러리를 ROS 노드에서 어떻게 호출되는지 살펴보겠다.imagenet 노드를 구현한 [C++ 코드](https://github.com/dusty-nv/ros_deep_learning/blob/master/src/node_imagenet.cpp)에서 이미지를 구독했을 때 void img\_callback( const sensor\_msgs::ImageConstPtr& input ) 함수가 불려진다.이 함수는 아래와 같다. 처음 부분은 입력 이미지를 추론 엔진이 사용하는 입력 포맷으로 변환하는 부분이다.이후 이미지를 분류하기 위해 deep learning 추론 엔진을 부르는 부분은 net->Classify(); 이다.4개의 파라미터를 요구하는데 첫번째는 이미지이고 두번째, 세번째는 이미지의 size 정보이다. 나머지는 추론 확률을 받기 위한 변수이다.이 함수는 분류한 이미지의 레이블 번호를 반환한다. 자세한 정보는 [Jetson Inference: imageNet Class Reference](https://rawgit.com/dusty-nv/jetson-inference/python/docs/html/classimageNet.html#a11d6b18e0036ef5a329192f8b659dff9) 를 참고한다.

```
void img_callback( const sensor_msgs::ImageConstPtr& input )
```

```
{
```

```
    // convert the image to reside on GPU
```

```
    if( !cvt || !cvt->Convert(input) )
```

```
    {
```

```
        ROS_INFO("failed to convert %ux%u %s image", input->width, input->height, input->encoding.c_str());
```

```
        return;
```

```
    }
```

```
```

```
    // classify the image
```

```
    float confidence = 0.0f;
```

```
    const int img_class = net->Classify(cvt->ImageGPU(), cvt->GetWidth(), cvt->GetHeight(), &confidence);
```

```
```

```
    // verify the output
```

```
    ...
```

```
}
```

위 코드에서 net 변수를 초기화 하는 방법은 다음과 같다.

```
#include <jetson-inference/imageNet.h>
```

```
imageNet::NetworkType model = imageNet::NetworkTypeFromStr("googlenet"));
```

```
imageNet* net = imageNet::Create(model);
```

처음에는 ROS의 python2 code로 구현한 노드가 어떻게  deep learning 의 python3 API를 부르는지 궁금했는데 실상은 python을 사용하지 않고 둘다 C++로 코딩되어 있었다.<br>

#### jetbot\_ros 패키지 설치

이 패키지는 다음의 3개의 노드를 제공한다.

* jetbot\_motors.py 노드: 젯봇의 모터 제어 메시지를 구독하여 모터를 제어하는 노드
* jetbot\_oled.py 노드: 문자열 메시지를 구독하여 OLED display 에 표시하는 노드
* jetbot\_camera 노드: 카메라에서 이미지 프레임을 얻어 이를 발행하는 노드

이 패키지를 설치하기 전에 python2 코드로 모터를 구동할 수 있게 해주는 Adafruit 모터 드라이버 라이브러리와 OLED 디스플레이 드라이버를 먼저 설치해야 한다. jetbot에는 이미 기본으로 "기본 동작 확인" 장에서 사용한 jetbot이라는 모터 제어 라이브러리가 있으나 이는 python3 코드로 작성되어야 이 라이브러리를 사용할 수 있다. 그러나 ROS는 python3를 지원하지 않기 때문에 따로 python2용 드라이버를 설치해야 한다.

```
$ sudo apt install python-pip
```

```
$ pip install Adafruit-MotorHAT
```

```
$ pip install Adafruit-SSD1306
```

이후 패키지 설치는 다음과 같이 한다.

```
$ cd ~/workspace/catkin_ws/src
```

```
$ git clone https://github.com/dusty-nv/jetbot_ros
```

```
$ cd ../
```

```
$ catkin_make
```

**jetbot\_motors.py 노드 테스트**jetbot\_motors.py 노드는 다음의 3개의 토픽을 구독한다.

1. /jetbot\_motors/cmd\_dir : 진행 방향 (degree \[-180.0, 180.0], speed \[-1.0, 1.0])
2. /jetbot\_motors/cmd\_raw : 왼쪽/오른쪽 모터의 속도(speed \[-1.0, 1.0], speed \[-1.0, 1.0])
3. /jetbot\_motors/cmd\_str : String으로 표현된 진행 명령 (left/right/forward/backward/stop)

그런데, 이 글을 작성하는 현재는 3번 토픽만 구현되어 있다.jetbot\_motors.py 노드를 테스트하기 위해 다음과 같이 이 노드를 시작한다. (roscore는 동작중이라 가정)

```
$ rosrun jetbot_ros jetbot_motors.py
```

다음과 같이 메시지를 발행한다. 모터가 메시지에 따라 움직이는 것을 볼 수 있다.

```
$ rostopic pub /jetbot_motors/cmd_str std_msgs/String --once "forward"
```

```
$ rostopic pub /jetbot_motors/cmd_str std_msgs/String --once "backward"
```

```
$ rostopic pub /jetbot_motors/cmd_str std_msgs/String --once "left"
```

```
$ rostopic pub /jetbot_motors/cmd_str std_msgs/String --once "right"
```

```
$ rostopic pub /jetbot_motors/cmd_str std_msgs/String --once "stop"
```

만약 동작하지 않으면 다음과 같이 jetbot 사용자를 i2c 그룹에 포함시키고 다시 부팅한 다음 실행해 본다.가이드에 이것을 먼저 실행하도록 설명되어 있으나 실제로 해보니 이를 하지 않아도 실행되었다.

```
$ sudo usermod -aG i2c $USER
```

\
**jetbot\_oled.py 노드 테스트**jetbot\_oled.py 노드는 /jetbot\_old/user\_text 토픽을 구독하여 구독된 메시지를 OLED display에 출력한다.테스트는 다음과 같이 할 수 있다. "Auroca!"가 출력된다.

```
$ rosrun jetbot_ros jetbot_oled.py &
```

```
$ rostopic pub /jetbot_oled/user_text std_msgs/String --once "Auroca!"
```

**jetbot\_camera 테스트**jetbot\_camera 노드를 다음과 같이 실행해 보자.

```
$ rosrun jetbot_ros jetbot_camera
```

이후 카메라로 들어오는 비디오 프레임은 /jetbot\_camera/raw 토픽으로 발행된다.이때 타입은 sensor\_msgs::Image 이며 BGR8 형식으로 인코딩된 이미지이다.안타깝게도 발행되는 이미지를 볼수 있는 구독 노드가 참고 자료에서 주어지지 않았다.하지만 [rqt\_image\_view](http://wiki.ros.org/rqt_image_view) 도구를 사용하면 쉽게 볼 수 있지 않을까 해서 아래와 같이 시도해 보았다.만약 rqt\_image\_view 가 설치되어 있지 않다면 다음과 같이 설치한다.

```
sudo apt install ros-melodic-rqt-image-view
```

다음을 실행하고 /jetbot\_camera/raw를 구독해 본다.

```
$ rqt_image_view
```

일단 구독된 이미지는 보여지지 않고 다음과 같은 메시지를 볼 수 있다.

```
[ WARN] [1570983432.590765540]: [image_transport] It looks like you are trying to subscribe directly to a transport-specific image topic '/jetbot_camera/raw', in which case you will likely get a connection error. Try subscribing to the base topic '/jetbot_camera' instead with parameter ~image_transport set to 'raw' (on the command line, _image_transport:=raw). See http://ros.org/wiki/image_transport for details.
```

메시지에 따르면 발행되는 이미지를 날로(raw) 구독하는 경우 워낙 밀고들어오는 메시지가 많아서 통신이 안될 수 있으므로 image\_transport 패키지를 사용해서 압축해서 구독하라는 의미로 보인다. [image\_transport의 Wiki](http://ros.org/wiki/image_transport) 를 참고해서 각자 시도해 보자.\
이 이미지 메시지 타입에 대한 자세한 정보는 [여기를](https://docs.ros.org/melodic/api/sensor_msgs/html/msg/Image.html) 참고한다.

### 마치며

지금까지 소개한 ROS Framework의 노드를 활용하면 앞장의 충돌회피, 물체따라가기, 길 따라가기를 ROS framework으로 재작성할 수 있다.즉 jetbot\_camera 노드로 카메라 이미지를 발행하고 이를 imagenet/detectnet/segnet 노드로 실시간 분석하여 적절하게 모터제어 메시지를 발행한다. 최종적으로 jetbot\_motor.py 노드가 이 메시지를 구독하여 모터를 움직이게 하는 식이다.이를 그래프로 도시하면 아래와 같다.

![image.png](https://coda.io/contentProxy/VhxJbAh6af/blobs/bl-340espUvHI/d42eae1feb404d9f27dae404dacfcb944114de845517a7baf9a0c19f02b28bcbe4f32941f201443450d6b8456a512d8487e01eb053b7971557555da3d934f5aca4614d89e88f5bde005b69c4b0b56914cc6caff3855f7b742b4a790efd44244e26d58d9d)

\
이를 위해서는 imagenet/detectnet/segnet 노드가 이미지의 분석결과로부터 어떻게 모터를 제어할 지에 대한 제어 전략 구현과 jetbot\_motors.py 노드로 메시지를 발행하는 코드를 추가하는 수정이 필요하다.기존의 프로그래밍 방법에 비해 ROS를 사용하여 프로그래밍하는 방법의 장점은 여기서는 사실 크지 않을 것 같다. 다만 ROS 프로그래밍을 연습하는 예로서 의미는 찾을 수 있다.


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://robertchoi.gitbook.io/jetbot/6-ros.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
