OpenCV на STM32F7-Discovery

OpenCV на STM32F7-Discovery Аз съм един от разработчиците на операционната система Embox, и в тази статия ще разкажа как успях да стартирам 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, но за това ще спомена в друга статия 🙂

След кратко проучване, в какъв точен формат се съхранява резултатът от работата на детектора на ръбове, получаваме изображението.

OpenCV на STM32F7-Discovery

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

OpenCV на STM32F7-Discovery

Резултат

Стартиране на STM32F7Discovery

На 32F746GDISCOVERY има няколко хардуерни секции за памет, които можем да използваме по един или друг начин

  1. 320KiB оперативна памет
  2. 1MiB флаш памет за образа
  3. 8MiB SDRAM
  4. 16MiB QSPI NAND флаш-памет
  5. Слот за 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, следователно, тази цел беше постигната, ура!

OpenCV на STM32F7-Discovery

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:02

gateway — адресът на хоста, от който ще бъде зареждан образа, address — адресът на платката.

След това събираме зареждача:

    , затова потребителите на Windows трябва да инсталират Cygwin или да се справят сами с Visual Studio и библиотеките.

2: Обикновено зареждане на зареждача (извинявайте за каламбура) на платката — тук няма нищо специфично, нужно е да се направи както за всяко друго приложение за STM32F7Discovery. Ако не знаете как става, можете да прочетете за това. тук.
3: Компилация на образа с конфиг за OpenCV.

    make confload-platform/opencv/stm32f7discovery
    make

4: Извлечение от 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.bin

5: Чрез 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.1

6: Чрез командата 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

Купете надежден хостинг за сайтове със защита от DDoS, VPS и VDS сървъри 🔥 Купете надежден хостинг за сайтове със защита от DDoS, VPS и VDS сървъри | ProHoster