Аз съм един от разработчиците на операционната система , и в тази статия ще разкажа как успях да стартирам OpenCV на платката STM32746G.
Ако напишете в търсачката нещо като «OpenCV on STM32 board», можете да намерите доста информация за хора, които се интересуват от използването на тази библиотека на платки STM32 или други микроконтролери.
Има няколко видеа, които, съдейки по заглавията, трябва да демонстрират това, което е необходимо, но обикновено (във всички видеа, които видях) на платката STM32 само се получава картинка от камерата и резултатът се извежда на екрана, а самата обработка на изображението се прави или на обикновен компютър, или на по-мощни платки (например Raspberry Pi).
Защо е трудно?
Популярността на търсените запитвания се обяснява с факта, че OpenCV е най-популярната библиотека за компютърно зрение, а това означава, че много разработчици я познават, и възможността да се стартира код, подготвен за десктопа, на микроконтролер значително опростява процеса на разработка. Но защо все още няма някакви популярни готови рецепти за решаване на този проблем?
Проблемът с използването на OpenCV на малки платки е свързан с две особености:
- Ако компилирате библиотеката дори с минималния набор от модули, във флаш паметта на същата STM32F7Discovery просто не ще събира (дори без да се брои ОС) заради много голямия код (няколко мегабайта инструкции)
- Самата библиотека е написана на C++, а значит
- Нужна е поддръжка на C++ рантайма (изключения и т.н.)
- Има недостатъчна поддръжка на LibC/Posix, които обикновено са налични в операционните системи за вградени системи — нужна е стандартна библиотека за C++ и стандартна библиотека от шаблони STL (vector и т.н.)
Портиране на Embox
Както обикновено, преди портиране на някакви програми в операционна система, е добре да опитате да я съберете в формата, в който са я замислили разработчиците. В нашия случай проблеми с това няма — изходния код може да се намери на , библиотеката се събира под GNU/Linux с обикновен cmake.
От добрите новини — OpenCV от самото начало може да се събира във вид на статична библиотека, което прави портирането по-лесно. Събираме библиотеката със стандартна конфигурация и наблюдаваме колко място заема. Всеки модул се събира в отделна библиотека.
> size lib/*so --totals
text data bss dec hex filename
1945822 15431 960 1962213 1df0e5 lib/libopencv_calib3d.so
17081885 170312 25640 17277837 107a38d lib/libopencv_core.so
10928229 137640 20192 11086061 a928ed lib/libopencv_dnn.so
842311 25680 1968 869959 d4647 lib/libopencv_features2d.so
423660 8552 184 432396 6990c lib/libopencv_flann.so
8034733 54872 1416 8091021 7b758d lib/libopencv_gapi.so
90741 3452 304 94497 17121 lib/libopencv_highgui.so
6338414 53152 968 6392534 618ad6 lib/libopencv_imgcodecs.so
21323564 155912 652056 22131532 151b34c lib/libopencv_imgproc.so
724323 12176 376 736875 b3e6b lib/libopencv_ml.so
429036 6864 464 436364 6a88c lib/libopencv_objdetect.so
6866973 50176 1064 6918213 699045 lib/libopencv_photo.so
698531 13640 160 712331 ade8b lib/libopencv_stitching.so
466295 6688 168 473151 7383f lib/libopencv_video.so
315858 6972 11576 334406 51a46 lib/libopencv_videoio.so
76510375 721519 717496 77949390 4a569ce (TOTALS)Както личи от последния ред, .bss и .data не заемат много място, но кода е над 70 МиБ. Ясно е, че ако това бъде линковано статично с конкретно приложение, кода ще намалее.
Нека се опитаме да премахнем колкото се може повече модули, за да сглобим минимален пример (който, например, просто ще изведе версията на OpenCV), така че да гледаме. cmake .. -LA и да изключим във опциите всичко, което може да бъде изключено.
-DBUILD_opencv_java_bindings_generator=OFF
-DBUILD_opencv_stitching=OFF
-DWITH_PROTOBUF=OFF
-DWITH_PTHREADS_PF=OFF
-DWITH_QUIRC=OFF
-DWITH_TIFF=OFF
-DWITH_V4L=OFF
-DWITH_VTK=OFF
-DWITH_WEBP=OFF> size lib/libopencv_core.a --totals
text data bss dec hex filename
3317069 36425 17987 3371481 3371d9 (TOTALS)От една страна, това е само един модул на библиотеката, от друга страна, това е без оптимизация на компилатора по размер на кода (-Os). ~3 МиБ код — това все още е доста, но вече дава надежда за успех.
Стартиране в емулатор.
На емулатора е много по-лесно да се отстраняват грешки, затова първо ще се уверим, че библиотеката работи на qemu. Като емулирана платформа избрах Integrator/CP, защото, от една страна, това също е ARM, а от друга, Embox поддържа графичен изход за тази платформа.
В Embox има механизъм за сглобяване на външни библиотеки, с негова помощ добавям OpenCV като модул (предавайки същите опции за "минимално" сглобяване под формата на статични библиотеки), след което добавям най-простото приложение, което изглежда така:
version.cpp:
#include
#include
int main() {
printf("OpenCV: %s", cv::getBuildInformation().c_str());
return 0;
}Сглобяваме системата, стартираме — получаваме очаквания изход.
root@embox:\/ #opencv_version
OpenCV:
Обща конфигурация за OpenCV 4.0.1 =====================================
Контрол на версията: bd6927bdf-dirty
Платформа:
Времеви печат: 2019-06-21T10:02:18Z
Хост: Linux 5.1.7-arch1-1-ARCH x86_64
Цел: Generic arm-unknown-none
CMake: 3.14.5
Генератор на CMake: Unix Makefiles
Инструмент за изграждане на CMake: \/usr\/bin\/make
Конфигурация: Debug
Характеристики на CPU\/HW:
Основен:
изисква: DETECT
деактивирано: VFPV3 NEON
C\/C++:
Изградено ли е като динамични библиотеки?: НЕСледващата стъпка е да стартирате пример, най-добре стандартен от онези, които предлагат самите разработчици . Избрах .
Примерът трябваше да бъде преработен малко, за да се показва изображението с резултата директно в framebuffer. Това беше необходимо, тъй като функцията imshow() може да рисува изображения чрез интерфейсите QT, GTK и Windows, които, разбира се, в конфигурацията за STM32 определено няма да намерите. Всъщност, QT също може да бъде стартиран на STM32F7Discovery, но за това ще спомена в друга статия 🙂
След кратко проучване, в какъв точен формат се съхранява резултатът от работата на детектора на ръбове, получаваме изображението.

Оригиналното изображение

Резултат
Стартиране на STM32F7Discovery
На 32F746GDISCOVERY има няколко хардуерни секции за памет, които можем да използваме по един или друг начин
- 320KiB оперативна памет
- 1MiB флаш памет за образа
- 8MiB SDRAM
- 16MiB QSPI NAND флаш-памет
- Слот за microSD карта
SD картата може да се използва за съхранение на изображения, но в контекста на стартиране на минимален пример това не е много полезно.
Дисплеят има резолюция 480×272, а следователно, паметта за framebuffer ще съставлява 522 240 байта при дълбочина 32 бита, т.е. това е повече от размера на оперативната памет, така че framebuffer и купчината (която ще е необходима за OpenCV, за да съхранява данни за изображения и помощни структури) ще бъдат разположени в SDRAM, а всичко останало (памет за стекове и други системни нужди) ще отиде в RAM.
Ако вземете минималната конфигурация за STM32F7Discovery (изключете цялата мрежа, всички команди, направете стековете колкото се може по-малки и т.н.) и добавите OpenCV с примери, нужната памет ще бъде следната:
text data bss dec hex filename
2876890 459208 312736 3648834 37ad42 build\/base\/bin\/emboxЗа тези, които не са много запознати с това, какви секции къде се поставят, ще обясня: в .text и .rodata се намират инструкции и константи (на грубо, readonly-данни), в .data се поставят изменяемите данни, в .bss съдържат „нулевите“ променливи, които, все пак, се нуждаят от място (тази секция „ще отиде“ в RAM).
Добрата новина е, че .data/.bss трябва да се поберат, а ето с .text проблем — под образа има само 1MiB памет. Може да изхвърлим изображението от примера и да го четем, например, от SD-карта в паметта при стартиране, но fruits.png тежи около 330KiB, така че това не решава проблема: голяма част от .text състои именно от кода на OpenCV. .text На голямо ниво, остава само едно — да заредим част от кода на QSPI-флашката (тя има специален режим на работа за мапиране на паметта на системната шина, така че процесорът може да получава достъп до тези данни директно). Въпреки това възниква проблем: първо, паметта на QSPI-флашката не е достъпна веднага след рестартиране на устройството (трудно е да се инициализира режим на мапирана памет), второ, не може да „флашне“ тази памет с обикновения загрузчик.
Като резултат беше решено да се свърже целия код в QSPI и да се флашне с написан от нас загрузчик, който ще получава нужния бинарен файл по TFTP.
Идеята да се портне тази библиотека на Embox се появи преди около година, но отново и отново беше отлагана по различни причини. Една от тях е поддръжката на libstdc++ и стандартната шаблонна библиотека. Проблемът с поддръжката на C++ в Embox надхвърля обхвата на тази статия, затова тук само ще кажа, че успяхме да постигнем тази поддръжка в необходимия обем за работа на тази библиотека 🙂
Резултат
В крайна сметка тези проблеми бяха преодолени (поне в достатъчна степен за работа на примера OpenCV), и примерът стартира. 40 дълги секунди отнема на дъската да намери границите с филтър Кенни. Това е, разбира се, много дълго (има идеи как да се оптимизира, за това може да се напише отделна статия при успех).
Все пак, междинната цел беше създаването на прототип, който да покаже принципната възможност за стартиране на OpenCV на STM32, следователно, тази цел беше постигната, ура!

tl;dr: стъпка по стъпка инструкция
0: Изтеглете изходниците на Embox, например така:
git clone https://github.com/embox/embox && cd ./embox
1: Нека започнем с компилирането на загрузчика, който „ще флашне“ QSPI-флашката.make confload-arm/stm32f7cube
направи confload-arm/stm32f7cubeСега трябва да конфигурираме мрежата, тъй като ще зареждаме образа по TFTP. За да зададем IP адресите на платката и хоста, трябва да променим файла conf/rootfs/network.
Примерна конфигурация:
iface eth0 inet static
address 192.168.2.2
netmask 255.255.255.0
gateway 192.168.2.1
hwaddress aa:bb:cc:dd:ee:02gateway — адресът на хоста, от който ще бъде зареждан образа, address — адресът на платката.
След това събираме зареждача:
, затова потребителите на Windows трябва да инсталират Cygwin или да се справят сами с Visual Studio и библиотеките.2: Обикновено зареждане на зареждача (извинявайте за каламбура) на платката — тук няма нищо специфично, нужно е да се направи както за всяко друго приложение за STM32F7Discovery. Ако не знаете как става, можете да прочетете за това. .
3: Компилация на образа с конфиг за OpenCV.
make confload-platform/opencv/stm32f7discovery
make4: Извлечение от ELF секции, които трябва да се запишат в QSPI, в qspi.bin
arm-none-eabi-objcopy -O binary build/base/bin/embox build/base/bin/qspi.bin
--only-section=.text --only-section=.rodata
--only-section='.ARM.ex*'
--only-section=.dataВ директорията conf има скрипт, който прави това, така че можете да го стартирате.
./conf/qspi_objcopy.sh # Нужният бинарен файл -- build/base/bin/qspi.bin5: Чрез tftp зареждаме qspi.bin.bin на QSPI флаш памет. На хоста за целта трябва да копирате qspi.bin в кореновата папка на tftp сървъра (обикновено това е /srv/tftp/ или /var/lib/tftpboot/; пакетите за съответния сървър ги има в повечето популярни дистрибуции, обикновено се наричат tftpd или tftp-hpa, понякога трябва да направите systemctl start tftpd.service за стартиране).
# вариант для tftpd
sudo cp build/base/bin/qspi.bin /srv/tftp
# вариант для tftp-hpa
sudo cp build/base/bin/qspi.bin /var/lib/tftpbootНа Embox (т.е. в зареждача) трябва да изпълните следната команда (предполагаме, че адресът на сървъра е 192.168.2.1):
embox> qspi_loader qspi.bin 192.168.2.16: Чрез командата goto трябва да "скочите" в QSPI паметта. Конкретната локация ще варира в зависимост от това как образът се свързва, можете да видите този адрес с командата mem 0x90000000 (адресът на старта е втората 32-битова дума на образа); също така е необходимо да зададете стека с флага -s, адресът на стека е на адрес 0x90000000, пример:
embox>mem 0x90000000
0x90000000: 0x20023200 0x9000c27f 0x9000c275 0x9000c275
↑ ↑
това е адресът това е адресът
на стека на първата
инструкция
embox>goto -i 0x9000c27f -s 0x20023200 # Флагът -i е необходим, за да се забранят прекъсванията по време на инициализацията на системата7: Стартиране
embox> edges 20и се наслаждаваме на 40-секундно търсене на ръбове 🙂
Ако нещо се обърка — пишете issue в , или в пощенската разписка embox-devel@googlegroups.com, или в коментарите тук.
Източник: habr.com
