OpenCV na STM32F7-Discovery

OpenCV na STM32F7-Discovery Jestem jednym z twórców systemu operacyjnego Skrzynka odbiorcza, aw tym artykule opowiem o tym, jak udało mi się uruchomić OpenCV na płycie STM32746G.

Jeśli wpiszesz w wyszukiwarkę coś w rodzaju „OpenCV na płytce STM32”, ​​możesz znaleźć sporo osób zainteresowanych użyciem tej biblioteki na płytkach STM32 lub innych mikrokontrolerach.
Jest kilka filmików, które sądząc po nazwie powinny pokazywać co trzeba, ale zwykle (we wszystkich filmikach jakie widziałem) na płytce STM32 odbierany był tylko obraz z kamery i wynik wyświetlał się na ekranie, a samo przetwarzanie obrazu odbywało się albo na zwykłym komputerze, albo na mocniejszych płytach (na przykład Raspberry Pi).

Dlaczego to trudne?

Popularność zapytań wynika z faktu, że OpenCV jest najpopularniejszą biblioteką komputerową, co oznacza, że ​​zna ją więcej programistów, a możliwość uruchamiania gotowego kodu na mikrokontrolerze znacznie upraszcza proces programowania. Ale dlaczego wciąż nie ma popularnych gotowych przepisów na rozwiązanie tego problemu?

Problem używania OpenCV na małych szalach wiąże się z dwiema cechami:

  • Jeśli skompilujesz bibliotekę nawet z minimalnym zestawem modułów, po prostu nie zmieści się ona w pamięci flash tego samego STM32F7Discovery (nawet bez uwzględnienia systemu operacyjnego) ze względu na bardzo duży kod (kilka megabajtów instrukcji)
  • Sama biblioteka jest napisana w C++, co oznacza
    • Potrzebujesz wsparcia dla pozytywnego czasu działania (wyjątki itp.)
    • Niewielkie wsparcie dla LibC/Posix, które zwykle znajduje się w systemie operacyjnym dla systemów wbudowanych - potrzebujesz standardowej biblioteki plus i standardowej biblioteki szablonów STL (wektor itp.)

Portowanie do Emboxa

Jak zwykle, przed przeniesieniem jakichkolwiek programów do systemu operacyjnego, warto spróbować zbudować go w formie, w jakiej zamierzyli to programiści. W naszym przypadku nie ma z tym żadnych problemów - kod źródłowy można znaleźć na stronie github, biblioteka jest zbudowana pod GNU/Linux za pomocą zwykłego cmake.

Dobrą wiadomością jest to, że OpenCV można zbudować jako statyczną bibliotekę od razu po wyjęciu z pudełka, co ułatwia przenoszenie. Zbieramy bibliotekę ze standardową konfiguracją i sprawdzamy, ile miejsca zajmują. Każdy moduł jest gromadzony w oddzielnej bibliotece.

> 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)

Jak widać z ostatniego wiersza, .bss i .data nie zajmują dużo miejsca, ale kod to ponad 70 MiB. Oczywiste jest, że jeśli jest to statycznie powiązane z konkretną aplikacją, kod będzie mniejszy.

Spróbujmy wyrzucić jak najwięcej modułów, aby zmontować minimalny przykład (który, na przykład, po prostu wyświetli wersję OpenCV), więc wyglądamy cmake .. -LA i wyłącz w opcjach wszystko co się wyłącza.

        -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)

Z jednej strony to tylko jeden moduł biblioteki, z drugiej strony bez optymalizacji kompilatora pod kątem rozmiaru kodu (-Os). ~3 MiB kodu to wciąż sporo, ale już daje nadzieję na sukces.

Uruchom w emulatorze

Dużo łatwiej jest debugować na emulatorze, więc najpierw upewnij się, że biblioteka działa na qemu. Jako emulowaną platformę wybrałem Integrator / CP, ponieważ po pierwsze jest to również ARM, a po drugie Embox obsługuje wyjście graficzne dla tej platformy.

Embox posiada mechanizm budowania zewnętrznych bibliotek, za jego pomocą dodajemy OpenCV jako moduł (przekazując wszystkie te same opcje dla „minimalnego” buildu w postaci bibliotek statycznych), po czym dodaję prostą aplikację wyglądającą tak:

version.cpp:

#include <stdio.h>
#include <opencv2/core/utility.hpp>

int main() {
    printf("OpenCV: %s", cv::getBuildInformation().c_str());

    return 0;
}

Montujemy system, uruchamiamy go - otrzymujemy oczekiwany wynik.

root@embox:/#opencv_version                                                     
OpenCV: 
General configuration for OpenCV 4.0.1 =====================================
  Version control:               bd6927bdf-dirty

  Platform:
    Timestamp:                   2019-06-21T10:02:18Z
    Host:                        Linux 5.1.7-arch1-1-ARCH x86_64
    Target:                      Generic arm-unknown-none
    CMake:                       3.14.5
    CMake generator:             Unix Makefiles
    CMake build tool:            /usr/bin/make
    Configuration:               Debug

  CPU/HW features:
    Baseline:
      requested:                 DETECT
      disabled:                  VFPV3 NEON

  C/C++:
    Built as dynamic libs?:      NO
< Дальше идут прочие параметры сборки -- с какими флагами компилировалось,
  какие модули OpenCV включены в сборку и т.п.>

Kolejnym krokiem jest uruchomienie jakiegoś przykładu, najlepiej jednego ze standardowych oferowanych przez samych programistów. na Twojej stronie. wybieram sprytny wykrywacz granic.

Przykład trzeba było lekko przepisać, aby obraz z wynikiem wyświetlał się bezpośrednio w buforze ramki. Musiałem to zrobić, bo. funkcjonować imshow() może rysować obrazy przez interfejsy QT, GTK i Windows, czego oczywiście na pewno nie będzie w konfiguracji dla STM32. W rzeczywistości QT można również uruchomić na STM32F7Discovery, ale zostanie to omówione w innym artykule 🙂

Po krótkim wyjaśnieniu w jakim formacie zapisywany jest wynik detektora krawędzi otrzymujemy obraz.

OpenCV na STM32F7-Discovery

oryginalny obraz

OpenCV na STM32F7-Discovery

Doświadcz mocnych i skutecznych rezultatów

Działa na STM32F7Discovery

W 32F746GDISCOVERY istnieje kilka sprzętowych sekcji pamięci, które możemy wykorzystać w taki czy inny sposób

  1. 320KiB pamięci RAM
  2. 1MiB flash dla obrazu
  3. SDRAM 8 MB
  4. Pamięć flash 16 MB QSPI NAND
  5. gniazdo kart microSD

Karta SD może służyć do przechowywania obrazów, ale w kontekście uruchamiania minimalnego przykładu nie jest to zbyt przydatne.
Wyświetlacz ma rozdzielczość 480×272, co oznacza, że ​​pamięć framebuffer będzie miała 522 240 bajtów na głębokości 32 bitów, tj. to więcej niż rozmiar pamięci RAM, więc bufor ramki i sterta (które będą wymagane, w tym dla OpenCV, do przechowywania danych do obrazów i struktur pomocniczych) zostaną umieszczone w SDRAM, wszystko inne (pamięć na stosy i inne potrzeby systemowe) ) trafi do pamięci RAM.

Jeśli weźmiemy minimalną konfigurację dla STM32F7Discovery (wyrzucimy całą sieć, wszystkie polecenia, zmniejszymy stosy do minimum itp.) i dodamy tam OpenCV z przykładami, wymagana pamięć będzie następująca:

   text    data     bss     dec     hex filename
2876890  459208  312736 3648834  37ad42 build/base/bin/embox

Dla tych, którzy nie są zaznajomieni z tym, które sekcje idą gdzie, wyjaśnię: w .text и .rodata znajdują się instrukcje i stałe (z grubsza mówiąc dane tylko do odczytu). .data dane są zmienne, .bss istnieją zmienne „zerowane”, które jednak potrzebują miejsca (ta sekcja „przejdzie” do pamięci RAM).

Dobrą wiadomością jest to, że .data/.bss powinien pasować, ale z .text problem polega na tym, że na obraz jest tylko 1 MB pamięci. Można wyrzucić .text obrazek z przykładu i wczytać go np. z karty SD do pamięci przy starcie, ale fruits.png waży około 330KiB, więc to nie rozwiąże problemu: większość .text składa się z kodu OpenCV.

W zasadzie pozostaje tylko jedno - załadowanie części kodu na pamięć flash QSPI (posiada ona specjalny tryb działania do mapowania pamięci na magistralę systemową, dzięki czemu procesor ma bezpośredni dostęp do tych danych). W takim przypadku pojawia się problem: po pierwsze, pamięć dysku flash QSPI nie jest dostępna natychmiast po ponownym uruchomieniu urządzenia (trzeba osobno zainicjować tryb mapowania pamięci), a po drugie, nie można „flashować” tej pamięci za pomocą znany bootloader.

W rezultacie zdecydowano się połączyć cały kod w QSPI i sflashować go samodzielnie napisanym programem ładującym, który otrzyma wymagany plik binarny przez TFTP.

Doświadcz mocnych i skutecznych rezultatów

Pomysł przeportowania tej biblioteki na Embox pojawił się jakiś rok temu, ale z różnych przyczyn był wielokrotnie przekładany. Jednym z nich jest obsługa libstdc++ i standardowej biblioteki szablonów. Problem obsługi C++ w Emboksie wykracza poza zakres tego artykułu, więc tutaj powiem tylko, że udało nam się to wsparcie osiągnąć w odpowiedniej ilości, aby ta biblioteka działała 🙂

Ostatecznie problemy te zostały przezwyciężone (przynajmniej na tyle, aby przykład OpenCV działał) i przykład został uruchomiony. Wyszukiwanie granic za pomocą filtra Canny'ego zajmuje tablicy 40 długich sekund. To oczywiście za długo (trwają rozważania, jak tę sprawę zoptymalizować, w przypadku sukcesu będzie można o tym napisać osobny artykuł).

OpenCV na STM32F7-Discovery

Jednak celem pośrednim było stworzenie prototypu, który pokaże fundamentalne możliwości uruchomienia OpenCV na odpowiednio STM32, cel ten został osiągnięty, hurra!

tl;dr: instrukcje krok po kroku

0: Pobierz źródła Embox, takie jak to:

    git clone https://github.com/embox/embox && cd ./embox

1: Zacznijmy od zmontowania programu ładującego, który „flashuje” dysk flash QSPI.

    make confload-arm/stm32f7cube

Teraz musisz skonfigurować sieć, ponieważ. Prześlemy obraz przez TFTP. Aby ustawić adresy IP płyty i hosta, musisz edytować plik conf/rootfs/network.

Przykład konfiguracji:

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 - adres hosta, z którego zostanie załadowany obraz, address - adres zarządu.

Następnie zbieramy program ładujący:

    make

2: Zwykłe ładowanie bootloadera (przepraszam za kalambur) na płytce - nie ma tu nic konkretnego, trzeba to zrobić jak w przypadku każdej innej aplikacji dla STM32F7Discovery. Jeśli nie wiesz, jak to zrobić, możesz o tym przeczytać tutaj.
3: Kompilowanie obrazu z konfiguracją dla OpenCV.

    make confload-platform/opencv/stm32f7discovery
    make

4: Wyciąg z sekcji ELF do zapisania w QSPI do 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

W katalogu conf znajduje się skrypt, który to robi, więc możesz go uruchomić

    ./conf/qspi_objcopy.sh # Нужный бинарник -- build/base/bin/qspi.bin

5: Używając tftp, pobierz qspi.bin.bin na dysk flash QSPI. Aby to zrobić, skopiuj plik qspi.bin na hoście do głównego folderu serwera tftp (zwykle /srv/tftp/ lub /var/lib/tftpboot/; pakiety dla odpowiedniego serwera są dostępne w najpopularniejszych dystrybucjach, zwykle nazywanych tftpd lub tftp-hpa, czasem trzeba to zrobić systemctl start tftpd.service zacząć).

    # вариант для tftpd
    sudo cp build/base/bin/qspi.bin /srv/tftp
    # вариант для tftp-hpa
    sudo cp build/base/bin/qspi.bin /var/lib/tftpboot

Na Emboksie (czyli w bootloaderze) należy wykonać następującą komendę (zakładamy, że serwer ma adres 192.168.2.1):

    embox> qspi_loader qspi.bin 192.168.2.1

6: Z polecenia goto musisz "wskoczyć" do pamięci QSPI. Konkretna lokalizacja będzie się różnić w zależności od tego, jak obraz jest połączony, możesz zobaczyć ten adres za pomocą polecenia mem 0x90000000 (adres początkowy mieści się w drugim 32-bitowym słowie obrazu); będziesz także musiał oflagować stos -s, adres stosu to 0x90000000, przykład:

    embox>mem 0x90000000
    0x90000000:     0x20023200  0x9000c27f  0x9000c275  0x9000c275
                      ↑           ↑
              это адрес    это  адрес 
                стэка        первой
                           инструкции

    embox>goto -i 0x9000c27f -s 0x20023200 # Флаг -i нужен чтобы запретить прерывания во время инициализации системы

    < Начиная отсюда будет вывод не загрузчика, а образа с OpenCV >

7: Uruchom

    embox> edges 20

i ciesz się 40-sekundowym przeszukiwaniem granicy 🙂

Jeśli coś pójdzie nie tak - napisz problem w nasze repozytoriumlub do listy mailingowej [email chroniony]lub w komentarzu tutaj.

Źródło: www.habr.com

Dodaj komentarz