Как мы приручали «Торнадо» для опасного производства

в 14:16, , рубрики: RCRobotics, SDK, Unitree, автоматизация, интеграция, нейронные сети, промышленные роботы, робототехника, роботы, техническое зрение

Или как отечественный робот вышел в автономный патруль

Как мы приручали «Торнадо» для опасного производства - 1

Аннотация

Наша небольшая команда интеграторов получила интересную задачу: помочь химическому предприятию автоматизировать мониторинг множества приборов, разбросанных по территории. В статье расскажем, почему остановили выбор на российской платформе, как добивались автономности, сбора данных с приборов и трансляции видео маршрута.

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


Как все начиналось?

На одном крупном химическом предприятии задумались о том, как защитить сотрудников от ежедневных рисков, связанных с регулярными обходами территории. Задача была понятной, решение — очевидным: отправить на опасные участки робота, которому не нужна надбавка за вредность, страховка и дополнительный отпуск.

Но, как выяснилось, просто найти подходящую платформу не так‑то просто. Робот должен был выдержать не только суровые условия российской эксплуатации, но и удовлетворять целому списку требований заказчика:

Что требовалось:

  • Патрулировать территорию протяженностью до 3 км — и делать это без вмешательства оператора.

  • Преодолевать лестницы, бордюры и другие неровности ландшафта выстой до 20см

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

  • Выдерживать температуру до -30 °C, высокую запылённость и водные преграды.

  • Уметь двигаться автономно, но при необходимости — переключаться на ручное удалённое управление.

  • Вести видеонаблюдение с нескольких камер.

  • Быть готовым (или хотя бы потенциально способным) к установке манипулятора для ручных действий.

  • Контролировать робота через Wi‑Fi, которым было покрыто предприятие.


Выбор платформы: «Торнадо» против Unitree B2

В конечном итоге на выбор были две платформы: китайский Unitree B2 и российский RCRobotics «Торнадо». После предварительного изучения характеристик стало понятно, что есть о чем подумать.

Unitree B2

Unitree B2

Unitree B2
  • Четвероногий робот (шагающая платформа), габариты 1,1 м × 0,45 м × 0,65 м.

  • Максимальная скорость: до 20 км/ч.

  • Высота преодолеваемых препятствий: до 40 см. В том числе прыжки до 1.2 метра

  • Грузоподъёмность: 20 кг (грузоподъемность манипулятора Z1 — до 2 кг).

  • Время автономной работы: до 4 часов (при нагрузке 20 кг).

  • Температура эксплуатации: от -20°C до +55°C.

  • SDK на основе ROS и стандартных интерфейсов.

Почему не подошёл

  • Нас смутил температурный лимит до -20 °C (у нас и холоднее бывает).

  • Слабый манипулятор и 6 степеней подвижности наложат дополнительные сложности при ручном управлении.

  • Встроить дополнительную электронику (если понадобится) внутрь робота скорей всего невозможно.

  • Одна штатная курсовая телекамера, которой очевидно будет недостаточно при телеуправлении. Соответственно, придется устанавливать и коммутировать несколько дополнительных телекамер как на робот, так и на манипулятор самостоятельно.

  • Да и габариты… «Робопёс» в рабочем режиме довольно громоздкий, что в целом не приветствовалось.

RCRobotics «Торнадо»

RCRobotics Торнадо

RCRobotics Торнадо
  • Колёсно‑шагающая платформа, габариты 0,8м*0,46м*0,3м.

  • Максимальная скорость: до 7 км/ч.

  • Высота преодолеваемых препятствий: до 22 см. (также преодоление лестниц)

  • Температурный диапазон: от -30°C до +50°C (впоследствии расширен производителем до -40°C).

  • Полезная нагрузка: до 25 кг (грузоподъемность манипулятора до 15 кг).

  • Время автономной работы: от 5 до 24 часов (сон до 900 часов)

  • Открытый SDK.

  • Бонус: быстросъёмный манипулятор и русскоязычная поддержка.

    Минусы: Не прыгает, но нам это и не требовалось.

Основное отличие между роботами, на наш взгляд, в идеологии управления, заложенной производителем:

В2 разработан преимущественно для автономного движения. Отсюда отсутствие необходимости в оснащении как робота, так и манипулятора средствами телевизионного наблюдения. Нет и пульта управления, поскольку тапнуть пальцем в заданную оператором точку на построенной заранее 3D карте можно и на экране смартфона. А обучение любой «полезной работе» производитель перекладывает на плечи покупателя, предоставив ему в помощь инструменты для разработки ПО.

«Торнадо», наоборот, был изначально спроектирован как робот для телеуправления. У него установлены: две курсовые телекамеры с подсветкой и микрофоном, две телекамеры с осветителем и поворотным механизмом на стойке в кормовой части и на самом манипуляторе предусмотрено несколько телекамер. Кроме того, вместе с роботом поставляется и пульт управления для полноценного телеуправления всеми функциями робота.

Это и побудило нас выбрать для дальнейшей модернизации именно «Торнадо», поскольку для нашей команды было значительно удобнее заниматься «будущими мозгами», чем распихивать по роботу телекамеры, тащить кабели, паять видео коммутаторы и выдумывать для заказчика полнофункциональный пульт.

Постановка задачи и общая архитектура решения

Что мы хотели сделать

  1. Полная автономия: Робот должен ездить по GPS‑координатам без постоянного вмешательства оператора, при этом объезжать препятствия и не терять маршрут.

  2. Дополнить систему газоанализаторами: Чтобы по ходу следования робота собирать пробы воздуха и отслеживать отклонения.

  3. Мониторинг приборов и распознавание apriltag: в местах, где расположены приборы, клеим метки apriltag: робот, придя на указанную GPS‑точку, распознаёт метку и ищет индикаторы рядом с ней, считывая показатели.

  4. Сервер и Wi‑Fi: На предприятии есть Wi‑Fi, который необходимо использовать для видеопотока, передачи сенсорных данных и загрузки новых координат (если маршрут нужно скорректировать «на ходу»). Также требуется организовать сервер, который хранит и выдаёт роботу список GPS‑точек, принимает видео и телеметрию.

  5. Сбор отчётов и экстренных уведомлений: Робот постоянно производит сбор проб (газы, температуру и т. п.) и при выходе за пороговые значения должен моментально сообщить серверу, который предупредит оператора.

Собрав этот список, мы обратились к ребятам из RCRobotics и выяснили, что для этого уже предусмотрено, а что придётся дорабатывать самим. Благо, производитель сразу пошёл навстречу и предложил парочку апгрейдов (об этом позже).


Что было «из коробки»

  • Сам робот: Колёсная платформа с базовыми датчиками (температуры, влажности, IMU, GPS), по умолчанию рассчитанная на температуру до ‑30 °C.

  • Поворотная камера, с 2мя объективами — широкоугольной и с ZOOM x50, расположенными на двухстепенном поворотном механизме на стойке в кормовой части робота.

  • Одноплатный компьютер NVIDIA Jetson Orin NX внутри робота, к которому подключены необходимые бортовые интерфейсы робота: CAN шина, протянутая по всей платформе, 4 видео потока — 2 статичные с переднего и заднего курсовых блоков и 2 динамичные (в которые могут выводится все остальные телекамеры).

  • SDK Позволяет работать с ключевыми функциями «Торнадо»: запускать движение, смотреть статус системы (батарея, температура приводов), переключать источник видео в динамичных потоках, а также взаимодействовать со всем оборудованием робота.

Реализация

Расширение температурного диапазона до -40 °C

Во многих регионах России зимы такие, что -30 °C воспринимается как лёгкая прохлада, а не мороз. Поэтому и изначально заявленный производителем робота рабочий диапазон до -30 °C вызвал у нас сдержанный оптимизм и одновременно тихое беспокойство. Решили уточнить напрямую у производителя, можно ли получить дополнительный запас по морозостойкости.

Честно говоря, ожидали ответа в духе: «Да, конечно, нужно заменить пару десятков компонентов на морозостойкие и стоимость изделия вырастет примерно на 20%». Однако, разработчики удивили, заявив, что они итак используют электронные компоненты с расширенным температурным диапазоном, которые считаются не только морозостойкими, но и более надежными в целом. Поэтому с электроникой проблем никаких не оказалось — работает при -40 °C без вопросов, а некоторые компоненты и при ещё более низких температурах.

А вот с камерами возникла совершенно неожиданная ситуация. Оказывается, камеры отдельно рассчитаны всего на -15 °C. То есть получается, что при -30 °C камеры просто обязаны замерзнуть и отказаться работать. Но разработчик предусмотрел и это: камеры, установленные в роботе, находятся в специальных термокожухах с системой автоматического подогрева, которая включается, когда температура падает ниже заданного порога. При активации прогрева они готовы к работе за 10 минут даже при -40 °C, а если ждать лень, можно согреть робота нежными руками инженера, но тут уже о комфорте речи нет. К слову, этот момент стал неожиданным и весьма приятным бонусом в пользу платформы «Торнадо» в сравнении с B2. В противном случае нам пришлось бы либо громоздить на платформу уличные камеры в массивных термокожухах, либо самостоятельно придумывать свои собственные варианты нагрева. Согласитесь, звучит не слишком заманчиво.

Как выяснилось далее, главным фактором, ограничивающим рабочий температурный диапазон робота, оказался аккумулятор. В «Торнадо» используется литий‑железо‑фосфатный (LiFePO4) аккумулятор, чья «химия» способна нормально работать лишь до -30 °C. Дополнительным ограничением оказалась смазка приводов, которая по умолчанию имеет тот же температурный предел, что и аккумулятор.

По нашей просьбе производитель поставил нам робот с авиационной смазкой с заявленной морозостойкостью до -60 °C, причем это даже не повлияло на оговоренную ранее цену робота. А вот по поводу аккумулятора сразу готовых решений не нашлось, и пришлось взять паузу, чтобы подумать. Ну, действительно, не возвращаться же к свинцовым аккумуляторам, у которых при тех же размерах ёмкость упадёт раза в три.

Вы можете представить наше удивление, когда спустя время мы получили обновление прошивки, добавляющее функцию прогрева аккумулятора. Как мы поняли, обогрев каким‑то магическим образом был реализован с помощью основных тяговых приводов, расположенных рядом с отсеком АКБ. Поскольку при включении обогрева тепло явно исходило от них, но при этом колёса стояли неподвижно и было абсолютно тихо. Признаться честно, как именно это работает, мы так и не разобрались, но главное — оно реально работает!

Разумеется, энергия на подогрев шла от самого аккумулятора, что немного сокращало общее время автономной работы. Кроме того, разработчик настойчиво рекомендовал не оставлять робот ночевать на улице, а использовать прогрев исключительно для поддержания работоспособности при температуре ниже -30 °C, при условии предварительного хранения в тепле.

В отсутствие более простых решений, мы были вынуждены согласиться с предложенным вариантом. И, честно говоря, в итоге даже не пожалели.

Воспользоваться этим теперь можно так

Пример кода Python:

from tornado_sdk import Robot
robot = Robot()
current_temp = robot.get_temperature()
if current_temp < -15:
    robot.enable_battery_heating()
    print("Подогрев аккумулятора включён...")

Интеграция газоанализаторов

На химическом предприятии газоанализаторы играют важную роль — они фиксируют концентрацию опасных веществ в воздухе, позволяя оперативно реагировать на утечки или отклонения. Мы разместили датчики на легкосъемной крышке робота, которая при закрытии подключается к общей CAN‑шине.

Поскольку приборы работают по интерфейсу RS232, для их интеграции мы разработали собственную плату‑переходник с прошивкой на C, опрашивающую датчики раз в секунду и передающую полученные данные в CAN.

И в связи с тем, что штатный SDK ничего не знает про наши датчиками, придется самим реализовать обработку на Nvidia Jetson.

Плата-преобразователь RS232-cAN

Плата‑преобразователь RS232-CAN

CAN‑шина — это настоящий ад информационных посылок, а нам нужно отфильтровать именно те сообщения, которые касаются газоанализаторов. Мы решили использовать идентификатор 0×10 000 из выделенного диапазона (0×10 000 — 0×12 000) для наших посылок. Как только данные приходят, мы сохраняем их в глобальный буфер.

Что такое CAN?

Шина CAN (Controller Area Network) — это промышленный стандарт обмена данными, который давно и успешно используется в автомобилях, промышленной автоматике и робототехнике. Ее преимущество в том, что устройства общаются между собой всего по двум проводам, причем на высокой скорости и с гарантированной доставкой сообщений.

Вся магия CAN заключается в особой структуре сообщений (посылок):

Идентификатор (ID): это по сути уникальный номер сообщения (11 бит в стандартном CAN и 29 бит в расширенном CAN). Чем меньше число в идентификаторе, тем выше приоритет сообщения. Благодаря этому самые важные данные (например, сигнал от приводной системы) всегда отправляются первыми.
Поле данных (Data): непосредственно информация, которую нужно передать. Максимум до 8 байт данных в одном стандартном CAN‑сообщении и до 64 байт в CAN FD (усовершенствованная версия шины CAN).
Контрольное поле и поле подтверждения (CRC и ACK): обеспечивают гарантию того, что сообщение дошло без искажений. Получатель проверяет целостность сообщения и отправляет подтверждение обратно.

Как это выглядит на практике? Допустим, датчик температуры хочет отправить данные центральному контроллеру. Он формирует сообщение с уникальным идентификатором, соответствующим типу данных (например, 0×100), и отправляет его по сети. Каждый узел сети слушает шину и получает только те сообщения, которые соответствуют конфигурации их фильтров. Это позволяет подключать к шине десятки устройств, и каждое будет «слышать» только нужную информацию, не отвлекаясь на лишний шум.
Таким образом, шина CAN обеспечивает высокую надежность и простоту организации связи в роботизированных системах и автомобильной электронике.

Ниже приведён пример кода, который иллюстрирует эту идею:

import can
import time
import threading
# Глобальный буфер для хранения последних данных газоанализатора
gas_data_buffer = None
 
# Настройка CAN-шины (например, для Linux используем socketcan)
bus = can.interface.Bus(channel='can0', bustype='socketcan')
 
def can_listener():
    global gas_data_buffer
    while True:
        # Читаем сообщение из CAN-шины (timeout 1 секунда)
        msg = bus.recv(timeout=1.0)
        if msg is not None:
            # Фильтруем нужные посылки по идентификатору (0x10000=65536)
            if msg.arbitration_id == 65536:
                try:
                    #Допустим, что первые 2 байта – уровень аммиака, следующие 2 – уровень хлора
                    nh3 = int.from_bytes(msg.data[0:2], byteorder='big')
                    cl2 = int.from_bytes(msg.data[2:4], byteorder='big')
                    gas_data_buffer = {
                        "NH3": nh3,
                        "Cl2": cl2,
                        "timestamp": time.time()
                    }
                    print("Обновлены данные газоанализатора:",
gas_data_buffer)
                except Exception as e:
                    print("Ошибка обработки данных:", e)
        # Небольшая задержка, чтобы не загружать процессор
        time.sleep(0.1)
 
# Запускаем CAN-слушатель в отдельном потоке
listener_thread = threading.Thread(target=can_listener, daemon=True)
listener_thread.start()
 
def read_gas_sensors():
    """
    Возвращает последние данные с газоанализаторов из глобального буфера.
    Если данные еще не получены, возвращает None.
    """
    return gas_data_buffer
 
# Пример использования функции:
while True:
    data = read_gas_sensors()
    if data is not None:
        print("Последние данные:", data)
    time.sleep(1)

В итоге, мы получаем возможность в реальном времени анализировать концентрации различных газов без лишних элементов ненадежности в виде кабелей и разъемов.

"Торнадо" с газоанализаторной системой

«Торнадо» с газоанализаторной системой

Переход со штатного радиоканала (1,2 ГГц) на Wi‑Fi

1. Общая концепция

Штатный радиоканал на 1,2 ГГц отлично подходит для передачи 1 видеопотока, базовых команд и телеметрии, однако для стриминга 2–3 камер и сенсорных данных его пропускной способности недостаточно. Кроме того, на химическом предприятии уже развернута локальная Wi‑Fi‑сеть, что дает возможность централизованно собирать данные, обновлять маршрут (GPS‑координаты) и, при необходимости, оперативно оповещать операторов.

Решение:
На плате NVIDIA Jetson есть Wi‑Fi‑модуль с помощью которого мы реализуем двухрежимную работу:

  • Режим онлайн: При наличии стабильного соединения робот передает видеопоток и данные с датчиков на локальный сервер в реальном времени.

  • Режим автономной работы: Если связь прерывается, данные кэшируются во внутренней памяти. После восстановления соединения накопленные данные синхронизируются с сервером.

2. Архитектура системы

2.1 На стороне робота (клиентская часть)

Wi‑Fi модуль:
На базе Linux (например, Debian или Ubuntu NVIDIA Jetson) модуль настраивается с помощью wpa_supplicant или Network Manager. Это позволяет роботу автоматически подключаться к корпоративной сети.

Передача данных:

Видео: Для видеопотока используется GStreamer, который формирует RTSP‑поток, доступный для операторов и серверного ПО.

Телеметрия: Все сенсорные данные (показания газоанализаторов, температуры, GPS‑координаты) передаются через MQTT. При стабильном соединении данные отправляются в реальном времени, а в случае временной потери Wi‑Fi они сохраняются в локальном кэше и отправляются при восстановлении связи.

2.2 Серверная часть

  • Прием видеопотока:
    RTSP‑сервер принимает и ретранслирует видео с робота, обеспечивая оператору доступ к изображению в реальном времени.

  • Приём телеметрии:
    Сервер, подписанный на MQTT‑топики, принимает входящие данные с датчиков и сохраняет их в базу данных или передаёт на аналитическую систему.

  • Обновление маршрута:
    Сервер хранит список GPS‑точек, который может динамически обновляться оператором. При восстановлении связи робот получает обновленные данные.

  • Буферизация: Если связь пропадает, локальный кэш данных на роботе гарантирует, что никакая информация не теряется — после восстановления Wi‑Fi накопленные данные отправляются на сервер.

3. Пример реализации

В этом примере мы рассмотрим два аспекта:

  1. Передача телеметрии по MQTT.

  2. Настройка видеопотока через GStreamer для формирования RTSP‑сессии.

3.1 Передача телеметрии по MQTT (клиентская часть)

python

import time
import json
import cv2
import os
import paho.mqtt.client as mqtt
 
# Конфигурация MQTT
MQTT_BROKER = "192.168.0.100"  # IP адрес MQTT-брокера
MQTT_PORT = 1883
MQTT_TOPIC = "robot/telemetry"
 
# Инициализация MQTT-клиента
mqtt_client = mqtt.Client()
mqtt_client.connect(MQTT_BROKER, MQTT_PORT, 60)
mqtt_client.loop_start()
 
# Путь для локального кэширования данных (если Wi-Fi недоступен)
BUFFER_FILE = "local_buffer.jsonl"
 
# Функция для кэширования данных
def cache_data(payload):
    with open(BUFFER_FILE, "a") as f:
        f.write(json.dumps(payload) + "n")
 
# Функция для синхронизации кэша
def sync_cache():
    if not os.path.exists(BUFFER_FILE):
        return
    new_lines = []
    with open(BUFFER_FILE, "r") as f:
        for line in f:
            try:
                data = json.loads(line.strip())
                result = mqtt_client.publish(MQTT_TOPIC, json.dumps(data))
                if result.rc != 0:
                    new_lines.append(line)
                else:
                    print("Кэшированные данные отправлены:", data)
            except Exception as e:
                print("Ошибка обработки строки:", e)
                new_lines.append(line)
    with open(BUFFER_FILE, "w") as f:
        f.writelines(new_lines)
 
# Функция для получения телеметрических данных
def get_sensor_data():
    """
    Собираем все данные с сенсоров.
    Включаем данные с газоанализаторов, GPS, температуры и др.
    """
    gas_data = read_gas_sensors() # Значения с газоанализаторов
    # Здесь можно добавить вызовы к другим датчикам (например, GPS, температуры)
    other_data = {
        "temperature": 5,                      # Пример температурного показателя
        "gps": {"lat": 55. 770129, "lon": 37. 557611}  # Пример GPS-координат
    }
    sensor_data = {"timestamp": time.time()} #Текущее время
    sensor_data.update(gas_data)
    sensor_data.update(other_data)
    return sensor_data
 
while True:
    sensor_data = get_sensor_data()
    payload = sensor_data

    # Публикуем данные через MQTT
    result = mqtt_client.publish(MQTT_TOPIC, json.dumps(payload))
    if result.rc == 0:
        print("Данные отправлены:", payload)
        # Попытка отправить накопленные данные, если они есть
        sync_cache()
    else:
        print("Ошибка отправки данных, кэшируем")
        cache_data(payload)
 
    # Задержка между отправками (например, 1 секунда)
    time.sleep(1) 

3.2 Настройка видеопотока через GStreamer

Для формирования видеопотока мы используем GStreamer, который настраивается отдельно. Пример команды для создания RTSP‑сервера может выглядеть следующим образом:

bash

gst-launch-1.0 -v v4l2src device=/dev/video0 ! 
    video/x-raw, width=1280, height=720, framerate=30/1 ! 
    videoconvert ! x264enc speed-preset=ultrafast tune=zerolatency ! 
    rtph264pay config-interval=1 name=pay0 pt=96 ! 
    udpsink host=192.168.0.100 port=5000 

Описание:

  • v4l2src — источник видео с камеры.

  • video/x‑raw, width=1280, height=720, framerate=30/1 — формат исходного видео.

  • videoconvert — преобразование формата видео.

  • x264enc — кодирование видео с настройками для низкой задержки.

  • rtph264pay — упаковка в RTP‑пакеты.

  • udpsink — отправка видеопотока на сервер по UDP.

Настройка GStreamer выполняется отдельно от Python‑кода и обеспечивает формирование RTSP‑сессии с низкой задержкой и аппаратным ускорением.

3.3 Серверная часть для приёма телеметрии и видео

На сервере для телеметрии можно использовать брокер MQTT (например, Mosquitto) и обработчик на Python. Пример для приёма телеметрии:

python

import json
import paho.mqtt.client as mqtt
 
MQTT_BROKER = "192.168.0.100"
MQTT_PORT = 1883
MQTT_TOPIC = "robot/telemetry"
 
def on_message(client, userdata, msg):
    try:
        data = json.loads(msg.payload.decode())
        print("Получены данные:", data)
        # Здесь данные можно сохранить в БД или передать в аналитическую систему
    except Exception as e:
        print("Ошибка обработки сообщения:", e)
 
mqtt_client = mqtt.Client()
mqtt_client.on_message = on_message
mqtt_client.connect(MQTT_BROKER, MQTT_PORT, 60)
mqtt_client.subscribe(MQTT_TOPIC)
mqtt_client.loop_forever()

Для видеотрансляции используется поток, сформированный GStreamer и передаваемый через RTSP-сервер, который обеспечивает просмотр и запись видео в реальном времени.

Управление роботом и передача команд от сервера осуществляется по тому же MQTT-протоколу, что и получение телеметрии. Это позволяет не только отслеживать состояние робота, но и дистанционно обновлять маршруты, а также управлять оборудованием на борту.

4. Ключевые моменты реализации

  • Мониторинг соединения и кэширование:
    При потере Wi‑Fi соединения данные сохраняются в локальном кэше (файл с JSON‑строками) и отправляются на сервер при восстановлении связи.

  • Передача видеопотока:
    Видеоданные передаются через GStreamer, формируя RTSP‑сессию с аппаратным ускорением.

  • Синхронизация времени и безопасность:
    Для корректной временной маркировки стоит синхронизировать часы робота с сервером (например, через NTP). Также обязательно следует обеспечить защиту передачи данных — например, через использование TLS/SSL.

  • Обновление маршрута:
    Сервер динамически обновляет список GPS‑точек, и при восстановлении связи робот получает актуальные данные для корректировки маршрута.

  • Параллельная работа двух каналов
    Несмотря на то, что управление роботом теперь осуществляется по Wi‑Fi, стандартный радиоканал не отключается полностью, а продолжает работать в энергосберегающем режиме и «слушать» эфир. В случае появления сигнала по заводскому радиоканалу он считается более приоритетным и управление автоматически переключается на него, обеспечивая дополнительную надёжность и возможность ручного контроля робота в экстренной ситуации.


Алгоритм обхода препятствий

Передний курсовой блок

Передний курсовой блок

Изначально мы планировали установить LiDAR для создания подробной карты окружающей среды. Однако из‑за плотного размещения приборов на крышке оказалось сложно обеспечить равномерное покрытие всех сторон и минимизировать слепые зоны. Производитель предложил альтернативное решение: дополнить робот датчиками глубины в переднем и заднем курсовых блоках. Эти датчики возвращают матрицы точек размером 16×8, где каждая ячейка содержит расстояние до ближайшего объекта (до 4 метров). Они, расположены на высоте 0.25 м от земли, имеют вертикальный угол обзора около 70° (от –35° до +35° относительно горизонтали) и, смотрящие вперёд и назад, фиксируют расстояния до объектов. При нормальной езде датчик видит напольное покрытие, а при подъезде к препятствию — его боковую или верхнюю грань.

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

Геометрическая основа

  1. Ожидаемое расстояние до пола: При ровной поверхности для каждого луча, направленного вниз под углом θ, расстояние до пола вычисляется по формуле:

    d_floor(θ) = h / sin(|θ|) где h = 0.25 м.

    Например, для θ = –15°: d_floor ≈ 0.25 / sin(15°) ≈ 0.25 / 0.2588 ≈ 0.97 м.

    Таким образом, выбираем строки матрицы, для которых ожидаемое расстояние лежит примерно в диапазоне 0.8–1.2 м — это соответствует обзору на расстоянии около 1 м от робота.

  2. Высота препятствия:
    Если препятствие имеет высоту h_obs, его верхняя грань находится на уровне: h_top = h — h_obs. В случае, когда датчик видит именно эту верхнюю грань, измеренное расстояние вдоль луча будет: d_obstacle(θ) = h_top / sin(|θ|). Тогда можно вычислить оценочную высоту, как h_est = h — d_measured * sin(|θ|). При ровном покрытии d_measured ≈ d_floor, и h_est ≈ 0 (то есть препятствия нет). Если же препятствие присутствует, h_est приближается к h_obs, хотя из‑за бокового обзора большинство лучей может фиксировать значения, немного отличающиеся от истинного значения.

Алгоритм оценки препятствия

Чтобы оценить ситуацию примерно за 1 м до препятствия, используем следующий алгоритм:

  1. Выбор региона интереса (ROI): Из матрицы измерений выбираем строки, для которых ожидаемое расстояние d_floor(θ) больше 0.8 м. Для h = 0.25 м это соответствует углам около –15° до 0°.

  2. Вычисление h_est для каждой строки ROI: Для каждой строки с индексом i, вычисляем угол θ_i (например, используя линейную интерполяцию от –35° до 0°).

    Затем:

    • Вычисляем d_floor_i = h / sin(|θ_i|).

    • Пусть d_measured_i — минимальное измеренное расстояние в данной строке.

    • Вычисляем: h_est i = h — dmeasured_i * sin(|θ_i|)

  3. Агрегация результатов: Рассчитываем медиану h_est по выбранным строкам ROI и также определяем долю строк, где h_est превышает критический порог (например, 0.20 м).

  4. Принятие решения: Если медианное h_est превышает максимально проходимую высоту (например, max_traverse_height = 0.22 м) или если значительная доля строк (например, более 5–10%) показывает h_est ≥ 0.20 м, система считает препятствие опасным и инициирует изменение маршрута. Если же большинство строк фиксирует значения, близкие к 0 (то есть, пол виден нормально), препятствие считается проходимым.

Пример реализации на Python

import numpy as np
import math
import time
 
# Константы
h = 0.25                      # Высота датчика над полом (м)
max_traverse_height = 0.22    # Максимальная проходимая высота препятствия (м)
critical_threshold = 0.20     # Критический порог h_est (м)
min_fraction = 0.25           # Минимальная доля строк ROI с h_est >= critical_threshold 
def compute_theta(i, num_rows):
    """
    Вычисляем угол для строки i нижней половины матрицы.
    i=0 соответствует нижней строке (угол -35°), 
    i=num_rows-1 соответствует углу ближе к горизонту (0°).
    """
    theta_min = -35  # нижний угол (в градусах)
    theta_max = 0    # верхний угол
    return math.radians(theta_min + (theta_max - theta_min) * i / (num_rows - 1))
 
def analyze_ROI(matrix):
    """
    Анализируем нижнюю половину матрицы, выбираем строки с ожидаемым d_floor от 0.8 м.
    Для каждой строки вычисляем h_est = h - d_measured * sin(|θ|).
    Возвращаем медианное h_est и долю строк, где h_est >= critical_threshold.
    """
    M, N = matrix.shape
    lower_half = matrix[:M//2, :]  # нижняя половина матрицы
    num_rows = lower_half.shape[0]
    h_est_list = []
    count_high = 0
    count_total = 0
 
    for i in range(num_rows):
        theta = compute_theta(i, num_rows)
        d_floor = h / abs(math.sin(theta))
        if 0.8 <= d_floor:
            count_total += 1
            d_measured = np.min(lower_half[i, :])
            h_est = h - d_measured * abs(math.sin(theta))
            h_est_list.append(h_est)
            if h_est >= critical_threshold:
                count_high += 1
    if h_est_list:
        median_h = np.median(h_est_list)
        fraction = count_high / count_total if count_total > 0 else 0
        return median_h, fraction
    else:
        return None, 0
 
def decide_obstacle(matrix):
    """
    Принимаем решение: если медианное h_est > max_traverse_height 
    или значительная доля строк ROI (более min_fraction) показывает h_est >= critical_threshold,
    препятствие считается опасным.
    """
    median_h, fraction = analyze_ROI(matrix)
    if median_h is None:
        print("ROI не определена, продолжаем движение")
        return False  # Нет данных – считаем, что путь свободен
    print("Медианная h_est:", median_h, "Доля строк с h_est >= critical_threshold:", fraction)
    if median_h > max_traverse_height or fraction > min_fraction:
        print("Препятствие слишком высокое, требуется объезд")
        return True
    else:                                  
        print("Путь проходим, препятствие невелико")
        return False
 
# Пример использования:
matrix = robot.get_depth_matrix("front_depth") # Получаем матрицу измерений от датчика глубины
    navigation.change_route() 
else:
    # Продолжаем движение
    pass

Дополнительные замечания

  • Учет боковых слепых зон: Датчики, установленные на переднем и заднем блоках, не покрывают боковые зоны. Если требуется повышенная точность, можно дополнить систему анализом видеопотоков с камер или установить дополнительные датчики.

  • Интеграция с GPS: GPS поможет скорректировать маршрут, если алгоритм обхода препятствий временно изменил траекторию движения.

  • Анализ «пола» В алгоритм также следует добавить анализ напольного покрытия и корреляцию с IMU, чтобы учитывать порог преодолеваемого препятствия в зависимости от уклона ландшафта и избежать падения с каких‑либо платформ.


Поиск apriltag‑меток и локальная обработка показаний приборов

1. Задача поиска меток

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

  • Ориентация: Метка указывает направление объезда или сигнализирует о необходимости заезда в помещение (например, «проезжай справа» или «проезжай подо мной»).

  • Локализация прибора: Метка сигнализирует, что непосредственно рядом находится прибор мониторинга, с которого нужно считать показания.

Используемая библиотека apriltag позволяет обнаруживать эти метки на изображении быстро и точно.

2. Алгоритм управления камерой для поиска меток

SDK предоставляет базовые команды для управления камерой — можно выбирать видеопоток, задать угол поворота (pan), наклона (tilt) или управлять скоростью вращения. Логику поиска меток мы реализуем через последовательное изменение ракурса камеры.

Поворотная камера на стойке

Поворотная камера на стойке

Представьте, что камера постепенно поворачивается, пока не обнаружит метку. В нашем случае камера проходит полный круг (от –180° до +180°) с шагом 30°. Конечно, в реальных условиях нужно учитывать и ориентацию робота в пространстве, например если робот стоит на неровной поверхности, данные с IMU помогут скорректировать горизонтальное направление — но в данном примере мы упростим алгоритм.

Пример кода python:

import cv2
import apriltag
import time
 
# Инициализация детектора apriltag
detector = apriltag.Detector()
 
def set_camera_angle(pan, tilt):
    """
    Устанавливаем угол поворота камеры.
    :param pan: угол по горизонтали (-180° до +180°)
    :param tilt: угол наклона по вертикали
    """
    robot.camera.move(pan=pan, tilt=tilt)
    time.sleep(0.5)  # задержка для стабилизации изображения
 
def capture_frame():
    """
    Переключаемся на нужный видеопоток и получаем кадр.
    """
    robot.camera.switch_to("rear_cam")
    return robot.camera.get_frame()
 
    """
    Перебираем углы камеры по горизонтали от -180° до +180° с шагом 30°.
    При обнаружении метки возвращаем метку, текущий кадр и угол, на котором обнаружена метка.
    """
    for pan in range(-180, 181, 30):
        tilt = 0  # фиксированный угол наклона; в реальной системе можно добавить вложенный цикл
        set_camera_angle(pan, tilt)
        frame = capture_frame()
        tags = detector.detect(gray)
        if tags:
            print("Найдена метка при pan =", pan)
            return tags[0], frame, pan
    return None, None, None
 
tag, tag_frame, tag_pan_angle = search_for_tag()
if tag:
    tag_info = {
        "id": tag.tag_id,
        "center": tag.center.tolist(),          # координаты центра метки (пиксели, относительно изображения)
    }
    # Упрощённо: если камера установлена строго вперед, найденный angle указывает на направление метки.
    # В продакшене следует учитывать данные IMU для корректной ориентации.
    relative_orientation = tag_pan_angle
    print("Информация о метке:", tag_info)
else:
    print("Метка не обнаружена")

Итоговая архитектура мониторинга

  1. Сенсоры на борту робота:

    • Газоанализаторы: Подключенные через RS232‑CAN регулярно опрашиваются и предоставляют данные о концентрациях опасных веществ в окружающей среде.

  2. Поиск apriltag‑меток и ориентация:

    • Прибывая в ключевые точки маршрута, робот активирует широкоугольную камеру и посредством базовых команд SDK (панорамирование и наклон) сканирует пространство.

    • Камера проходит полный круг (от –180° до +180°) для обнаружения меток, даже если они находятся позади робота.

    • При обнаружении метки фиксируется угол pan, на котором она была найдена, что позволяет определить относительное положение робота.

  3. Локальная обработка показаний приборов:

После обнаружения apriltag‑метки, сигнализирующей о наличии прибора мониторинга, система начинает анализировать видеопоток уже не только с широкоугольной камеры поворотного устройства, но и с камеры с ZOOM‑объективом (их оптические оси параллельны). Благодаря такому решению достигается «эффект лупы»: широкоугольная камера помогает быстро обнаружить прибор и выбрать точку интереса, а камера с ZOOM позволяет рассмотреть все детали с любого расстояния. Далее активируется локальная нейросеть на NVIDIA Jetson Orin NX для распознавания показаний прибора. Такой подход позволяет снизить задержки и вероятность ошибок при обработке данных.

Пример кода с использованием PyTorch:

import cv2
import torch
 
# Загрузка предобученной модели для распознавания показаний прибора
model = torch.load('instrument_reader.pt')
model.eval()
 
def read_instrument(frame, bbox):
    """
    Считываем показания прибора в заданной области.
    :param frame: исходный кадр с камеры
    :param bbox: bounding box в формате (x, y, w, h)
    """
    x, y, w, h = bbox
    roi = frame[y:y+h, x:x+w]
    
    # Подготовка изображения для модели: изменение размера до 224x224, нормализация и преобразование в формат (C, H, W)
    print("Относительная ориентация робота к метке:", relative_orientation, "°")
    input_tensor = cv2.resize(roi, (224, 224))
    input_tensor = input_tensor.transpose(2, 0, 1) / 255.0
    input_tensor = torch.tensor(input_tensor, dtype=torch.float32).unsqueeze(0)
    
        "corners": [corner.tolist() for corner in tag.corners]  # координаты углов метки
    with torch.no_grad():
        result = model(input_tensor)
# Пример использования алгоритма
    
    instrument_value = result.item()  # Предполагаем, что модель возвращает числовое значение
    return instrument_value
        gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
 
# Пример интеграции: вычисляем bounding box на основе координат углов найденной метки
def search_for_tag():
if tag:
    corners = tag.corners.astype(int)
    x_min = corners[:, 0].min()
    y_min = corners[:, 1].min()
    x_max = corners[:, 0].max()
    y_max = corners[:, 1].max()
if decide_obstacle(matrix):
    bbox = (x_min, y_min, x_max - x_min, y_max - y_min)
    
    instrument_value = read_instrument(tag_frame, bbox)
    print("Распознано показание прибора:", instrument_value)
  • Передача данных:

    • Полученные данные (показания газоанализаторов, результаты распознавания приборов, GPS‑координаты и информация о метках) передаются на сервер для дальнейшего мониторинга и анализа.


Итог

  • Расширение температурного диапазона: Благодаря реализации системы автоматического подогрева аккумулятора (метод enable_battery_heating()) робот теперь стабильно работает даже при -40 °C.

  • Переход на Wi‑Fi с параллельной работой штатного радиоканала: Использование Wi‑Fi‑модуля с GStreamer/RTSP для видеопотока и MQTT для телеметрии обеспечило стабильную и быструю передачу данных. Это позволило централизованно собирать видео и сенсорные данные, обновлять маршруты и оперативно оповещать операторов. При этом заводской радиоканал продолжает работать в фоновом энергосберегающем режиме, обеспечивая дополнительную надежность и возможность экстренного ручного управления в случае сбоя Wi‑Fi.

  • Интеграция сенсоров и алгоритмов мониторинга: Объединение данных с газоанализаторов, камер (включая поиск apriltag‑меток для точного позиционирования) и датчиков (температура, GPS) позволило создать гибкую систему мониторинга. Локальная обработка показаний приборов посредством нейронной сети снижает задержки и повышает точность измерений.

  • Обход препятствий: Вместо сложного и дорогого LiDAR мы использовали датчики глубины, установленные на переднем и заднем курсовых блоках. Анализ матриц точек позволяет оперативно обнаруживать препятствия и корректировать маршрут в режиме реального времени, что обеспечивает автономное патрулирование даже в условиях ограниченной видимости.

"Торнадо" с газоанализатором на крышке

«Торнадо» с газоанализатором на крышке

Заключение

Конечно, над системой ещё стоит поработать. Например, газоанализатор оказался тяжелее чем мы рассчитывали и в такой компоновке поднял центр тяжести робота, снизив максимально преодолеваемый уклон — печально, но поправимо. И да, решение с метками отлично работает недалеко от входа, но от приборов в глубине помещений труднее вернуться при плохом подключении GPS. Тут уже пригодилось бы картографирование и возврат по созданной карте.

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

Отдельно хотим поблагодарить коллектив разработчиков робота «Торнадо» за оказанное содействие и оперативное решение возникающих у нас вопросов.
Ссылка на их ТГК t.me/RCRobotics_official

Автор: MyTechnoBlog

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js