Оценка здоровья растений с TinyML: Практическое руководство
Компоненты и материалы
| Arduino Nano 33 BLE Sense | 1 |
| Шаговый двигатель (Digilent) | 1 |
| Драйвер шагового двигателя DRV8825 | 1 |
Инструменты
- 3D-принтер
- Паяльная станция
Приложения и сервисы
- Microsoft VS Code
- Arduino IDE
- Edge Impulse Studio
О проекте
Преамбула
Как и люди, растения могут заболеть. Как у вас может появиться сыпь от кожного заражения, листья растения могут пожелтеть или покрыться пятнами от грибка или другого патогена. Используя машинное обучение, можно сканировать цвета и обучать модель определять, когда цвет листа «неправильный».
Аппаратная часть
Мозг проекта — Arduino Nano 33 BLE Sense. Выбран по нескольким причинам: богатый набор мощных сенсоров (9DoF IMU, APDS-9960 — цвет, жесты, приближенность, яркость; микрофон; комбо датчик температуры/влажности/давления). Для перемещения платы по листу и замеров используются пара шаговых двигателей с драйверами DRV8825.
Настройка TinyML
Для этого проекта встроенные сенсоры Nano 33 BLE Sense в Edge Impulse не подойдут (там только акселерометр и микрофон). Придется использовать data forwarder вместо serial daemon. Создал новый проект, установил Edge Impulse CLI через Node.js/NPM: npm install -g edge-impulse-cli. Возможно, нужно добавить путь установки в PATH. Запустил edge-impulse-data-forwarder, проверил работу, вышел Ctrl+C.
Чтение цвета
APDS-9960 измеряет цвет, отражая ИК-свет от поверхности и анализируя непоглощенные длины волн. Для общения с датчиком лучше установить библиотеку Arduino APDS9960. В коде APDS-9960 инициализируется, затем в loop ждет данные цвета. Если чтение доступно, считывает цвет через APDS.readColor() и приближенность. Каждая RGB-компонента конвертируется из 0..2^16-1 в отношение значения к сумме.
Сканирующий риг
Сканирование цвета листа осуществляется перемещением рига в двух осях, проходящим различные участки листа под встроенным APDS-9960. Каждая ось движется вращением линейного штока по или против часовой стрелки. Вся система спроектирована в Fusion 360, рендеры ниже:
Ось X опирается на ось Y, позволяя верхнему блоку двигаться по обеим осям. Дополнительное V-колесо на оси Y поддерживает вес шагового двигателя. Детали напечатаны PLA с ~45% заполнением.
Сбор данных
При старте шаговые не знают свою позицию, поэтому две оси идут в 홈 пошагово до срабатывания концевиков. Затем инициализируется APDS-9960. Задается ограничивающая рамка (bounding box) как два массива из двух элементов — противоположные углы бокса. Выбирается случайная точка внутри, шаговые едут в эту позицию, считывая цвета по пути.
Обработка и отправка цветовых данных
Цвета читаются через APDS.readColor(). После расчета суммы вычисляется процент и отправляется по USB через Serial.printf(). Значения разделены запятыми, каждое чтение — новой строкой. Data forwarder получает данные и отправляет в Edge Impulse cloud как обучающую выборку с меткой (healthy/unhealthy).
Обучение модели
После сбора данных создаем импульс: трехосевой временной ряд, спектральный анализ, Keras-блок. На скриншотах — как генерировал признаки из данных.
Тестирование
Собрал новые тестовые данные на больном листе. Точность модели ~63%, при подаче тестовых признаков модель правильно классифицировала лист в большинстве случаев.
Точность можно улучшить, добавив больше обучающих данных и снизив скорость обучения.
Код
leafReader.ino (C/C++)
#include#include #include #include "pinDefs.h" int r, g, b, c, p; float sum; AccelStepper xStepper(AccelStepper::DRIVER, STEPPER_1_STEP, STEPPER_1_DIR); AccelStepper yStepper(AccelStepper::DRIVER, STEPPER_2_STEP, STEPPER_2_DIR); MultiStepper steppers; const long boundingBox[2][2] = { {0, 0}, {40, 40} }; void setup() { Serial.begin(115200); while(!Serial); if(!APDS.begin()) { Serial.println("Could not init APDS9960"); while(1); } pinMode(X_AXIS_HOMING_SW, INPUT_PULLUP); pinMode(Y_AXIS_HOMING_SW, INPUT_PULLUP); xStepper.setPinsInverted(X_AXIS_DIR); yStepper.setPinsInverted(Y_AXIS_DIR); xStepper.setMaxSpeed(150); yStepper.setMaxSpeed(150); steppers.addStepper(xStepper); steppers.addStepper(yStepper); homeMotors(); } void loop() { long randomPos[2]; randomPos[0] = random(boundingBox[0][0], boundingBox[1][0]) * STEPS_PER_MM; randomPos[1] = random(boundingBox[0][1], boundingBox[1][1]) * STEPS_PER_MM; steppers.moveTo(randomPos); while(steppers.run()) { if(!APDS.colorAvailable() || !APDS.proximityAvailable()) {} else { APDS.readColor(r, g, b, c); sum = r + g + b; p = APDS.readProximity(); if(!p && c > 10 && sum >= 0) { float rr = r / sum, gr = g / sum, br = b / sum; Serial.printf("%1.3f,%1.3f,%1.3f\n", rr, gr, br); } } } } void homeMotors() { while(digitalRead(X_AXIS_HOMING_SW)) xStepper.move(-1); while(digitalRead(Y_AXIS_HOMING_SW)) yStepper.move(-1); xStepper.setCurrentPosition(0); yStepper.setCurrentPosition(0); }
pinDefs.h
#define STEPPER_1_STEP 2 #define STEPPER_1_DIR 3 #define STEPPER_2_STEP 4 #define STEPPER_2_DIR 5 #define X_AXIS_HOMING_SW 6 #define Y_AXIS_HOMING_SW 7 #define X_AXIS_DIR false #define Y_AXIS_DIR false #define MICROSTEPS 1 #define STEPS_PER_MM 160 * MICROSTEPS
3D-модели для печати
CAD-файл на thingiverse.com
Схемы
Производственный процесс
- Датчик CO2 K30: мониторинг качества воздуха в реальном времени на Arduino
- Устройство для общения глухосліпых людей с помощью 1Sheeld и Arduino
- Умная копилка: управление монетоприёмником на Arduino Nano
- Управление светодиодом по Bluetooth с Arduino: простой DIY-гид
- Интеграция ёмкостного сканера отпечатков DFRobot с Arduino или ESP8266
- Интерактивный HMI-проект на Nextion с Arduino Mega и датчиками
- Роботизированная рука 6 степеней свободы на Arduino Mega с управлением через Nunchuk
- Управление двумя шаговыми двигателями через джойстик на Arduino Nano — простой урок (Visuino)
- Измерение солнечной радиации на Arduino UNO с Ethernet Shield
- Компактный радар на Arduino Nano с HC-SR04 и дисплеем 1.8"