Я змусив Luckfox думати: NPU, RetinaFace і перший inference
Серія «STM32 з нуля без HAL» · Частина 9 · Місяць 3
Місяць 3 добігає кінця. За цей час ми розібрали Device Tree, навчили Linux керувати STM32 через UART, і закрили GStreamer — стрімінг з Luckfox працює. Залишилась одна тема яку я відкладав бо вона здавалась складною: NPU.
20 травня я опублікував спецвипуск про TinyML на Pi Pico — там ми запускали нейромережу на мікроконтролері через TFLite Micro. Програмний inference, без спеціалізованого заліза, все на Cortex-M0+. Це було чесно і повільно.
Luckfox Pico Pro — інша історія. RV1106 має окремий апаратний блок для нейромереж — RKNPU. Сьогодні розберемось що це таке, як його запустити, і чому перший очевидний шлях виявився хибним.
Спойлер: до живих координат обличчя з камери в реальному часі ми дійдемо. Але спочатку — граблі.
◆ Що таке NPU і навіщо він потрібен
CPU вміє робити все. Але «все» — це дорого з точки зору енергії і часу. Нейромережа — це мільйони операцій множення матриць. CPU робить їх послідовно, витрачаючи такти і гріючись.
NPU (Neural Processing Unit) — це спеціалізований блок який робить тільки одне: матричні операції. Швидко і з мінімальним енергоспоживанням. Саме тому його ставлять у смартфони, відеокамери, і embedded плати для edge AI.
На RV1106 стоїть RKNPU від Rockchip. Перевіряємо що він живий:
|
1 2 |
dmesg | grep -i npu |
|
1 2 3 4 |
[ 3.441191] RKNPU ff660000.npu: RKNPU: rknpu iommu device-tree entry not found!, using non-iommu mode [ 3.441685] RKNPU ff660000.npu: RKNPU: Initialized RKNPU driver: v0.9.2 for 20230825 [ 3.441774] RKNPU ff660000.npu: dev_pm_opp_set_regulators: no regulator (rknpu) found: -19 |
Драйвер версії v0.9.2 від 2023-08-25 — живий. /dev/rknpu є. Але це тільки ядро — userspace частина інша історія.
◆ Знаходимо частини пазлу
Для роботи з NPU потрібні три речі: драйвер (є), runtime бібліотека (librknnmrt.so), і модель (.rknn файл). Шукаємо:
|
1 |
find / -name "librknnmrt*" 2>/dev/null |
|
1 2 |
/oem/usr/lib/librknnmrt.so |
Є — але не там де очікуєш. Стандартний /usr/lib порожній. Бібліотека живе в /oem/usr/lib.
⚠ Якщо будеш шукати через
strings /usr/lib/librknnmrt.so— отримаєшNo such file or directory. Не лякайся, шукай в/oem/usr/lib.
Перевіряємо версію:
|
1 2 |
strings /oem/usr/lib/librknnmrt.so | grep "librknnmrt version" |
|
1 2 |
librknnmrt version: 1.6.0 (2de554906@2024-01-17T14:53:41) |
І одразу бачимо в тих самих рядках тривожне:
|
1 2 3 4 |
please try updating to the latest version of the toolkit2 and runtime E RKNN: incompatible model version E RKNN: Mismatch driver version |
Rockchip вбудував попередження про версійний конфлікт прямо в бібліотеку. Це натяк — версії драйвера і runtime мають збігатись. Запам’ятовуємо: драйвер v0.9.2, runtime v1.6.0.
Чи знайде її лінкер? Перевіряємо:
|
1 2 |
echo $LD_LIBRARY_PATH |
|
1 2 |
/oem/usr/lib:/oem/lib: |
Пощастило — наш кастомний Buildroot образ вже має /oem/usr/lib в LD_LIBRARY_PATH. Нічого додатково налаштовувати не треба.
Шукаємо моделі:
|
1 |
find / -name "*.rknn" 2>/dev/null |
Порожньо. Моделей на платі немає. Вони є в SDK на PC — але туди ще дістанемось.
◆ Перша спроба — rk_smart_door. І чому це хибний шлях
В SDK Luckfox є папка project/app/rk_smart_door. Там лежать готові .rknn моделі: detection.rknn, recognition.rknn, headpose.rknn, fas_ir.rknn. Здається — ось воно, бери і запускай.
|
1 |
find ~/luckfox-pico/project/app/rk_smart_door -name "*.rknn" 2>/dev/null |
|
1 2 3 4 5 |
.../algo/models/detection.rknn .../algo/models/recognition.rknn .../algo/models/headpose.rknn .../algo/models/fas_ir.rknn |
Відкриваємо CMakeLists.txt і бачимо:
|
1 2 3 4 5 |
target_link_libraries(${PROJECT_NAME} libpthread.a librt.a librockit.a librockchip_mpp.a librkaiq.a librtsp.a librkaudio_detect.a libaec_bf_process.a libm.a librockiva.a libstdc++.a librknnmrt.a librga.a libsmartIr.a) |
⚠
rk_smart_door— це повноцінний продакшн додаток для розумного дверного замка. З UVC, ISP pipeline, RTSP стрімінгом, аудіо детекцією і SmartIR підсвічуванням. 10+ залежностей, збірка тільки в контексті повного SDK build.
Нам не потрібен дверний замок. Нам потрібен мінімальний inference. Йдемо далі.
◆ Правильний шлях — luckfox_pico_rknn_example
Rockchip і LuckfoxTECH мають окремий репозиторій з мінімальними прикладами:
|
1 2 3 |
git clone https://github.com/LuckfoxTECH/luckfox_pico_rknn_example ls luckfox_pico_rknn_example/example/ |
|
1 2 3 4 |
luckfox_pico_retinaface_facenet luckfox_pico_retinaface_facenet_spidev luckfox_pico_yolov5 |
Три приклади. Нас цікавить перший — RetinaFace + FaceNet. RetinaFace детектує обличчя (знаходить bounding box), FaceNet розпізнає (порівнює з еталоном).
Перевіряємо версію бібліотеки в репо:
|
1 2 |
strings luckfox_pico_rknn_example/lib/uclibc/librknnmrt.so | grep "librknnmrt version" |
|
1 2 |
librknnmrt version: 1.6.0 (9a7b5d24c@2023-12-13T17:33:10) |
⚠ Версія
1.6.0збігається з платою. Але білди різні: репо2023-12-13, плата2024-01-17. Той самий мажорний реліз але місяць різниці. Запам’ятовуємо як потенційне джерело проблем — перевіримо при запуску.
◆ Збираємо
CMakeLists бере toolchain з змінної оточення LUCKFOX_SDK_PATH. Встановлюємо і збираємо:
|
1 2 3 4 |
export LUCKFOX_SDK_PATH=/home/alex/luckfox-pico cd luckfox_pico_rknn_example ./build.sh |
Скрипт запитає інтерактивно:
uclibcабоglibc→ вибираємо 1 (uclibc — наш Buildroot образ)- який приклад → вибираємо 1 (
luckfox_pico_retinaface_facenet)
|
1 2 3 4 5 6 7 |
[100%] Built target luckfox_pico_retinaface_facenet -- Installing: .../install/uclibc/luckfox_pico_retinaface_facenet_demo/luckfox_pico_retinaface_facenet -- Up-to-date: .../model/RetinaFace.rknn -- Up-to-date: .../model/mobilefacenet.rknn -- Up-to-date: .../model/test.jpg -- Up-to-date: .../lib/librga.so |
Зібралось з першого разу. Порівняй з rk_smart_door де треба весь SDK build — різниця відчутна.
Що отримали:
|
1 2 3 4 5 6 7 8 9 |
install/uclibc/luckfox_pico_retinaface_facenet_demo/ ├── luckfox_pico_retinaface_facenet ← бінарник ├── model/ │ ├── RetinaFace.rknn ← детекція облич │ ├── mobilefacenet.rknn ← розпізнавання │ └── test.jpg ← еталонне фото └── lib/ └── librga.so |
Копіюємо на плату:
|
1 2 3 4 |
scp -o PubkeyAuthentication=no -r \ install/uclibc/luckfox_pico_retinaface_facenet_demo \ root@192.168.1.125:/root/ |
⚠ Прапор
-o PubkeyAuthentication=noобов’язковий якщо SSH видаєToo many authentication failures. Це відбувається коли в~/.ssh/накопичилось багато ключів і сервер відхиляє з’єднання до перебору всіх.
◆ Запускаємо. І одразу перший сюрприз
Заходимо на плату і запускаємо:
|
1 2 3 |
cd /root/luckfox_pico_retinaface_facenet_demo ./luckfox_pico_retinaface_facenet model/ test.jpg |
|
1 |
./luckfox_pico_retinaface_facenet <retinaface model_path> <facenet model_path> <reference pic_path> |
⚠ Програма очікує три окремі аргументи — не папку і фото.
test.jpgце не вхідне зображення для детекції, це еталонне фото для FaceNet (reference face для порівняння). Детекція іде з камери в реальному часі.
Правильний синтаксис:
|
1 2 3 4 5 |
./luckfox_pico_retinaface_facenet \ model/RetinaFace.rknn \ model/mobilefacenet.rknn \ model/test.jpg |
Але перед запуском — обов’язковий крок.
◆ RkLunch-stop.sh — завжди перед NPU
|
1 2 |
RkLunch-stop.sh |
|
1 2 3 4 5 6 |
Stop Application ... 872 root rkipc -a /oem/usr/share/iqfiles rkipc active ... rkipc exit |
⚠
rkipc— це процес який керує камерою і ISP при звичайному завантаженні. Він тримає/dev/video*і може блокувати доступ до NPU або камери. Завжди зупиняй його перед запуском NPU демо черезRkLunch-stop.sh.
◆ NPU живе
Запускаємо з правильними аргументами і наводимо камеру на обличчя:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
init retinaface init facenet Retinaface Info model input num: 1, output num: 3 input tensors: index=0, name=input.1, n_dims=4, dims=[1, 640, 640, 3], ... output tensors: index=0, name=515 ← bounding boxes index=1, name=553 ← confidence scores index=2, name=592 ← landmarks (5 точок обличчя) Facenet Info model input num: 1, output num: 1 input tensors: index=0, name=input.1, dims=[1, 160, 160, 3] output tensors: index=0, name=313, dims=[1, 128] ← 128-мірний embedding вектор Init success |
Моделі завантажились без помилок. Версійного конфлікту не сталось — 1.6.0 з репо і 1.6.0 з плати спрацювали разом.
Камера ініціалізується через rkaiq ISP, і починається детекція:
|
1 2 3 4 5 |
@ (105 63 176 134) 1.345 @ (105 60 176 130) 1.291 @ (99 60 170 130) 1.360 @ (102 60 173 130) 1.397 |
Кожен рядок — знайдене обличчя:
|
1 2 3 |
@ (x1 y1 x2 y2 ) score @ (105 63 176 134) 1.345 |
x1 y1 — верхній лівий кут bounding box. x2 y2 — нижній правий. score — впевненість моделі. Значення 1.1–1.5 означає що модель впевнена.
Детекція стабільна, реальний час. NPU робить те для чого створений.
◆ Що далі — Стаття 10
Зараз програма виводить координати в консоль. У Статті 10 ці координати стануть командами:
|
1 2 3 4 5 6 7 |
Камера → RetinaFace на NPU (детекція) → вирізаємо ROI обличчя → HTTP POST на сервер з базою → сервер порівнює через FaceNet embeddings → відповідь: OK / UNKNOWN → якщо OK: UART3 → STM32 → реле спрацьовує |
«Luckfox думає, STM32 діє» — повний пайплайн.
До речі, поки розбирався з підключенням Luckfox по ethernet — задумався: а як в реальних автономних системах з’єднують vision модулі з мозком? CAN? Automotive Ethernet? Окремий SPI? Якщо є досвід з роботизованих систем або automotive — цікаво почути в коментарях.
◆ Гайд-шпаргалка
Всі команди в одному місці.
На PC:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
# Клонуємо репо з прикладами git clone https://github.com/LuckfoxTECH/luckfox_pico_rknn_example cd luckfox_pico_rknn_example # Встановлюємо шлях до SDK export LUCKFOX_SDK_PATH=/home/alex/luckfox-pico # Збираємо (вибираємо uclibc → retinaface_facenet) ./build.sh # Копіюємо на плату scp -o PubkeyAuthentication=no -r \ install/uclibc/luckfox_pico_retinaface_facenet_demo \ root@192.168.1.125:/root/ |
На платі:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 |
# Зупиняємо rkipc — обов'язково RkLunch-stop.sh # Запускаємо cd /root/luckfox_pico_retinaface_facenet_demo ./luckfox_pico_retinaface_facenet \ model/RetinaFace.rknn \ model/mobilefacenet.rknn \ model/test.jpg # Наводимо камеру на обличчя # Очікуємо рядки виду: @ (x1 y1 x2 y2) score |
Перевірка NPU і бібліотеки:
|
1 2 3 4 5 6 7 8 9 |
# Драйвер dmesg | grep -i npu | grep -i version # Бібліотека (увага — вона в /oem/usr/lib, не в /usr/lib) strings /oem/usr/lib/librknnmrt.so | grep "librknnmrt version" # LD_LIBRARY_PATH має включати /oem/usr/lib echo $LD_LIBRARY_PATH |
Блог серії: nodejs.jdymora.com/category/embedded/stm32
Репозиторій: github.com/pipicosim800-maker/stm32F103