Простая установка ROS2 на Ubuntu 22.04: Руководство для начинающих
В сфере робототехники начальное обучение играет критическую роль, и я, как преподаватель робототехники, знаю, насколько важно делать сложные вещи понятными. Эта статья предназначена для школьников, студентов и энтузиастов, которые только начинают свой путь. Мы сосредоточимся на базовых аспектах: как установить ROS2 на Ubuntu 22.04 и запустить первые ноды для работы с изображениями и текстом.
Цель этой статьи — помочь вам быстро начать работу с ROS2, поэтому многие вещи, связанные с безопасностью или профессиональным использованием Linux опущены, хотя буду рад, если вы отметите их в комментариях. Мы пошагово разберем процесс установки и запуска трех нод: одна будет считывать изображения с веб-камеры и передавать картинку и текстовое сообщение, вторая — получить изображение для дальнейшей обработки, а третья — получить текстовые сообщения. Это основа, которая позволит вам в будущем создавать различные проекты.
Этот материал был проверен на Raspberry Pi 4 с Ubuntu Server 22.04.
Приступим к установке ROS2.
Установка 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
, что гарантирует однозначное подключение к выбранной камере.
Создание ноды 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
Это нода будет выполнять следующие задачи:
Получение изображений: С помощью камеры, используя библиотеку OpenCV
Обработка и публикация: Используя библиотеку
cv_bridge
, нода преобразует полученные изображения в формат, совместимый с ROS, и отправляет их в топикcamera/image
. Это делает изображения доступными для других нод в ROS.Публикация информации: Она также отправляет текстовые сообщения, которые содержат информацию о времени захвата каждого изображения, в топик
camera/info
.Сохранение изображений: Функция
save_image
сохраняет захваченные изображения в определённой директории и ограничивает количество хранящихся изображений до последних 20, удаляя старые.Таймеры: Устанавливаются два таймера — один для функции
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
Теперь ваша нода запущена и работает. Откройте второй терминал, для работы со следующей нодой!
Создание ноды 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
Создание ноды 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-файла:
Создание 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'
),
])
Добавьте 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
.Инструкция по запуску
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-файлы и запускать их, чтобы ноды работали вместе. Я бы сказал, что практика, работа с ошибками и тестирование — лучшие помощники в обучении. Удачи в разработке!