Практическое использование ROS на Raspberry Pi — часть 2

в 20:54, , рубрики: arduino, ROS, робототехника

Добрый день, уважаемые читатели ! Это вторая статья из цикла статей о практическом использовании ROS на Raspberry Pi. В первой статье цикла я описал установку необходимых компонент ROS и настройку рабочего окружения для работы.
Во второй части цикла мы приступим к практическому использованию возможностей ROS на платформе Raspberry Pi. Конкретно в данной статье я собираюсь рассказать об использовании камеры Raspberry Pi Camera Board на Raspberry Pi в связке с ROS для решения задач компьютерного зрения. Кто заинтересован, прошу под кат.

Камера RPi Camera Board

Для работы нам нужна будет такая вот камера Raspberry Pi Camera Board:
image

Эта камера подключается напрямую к графическому процессору через CSi-разъем на плате, что позволяет записывать и кодировать изображение с камеры без использования процессорного времени. Для подключения камеры используется ZIF-шлейф. Разъем для подключения шлейфа на плате находится между портами Ethernet и HDMI:

image

Установка библиотек

Итак приступим к установке необходимых библиотек. Для начала установим OpenCV:

$ sudo apt-get install libopencv-dev

Для использования камеры Raspberry Pi Camera нам будет нужна библиотека raspicam. Скачайте архив отсюда.
Далее установим библиотеку:

$ tar xvzf raspicamxx.tgz
$ cd raspicamxx
$ mkdir build
$ cd build
$ cmake ..
$ make
$ sudo make install
$ sudo ldconfig

Нужно включить поддержку камеры в Raspbian через программу raspi-config:

$ sudo raspi-config

Выберите опцию 5 — Enable camera, сохраните выбор и выполните ребут системы.

Начало работы с ROS

Для удобства работы создадим новый catkin воркспейс для своих пакетов:

$ mkdir -p ~/driverobot_ws/src
$ cd ~/driverobot_ws/src
$ catkin_init_workspace
$ cd ~/driverobot_ws
$ catkin_make

Создайте новый пакет ROS:

$ catkin_create_pkg raspi_cam_ros image_transport cv_bridge roscpp std_msgs sensor_msgs compressed_image_transport opencv2

Спецификация команды catkin_create_pkg следующая: catkin_create_pkg <package_name> [depend1] [depend2],
где вы можете указать под depend сколько угодно зависимостей — библиотек, которые будет использовать пакет.
Эта команда создает каркас проекта ROS: пустую директорию src для скриптов узлов и конфигурационные файлы CMakeLists.txt and package.xml.
Я нашел простой скрипт, который получает кадры с камеры и публикует их с помощью «паблишера» (publisher), и адаптировал его под ROS Indigo. Скачать его можно отсюда.
Для его использования нужно установить Raspberry Pi UV4L camera driver:

$ curl http://www.linux-projects.org/listing/uv4l_repo/lrkey.asc | sudo apt-key add -

Добавьте следующую строку в файл /etc/apt/sources.list

deb http://www.linux-projects.org/listing/uv4l_repo/raspbian/ wheezy main

, обновите пакеты и выполните установку:

$ sudo apt-get update
$ sudo apt-get install uv4l uv4l-raspicam

Для загрузки драйвера при каждой загрузке системы также установим необязательный пакет:

$ sudo apt-get install uv4l-raspicam-extras

Перейдем к редактированию пакета. Вставьте в ваш файл CMakeLists.txt строки отсюда.
Самые важные строки в файле CMakeLists.txt:

link_directories(/usr/lib/uv4l/uv4lext/armv6l/)
…
target_link_libraries(capture ${catkin_LIBRARIES} uv4lext)

Таким образом мы добавляем ссылку на драйвер uv4l, необходимый для компиляции пакета.
В конце файла мы задаем специальные строки для нашего узла:

add_executable(capture src/capturer.cpp)
target_link_libraries(capture ${catkin_LIBRARIES} uv4lext)

Строка add_executable создает бинарник для запуска и target_link_lbraries линкует дополнительные библиотеки для бинарника capture.
Вставьте недостающие строки отсюда в файл package.xml, чтобы он выглядел вот так:

<?xml version="1.0"?>                                                                                            
<package>                                                                                                        
  <name>test_rpi_cam</name>                                                                                      
  <version>0.0.0</version>                                                                                       
  <description>The test_rpi_cam package</description>                                                            
                                                                                                                 
  <maintainer email="pi@todo.todo">pi</maintainer>                                                               
                                                                                                                 
  <license>TODO</license>                                                                                        
  <buildtool_depend>catkin</buildtool_depend>                                                                    
  <build_depend>cv_bridge</build_depend>                                                                         
  <build_depend>image_transport</build_depend>                                                                   
  <build_depend>roscpp</build_depend>                                                                            
  <build_depend>std_msgs</build_depend>                                                                          
  <build_depend>sensor_msgs</build_depend>                                                                       
  <build_depend>opencv2</build_depend>                                                                           
  <build_depend>compressed_image_transport</build_depend>                                                        
  <run_depend>cv_bridge</run_depend>                                                                             
  <run_depend>image_transport</run_depend>                                                                       
  <run_depend>roscpp</run_depend>                                                                                
  <run_depend>std_msgs</run_depend>                                                                              
  <run_depend>sensor_msgs</run_depend>                                                                           
  <run_depend>opencv2</run_depend>                                                                               
  <run_depend>compressed_image_transport</run_depend>                                                                                                                                                                    
</package>

В первых строках задаются базовые параметры узла — название, версия, описание, информация об авторе. Строки build_depend определяют зависимости от библиотек, необходимые для компиляции пакета, а строки run_depend — зависимости, необходимые для запуска кода в пакете.
Создайте файл capturer.cpp внутри папки src и вставьте строки отсюда. Здесь в методе main() происходит инициализация узла и его запуск в цикле:

ros::init(argc, argv,"test_rpi_cam");
ros::NodeHandle n;
UsbCamNode a(n);
a.spin();

Вся логика скрипта заключается в том, что мы получаем картинку с камеры средствами OpenCV, оборачиваем ее в сообщение для ROS в методе fillImage и публикуем в топик. Здесь используется пакет image_transport для создания “паблишера” картинки.
Запустим драйвер uv4l выполнив команду:

$ uv4l --driver raspicam --auto-video_nr --width 640 --height 480 --nopreview

что создаст MJPEG-стриминг.
Скомпилируем наш узел ROS:

$ roscore
$ cd ~/driverobot_ws
$ catkin_make

Проверьте значение переменной ROS_PACKAGE_PATH:

echo $ROS_PACKAGE_PATH
/opt/ros/indigo/share:/opt/ros/indigo/stacks

Значение переменной ROS_PACKAGE_PATH должно включать путь до нашего воркспейса. Добавим наш воркспейс в путь:

$ source devel/setup.bash

Теперь запустив команду echo $ROS_PACKAGE_PATH еще раз мы должны увидеть подобный вывод:

/home/youruser/catkin_ws/src:/opt/ros/indigo/share:/opt/ros/indigo/stacks

, где /home/<user_name>/catkin_ws/src — это путь до нашего воркспейса. Это означает, что ROS может «видеть» наши узлы, созданные в catkin_ws и мы можем их запускать через rosrun.
Запустим наш узел ROS:

$ rosrun test_rpi_cam capture

Запустим графическую программу rqt_image_view для отображения видеопотока с топика:

$ rosrun rqt_image_view rqt_image_view

Выберем топик image_raw в окне rqt_image_view

image

При запуске узла может возникнуть ошибка “Gtk-WARNING **: cannot open display: -1” при работе через ssh или “GdkGLExt-WARNING **: Window system doesn't support OpenGL.” при запуске в режиме работы удаленного рабочего стола VNC. Решение — подключиться к Raspberry Pi через SSH с X11 forwarding:

$ ssh -X pi@<host_pi>

Можно узнать с какой частотой публикуются сообщения в топик с помощью команды rostopic:

rostopic hz image_raw

Эта команда вычисляет частоту получения сообщений на топик каждую секунду и выводит ее в консоль.
У меня для модели B+ был вывод такого вида:

average rate: 7.905
              min: 0.075s max: 0.249s std dev: 0.02756s 

Как видим частота публикации сообщений — 8 Гц.
Я также проверил частоту публикации изображений с камеры на модели RPi 2. Здесь результаты были в разы лучше:

average rate: 30.005
              min: 0.024s max: 0.043s std dev: 0.00272s 

Сообщения уже публикуются с частотой 30 Гц, что является довольно хорошим увеличением скорости по сравнению с моделью B+.

Visual-based управление роботом с OpenCV и ROS

Сейчас мы напишем небольшой пакет ROS для использования компьютерного зрения на роботе с Raspberry Pi, который будет выполнять алгоритм распознавания (в нашем случае визуальное ориентирование методом line following) и публиковать величину необходимого смещения робота в топик. С другой стороны узел управления движением робота будет подписываться на этот топик и посылать команды управления движением на Arduino.
Сейчас добавим в скрипт capturer.cpp “паблишер”, который будет публиковать величину сдвига. Сначала включим определение типа сообщения для величины сдвига — std_msgs/Int16.

#include <std_msgs/Int16.h>

rosserial берет специальные файлы сообщений msg и генерирует исходный код для них. Используется такой шаблон:

package_name/msg/Foo.msg → package_name::Foo

Исходный код для стандартных сообщений rosserial хранится в папке package_name внутри директории ros_lib.
Далее инициализируем сообщение для данного типа:

std_msgs::Int16 shift_msg;

Создаем “паблишера”:

ros::Publisher shift_pub;

и внутри конструктора UsbCamNode даем ему определение:

shift_pub = nh.advertise<std_msgs/Int16>(“line_shift”, 1);

Здесь мы задаем тип сообщений и название топика для публикации.
Дальше добавим логику вычисления величины сдвига линии средствами OpenCV и ее публикации в топик line_shift в метод take_and_send_image() перед строкой #ifdef OUTPUT_ENABLED:

// Some logic for the calculation of offest
shift_msg.data = offset;
shift_pub.publish(shift_msg);

У меня нет готового алгоритма следования линии, поэтому читатель волен написать свою собственную логику здесь.
Фактически данные в сообщении сохраняются в поле data. Структуру сообщения можно посмотреть с помощью команды:

$ rosmsg show std_msgs/Int16

Теперь запустим узел:

$ rosrun raspi_cam_ros capturer

Используем команду rostopic echo для вывода данных, публикуемых в топик line_shift:

$ rostopic echo line_shift

Теперь добавим “сабскрайбер” в узле управления роботом. Включим определение типа сообщения:

#include <std_msgs/UInt16.h>

Затем добавляем callback-функцию, которая выполняется при получении сообщения из топика.

void messageCb(const std_msgs::UInt16& message) 
{
  int shift_val = int(message.data);
  
  char* log_msg;
  if(shift_val < 0) log_msg = "Left";
  else if(shift_val > 0 ) log_msg = "Right";
  else log_msg = "Forward";
  
  nh.loginfo(log_msg);
}

callback функция должна иметь тип void и принимать const ссылку типа сообщения в качестве аргумента.
Для простоты я вывожу в лог сообщение о направлении смещения линии. Вы можете здесь добавить собственную логику движения робота для своего сценария.
Создаем подписчика на сообщения из топика line_shift.

ros::Subscriber<std_msgs::UInt16> sub("line_shift", &messageCb);

Здесь задаем название топика и ссылку на callback-функцию.
Дальше идут стандартные методы скетча для rosserial_arduino:

void setup()
{
  nh.initNode();
  nh.subscribe(sub);
  
  Serial.begin(57600);
}

void loop()
{
  nh.spinOnce();
  delay(100);
}

Единственное отличие — это то, что мы добавляем nh.subscribe(sub) для создания фактической “подписки” узла на топик.
Скетч для управления роботом можно скачать отсюда.
Маленькая хитрость! В ROS существуют специальные launch файлы, которые позволяют запускать узлы как отдельные процессы автоматически с определенными параметрами. launch файлы создаются в формате xml и их синтаксис позволяет запускать множество узлов сразу. Однако launch файл не гарантирует, что узлы будут запущены в точно заданном порядке.
Можно создать launch файл для более легкого запуска сервера rosserial_python.

$ cd <catkin_ws>/src
$ catkin_create_pkg rosserial_controller
$ cd src/rosserial_controller
$ vim rosserial_controller.launch

Напишем launch файл такого содержания:

<launch>
 <node pkg="rosserial_python" type="serial_node.py" name="arduino_serial">
       <param name="port" value="/dev/ttyACM0"/>       
 </node>
</launch>

Скомпилируем и запустим его:

$ cd ~/<catkin_ws>
$ catkin_make
$ source devel/setup.bash
$ roslaunch rosserial_controller rosserial_controller.launch

Мы можем визуализировать значения, публикуемые в тему line_shift, с помощью утилиты rqt_plot, как это сделано в статье:

$ rqt_plot line_shift

Теперь вы можете использовать все преимущества камеры Raspberry Pi Camera и библиотеки OpenCV в своих сценариях визуального ориентирования робота, распознавания объектов, слежения и многих других. Дайте волю фантазии!
В следующий раз мы поговорим об управлении роботом в режиме teleoperation с помощью нажатия клавиш на клавиатуре.

Автор: vovaekb90

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js