OpenCV auf STM32F7-Discovery

OpenCV auf STM32F7-Discovery Ich bin einer der Entwickler des Betriebssystems Embox, und in diesem Artikel werde ich darüber berichten, wie ich OpenCV auf dem STM32746G-Board zum Laufen gebracht habe.

Wenn man in eine Suchmaschine etwas wie "OpenCV auf STM32-Board" eingibt, findet man viele, die sich für die Nutzung dieser Bibliothek auf STM32-Boards oder anderen Mikrocontrollern interessieren.
Es gibt einige Videos, die laut Titel anzeigen sollten, was erforderlich ist, aber normalerweise (in allen Videos, die ich gesehen habe) wurde auf dem STM32-Board nur das Bild von der Kamera erfasst und das Ergebnis auf dem Bildschirm ausgegeben, während die Bildverarbeitung entweder auf einem normalen Computer oder auf leistungsstärkeren Boards (wie z. B. Raspberry Pi) durchgeführt wurde.

Warum ist das schwierig?

Die Popularität der Suchanfragen erklärt sich dadurch, dass OpenCV die bekannteste Bibliothek für Computer Vision ist, was bedeutet, dass viele Entwickler damit vertraut sind. Außerdem vereinfacht die Möglichkeit, Desktop-Code auf einem Mikrocontroller auszuführen, den Entwicklungsprozess erheblich. Aber warum gibt es bis jetzt keine populären fertigen Rezepte zur Lösung dieses Problems?

Die Nutzung von OpenCV auf kleinen Platinen bringt zwei Herausforderungen mit sich:

  • Selbst wenn die Bibliothek mit dem minimalen Satz an Modulen kompiliert wird, passt sie aufgrund des sehr umfangreichen Codes (mehrere Megabyte an Anweisungen) einfach nicht in den Flash-Speicher des gleichen STM32F7Discovery (ganz zu schweigen vom Betriebssystem).
  • Die Bibliothek selbst ist in C++ geschrieben, was bedeutet,
    • dass Unterstützung für die C++-Laufzeitumgebung (Ausnahmen usw.) erforderlich ist.
    • Es gibt nur wenig Unterstützung für LibC/Posix, die normalerweise in Betriebssystemen für eingebettete Systeme vorhanden ist – die Standardbibliothek für C++ sowie die Standard-Template-Bibliothek STL (Vector usw.) werden benötigt.

Portierung auf Embox

Wie gewohnt ist es vor der Portierung von Programmen in ein Betriebssystem ratsam, es in der Form zu kompilieren, in der es von den Entwicklern gedacht war. In unserem Fall treten dabei keine Probleme auf – der Quellcode ist verfügbar unter GitHub, die Bibliothek kann mit dem üblichen cmake unter GNU/Linux kompiliert werden.

Die gute Nachricht ist, dass OpenCV direkt als statische Bibliothek kompiliert werden kann, was die Portierung erleichtert. Wir kompilieren die Bibliothek mit der Standardkonfiguration und sehen, wie viel Platz sie benötigt. Jedes Modul wird in einer separaten Bibliothek kompiliert.

> Größe lib/*so --totals
   Text    Daten     BSS     dez     hex Datei
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 (GESAMT)

Wie aus der letzten Zeile ersichtlich, nehmen .bss und .data nicht viel Platz ein, während der Code über 70 MiB ausmacht. Es ist klar, dass der Code bei einer statischen Verlinkung mit einer bestimmten Anwendung weniger werden würde.

Versuchen wir, so viele Module wie möglich zu entfernen, um ein minimales Beispiel zu erstellen (das beispielsweise einfach die OpenCV-Version ausgibt), also schauen wir uns das an. cmake .. -LA und schalten in den Optionen alles ab, was abgeschaltet werden kann.

        -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

> Größe lib/libopencv_core.a --totals
   text    data     bss     dec     hex Datei
3317069   36425   17987 3371481  3371d9 (GESAMT)

Einerseits handelt es sich nur um ein Modul der Bibliothek, andererseits erfolgt dies ohne Codegröße-Optimierung durch den Compiler (-Os). ~3 MiB Code — das ist immer noch relativ viel, gibt aber bereits Anlass zur Hoffnung auf Erfolg.

Ausführung im Emulator

Es ist viel einfacher, im Emulator zu debuggen. Daher stellen wir zuerst sicher, dass die Bibliothek auf qemu funktioniert. Als emulierte Plattform habe ich Integrator/CP gewählt, weil es erstens ebenfalls ARM ist und zweitens Embox die Grafikausgabe für diese Plattform unterstützt.

In Embox gibt es einen Mechanismus zum Kompilieren externer Bibliotheken. Damit fügen wir OpenCV als Modul hinzu (indem wir alle gleichen Optionen für den „minimalen“ Kompilierungsansatz in Form von statischen Bibliotheken übergeben). Danach füge ich eine einfachste Anwendung hinzu, die so aussieht:

version.cpp:

#include 
#include 

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

    return 0;
}

Wir kompilieren das System, starten es — und erhalten die erwartete Ausgabe.

root@embox:/#opencv_version                                                     
OpenCV: 
Allgemeine Konfiguration für OpenCV 4.0.1 =====================================
  Versionskontrolle:            bd6927bdf-dirty

  Plattform:
    Zeitstempel:                2019-06-21T10:02:18Z
    Host:                       Linux 5.1.7-arch1-1-ARCH x86_64
    Ziel:                       Generic arm-unknown-none
    CMake:                      3.14.5
    CMake-Generator:            Unix Makefiles
    CMake-Bautool:              /usr/bin/make
    Konfiguration:              Debug

  CPU/HW-Funktionen:
    Basislinie:
      angefordert:               DETECT
      deaktiviert:               VFPV3 NEON

  C/C++:
    Als dynamische Bibliotheken gebaut?:  NEIN

Der nächste Schritt besteht darin, ein Beispiel zu starten, am besten eines der Standardbeispiele, die die Entwickler selbst anbieten. auf ihrer Website.. Ich habe mich für den Canny-Kantendetektor.

entschieden. Das Beispiel musste ein wenig umgeschrieben werden, um das Ergebnisbild direkt im Framebuffer anzuzeigen. Das war notwendig, da die Funktion imshow() Bilder über die Schnittstellen QT, GTK und Windows anzeigen kann, die natürlich in der Konfiguration für STM32 nicht vorhanden sein werden. Tatsächlich kann QT auch auf STM32F7Discovery ausgeführt werden, aber das wird in einem anderen Artikel beschrieben 🙂

Nach kurzer Klärung, in welchem Format die Ergebnisse des Kantendetektors gespeichert werden, erhalten wir das Bild.

OpenCV auf STM32F7-Discovery

Das Originalbild

OpenCV auf STM32F7-Discovery

Ergebnis

Start auf STM32F7Discovery

Auf dem 32F746GDISCOVERY gibt es mehrere Hardware-Speicherabschnitte, die wir auf verschiedene Weise nutzen können.

  1. 320 KiB RAM
  2. 1 MiB Flash-Speicher für das Image
  3. 8 MiB SDRAM
  4. 16 MiB QSPI NAND-Flash
  5. Anschluss für microSD-Karte

Die SD-Karte kann zur Speicherung von Bildern verwendet werden, ist jedoch im Kontext des Starts eines minimalen Beispiels nicht besonders nützlich.
Das Display hat eine Auflösung von 480×272, wodurch der Speicher für den Framebuffer 522.240 Bytes bei einer Tiefe von 32 Bit betragen wird. Das ist mehr als der Speicher des RAM, daher werden wir den Framebuffer und den Heap (der auch für OpenCV benötigt wird, um Daten für Bilder und Hilfsstrukturen zu speichern) im SDRAM ablegen, während alles andere (Speicher für Stacks und andere Systembedürfnisse) in den RAM gelegt wird.

Wenn man die Minimal-Konfiguration für STM32F7Discovery nimmt (alle Netzwerkfunktionen entfernt, alle Befehle entfernt, die Stacks so klein wie möglich macht, usw.) und dort OpenCV mit Beispielen hinzufügt, ergeben sich folgende Anforderungen an den Speicher:

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

Für diejenigen, die nicht ganz sicher sind, welche Sektionen wo hinein gehören, eine Erklärung: In .text und .rodata liegen die Anweisungen und Konstanten (grob gesagt, readonly-Daten), in .data liegen die veränderlichen Daten, in .bss liegen die „nullisierten“ Variablen, für die jedoch Speicher benötigt wird (diese Sektion wird in RAM abgelegt).

Die gute Nachricht ist, dass .data/.bss untergebracht werden müssen, aber das .text Problem ist — es steht nur 1MiB Speicher für das Image zur Verfügung. Man könnte das Bild aus dem Beispiel entfernen und es beispielsweise beim Start von einer SD-Karte in den Speicher laden, aber fruits.png wiegt etwa 330KiB, sodass dieses Problem nicht gelöst wird: der Großteil .text Das Bild aus dem Beispiel wird zum Beispiel beim Start von einer SD-Karte in den Speicher geladen, aber fruits.png hat ein Gewicht von etwa 330 KiB, sodass dieses Problem nicht gelöst wird: der Großteil .text besteht tatsächlich aus OpenCV-Code.

Im Großen und Ganzen bleibt nur eines — einen Teil des Codes auf den QSPI-Flash-Speicher zu laden (dieser verfügt über einen speziellen Betriebsmodus zur Speicherabbildung auf den Systembus, sodass der Prozessor direkt auf diese Daten zugreifen kann). Dabei tritt folgendes Problem auf: Erstens ist der Speicher des QSPI-Flashs unmittelbar nach dem Neustart des Geräts nicht verfügbar (der memory-mapped-Modus muss separat initialisiert werden), zweitens kann dieser Speicher nicht mit einem gewohnten Bootloader programmiert werden.

Schließlich wurde beschlossen, den gesamten Code in QSPI zu verlinken und ihn mit einem selbstgeschriebenen Bootloader zu flashen, der das benötigte Binary über TFTP abruft.

Ergebnis

Die Idee, diese Bibliothek auf Embox zu portieren, kam vor etwa einem Jahr auf, wurde jedoch immer wieder aus verschiedenen Gründen verschoben. Einer davon ist die Unterstützung von libstdc++ und der Standard-Template-Bibliothek. Das Problem der C++-Unterstützung in Embox geht über den Rahmen dieses Artikels hinaus, daher sage ich hier nur, dass es uns gelungen ist, diese Unterstützung im notwendigen Umfang für den Betrieb dieser Bibliothek zu erreichen 🙂

Letztendlich wurden diese Probleme überwunden (zumindest in ausreichendem Maße, um das OpenCV-Beispiel zum Laufen zu bringen), und das Beispiel startete. Die Platine benötigt 40 lange Sekunden, um mit dem Canny-Filter die Kanten zu finden. Das ist natürlich zu lange (es gibt Überlegungen, wie man dies optimieren kann; darüber kann in einem separaten Artikel im Erfolgsfall geschrieben werden).

OpenCV auf STM32F7-Discovery

Dennoch war das Zwischenziel, einen Prototyp zu erstellen, der die grundsätzliche Möglichkeit zeigt, OpenCV auf STM32 auszuführen; diese Zielsetzung wurde erreicht, hurra!

tl;dr: Schritt-für-Schritt-Anleitung

0: Laden Sie die Embox-Quellcodes herunter, zum Beispiel so:

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

1: Lassen Sie uns mit dem Erstellen des Bootloaders beginnen, der den QSPI-Flash flashen wird.

    make confload-arm/stm32f7cube

Jetzt müssen wir das Netzwerk konfigurieren, da wir das Image über TFTP laden werden. Um die IP-Adressen für das Board und den Host festzulegen, muss die Datei conf/rootfs/network geändert werden.

Beispielkonfiguration:

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 — die Adresse des Hosts, von dem das Image geladen wird, address — die Adresse des Boards.

Nachdem dies erledigt ist, bauen wir den Bootloader:

    make

2: Normales Laden des Bootloaders (Entschuldigung für das Wortspiel) auf das Board — hier gibt es nichts Besonderes, dies muss wie bei jeder anderen Anwendung für STM32F7Discovery durchgeführt werden. Wenn Sie nicht wissen, wie das geht, können Sie darüber lesen. hier.
3: Kompilierung des Images mit der Konfiguration für OpenCV.

    make confload-platform/opencv/stm32f7discovery
    make

4: Extrahieren der ELF-Sektionen, die in den QSPI geschrieben werden müssen, in 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

Im Verzeichnis conf befindet sich ein Skript, das dies erledigt, also können Sie es ausführen.

    ./conf/qspi_objcopy.sh # Die benötigte Binärdatei -- build/base/bin/qspi.bin

5: Über tftp laden wir qspi.bin.bin auf den QSPI-Flash. Dazu muss auf dem Host die Datei qspi.bin in das Wurzelverzeichnis des tftp-Servers kopiert werden (gewöhnlich ist das /srv/tftp/ oder /var/lib/tftpboot/; die Pakete für den entsprechenden Server sind in den meisten gängigen Distributionen verfügbar, normalerweise unter dem Namen tftpd oder tftp-hpa, manchmal ist es nötig zu starten) systemctl start tftpd.service zum Start).

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

Auf Embox (d.h. im Bootloader) muss der folgende Befehl ausgeführt werden (wir gehen davon aus, dass die Adresse des Servers 192.168.2.1 ist):

    embox> qspi_loader qspi.bin 192.168.2.1

6: Mit dem Befehl goto müssen wir in den QSPI-Speicher "springen". Der genaue Speicherort variiert je nach Verlinkung des Abbildes, diese Adresse kann mit dem Befehl mem 0x90000000 (die Startadresse liegt im zweiten 32-Bit-Wort des Abbildes); außerdem muss der Stack mit einem Flag festgelegt werden, -sdie Stack-Adresse befindet sich bei 0x90000000, Beispiel:

    embox>mem 0x90000000
    0x90000000:     0x20023200  0x9000c27f  0x9000c275  0x9000c275
                      ↑           ↑
              dies ist die Adresse    dies ist die Adresse 
                des Stacks        der ersten
                           Instruktion

    embox>goto -i 0x9000c27f -s 0x20023200 # Das Flag -i ist nötig, um Unterbrechungen während der Systeminitialisierung zu verhindern

7: Wir starten

    embox> edges 20

und genießen eine 40-sekündige Grenzsuche 🙂

Falls etwas schiefgeht, schreiben Sie ein Issue in unserem Repository, oder an die Mailingliste embox-devel@googlegroups.com, oder hier in die Kommentare.

Quelle: habr.com

Kaufen Sie zuverlässiges Hosting für Websites mit DDoS-Schutz, VPS VDS-Server 🔥 Kaufen Sie zuverlässiges Hosting für Websites mit DDoS-Schutz, VPS VDS-Server | ProHoster