Простая установка ROS2 на Ubuntu 22.04: Руководство для начинающих

3eef047702c1b6a9aa8e09e9c4d05446.png

В сфере робототехники начальное обучение играет критическую роль, и я, как преподаватель робототехники, знаю, насколько важно делать сложные вещи понятными. Эта статья предназначена для школьников, студентов и энтузиастов, которые только начинают свой путь. Мы сосредоточимся на базовых аспектах: как установить ROS2 на Ubuntu 22.04 и запустить первые ноды для работы с изображениями и текстом.

Цель этой статьи — помочь вам быстро начать работу с ROS2, поэтому многие вещи, связанные с безопасностью или профессиональным использованием Linux опущены, хотя буду рад, если вы отметите их в комментариях. Мы пошагово разберем процесс установки и запуска трех нод: одна будет считывать изображения с веб-камеры и передавать картинку и текстовое сообщение, вторая — получить изображение для дальнейшей обработки, а третья — получить текстовые сообщения. Это основа, которая позволит вам в будущем создавать различные проекты.

Этот материал был проверен на Raspberry Pi 4 с Ubuntu Server 22.04.

Приступим к установке ROS2.

  1. Установка ROS2 Humble на Ubuntu 22.04

Шаг 1: Установка Locale

Нам нужно убедиться, что наша система использует UTF-8:

sudo apt update && sudo apt install locales
sudo locale-gen en_US en_US.UTF-8
sudo update-locale LC_ALL=en_US.UTF-8 LANG=en_US.UTF-8
export LANG=en_US.UTF-8

Шаг 2: Добавление репозитория ROS 2

sudo apt update && sudo apt install curl gnupg2 lsb-release
curl -s https://raw.githubusercontent.com/ros/rosdistro/master/ros.asc | sudo apt-key add -

Добавьте репозиторий в список источников:

sudo sh -c 'echo "deb [arch=$(dpkg --print-architecture)] http://packages.ros.org/ros2/ubuntu $(lsb_release -cs) main" > /etc/apt/sources.list.d/ros2.list'

Шаг 3: Установка ROS 2

Сначала обновите список пакетов:

sudo apt update

Затем установите ROS 2:

sudo apt install ros-humble-desktop

Шаг 4: Источник ROS 2 setup.bash в .bashrc

Это позволит ROS 2 быть доступным каждый раз, когда вы открываете новое окно терминала:

echo "source /opt/ros/humble/setup.bash" >> ~/.bashrc

Обновите существующий терминал, запустив:

source ~/.bashrc

Шаг 5: Установите пакет setuptools. Это предотвращает ошибки при компиляции пакетов.

pip3 install setuptools==58.2.0

На этом этапе установочный процесс ROS2 на Ubuntu 22.04 успешно завершен, и мы можем переходить к следующей части — созданию нод. Но перед этим давайте уточним несколько ключевых терминов, которые будут использоваться далее.

Ноды — это основные исполняемые процессы в ROS2, которые выполняют конкретные задачи. Каждая нода может считывать данные, принимать решения или управлять действиями в системе робота. В контексте нашего проекта, мы создадим ноды, которые будут работать с изображениями и текстовыми сообщениями.

Топики — это каналы именованных данных, по которым ноды могут обмениваться сообщениями (с различными типами данных). Это позволяет одной ноде публиковать информацию, которая затем читается другими нодами. Топики — это способ передачи информации между нодами без необходимости знать детали их реализации. Этот подход позволяет разграничить процессы, что существенно упрощает разработку робота в команде.

Теперь, когда мы разобрались с основными понятиями, давайте начнем с создания первой ноды. Эта нода будет называться 'CaptureCamera', и её задача — считывать изображения с подключенной к системе веб-камеры.

В контексте программирования нод для робототехнических систем, выбор камеры на основе её индекса с помощью функции cv2.VideoCapture(0) может привести к нестабильности, поскольку нумерация устройств подвержена изменениям при перезагрузке системы. Такой подход осложняет однозначную идентификацию камеры, в случае, когда камер в роботе несколько

Для обеспечения правильного выбора видеоустройства предпочтительно использование статических путей к устройствам, используя адреса USB-портов, к которым они подключены. В Linux это достигается путём обращения к ссылкам в /dev/v4l/by-path/, которые не изменяются между сеансами и указывают на конкретные физические порты.

Примером такого подхода служит следующий код:

path = "/dev/v4l/by-path/platform-fd500000.pcie-pci-0000:01:00.0-usb-0:1.2:1.0-video-index0"
cap = cv2.VideoCapture(path, cv2.CAP_V4L)

Здесь path указывает на уникальный идентификатор устройства, связанный с конкретным портом USB. Объект VideoCapture инициализируется с использованием этого пути и флага cv2.CAP_V4L, что гарантирует однозначное подключение к выбранной камере.

  1. Создание ноды CaptureCamera

Чтобы создать ноду CaptureCamera, вам понадобится библиотека для работы с камерой. В этом примере мы будем использовать cv_bridge, который является интерфейсом между ROS2 и OpenCV.

Шаг 1: Создайте новый пакет

mkdir -p ~/ros2_ws/src
cd ~/ros2_ws/src
ros2 pkg create --build-type ament_python capture_camera

Шаг 2: Установите зависимости

sudo apt install ros-humble-cv-bridge

Шаг 3: Создайте новый файл Python в вашем пакете

nano ~/ros2_ws/src/capture_camera/capture_camera/capture_camera.py

Шаг 4: Добавьте следующий код в capture_camera.py

Это нода будет выполнять следующие задачи:

  1. Получение изображений: С помощью камеры, используя библиотеку OpenCV

  2. Обработка и публикация: Используя библиотеку cv_bridge, нода преобразует полученные изображения в формат, совместимый с ROS, и отправляет их в топик camera/image. Это делает изображения доступными для других нод в ROS.

  3. Публикация информации: Она также отправляет текстовые сообщения, которые содержат информацию о времени захвата каждого изображения, в топик camera/info.

  4. Сохранение изображений: Функция save_image сохраняет захваченные изображения в определённой директории и ограничивает количество хранящихся изображений до последних 20, удаляя старые.

  5. Таймеры: Устанавливаются два таймера — один для функции capture_and_publish, вызываемой каждую секунду для захвата и публикации изображений, и второй для функции save_image, вызываемой каждые 5 секунд для сохранения изображений.

Все эти действия обеспечивают поток данных от камеры к нодам робота и позволяют разработчикам использовать эти данные для различных задач.

# Импортируем необходимые библиотеки
import rclpy
from rclpy.node import Node
from cv_bridge import CvBridge
from sensor_msgs.msg import Image
from std_msgs.msg import String
import cv2
import time
import os
import glob

# Определяем класс CaptureCameraNode, который является ROS2-нодой для захвата изображений с камеры
class CaptureCameraNode(Node):
    def __init__(self):
        # Инициализируем родительский класс Node с именем 'capture_camera'
        super().__init__('capture_camera')
        
        # Создаем объект CvBridge для преобразования изображений между OpenCV и ROS
        self.bridge = CvBridge()

        # Создаем издателей для публикации изображений и текстовых сообщений
        self.publisher = self.create_publisher(Image, 'camera/image', 10)
        self.text_publisher = self.create_publisher(String, 'camera/info', 10)

        # Определяем путь к устройству камеры
        path = "/dev/v4l/by-path/platform-fd500000.pcie-pci-0000:01:00.0-usb-0:1.2:1.0-video-index0"
        # Инициализируем объект VideoCapture для захвата видео с камеры
        self.cap = cv2.VideoCapture(path, cv2.CAP_V4L)
        print("Camera created", self.cap)

        # Создаем таймер, который вызывает функцию capture_and_publish каждую секунду
        self.timer = self.create_timer(1.0, self.capture_and_publish)

        # Определяем путь для сохранения изображений
        self.image_save_path = "/home/pi/images"
        # Если папка для сохранения изображений не существует, создаем ее
        os.makedirs(self.image_save_path, exist_ok=True)
        
        # Создаем таймер, который вызывает функцию save_image каждые 5 секунд
        self.save_timer = self.create_timer(5.0, self.save_image)

    # Функция для захвата и публикации изображения
    def capture_and_publish(self):
        # Захватываем изображение с камеры
        ret, frame = self.cap.read()
        if ret:
            # Выводим текущее время захвата на консоль
            print("Last time of get image:", time.ctime())
            
            # Преобразуем изображение в формат ROS и публикуем его
            msg = self.bridge.cv2_to_imgmsg(frame, "bgr8")
            self.publisher.publish(msg)

            # Подготавливаем и публикуем текстовое сообщение с временем захвата изображения
            msg = String()
            msg.data = f"Last time of get image: {time.ctime()}"
            self.text_publisher.publish(msg)

    # Функция для сохранения изображения и обеспечения хранения только 20 последних изображений
    def save_image(self):
        ret, frame = self.cap.read()
        if ret:
            # Сохраняем изображение с учетом временной метки
            timestamp = time.strftime("%Y%m%d-%H%M%S")
            filename = os.path.join(self.image_save_path, f"image_{timestamp}.jpg")
            cv2.imwrite(filename, frame)
            print(f"Saved image to {filename}")
            
            # Получаем список всех сохраненных изображений
            all_images = glob.glob(os.path.join(self.image_save_path, "*.jpg"))
            # Сортируем список изображений по дате создания
            sorted_images = sorted(all_images, key=os.path.getmtime)
            # Удаляем старые изображения, оставляя только 20 последних
            while len(sorted_images) > 20:
                os.remove(sorted_images[0])
                del sorted_images[0]

def main(args=None):
    # Инициализируем ROS
    rclpy.init(args=args)
    # Создаем объект нашей ноды
    capture_camera = CaptureCameraNode()
    # Запускаем цикл обработки ROS
    rclpy.spin(capture_camera)

if __name__ == '__main__':
    main()

Шаг 5: Измените следующий код в ~/ros2_ws/src/capture_camera/setup.py

Это необходимо для правильной сборки пакета и последующего запуска.

from setuptools import find_packages, setup

package_name = 'capture_camera'

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

Шаг 6: Скомпилируйте ноду.

cd ~/ros2_ws/
source install/setup.bash
colcon build

Шаг 7: Запустите ноду.

ros2 run capture_camera capture_camera

Теперь ваша нода запущена и работает. Откройте второй терминал, для работы со следующей нодой!

  1. Создание ноды ProcessingCamera

Теперь создадим ноду ProcessingCamera, которая будет получать изображение и обрабатывать его.

Эта нода будет подписана на топик camera/image, где она будет ожидать поступления изображений от ноды захвата камеры.

В этой ноде также используется CvBridge для преобразования изображений из формата, понятного ROS, в формат, с которым может работать библиотека OpenCV. Каждый раз, когда в топик приходит новое изображение, вызывается функция image_callback. В этой функции изображение преобразуется в формат OpenCV, что позволяет ноде ProcessingCamera анализировать и обрабатывать изображение.
В нашем случае мы просто будем читать разрешение картинки и выводить его в консоль.

Шаг 1: Создайте новый пакет

cd ~/ros2_ws/src
ros2 pkg create --build-type ament_python processing_camera

Шаг 2: Создайте новый файл Python в вашем пакете

nano ~/ros2_ws/src/processing_camera/processing_camera/processing_camera.py

Шаг 4: Добавьте следующий код в processing_camera.py

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

class ImageReaderNode(Node):

    def __init__(self):
        super().__init__('image_reader_node')  # Инициализируем родительский класс Node с именем 'image_reader_node'

        # Создаем объект CvBridge для преобразования изображений между OpenCV и ROS
        self.bridge = CvBridge()

        # Подписываемся на тему 'camera/image' для чтения изображений
        self.subscription = self.create_subscription(
            Image, 
            'camera/image', 
            self.image_callback, 
            10
        )
        self.subscription

    def image_callback(self, msg):
        # Преобразуем изображение из формата ROS в формат OpenCV
        cv_image = self.bridge.imgmsg_to_cv2(msg, "bgr8")

        # Получаем разрешение изображения
        height, width, _ = cv_image.shape
        print(f"Image resolution: {width}x{height}")

def main(args=None):
    rclpy.init(args=args)
    image_reader = ImageReaderNode()
    rclpy.spin(image_reader)

if __name__ == '__main__':
    main()

Шаг 5: Измените следующий код в ~/ros2_ws/src/processing_camera/setup.py

Шаг 6: Скомпилируйте обе ноды

cd ~/ros2_ws/
source install/setup.bash
colcon build

Шаг 7: Запустите ноды в разных терминалах

cd ~/ros2_ws/
source install/setup.bash
ros2 run capture_camera capture_camera


cd ~/ros2_ws/
source install/setup.bash
ros2 run processing_camera processing_camera
  1. Создание ноды InfoReaderNode

Теперь создадим ноду InfoReaderNode, которая читать текстовые сообщения из топика camera/info в выводить их в консоль.

Все шаги аналогичны предыдущим. Код для ноды:

import rclpy
from rclpy.node import Node
from std_msgs.msg import String

class InfoReaderNode(Node):

    def __init__(self):
        super().__init__('info_reader_node')  # Инициализируем родительский класс Node с именем 'info_reader_node'
        
        # Подписываемся на топик 'camera/info' для чтения текстовых сообщений
        self.subscription = self.create_subscription(
            String, 
            'camera/info', 
            self.info_callback, 
            10
        )
        self.subscription

    def info_callback(self, msg):
        # Выводим полученное текстовое сообщение в консоль
        print(f"Received info: {msg.data}")

def main(args=None):
    rclpy.init(args=args)
    info_reader = InfoReaderNode()
    rclpy.spin(info_reader)

if __name__ == '__main__':
    main()

Для удобного запуска нод, находящихся в разных пакетах ROS2, вам нужно создать launch-файл. Обычно launch-файл размещается в папке launch внутри одного из ваших пакетов ROS2 или в отдельном пакете, предназначенном только для launch-файлов.

Вот шаги по созданию и запуску launch-файла:

  1. Создание launch-файла

Создайте файл с именем camera_launch.py (или другим на ваше усмотрение)

в папке launch одного из ваших пакетов:

from launch import LaunchDescription
from launch_ros.actions import Node

def generate_launch_description():
    return LaunchDescription([
        # Запуск ноды захвата изображений
        Node(
            package='capture_camera',
            executable='capture_camera',
            name='capture_camera'
        ),
        # Запуск ноды для чтения изображений и вывода их размеров
        Node(
            package='processing_camera',
            executable='processing_camera',
            name='processing_camera'
        ),
        # Запуск ноды для чтения текстовой информации из camera/info
        Node(
            package='info_reader',
            executable='info_reader_node',
            name='info_reader_node'
        ),
    ])

  1. Добавьте launch-файл в ваш пакет: Убедитесь, что launch-файл добавлен в data_files в setup.py файлах соответствующих пакетов, чтобы он устанавливался вместе с пакетом.

    Предположим, что ваш launch-файл называется camera_launch.py и находится в папке launch внутри пакета capture_camera. Вот как может выглядеть соответствующий раздел setup.py:

    from setuptools import find_packages, setup
    import os
    
    package_name = 'capture_camera'
    
    setup(
        name=package_name,
        version='0.0.0',
        packages=find_packages(exclude=['test']),  # Exclude any directories named 'test'
        data_files=[
            ('share/ament_index/resource_index/packages',
                ['resource/' + package_name]),
            ('share/' + package_name, ['package.xml']),
            # Включаем launch файлы
            (os.path.join('share', package_name, 'launch'), ['launch/camera_launch.py']),
        ],
        install_requires=['setuptools'],
        zip_safe=True,
        maintainer='pi',
        maintainer_email='pi@todo.todo',
        description='TODO: Package description',
        license='TODO: License declaration',
        tests_require=['pytest'],
        entry_points={
            'console_scripts': [
                'capture_camera = capture_camera.capture_camera:main',
            ],
        },
    )
    

    Обратите внимание на следующую строку:

    (os.path.join('share', package_name, 'launch'), ['launch/camera_launch.py']),

    Эта строка говорит setuptools, что нужно включить файл camera_launch.py в папку share//launch внутри установочного каталога. Когда вы устанавливаете пакет с помощью colcon build, этот файл будет скопирован в указанное место, и вы сможете запустить его через ros2 launch.

  2. Инструкция по запуску

source /opt/ros/humble/setup.bash  # замените "humble" на вашу версию ROS 2, если она другая
cd ~/ros2_ws
colcon build
  • Выполните source install/setup.bash, это обновляет текущую сессию терминала с необходимыми переменными среды и конфигурациями для работы с ROS. Это важно делать каждый раз при открытии нового терминала, если вы собираетесь работать с ROS, так как без этого команды ROS могут быть недоступны или работать некорректно.

source install/setup.bash
ros2 launch capture_camera camera_launch.py 

И вот мы завершили основные действия с ROS2 для начала работы. Мы научились устанавливать и настраивать ноды, создавать launch-файлы и запускать их, чтобы ноды работали вместе. Я бы сказал, что практика, работа с ошибками и тестирование — лучшие помощники в обучении. Удачи в разработке!

© Habrahabr.ru