Python-клиент¶
В репозитории есть готовый клиент examples/ros/control.py. Он вызывает сервис
управления и умеет подписываться на результаты. Его можно запускать на компьютере
SmartCamera или на другом компьютере с ROS 2 Jazzy и собранным пакетом
smart_camera_interfaces.
Подготовить терминал¶
Откройте второй терминал, оставив сервер работающим:
cd ~/SmartCamera
source /opt/ros/jazzy/setup.bash
source install/setup.bash
python3 examples/ros/control.py --help
Если проект установлен в другом каталоге, замените ~/SmartCamera. На удалённой
машине согласуйте ROS_DOMAIN_ID с сервером. При разных доменах сервис не будет
обнаружен, даже если веб-панель открывается по HTTP.
От списка камер до первого результата¶
Получите камеры и модели:
python3 examples/ros/control.py cameras
python3 examples/ros/control.py models
В первом ответе найдите cameras[].id, во втором — models[].id. Название модели
помогает выбрать запись, но в запрос передаётся её ID. Подставьте свои значения:
CAMERA_ID=workbench
MODEL_ID=вставьте_id_модели
python3 examples/ros/control.py create --json \
"{\"camera_id\":\"$CAMERA_ID\",\"model_id\":\"$MODEL_ID\",\"enabled\":true,\"fps\":5,\"device\":\"cpu\",\"parameters\":{\"conf\":0.4,\"iou\":0.6}}"
Сохраните id созданного запуска. Ниже RUN_ID — этот ID, а не ID модели.
RUN_ID=вставьте_id_запуска
python3 examples/ros/control.py get --target "$RUN_ID"
python3 examples/ros/control.py watch --target "$RUN_ID"
watch печатает кадры по мере обработки: номер, систему координат, обнаруженные
классы, confidence, рамки и ID треков. Для завершения просмотра нажмите Ctrl+C.
Подписка остановится, а модель продолжит работать.
Для остановки самой модели сначала перечитайте её состояние и возьмите текущую
ревизию. Например, если revision равна 1:
python3 examples/ros/control.py set --target "$RUN_ID" --revision 1 \
--json '{"enabled":false}'
Если настройки уже менялись, число будет другим. Ошибка revision_conflict
означает, что нужно повторить get и использовать новую ревизию. Не подставляйте
постоянно одно и то же число в управляющую программу.
Подписчик для своей программы¶
Этот пример не управляет моделями. Он слушает уже работающие запуски и выводит
рамки объектов. Сохраните его как listen_detections.py и запустите после
source install/setup.bash:
import rclpy
from rclpy.node import Node
from rclpy.qos import qos_profile_sensor_data
from smart_camera_interfaces.msg import InferenceResult
class Detections(Node):
def __init__(self):
super().__init__("my_detections")
self.subscription = self.create_subscription(
InferenceResult,
"/smart_camera/inference/results",
self.on_frame,
qos_profile_sensor_data,
)
def on_frame(self, message):
# При нескольких запусках здесь можно отфильтровать message.run_id.
stamp = message.header.stamp
print(message.camera_id, message.sequence,
f"{stamp.sec}.{stamp.nanosec:09d}", message.header.frame_id)
for obj in message.objects:
if obj.has_box:
left, top, right, bottom = obj.xyxy
print(obj.class_name, round(obj.confidence, 3),
(left, top, right, bottom), obj.track_id)
rclpy.init()
node = Detections()
try:
rclpy.spin(node)
except KeyboardInterrupt:
pass
finally:
node.destroy_node()
rclpy.shutdown()
python3 listen_detections.py
qos_profile_sensor_data совместим с издателем результатов: best-effort,
volatile. Если задать подписчику reliable, сообщения от best-effort издателя
не придут. Наличие топика в ros2 topic list само по себе не подтверждает
совместимость QoS.
Полные выходы модели¶
Массив objects удобен для обычной детекции. Для вероятностей классификации,
растровых масок и нестандартных выходов прочитайте JSON:
import json
payload = json.loads(message.data_json)
raw = payload["raw"]
outputs = payload["outputs"]
probabilities = raw.get("probs", {}).get("data")
Доступные поля зависят от задачи модели. Проверяйте их наличие; классификатор
может вернуть вероятности и пустой objects. Сервис уже заменяет ссылки на
временные NumPy-файлы значениями массивов, поэтому клиенту не нужен доступ
к каталогу записей сервера.
Систему координат и ограничения размеров сообщений смотрите
в описании ROS-интерфейса. Перевод рамки в 3D требует калибровки
и глубины; сами четыре числа xyxy являются пикселями.