Ich bin einer der Entwickler des Betriebssystems , 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 , 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?: NEINDer nächste Schritt besteht darin, ein Beispiel zu starten, am besten eines der Standardbeispiele, die die Entwickler selbst anbieten. . Ich habe mich für .
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.

Das Originalbild

Ergebnis
Start auf STM32F7Discovery
Auf dem 32F746GDISCOVERY gibt es mehrere Hardware-Speicherabschnitte, die wir auf verschiedene Weise nutzen können.
- 320 KiB RAM
- 1 MiB Flash-Speicher für das Image
- 8 MiB SDRAM
- 16 MiB QSPI NAND-Flash
- 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/emboxFü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).

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 ./embox1: Lassen Sie uns mit dem Erstellen des Bootloaders beginnen, der den QSPI-Flash flashen wird.
make confload-arm/stm32f7cubeJetzt 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:02gateway — die Adresse des Hosts, von dem das Image geladen wird, address — die Adresse des Boards.
Nachdem dies erledigt ist, bauen wir den Bootloader:
make2: 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. .
3: Kompilierung des Images mit der Konfiguration für OpenCV.
make confload-platform/opencv/stm32f7discovery
make4: 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=.dataIm 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.bin5: Ü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/tftpbootAuf 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.16: 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 verhindern7: Wir starten
embox> edges 20und genießen eine 40-sekündige Grenzsuche 🙂
Falls etwas schiefgeht, schreiben Sie ein Issue in , oder an die Mailingliste embox-devel@googlegroups.com, oder hier in die Kommentare.
Quelle: habr.com
