Используем библиотеку TensorFlow Lite и небольшие модели машинного обучения на Arduino для обнаружения людей через камеру.
Шаг 1. Комплектующие
Ниже приведем список комплектующих для нашего проекта обнаружения людей с использованием Arduino.
Компоненты оборудования
- Arduino Nano 33 BLE Sense × 1
- Arducam Mini 2MP × 1
- Зарядное устройство Adafruit Micro-Lipo × 1
- Литий-ионный полимерный аккумулятор Adafruit × 1
Программное обеспечение
- TensorFlow
- Arduino IDE
Библиотеку TensorFlow можно скачать на официальный сайте или на нашем сайте в разделе Библиотеки по этой ссылке.
Инструменты
- Макет, 170-контактный
- Провода-перемычки, 40х6" (150 мм)
Одним из захватывающих аспектов работы моделей машинного обучения на встроенном оборудовании является возможность использования устройств с низким энергопотреблением с большей контекстной осведомленностью для запуска устройств с высоким энергопотреблением только тогда, когда это полезно.
В качестве подтверждения концепции мы хотим использовать маломощный Arduino Nano 33 BLE Sense и ArduCam Mini 2MP вместе с библиотекой TensorFlow Lite, чтобы запускать реле (включение/выключение) при распознавании человека.
Шаг 2. Схема соединения
Используйте схему ниже для подключения Arduino Nano 33 BLE Sense к ArduCam Mini 2MP. На этой диаграмме также показано, как добавить аккумулятор после того, как вы прошили Arduino. Нажмите на схему для увеличения.
Шаг 3. Прошивка модели
В официальном репозитории TensorFlow было выпущено несколько примеров мини-моделей, наиболее распространенным примером из которых является модель micro_speech.
Мы хотели для демо использовать модель person_detection, однако на момент написания этого поста не все ресурсы были доступны. Из примера micro_speech мы увидели, что эти zip-пакеты Arduino хранятся в том же месте, что и сборки TensorFlow от Google. При переходе по этой ссылке вы увидите дерево документов со ссылками на все виды сборок:
Использовав быстрый поиск по дереву документов, мы смогли найти недостающий пакет.
Скачиваем пакет по ссылке и переименовываем в person_detection.zip. Это будет пакет, который мы загружаем в виде библиотеки в нашу Arduino IDE, как написано в официальной инструкции.
После загрузки этого zip-пакета мы хотим внести еще пару изменений, чтобы все работало гладко. Находим где установлена библиотека Arduino и открываем файл library.properties для редактирования.
# In our case, our Arduino library is in ~/Arduino/libraries $ cd ~/Arduino/libraries/person_detection $ vim library.properties
Измените первую строку, которая объявляет имя библиотеки на TensorFlowLite:person_detection. Ваш файл должен выглядеть так:
name=TensorFlowLite:person_detection version=1.14-ALPHA author=TensorFlow Authors maintainer=Pete Warden <petewarden@google.com> sentence=Allows you to run machine learning models locally on your device. paragraph=This library runs TensorFlow machine learning models on microcontrollers, allowing you to build AI/ML applications powered by deep learning and neural networks. With the included examples, you can recognize speech, detect people using a camera, and recognise "magic wand" gestures using an accelerometer. The examples work best with the Arduino Nano 33 BLE Sense board, which has a microphone and accelerometer. category=Data Processing url=https://www.tensorflow.org/lite/microcontrollers/overview ldflags=-lm includes=TensorFlowLite.h
Эти действия делают всё видимым в меню примеров Arduino IDE. Наконец, перейдем в каталог examples/ (рус. - примеры) там же и переименовываем подкаталог в person_detection. Все эти изменения устраняют проблемы запуска демонстрации. Вы можете почитать официальную инструкцию по загрузке библиотек для ArduCam и JPEG декодирования.
Чтобы запустить пример, перейдите в:
Файлы -> Примеры -> TensorflowLite:person_detection (англ. - Files -> Examples -> TensorflowLite:person_detection)
Выберите пример сценария person_detection. Убедитесь, что ваш Arduino Nano BLE 33 Sense подключен к вашему компьютеру и выбран для перепрограммирования в Arduino IDE. Залейте пример и он должен запуститься немедленно. Индикатор на плате будет мигать синим цветом, указывая на то, что изображение было снято и вывод был завершен. Свет станет красным, если он не обнаружил человека, и станет зеленым, если человек обнаружен. Вывод занимает примерно 19 секунд, так как модель обнаружения человека достаточно большая для нашего устройства.
Шаг 4. Код проекта
Программа ниже использует библиотеку Arduino BLE для отправки оповещения на периферийное устройство через Bluetooth при обнаружении человека.
Библиотеку ArduinoBLE можно скачать на нашем сайте по этой ссылке.
#include <ArduinoBLE.h> #include <TensorFlowLite.h> #include "main_functions.h" #include "detection_responder.h" #include "image_provider.h" #include "model_settings.h" #include "person_detect_model_data.h" #include "tensorflow/lite/experimental/micro/kernels/micro_ops.h" #include "tensorflow/lite/experimental/micro/micro_error_reporter.h" #include "tensorflow/lite/experimental/micro/micro_interpreter.h" #include "tensorflow/lite/experimental/micro/micro_mutable_op_resolver.h" #include "tensorflow/lite/schema/schema_generated.h" #include "tensorflow/lite/version.h" // Globals, used for compatibility with Arduino-style sketches. namespace { tflite::ErrorReporter* error_reporter = nullptr; const tflite::Model* model = nullptr; tflite::MicroInterpreter* interpreter = nullptr; TfLiteTensor* input = nullptr; // An area of memory to use for input, output, and intermediate arrays. constexpr int kTensorArenaSize = 70 * 1024; static uint8_t tensor_arena[kTensorArenaSize]; } // namespace // The name of this function is important for Arduino compatibility. void setup() { // Set up logging. Google style is to avoid globals or statics because of // lifetime uncertainty, but since this has a trivial destructor it's okay. // NOLINTNEXTLINE(runtime-global-variables) static tflite::MicroErrorReporter micro_error_reporter; error_reporter = µ_error_reporter; // Map the model into a usable data structure. This doesn't involve any // copying or parsing, it's a very lightweight operation. model = tflite::GetModel(g_person_detect_model_data); if (model->version() != TFLITE_SCHEMA_VERSION) { error_reporter->Report( "Model provided is schema version %d not equal " "to supported version %d.", model->version(), TFLITE_SCHEMA_VERSION); return; } // Pull in only the operation implementations we need. // This relies on a complete list of all the ops needed by this graph. // An easier approach is to just use the AllOpsResolver, but this will // incur some penalty in code space for op implementations that are not // needed by this graph. // // tflite::ops::micro::AllOpsResolver resolver; // NOLINTNEXTLINE(runtime-global-variables) static tflite::MicroMutableOpResolver micro_mutable_op_resolver; micro_mutable_op_resolver.AddBuiltin( tflite::BuiltinOperator_DEPTHWISE_CONV_2D, tflite::ops::micro::Register_DEPTHWISE_CONV_2D()); micro_mutable_op_resolver.AddBuiltin(tflite::BuiltinOperator_CONV_2D, tflite::ops::micro::Register_CONV_2D()); micro_mutable_op_resolver.AddBuiltin( tflite::BuiltinOperator_AVERAGE_POOL_2D, tflite::ops::micro::Register_AVERAGE_POOL_2D()); // Build an interpreter to run the model with. static tflite::MicroInterpreter static_interpreter( model, micro_mutable_op_resolver, tensor_arena, kTensorArenaSize, error_reporter); interpreter = &static_interpreter; // Allocate memory from the tensor_arena for the model's tensors. TfLiteStatus allocate_status = interpreter->AllocateTensors(); if (allocate_status != kTfLiteOk) { error_reporter->Report("AllocateTensors() failed"); return; } // Get information about the memory area to use for the model's input. input = interpreter->input(0); /* BLE initialize */ BLE.begin(); //BLE.scanForUuid("1801"); } // The name of this function is important for Arduino compatibility. void loop() { // Get image from provider. if (kTfLiteOk != GetImage(error_reporter, kNumCols, kNumRows, kNumChannels, input->data.uint8)) { error_reporter->Report("Image capture failed."); } // Run the model on this input and make sure it succeeds. if (kTfLiteOk != interpreter->Invoke()) { error_reporter->Report("Invoke failed."); } TfLiteTensor* output = interpreter->output(0); // Process the inference results. uint8_t person_score = output->data.uint8[kPersonIndex]; uint8_t no_person_score = output->data.uint8[kNotAPersonIndex]; RespondToDetection(error_reporter, person_score, no_person_score); /* Send inference via bluetooth */ BLE.scanForUuid("1801"); delay(500); // delay to find uuid BLEDevice peripheral = BLE.available(); Serial.println(peripheral); if (peripheral){ if (peripheral.localName() != "PersonDetectionMonitor") { return; } BLE.stopScan(); if (person_score > no_person_score){ sendAlert(peripheral,2); } else{ sendAlert(peripheral,0); } //peripheral.disconnect(); } else{ Serial.println("Peripheral not available"); } } void sendAlert(BLEDevice peripheral, int sendsignal) { // connect to the peripheral Serial.println("Connecting ..."); if (peripheral.connect()) { Serial.println("Connected"); } else { Serial.println("Failed to connect!"); return; } // discover peripheral attributes Serial.println("Discovering attributes ..."); if (peripheral.discoverAttributes()) { Serial.println("Attributes discovered"); } else { Serial.println("Attribute discovery failed!"); peripheral.disconnect(); return; } // retrieve the alert level characteristic BLECharacteristic alertLevelChar = peripheral.characteristic("2A06"); if (!alertLevelChar) { Serial.println("Peripheral does not have alert level characteristic!"); peripheral.disconnect(); return; } else if (!alertLevelChar.canWrite()) { Serial.println("Peripheral does not have a writable alert level characteristic!"); peripheral.disconnect(); return; } if (peripheral.connected()) { if (sendsignal > 0){ alertLevelChar.writeValue((byte)0x02); Serial.println("Wrote high alert"); } else{ alertLevelChar.writeValue((byte)0x00); Serial.println("Wrote low alert"); } } peripheral.disconnect(); Serial.println("Peripheral disconnected"); }
Код ниже использует библиотеку Arduino BLE для подключения и прослушивания оповещения, срабатывающего устройством обнаружения человека, для включения/выключения реле:
#include <ArduinoBLE.h> #include "Arduino.h" // Relay on D8 int relay = 8; // BLE PersonDetection Service BLEService PersonDetectionService("1801"); // BLE Alert Level Characteristic BLEByteCharacteristic alertLevelChar("2A06", // standard 16-bit characteristic UUID BLERead | BLEWrite); // remote clients will be able to get notifications if this characteristic changes void setup() { Serial.begin(9600); // initialize serial communication while (!Serial); pinMode(LED_BUILTIN, OUTPUT); // initialize the built-in LED pin to indicate when a central is connected //pinMode(LEDR, OUTPUT); //pinMode(LEDG, OUTPUT); //pinMode(LEDB, OUTPUT); //pinMode(relay, OUTPUT); //digitalWrite(LEDR, LOW); //digitalWrite(LEDG, LOW); //digitalWrite(LEDB, LOW); digitalWrite(relay, LOW); // begin initialization if (!BLE.begin()) { Serial.println("starting BLE failed!"); while (1); } /* Set a local name for the BLE device This name will appear in advertising packets and can be used by remote devices to identify this BLE device The name can be changed but maybe be truncated based on space left in advertisement packet */ BLE.setLocalName("PersonDetectionMonitor"); BLE.setAdvertisedService(PersonDetectionService); // add the service UUID PersonDetectionService.addCharacteristic(alertLevelChar); // add the alert level characteristic BLE.addService(PersonDetectionService); // Add the battery service alertLevelChar.writeValue((byte)0x00); /* Start advertising BLE. It will start continuously transmitting BLE advertising packets and will be visible to remote BLE central devices until it receives a new connection */ // start advertising BLE.advertise(); Serial.println("Bluetooth device active, waiting for connections..."); } void loop() { // wait for a BLE central BLEDevice central = BLE.central(); // if a central is connected to the peripheral: if (central) { Serial.print("Connected to central: "); // print the central's BT address: Serial.println(central.address()); // turn on the LED to indicate the connection: digitalWrite(LED_BUILTIN, HIGH); // while the central is connected: while (central.connected()) { //Serial.println("Getting Alert Level:"); if (alertLevelChar.written()){ if (alertLevelChar.value()){ Serial.println("Got high alert"); digitalWrite(relay, HIGH); Serial.println("Set relay to HIGH"); } else{ Serial.println("Got low alert"); digitalWrite(relay, LOW); Serial.println("Set relay to LOW"); } } } // when the central disconnects, turn off the LED: digitalWrite(LED_BUILTIN, LOW); Serial.print("Disconnected from central: "); Serial.println(central.address()); } }
Шаг 5. Использование Arduino в качестве интеллектуального переключателя
Микроконтроллеры очень энергоэффективны, некоторые способны работать на батарейках типа «таблетка» годами. Благодаря недавним усилиям по минимизации моделей машинного обучения для работы на встроенных устройствах мы можем создавать интеллектуальные «переключатели», которые можно использовать для включения или запуска других устройств, которым для работы требуется больше энергии.
С данной моделью обнаружения человека, работающей на основе Arduino BLE, мы можем запустить устройство с высоким энергопотреблением, такое, например, как Donkeycar, используя шилд Sombrero. Мы будем использовать реле, подключенное к Arduino и включать/выключать его в зависимости от результатов логического вывода.
Подключите реле, pi и Arduino, следуя схеме подключения ниже (нажмите на схему для увеличения):
Теперь просто добавьте несколько строк в пример person_detection.ino (см. выше), чтобы посмотреть результаты вывода и переключить реле:
// at the top of the script, initialize a variable for the relay signal int relayOut = 10; // in the setup() loop, initialize the relayOut pin as output void setup(){ /* .... */ pinMode(relayOut, OUTPUT); } // in the void() loop: void loop() { // Get image from provider. if (kTfLiteOk != GetImage(error_reporter, kNumCols, kNumRows, kNumChannels, input->data.uint8)) { error_reporter->Report("Image capture failed."); } // Run the model on this input and make sure it succeeds. if (kTfLiteOk != interpreter->Invoke()) { error_reporter->Report("Invoke failed."); } TfLiteTensor* output = interpreter->output(0); // Process the inference results. uint8_t person_score = output->data.uint8[kPersonIndex]; uint8_t no_person_score = output->data.uint8[kNotAPersonIndex]; RespondToDetection(error_reporter, person_score, no_person_score); // Add the following lines: if (person_score >= no_person_score){ digitalWrite(relayOut, HIGH); } else { digitalWrite(relayOut, LOW); } }
Включив связь BLE между Arduino и приемником на DonkeyCar, мы можем распределить множество подобных датчиков для настройки приложений безопасности или наблюдения.