
Компьютерное зрение на коленке: распознаем дорожные знаки и управляем роботом на ESP32 и Arduino
Введение
Каждый, кто осваивает Arduino, проходит одни и те же этапы.
Сначала ты мигаешь светодиодом. Потом подключаешь датчик температуры — и вот уже температура выводится в монитор порта. Потом сервопривод — и какая-то пластиковая штуковина начинает смешно поворачиваться туда-сюда.
Потом появляется идея сделать систему автополива для цветов (которая в итоге их зальёт). Или мобильного робота, который будет «приносить тапочки». Правда, через месяц робот будет пылиться на полке, а датчики с него переедут в следующий проект :).
Дальше — закономерный этап: хочется управлять всем этим через интернет или со смартфона по каналу Bluetooth. Использовать Wi-Fi модули, заменить Arduino на ESP8266 или ESP32. Поднял сервер, написал пару кнопок — работает.
И если все это тебя зацепило, то возникает вопрос: а что дальше?
Я тоже прошёл эти этапы. И когда задумался, куда двигаться дальше, обратил взор на ИИ (Искусственный Интеллект). Не на тот, что «Skynet уничтожит человечество», а на самый простой — умение робота видеть знак «Стоп» и отличать его от пустой стены.
Воодушевленный идеей, начал собирать информацию про ИИ на микроконтроллерах — и быстро понял, что обычный Arduino Uno с его 2 КБ оперативной памяти (RAM) и 16 МГц для нейросетей решительно не подходит. Модель с распознаванием изображения туда не запихнуть. Даже самую простую.
Примечание
Кстати, это направление даже имеет своё название — TinyML (машинное обучение для микроконтроллеров с ограниченными ресурсами). Минимальные требования для простых задач (классификация звуков или данных с датчиков) — тактовая частота от 32 МГц, RAM от 32 КБ, Flash от 128 КБ у Uno даже близко нет.
В семействе Arduino существует подходящая плата — Arduino Nano BLE Sense с 1 МБ RAM. Но это уже другая история. Мне же хотелось:
-
Недорого — чтобы не пришлось закладывать квартиру.
-
Просто — без написания нейросетей с нуля на Python или C++.
-
На коленке — чтобы собрать за вечер на столе из того, что есть
И ответ нашёлся — модуль ESP32-CAM (рис. 1). У него 520 КБ RAM, частота 240 МГц и встроенный Wi-Fi. Этот модуль вместе с камерой можно без труда купить в РФ или заказать на AliExpress, А главное — он стоит как обед во «Вкусно и точка». Идеальный компромисс.
ESP32-CAM станет глазами и мозгом — он видит знаки и принимает решения. А крутить колёсами, моргать светодиодами и пищать зуммером будет Arduino. У меня под рукой была плата SPBot — это Arduino-совместимый контроллер со встроенным драйвером двигателей, светодиодами и разъёмами для датчиков (рис. 2). Можно взять любую другую Arduino + драйвер двигателя (например, TB6612 или L298N), но проводов будет больше.
Сам процесс сборки и программирования займёт один вечер. Но чтобы понять, какой путь правильный, а какой — ложный, мне потребовалось пару недель. Перебирал варианты, «наступал на грабли», отбрасывал лишнее. Теперь делюсь только работающим решением.
Почему связка ESP32-CAM + Arduino, а не один ESP32?
Вопрос резонный. ESP32-CAM поддерживает Wi-Fi, камеру и нейросети. Пины GPIO у модуля тоже есть. Почему не подключить моторы напрямую?
Потому что это больно.
ESP32-CAM не рассчитан на управление мощными нагрузками (например, двигателями). Для этого ему потребуется драйвер моторов, внешнее питание, согласование уровней, куча проводов. А еще захочется светодиоды для индикации, зуммер для звука, гироскоп для навигации. Итог — стол с паутиной проводов и отваливающиеся контакты.
Поэтому я использую связку:
-
ESP32-CAM — глаза и ИИ. Он распознает знаки.
-
Arduino-совместимая плата с драйвером моторов — тело. Она получает команды по UART и управляет движением, светом, звуком.
Архитектура системы
Архитектура системы простая и понятная как закон Ома (рис. 3).
-
Edge Impulse (облако): Обучаем нейросеть распознавать знаки «Стоп», «Налево», «Направо».
-
ESP32-CAM: Загружаем обученную модель. Камера ловит изображение, нейросеть его анализирует.
-
UART (провода): ESP32-CAM отправляет на Arduino символы: S (Stop), L (Left), R (Right).
-
Arduino (SPBot): Принимает символ и зажигает нужный цвет светодиода (красный, синий, зеленый).
-
Всё. Никаких облаков в реальном времени, всё локально. Робот самодостаточен.
Хватит теории, переходим к практике. Дальше — пошаговая инструкция. Предупреждаю: шаги придётся выполнять последовательно, ничего не пропуская. Поехали.
Шаг 1. Собираем датасет: 100 фото — и готово
Первый закон ML: чем больше качественных данных, тем лучше результат. Если дать нейросети 10 фото, она ничему не научится. Нужно минимум 50–100 фото на каждый знак (рис. 4).
Я использовал сам ESP32-CAM как фотоаппарат. Библиотека EloquentEsp32cam с примером скетча Collect_Images_for_EdgeImpulse25.ino превращает ESP32-CAM в веб-сервер для сбора изображений. Заходите в браузер по IP-адресу, нажимаете Start Collecting — камера снимает, Download — скачиваете ZIP с фотографиями.
Как это работает:
-
Загружаем скетч на ESP32-CAM. Полный код в электронном архиве (ссылка и описание архива в конце статьи).
-
Открываем Монитор порта, находим IP-адрес (например, 192.168.4.1).
-
Переходим по этому IP в браузере телефона или ПК.
-
Наводим камеру на знак «Стоп», жмем Start Collecting, потом Download (рис. 5).
-
Повторяем для «Налево» и «Направо».
На выходе — три ZIP-архива с 50–100 фото каждый. Идеально.
Шаг 2. Создаем модель ИИ в Edge Impulse
Регистрируемся на edgeimpulse.com (тут и нужен доступ из разрешенного региона). Создаем проект.
|
Внимание Edge Impulse требует доступа из региона, где сервис не заблокирован (например, Казахстан, Беларусь). Если прямого доступа нет — используйте TensorFlow Lite Micro, но в статье работаем через Edge Impulse как через самый быстрый способ для новичка. |
|
Примечание Платформа Edge Impulse хорошо документирована (https://docs.edgeimpulse.com/) В книге «Искусственный интеллект для периферийных устройств: осваиваем встраиваемые системы для машинного обучения«, написанной специалистами из компании Edge Impulse, исследованы возможности использования искусственного интеллекта при работе с периферийными устройствами и показаны основы работы на платформе EdgeImpulse. |

2.1. Загрузка данных
Загружаем три ZIP-архива, полученные на шаге 1, в раздел Data Acquisition (рис. 6). При загрузке указываем метки (stop, left, right).
Обратите внимание, что при загрузке Edge Impulse предложит выбрать тип разметки — сразу отвечаем Yes. Object Detection сначала находит знак целиком (в рамке), потом распознаёт. Нет знака — нет рамки. Обычная классификация будет пытается угадать наш знак даже по кусочку стены — и ошибаться.
2.2. Разметка данных
Теперь нужно вручную обвести рамками знаки на всех фото. Нудно, но один раз. На 150 фото у меня ушло 20 минут. Совет: делайте рамки квадратными, плотно облегающими знак (рис. 7).
2.3. Создание импульса и обучение
Теперь самое интересное. В Edge Impulse есть понятие «импульс» (Impulse) — это конвейер, который определяет, как исходные данные (наши фото) превратятся в признаки, а затем в результат распознавания.
Настраивается он в три блока (рис. 8):
-
Input block: Image data (размер 48×48 или 96×96. Я выбрал 48×48 для скорости).
-
Processing block: Image (преобразует картинку в числовые признаки).
-
Learning block: Object Detection (Images) — именно он отвечает за поиск знаков с рамками.
Сохраняем импульс и переходим к настройке нейросети. Открываем раздел Object detection. Основные параметры:
-
Number of training cycles: 30 эпох (один проход модели по всем данным).
-
Learning rate: 0.001 (скорость обучения — насколько сильно модель корректирует веса на каждом шаге).
-
Neural network architecture: MobileNetV2 0.35 — легковесная сеть, специально разработанная для мобильных и встраиваемых устройств. Идеально подходит для ESP32.
-
Жмём кнопку Start training. Через 10–15 минут получаем результат. У меня получилось F1-score = 97.8% (рис. 9).
|
Примечание Если захотите разобраться в теории — нейроны, функции активации, градиентный спуск, TinyML — всё это подробно разобрано в книге «Мобильные роботы с искусственным интеллектом на базе Arduino» (4-е издание). Там же — полные листинги и схемы подключения всех датчиков. В статье только практический каркас под конкретную задачу. |

2.4. Бонус: Проверяем модель прямо на смартфоне (без ESP32!)
А что, если у вас еще нет ESP32-CAM под рукой, но попробовать нейросеть на реальных объектах хочется прямо сейчас?
Edge Impulse позволяет загрузить обученную модель на смартфон и запустить ее в реальном времени — камера телефона будет распознавать ваши дорожные знаки (или яблоки с апельсинами) без единой строчки кода.
Как это сделать:
-
В проекте Edge Impulse перейдите в раздел Deployment.
-
Выберите Mobile App → Build. Edge Impulse сгенерирует APK-файл (для Android) или инструкцию для iOS.
-
Установите приложение на телефон (для Android — разрешите установку из неизвестных источников).
-
Откройте приложение, авторизуйтесь в своем аккаунте Edge Impulse.
-
Выберите ваш проект и загруженную модель.
-
Наведите камеру на дорожный знак
Приложение начнет распознавать объекты в реальном времени. На экране будут появляться bounding boxes с метками и процентом уверенности распознавания. Можете крутить знак в руках, менять освещение, пробовать разные ракурсы — нейросеть будет пытаться его узнать (рис. 10).
Когда убедитесь, что модель распознает знаки на телефоне уверенно (порог >0.7–0.8), можно смело экспортировать ее в Arduino library и загружать на ESP32-CAM — на микроконтроллере она будет работать точно так же.
Примечание
В главе 16 книги («Автопилот на Edge Impulse и ESP32-CAM») этот процесс описан подробно, включая скриншоты интерфейса мобильного приложения.
Шаг 3. Программируем ESP32-CAM: загружаем модель и настраиваем UART
Когда модель обучена и протестирована, её нужно «загрузить» в ESP32-CAM. Edge Impulse делает это элегантно — экспортирует модель как готовую библиотеку для Arduino.
3.1. Экспорт библиотеки
В левом меню Edge Impulse выбираем Deployment (Развёртывание). Нам нужен вариант Arduino library. Чуть ниже жмём Change target и выбираем Espressif ESP-EYE (ESP32 240 MHz).
Почему ESP-EYE, если у нас плата AI-Thinker? Это не ошибка. ESP-EYE — эталонная плата ESP32 с камерой от Espressif. У неё и AI-Thinker — совместимые конфигурации пинов камеры. Поэтому библиотека подойдёт.
Жмём кнопку Build. Через минуту скачивается ZIP-архив с библиотекой.
3.2. Установка библиотеки в Arduino IDE
Открываем Arduino IDE. Идём в меню Скетч → Подключить библиотеку → Добавить .ZIP библиотеку и выбираем скачанный архив. Всё, модель установлена.
3.3. Открываем пример
Теперь в среде разработки Arduino IDE в меню Файл → Примеры появляется папка с названием вашего проекта. Открываем Файл → Примеры → [Название проекта] → esp32_camera.
Это готовый скетч, который:
-
Инициализирует камеру.
-
Загружает нашу модель.
-
Захватывает изображение и прогоняет его через нейросеть.
Осталось его немного доработать — настроить камеру и добавить отправку команд по UART на Arduino.
3.4. Ключевые изменения в скетче
Сделаем в скетче следующие изменения:
1. Выбираем нашу плату AI-Thinker, а не ESP-EYE.
// #define CAMERA_MODEL_ESP_EYE // закомментировать#define CAMERA_MODEL_AI_THINKER // раскомментировать
2. Настраиваем аппаратный UART для связи с Arduino.
-
В начале скетча (там, где остальные #define) добавляем:
#define RXD1 14 // пин приёма данных (RX) — не используется, но задаём#define TXD1 15 // пин передачи данных (TX) — именно по нему будем отправлять команды
-
В блоке setup() запускаем Serial1 на пинах 14 и 15 со скоростью 9600 бод:
Serial1.begin(9600, SERIAL_8N1, RXD1, TXD1);pinMode(FLASH_GPIO_NUM, OUTPUT); // готовим вспышку
3. В основном цикле отправляем команды при обнаружении знака.
#if EI_CLASSIFIER_OBJECT_DETECTION == 1for (uint32_t i = 0; i < result.bounding_boxes_count; i++) { ei_impulse_result_bounding_box_t bb = result.bounding_boxes[i]; if (bb.value == 0) continue; // если уверенность нулевая — пропускаем // Включаем вспышку на 0.2 секунды — визуальный отклик, что знак найден digitalWrite(FLASH_GPIO_NUM, HIGH); delay(200); digitalWrite(FLASH_GPIO_NUM, LOW); // Отправляем символ на Arduino в зависимости от метки знака if (strcmp(bb.label, "stop") == 0) { Serial1.print('S'); // Стоп } else if (strcmp(bb.label, "left") == 0) { Serial1.print('L'); // Налево } else if (strcmp(bb.label, "right") == 0) { Serial1.print('R'); // Направо }}#endif
Примечание
Полный код скетча доступен в электронном архиве книги «Мобильные роботы с искусственным интеллектом на базе Arduino, 4-е издание», который можно скачать тут (https://zip.bhv.ru/9785977520720.zip) в папке Листинги → listing_16_01.
Загружаем скетч в ESP32-CAM. Готово!
Теперь наш модуль ESP32-CAM умеет распознавать знаки и отправлять команды по проводу на плату Arduino Uno (SPBot).
Шаг 4. Программируем плату управления (Arduino/SPBot)
Arduino должна слушать порт UART и зажигать светодиод. Схема подключения (только TX!) показана на рис. 11:
-
ESP32-CAM GPIO15 (TX1) → Arduino Pin 10 (RX SoftwareSerial)
-
GND ESP32-CAM → GND Arduino
Важно:
ESP32-CAM работает на 3.3 В, а Arduino — на 5 В. Сигнал «1» от ESP32-CAM — это 3.3 В, но для Arduino этого достаточно, чтобы считать его единицей.Передача данных однонаправленная (только от ESP к Arduino), а пины приёма на ESP32-CAM не задействованы. Поэтому он не получит 5 В и не выйдет из строя.
Скетч для Arduino (листинг Arduino_ESP32_RGB.ino):
#include <SoftwareSerial.h>// Программный UART на пинах 10 (RX) и 11 (TX)// Используем только RX (пин 10) для приема от ESP32-CAMSoftwareSerial espSerial(10, 11);// Пины RGB-светодиода (общий катод, ШИМ-пины)#define RED_PIN 3#define GREEN_PIN 5#define BLUE_PIN 6void setup() { Serial.begin(9600); // Отладка в монитор порта espSerial.begin(9600); // Связь с ESP32-CAM pinMode(RED_PIN, OUTPUT); pinMode(GREEN_PIN, OUTPUT); pinMode(BLUE_PIN, OUTPUT); // Гасим все цвета при старте analogWrite(RED_PIN, 0); analogWrite(GREEN_PIN, 0); analogWrite(BLUE_PIN, 0); Serial.println("Arduino ready. Waiting for commands...");}void loop() { if (espSerial.available()) { char cmd = espSerial.read(); // Отладочный вывод Serial.print("Command received: "); Serial.println(cmd); // Сбрасываем все цвета analogWrite(RED_PIN, 0); analogWrite(GREEN_PIN, 0); analogWrite(BLUE_PIN, 0); // Обрабатываем команду switch (cmd) { case 'S': // Stop — красный analogWrite(RED_PIN, 255); Serial.println("STOP detected! RED LED ON"); break; case 'L': // Left — синий analogWrite(BLUE_PIN, 255); Serial.println("LEFT detected! BLUE LED ON"); break; case 'R': // Right — зеленый analogWrite(GREEN_PIN, 255); Serial.println("RIGHT detected! GREEN LED ON"); break; default: Serial.print("Unknown command: "); Serial.println(cmd); break; } }}
Шаг 5. Тестируем систему
Вы обучили модель, загрузили её в ESP32-CAM, запрограммировали Arduino. Теперь нужно убедиться, что всё работает, прежде чем ставить робота на колёса.
Можно, конечно, водить листом бумаги со знаком перед камерой. Но я пошёл дальше — написал тестовое приложение на Scratch (да, том самом, детском языке программирования).
Почему Scratch?
-
Не нужно писать сложный GUI на Python или JavaScript.
-
Программа создаётся за 10 минут.
-
Можно быстро менять параметры: скорость появления знаков, их тип, порядок.
На экране ноутбука или планшета отображается «дорога» — движущаяся полоса с разметкой. На ней появляется один из трёх знаков: «Стоп», «Налево» или «Направо» (рис. 12).
Открыть приложение можно по ссылке https://scratch.mit.edu/projects/1322386761.
Как тестировать
-
Запускаем приложение на экране.
-
Направляем камеру ESP32-CAM на экран.
-
Приложение показывает знаки.
-
ESP32-CAM их распознаёт и отправляет команды на Arduino.
-
Светодиод загорается соответствующим цветом.
В результате тестирования неподвижного робота модель узнаёт знаки уверенно, с точностью 97.8%. Светодиод загорается нужным цветом, ошибок почти нет.
А потом попробовали повторить эксперимент в движении, используя те же знаки, на которых обучали модель. И тут начались нюансы (об этом — в следующем разделе).
Что дальше? Честно об обнаруженных шероховатостях
Когда я попробовал распознавать знаки на ходу (робот едет, камера смотрит вперёд), система стала работать хуже. Робот иногда проезжает мимо знака, не заметив его, или срабатывает с задержкой.
Почему так происходит? Две основные версии:
-
Динамическое размытие — камера «смазывает» изображение при движении. Нейросеть не успевает «поймать» чёткий кадр.
-
Производительности классического ESP32-CAM может не хватать для непрерывного анализа потока в реальном времени, особенно если робот едет быстро.
Что делаем (и что можете сделать вы):
-
Алгоритмически: притормаживать перед потенциальным знаком, снижать скорость в зонах распознавания. Это уже помогает.
-
«Железно»: перейти на более производительный модуль ESP32-CAM-S3 — у него два ядра, выше тактовая частота и аппаратное ускорение нейросетей (векторные инструкции). С ним распознавание «на ходу» должно стать заметно стабильнее (Таблица 1).
Табл. 1. Ключевые различия между классическим модулем ESP32-CAM и ESP32-CAM S3
|
Характеристика |
Классическая ESP32-CAM |
ESP32-CAM-S3 |
Преимущество S3 |
|
Процессор |
Xtensa Dual-core LX6 (до 240 МГц) |
Xtensa Dual-core LX7 (до 240 МГц) |
Новее архитектура |
|
Аппаратное ускорение ИИ |
Нет |
Да (векторные инструкции) |
Быстрее распознавание лиц/изображений |
|
Интерфейс камеры |
Параллельный |
Параллельный / MIPI CSI |
Выше выбор поддерживаемых матриц |
|
Bluetooth |
Версия 4.2 BR/EDR |
Версия 5.0 (BLE) |
Дальнобойнее, стабильнее |
|
USB |
Через внешний преобразователь USB-UART |
Встроенный USB-OTG |
Прошивка напрямую через Type-C |
|
Память (PSRAM) |
4 МБ |
До 8 МБ (в зависимости от модели) |
Больше буфер для изображений |
|
Энергопотребление |
Высокое (особенно с Wi-Fi) |
Оптимизировано, улучшен «глубокий сон» |
Больше времени автономной работы |
Но даже сейчас, на стационаре, система даёт 97.8% точности. А этого достаточно для многих прикладных задач, где робот не должен мчаться на полной скорости. Например, у меня в планах:
-
Снимать показания счётчиков воды и электричества — камеру раз в день снимает показания счетчика, нейросеть распознаёт цифры и отправляет в Telegram (или просто сохраняет в лог).
-
Контролировать газовую плиту и воду — камера смотрит на газовую плиту и раковину на кухне (горит/не горит, течёт/не течёт) и присылает уведомление, если что-то забыли выключить.
-
Распознавать включённый утюг или обогреватель — чтобы не уходить из дома с риском пожара.
-
Мониторить стрелочные приборы — нейросеть «читает» положение стрелки на старых манометрах или вольтметрах, передавая показания в систему умного дома.
Всё это — TinyML в действии. И всё это можно сделать на том же железе (или с небольшим апгрейдом).
Заключение
Что мы сделали за один вечер?
-
Собрали датасет с помощью ESP32-CAM.
-
Обучили нейросеть в Edge Impulse (F1-score 97.8%).
-
Загрузили модель в микроконтроллер с камерой.
-
Научили ESP32-CAM отправлять команды по UART.
-
Запрограммировали Arduino на реакцию.
Всё это работает локально, без интернета, на железе общей стоимостью около 1000 рублей за ESP32-CAM (остальное, скорее всего, уже есть на столе у Arduino-энтузиаста).
Кстати, о железе
Если вам удобнее получить всё «в одном коробке» — готовую платформу, где уже есть плата ESP32-CAM и SPBot, моторы, колеса, датчики и другие электронные компоненты, а также книга «Мобильные роботы с искусственным интеллектом на базе Arduino» и руководство пользователя, — обратите внимание на набор «Мобильные роботы: конструирование, управление, обучение (SPBot + ESP32-CAM)». В него входит всё необходимое для этого проекта и ещё десятка других: от движения по линии до навигации по гироскопу.
Видеоролик об этом наборе можно посмотреть на [видео на RuTube].
Edge Impulse понимает не только картинки
В этой статье мы работали с Object Detection (распознавание объектов на изображениях). Но платформа Edge Impulse позволяет обучать модели и для других типов данных:
-
Звук — классификация звуков (лай собаки, звонок в дверь, шум станка). Можно сделать умную няню или детектор разбитого стекла.
-
Данные с датчиков — акселерометр, гироскоп, магнитометр, температура, влажность. Обучаете модель распознавать жесты, вибрации, падения, движения.
-
Временны́е последовательности — предсказание поломки двигателя по вибрации, определение типа активности по движению.
-
Обработка простых команд (голосовые метки, ключевые слова) — для голосовых интерфейсов на микроконтроллерах.
То есть одна и та же платформа закрывает 80% задач TinyML в любительской и даже промышленной робототехнике. И всё это — без написания нейросетей с нуля, в том же веб-интерфейсе, где мы только что обучали модель распознавать знаки.
Представьте: вы подключаете к SPBot гироскоп MPU6050, собираете несколько примеров движения (вперёд, назад, поворот, удар) — и через Edge Impulse получаете модель, которая отличает «робот едет прямо» от «робот упёрся в стену». Всё локально, без облака, на том же ESP32 или даже на Arduino Nano BLE Sense.
Так что после дорожных знаков — открывайте для себя TinyML дальше. Возможности огромны.
Мир умных устройств ближе, чем кажется. И он не требует миллионных бюджетов. Только ESP32-CAM, немного терпения и доступ к платформе вроде Edge Impulse.
Удачи в экспериментах!
Файлы проекта
Все скетчи, упомянутые в статье, доступны в электронном архиве. Ниже приведён список ключевых файлов с кратким описанием — вы можете найти их в архиве по именам.
|
Имя файла |
Описание |
|
Collect_Images_for_EdgeImpulse25.ino |
Скетч для сбора датасета с помощью ESP32-CAM. |
|
esp32_camera48x48.ino |
Основной скетч для ESP32-CAM с распознаванием и отправкой команд по UART. |
|
Arduino_ESP32_RGB.ino |
Скетч для Arduino Uno (или SPBot) с управлением RGB-светодиодом (общий катод). |
|
SPBot_ESP32_RGB.ino |
Скетч для платы SPBot с адресными светодиодами WS2812. |
|
Traffic_sign.sb3 |
Тестовое приложение на Scratch для имитации дорожных знаков на экране. |
Скачать [Электронный архив] (ссылка)
ссылка на оригинал статьи https://habr.com/ru/articles/1044934/