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 es geschafft habe, OpenCV auf dem STM32746G-Board zum Laufen zu bringen.

Wenn man in die Suchmaschine etwas eingibt wie „OpenCV on STM32 board“, findet man ziemlich viele, die sich für die Nutzung dieser Bibliothek auf STM32-Boards oder anderen Mikrocontrollern interessieren.
Es gibt mehrere Videos, die, judging by the title, das demonstrieren sollen, was benötigt wird, aber normalerweise (in allen Videos, die ich gesehen habe) wurde auf dem STM32-Board nur der Empfang eines Bildes von der Kamera und die Ausgabe des Ergebnisses auf den Bildschirm gemacht, während die Bildverarbeitung entweder auf einem normalen Computer oder auf leistungsfähigeren Boards (zum Beispiel Raspberry Pi) stattfand.

Warum ist das schwierig?

Die Beliebtheit der Suchanfragen erklärt sich dadurch, dass OpenCV die beliebteste Bibliothek für Computer Vision ist, was bedeutet, dass mehr Entwickler damit vertraut sind, und die Möglichkeit, Desktop-fähigen Code auf einem Mikrocontroller auszuführen, vereinfacht den Entwicklungsprozess erheblich. Aber warum gibt es bisher keine beliebten, fertig erstellten Rezepte zur Lösung dieses Problems?

Das Problem bei der Verwendung von OpenCV auf kleinen Boards hängt mit zwei Besonderheiten zusammen:

  • Selbst wenn man die Bibliothek mit einem minimalen Satz von Modulen kompiliert, passt sie einfach nicht in den Flash-Speicher des STM32F7Discovery (auch ohne Berücksichtigung des Betriebssystems) aufgrund des sehr großen Codes (mehrere Megabyte an Anweisungen)
  • Die Bibliothek ist in C++ geschrieben, was bedeutet,
    • Es wird Unterstützung für den C++-Laufzeit benötigt (Ausnahmen usw.)
    • Es fehlt an Unterstützung für LibC/Posix, die normalerweise in Betriebssystemen für eingebettete Systeme vorhanden sind - es wird eine Standardbibliothek für C++ und die Standard-Template-Bibliothek STL (vector usw.) benötigt.

Portierung auf Embox

Wie üblich, vor der Portierung von Programmen in ein Betriebssystem ist es ratsam, zu versuchen, es in der Form zusammenzustellen, in der es von den Entwicklern gedacht war. In unserem Fall treten dabei keine Probleme auf - der Quellcode ist auf GitHub, die Bibliothek kann unter GNU/Linux mit dem gewöhnlichen cmake erstellt werden.

Die gute Nachricht ist, dass OpenCV direkt als statische Bibliothek gebaut werden kann, was die Portierung erleichtert. Wir bauen die Bibliothek mit der Standardkonfiguration und schauen, wie viel Platz sie einnimmt. Jedes Modul wird in eine separate Bibliothek gebaut.

> Größe lib/*so --totals
   text    data     bss     dec     hex dateiname
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)

Wie aus der letzten Zeile ersichtlich, nehmen .bss und .data nicht viel Speicherplatz ein, während der Code über 70 MiB beträgt. Es ist klar, dass, wenn dies statisch mit einer bestimmten Anwendung verknüpft wird, der Code geringer wird.

Versuchen wir, so viele Module wie möglich wegzulassen, um ein minimales Beispiel zu erstellen (das zum Beispiel einfach die Version von OpenCV 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 dateiname
3317069   36425   17987 3371481  3371d9 (TOTALS)

Einerseits ist das nur ein Modul der Bibliothek, andererseits ist das ohne den Compiler-Optimierungen für die Codegröße (-Os). ~3 MiB Code sind immer noch ziemlich viel, geben aber bereits Hoffnung auf Erfolg.

Ausführung im Emulator

Die Fehlersuche im Emulator ist viel einfacher, also stellen wir zunächst sicher, dass die Bibliothek auf qemu funktioniert. Als emulierte Plattform habe ich Integrator/CP gewählt, da es zum einen ebenfalls ARM ist und zum anderen Embox die grafische Ausgabe für diese Plattform unterstützt.

In Embox gibt es einen Mechanismus zum Erstellen externer Bibliotheken, mit dessen Hilfe fügen wir OpenCV als Modul hinzu (indem wir all die gleichen Optionen für den „minimalen“ Build 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 bauen 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:                      Generisch arm-unknown-none
    CMake:                       3.14.5
    CMake-Generator:             Unix Makefiles
    CMake-Bauwerkzeug:            \/usr\/bin\/make
    Konfiguration:               Debug

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

  C\/C++:
    Als dynamische Bibliotheken erstellt?:      NEIN

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

Das Beispiel musste ein wenig umgeschrieben werden, um das Bild mit dem Ergebnis direkt im Framebuffer anzuzeigen. Das war nötig, da die Funktion imshow() Bilder über die Interfaces QT, GTK und Windows zeichnen kann, die im Konfigurationsfile für STM32 selbstverständlich nicht vorhanden sind. Tatsächlich kann QT auch auf STM32F7Discovery ausgeführt werden, aber dazu wird in einem anderen Artikel mehr erzählt 🙂

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

OpenCV auf STM32F7-Discovery

Das originale Bild

OpenCV auf STM32F7-Discovery

Ergebnis

Start auf STM32F7Discovery

Auf dem 32F746GDISCOVERY gibt es mehrere Hardware-Speichersegmente, die wir mehr oder weniger nutzen können.

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

Die SD-Karte kann zur Speicherung von Bildern verwendet werden, ist im Kontext des minimalen Beispiels jedoch nicht besonders nützlich.
Der Bildschirm hat eine Auflösung von 480 × 272, was bedeutet, dass der Speicher für den Framebuffer 522.240 Byte bei einer Tiefe von 32 Bit betragen wird, d.h. das ist mehr als die Größe des RAM, sodass der Framebuffer und das Heap (das unter anderem für OpenCV benötigt wird, um Daten für Bilder und Hilfsstrukturen zu speichern) in SDRAM untergebracht werden, während alles andere (Speicher für Stacks und andere systemtechnische Bedürfnisse) in den RAM geht.

Wenn wir die minimale Konfiguration für STM32F7Discovery nehmen (das gesamte Netzwerk, alle Befehle entfernen, die Stacks so klein wie möglich machen usw.) und OpenCV mit Beispielen hinzufügen, sieht der benötigte Speicher folgendermaßen aus:

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

Für diejenigen, die nicht genau wissen, welche Sektionen wohin gehören, erkläre ich: in .text und .rodata liegen die Anweisungen und Konstanten (grob gesprochen, schreibgeschützte Daten), in .data liegen die veränderbaren Daten, in .bss liegen die "nullgesetzten" Variablen, die dennoch Platz benötigen (diese Sektion wird in den RAM „geschickt“).

Die gute Nachricht ist, dass .data/.bss untergebracht werden müssen, aber die .text Schwierigkeit besteht darin, dass es nur 1MiB Speicher für das Image gibt. Man könnte das Bild aus dem Beispiel herausnehmen und es beispielsweise von einer SD-Karte in den Speicher beim Starten lesen, aber fruits.png belegt etwa 330KiB, sodass dies das Problem nicht löst: der Großteil .text besteht tatsächlich aus OpenCV-Code. .text Im Großen und Ganzen bleibt nur eines übrig — die Speicherung eines Teils des Codes im QSPI-Flash (es hat einen speziellen Betriebsmodus zum Mappen des Speichers auf den Systembus, sodass der Prozessor direkt auf diese Daten zugreifen kann). Dabei entsteht folgendes Problem: Erstens ist der Speicher des QSPI-Flashs nach dem Neustart des Geräts nicht sofort verfügbar (der memory-mapped-Modus muss separat initialisiert werden), zweitens kann dieser Speicher nicht mit einem herkömmlichen Bootloader „programmiert“ werden.

Letztendlich wurde beschlossen, den gesamten Code im QSPI zu verlinken und ihn mit einem selbstgeschriebenen Bootloader zu flashen, der das benötigte Binärbild über TFTP abruft.

Die Idee, diese Bibliothek auf Embox zu portieren, kam bereits vor etwa einem Jahr auf, wurde aber immer wieder aus verschiedenen Gründen verschoben. Einer davon ist die Unterstützung von libstdc++ und der Standard Template Library. Das Problem der C++-Unterstützung in Embox fällt außerhalb dieses Artikels, daher sage ich hier nur, dass es uns gelungen ist, diese Unterstützung im erforderlichen Umfang für die Arbeit dieser Bibliothek zu erreichen 🙂

Ergebnis

Insgesamt wurden diese Probleme überwunden (zumindest in ausreichendem Maße für die Ausführung des OpenCV-Beispiels), und das Beispiel lief. Die Platine benötigt 40 lange Sekunden, um mit dem Canny-Filter die Konturen zu suchen. Das ist natürlich viel zu lange (es gibt Überlegungen, wie man dies optimieren könnte, darüber könnte man einen separaten Artikel schreiben, falls es Erfolg hat).

Nichtsdestotrotz war das Zwischenziel, einen Prototyp zu erstellen, der die grundsätzliche Möglichkeit zeigt, OpenCV auf STM32 zu starten, und dieses Ziel wurde erreicht, hurra!

OpenCV auf STM32F7-Discovery

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

0: Wir laden die Embox-Quellen herunter, zum Beispiel so:

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

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

make confload-arm/stm32f7cube

    make confload-arm/stm32f7cube

Jetzt muss das Netzwerk konfiguriert werden, da wir das Image über TFTP laden werden. Um die IP-Adressen der Karte und des Hosts festzulegen, muss die Datei conf/rootfs/network geändert werden.

Beispielkonfiguration:

iface eth0 inet statisch
    Adresse 192.168.2.2
    Netzmaske 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 der Karte.

Danach kompilieren wir den Bootloader:

    make

2: Normales Laden des Bootloaders (Entschuldigung für das Wortspiel) auf die Karte — hier gibt es nichts Spezifisches, es muss wie bei jeder anderen Anwendung für STM32F7Discovery gemacht werden. Wenn Sie nicht wissen, wie das geht, können Sie dazu lesen hier.
3: Kompilierung des Images mit Konfiguration für OpenCV.

    make confload-platform/opencv/stm32f7discovery
    make

4: Extraktion der ELF-Sektionen, die in 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 kann es gestartet werden

    ./conf/qspi_objcopy.sh # Das benötigte Binary -- build/base/bin/qspi.bin

5: Laden Sie mit tftp qspi.bin auf den QSPI-Flash. Dazu muss qspi.bin in das Stammverzeichnis des tftp-Servers auf dem Host kopiert werden (in der Regel ist das /srv/tftp/ oder /var/lib/tftpboot/; die Pakete für den entsprechenden Server sind in den meisten gängigen Distributionen erhältlich, normalerweise mit dem Namen tftpd oder tftp-hpa, manchmal muss man systemctl start tftpd.service zum Starten).

    # вариант для 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 Serveradresse 192.168.2.1 ist):

    embox> qspi_loader qspi.bin 192.168.2.1

6: Mit dem Befehl goto muss man in den QSPI-Speicher „springen“. Der spezifische Speicherort variiert je nachdem, wie das Image verlinkt ist; diese Adresse kann mit dem Befehl abgerufen werden mem 0x90000000 (die Startadresse liegt im zweiten 32-Bit-Wort des Images); außerdem muss der Stack mit der Flagge gesetzt werden -s, die Stack-Adresse liegt bei 0x90000000, Beispiel:

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

    embox>goto -i 0x9000c27f -s 0x20023200 # Das Flag -i ist erforderlich, um Interrupts während der Initialisierung des Systems zu deaktivieren

7: Starten wir

    embox> edges 20

und genießen die 40-sekündige Kantenfindung 🙂

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

Quelle: habr.com

60GB SSD 8Gb DDR4