После формирования класса распознавания лица на изображении, структура которого представлена на рисунке 1, его необходимо предоставить пакету ROS2 для использования, структура которого представлена на рисунке 2.
Рисунок 1. Структура класса распознавания лиц
Рисунок 2. Структура файлов пакета ROS2
Для обнаружения пакета инструментами ROS2, данная структура обязательно должна содержать файлы setup.py и __init__.py, первый из которых содержит всю информацию о программном продукте:
from setuptools import setup
package_name = "package_name"
setup(
name=package_name,
version="0.0.1",
packages=[package_name],
data_files=[
("share/ament_index/resource_index/packages",
["resource/" + package_name]),
("share/" + package_name, ["package.xml"]),
],
install_requires=["setuptools"],
entry_points={"console_scripts": [
"img_sub = " + package_name + ".img_sub:main",
"cam_pub = " + package_name + ".cam_pub:main",
"upate_users = " + package_name +
".update_users:main",
"greet_user = " + package_name + ".greet_user:main",
],
},
description="A face recognition package for the office
manager robot"
)
В данном файле проводится описание пакета; данные о его разработчике; указывается имя проекта, подпроекты, которые в него могут входить; дополнительные файлы, которые требует пакет для работы; библиотеки, которые должны быть предустановлены для корректного выполнения функций пакета, зависящих от них.
Второй файл показывает менеджеру пакетов, какой программный код должен быть запущен при загрузке пакета как библиотеки (следовательно, если ничего не нужно выполнять, файл может оставаться пустым [1]).
Пакет ROS2, реализующий распознавание лиц, должен состоять из четырех узлов – файла, отправляющего изображения в соответствующий поток сообщений; файла, принимающего отправляемое сообщение и обрабатывающего его в контексте обнаружения лиц; файла, выполняющего приветствие пользователя и файла, обновляющего известных пользователей.
Помимо setup.py файла, структура пакета ROS2 имеет следующие структурные особенности:
- файл package.xml служит указателем на данные пакета, дублируя информацию, уже присутствующую в setup.py. Этот файл читается непосредственно менеджером пакетов ROS2:
<?xml version="1.0"?>
<?xml-model href="http://download.ROS2.org/schema/package_format3.xsd"
schematypens="http://www.w3.org/2001/XMLSchema"?>
<package format="3">
<name>package_name</name>
<version>0.0.1</version>
<description>Description</description>
<exec_depend>rclpy</exec_depend>
<exec_depend>std_msgs</exec_depend>
<exec_depend>sensor_msgs</exec_depend>
<export>
<build_type>ament_python</build_type>
</export>
</package>
Файл описывает ту же информацию, что и файл setup.py, за тем исключением, что предоставляет дополнительные зависимости для работы проекта (rcply, std_msgs, sensor_msgs) и build_type, который показывает каким образом необходимо собирать данный пакет (при помощи CMake для C++ или ament_python для Python) [1].
- каталог resource, в который включен пустой файл с именем пакета. Именно этот файл сканируется ROS2 для точной идентификации пакета;
- файл setup.cfg, который служит коллекцией предустановок для проекта:
[develop]
script_dir=$base/lib/mngr_fr_pkg
[install]
install_scripts=$base/lib/mngr_fr_pkg
Данный код демонстрирует, что он показывает пакету ROS2 где выполняемые узлы должны храниться (script_dir) и устанавливаться (install_scripts).
Созданную библиотеку класса распознавания лиц можно загрузить стандартным способом:
from mngr_fr_pkg.mngr_facerec import FaceRecognizer
mngr_fr_pkg представляет собой название созданного пакета для ROS2, mngr_facerec – название файла библиотеки, а FaceRecognizer – имя используемого класса.
После того, как процесс интеграции класса был определен, можно переходить к разработкам узлов ROS2.
Узел публикации изображений реализует просто чтение видеопотока с видеокамеры робота, преобразует его в формат сообщений ROS2 и отправляет кадры в необходимый поток сообщений [2]:
class ImagePublisher(Node):
def __init__(self) -> None:
super().__init__("cam_pub")
self.__publisher = self.create_publisher(Image,
"frames", 10)
timer_period = 0.1
_ = self.create_timer(timer_period,
self.__timer_callback)
self.__cap = cv2.VideoCapture()
self.__br = CvBridge()
def __timer_callback(self) -> None:
self.__cap.open(0)
ret, frame = self.__cap.read()
if ret == True:
self.__publisher.publish(self.__br.cv2_to_imgmsg(
frame))
Код приводит пример программного построения узла ROS2 в виде класса. В нем создается поток сообщений frames формата Image, в который публикуются кадры видеопотока каждые 0.1 секунды при срабатывании тика таймера. Инициализация объекта класса проводится как представлено:
def main(args: list=None) -> None:
rclpy.init(args=args)
image_publisher = ImagePublisher()
rclpy.spin(image_publisher)
image_publisher.__cap.release()
image_publisher.destroy_node()
rclpy.shutdown()
В данном описании опущены некоторые методы и строки кода, которые служат исключительно входными точками для программы, например, такие как загрузка библиотек и стандартная проверка на назначение файла как выполняемого.
Запуск данного узла в среде ROS2:
ros2 run mngr_fr_pkg cam_pub
Здесь стоит отметить, что mngr_fr_pkg – это имя созданного пакета ROS2, а cam_pub – присовенное ноде имя.
Структура последующих узлов похожа, поэтому в последующих описаниях будут приведены только ключевые изменения.
Публикация сообщения распознанного пользователя схожа с тем, что было представлено в узле отправки изображений, только тип передаваемых данных не Image, а String. Ключевое отличие двух узлов в том, что в ноде обработки изображений используется подписчик на поток сообщений, в который посылаются изображения:
_ = self.create_subscription(
Image,
"frames",
self.__callback,
10)
В данном коде Image – тип данных, которым оперирует поток сообщений; frames – имя потока изображений; self.__callback – метод обработки поступающих сообщений; 10 указывает на количество сообщений в одно время, которое метод может обработать.
Следующее отличие – в загрузке и инициализации объекта разработанного класса распознавания лиц. Инициализация объекта класса:
self.__fr = FaceRecognizer(os.path.join(path, "Images"),
os.path.join(path, "encodings.pkl"),
os.path.join(path, "users.json"))
Последнее отличие – это заданный в подписчике на поток сообщений метод, который обрабатывает поступающие сообщения:
current_frame = self.__br.imgmsg_to_cv2(data)
self.__fr.recognize_faces(current_frame)
recognized_id = self.__fr.get_user_id()
if recognized_id == "-2" or self.__last_greeted == recognized_id:
return
if recognized_id == "-1":
self.__fr.create_user(current_frame)
return
json_data = self.__fr.get_json_data()
self.__last_greeted = recognized_id
msg = String()
msg.data = json_data[recognized_id]["name"]
self.__publisher.publish(msg)
Следующий узел, требующий разработки, – тот, что озвучивает приветствие распознанного сотрудника. Реализуется это за счет двух библиотек:
- gTTS, которая преобразует строку в аудиофайл;
- pygame, которая озвучивает поступающий аудиофайл.
Реализуется за счет сохранения полученного аудиофрагмента в поток записи/чтения:
io = BytesIO()
tts = gTTS(text, lang="ru")
tts.write_to_fp(io)
io.seek(0)
sound = pygame.mixer.Sound(io)
sound.play()
while pygame.mixer.get_busy():
time.sleep(1)
Первая строка инициализирует поток записи/чтения, вторая создает объект класса gTTS для преобразования строки в аудиофрагмент, третья сохраняет аудиофрагмент в поток записи/чтения, четвертая выбирает записанный фрагмент по индексу, пятая создает объект класса Sound, который выполняет озвучивание файла (в данном случае – потока), шестая воспроизводит полученный аудиофрагмент и цикл в конце ожидает озвучивания всего аудиофрагмента.
Данный код записан как метод, который вызывается каждый раз, когда должно быть произведено приветствие пользователя. Использование данного метода:
self.__speak_text("Здравствуйте, " + msg.data)
Здесь, msg.data – содержание сообщения потока сообщений.
Последний узел ROS2 не является ни подписчиком на поток сообщений, ни публикует никаких сообщений, потому что предназначен только для вызова объекта класса распознавания лиц с целью обновления файла дескрипторов пользователя:
self.__fr.update_users()
Таким образом, остается лишь инициализация пакета:
colcon build --symlink-install
Опцию --symlink-install необходимо указывать, так как ROS2 перенаправляет все исходные файлы (с расширением .py) проекта в одну папку, расположенную по отдельному пути, и обращение к сторонним файлам в коде может нарушиться.
Список литературы
- Официальная документация ROS2 Humble. Создание пакета. URL: https://docs.ros.org/en/humble/Tutorials/Beginner-Client-Libraries/Creating-Your-First-ROS2-Package.html (дата обращения: 29.07.2023).
- Официальная документация ROS2 Humble. Создание подписчика и публикатора к потоку сообщений. URL: https://docs.ros.org/en/humble/Tutorials/Beginner-Client-Libraries/Writing-A-Simple-Py-Publisher-And-Subscriber.html (дата обращения:01.08.2023).
- Официальная документация ROS2 Humble. Описание клиентских пакетов. URL: https://docs.ros.org/en/rolling/Concepts/About-ROS-2-Client-Libraries.html (дата обращения: 31.07.2023).