В сфере робототехники начальное обучение играет критическую роль, и я, как преподаватель робототехники, знаю, насколько важно делать сложные вещи понятными. Эта статья предназначена для школьников, студентов и энтузиастов, которые только начинают свой путь. Мы сосредоточимся на базовых аспектах: как установить 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/<package_name>/launch внутри установочного каталога. Когда вы устанавливаете пакет с помощью colcon build, этот файл будет скопирован в указанное место, и вы сможете запустить его через ros2 launch.

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

  • Убедитесь, что ваша среда ROS 2 активирована:

source /opt/ros/humble/setup.bash  # замените "humble" на вашу версию ROS 2, если она другая
  • Перейдите в корневую папку вашего workspace:

cd ~/ros2_ws
  • Запустите colcon build, чтобы пересобрать ваш пакет:

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

source install/setup.bash
  • Запустите launch-файл (при необходимости измените имя пакета или файла):

ros2 launch capture_camera camera_launch.py 

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

Комментарии (9)


  1. Jury_78
    11.11.2023 17:44
    +6

    Хотя бы пару слов, для тех кто не в теме, что за ROS?


    1. Stepan_Burmistrov Автор
      11.11.2023 17:44

      ROS2 — это второе поколение Robot Operating System, фреймворка, надстройки над операционной системой для удобной разработки робототехнических систем.

      https://habr.com/ru/articles/492058/


    1. Alesh
      11.11.2023 17:44

      Ага, еле сдержался, что бы не минусануть ;)


      1. solarplexus
        11.11.2023 17:44

        Странное желание - минусить то, что не знаешь. Я много каких технологий не знаю, например ReactOS. У меня и в мыслях не было бы желания минусануть статью про реактос, где сразу с места в карьер, без описания что это и для чего.


        1. Alesh
          11.11.2023 17:44

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


          1. solarplexus
            11.11.2023 17:44
            +1

            Могу предположить, что если что-то не знакомое, то скорей всего, вас не заинтересует.

            Возьмем несколько примеров.

            1. Человек знает что такое ROS. Мыслей о том что надо бы вначале описать что это - нет.

            2. Человек не знает что это. Человек не будет применять знания из статьи не зависимо узнает ли он предназначение ROS, или нет (похоже, что вы тут).

            3. Человек не знает что это. Заинтересуется, начнет изучать. Да, возможно, было бы полезным оставить абзац о том, что это. Но, каковы шансы?) (А может, вы тут?).

            Я не раз натыкался на подобного рода статьи без описания для чего это. Абсолютно было пофиг на отсутствие описания. Сам гуглил. А потом решал надо ли мне это.

            Ну, кстати, я знаком с ROS, добавил в закладки. Но, даже тут есть сомнения, что мне это пригодится).


  1. wl2776
    11.11.2023 17:44

    Статья является пересказом официальных инструкций от предыдущей версии.

    Оригинал последней версии


    1. Stepan_Burmistrov Автор
      11.11.2023 17:44

      Действительно, это так в начале статьи! Т.к. без этой информации статья была бы инструкцией "с 0".
      Прочитайте ее полностью и увидите, что в ней содержится много ценной для новичков информации о создании нод, принципов обмена между ними. Особенности подключения к камерам и создание лаунч файла.


  1. johnfound
    11.11.2023 17:44
    -1

    ROS это ReactOS