Entwerfen eines Sprachunabhängiges plattformübergreifendes Computer Vision SDK: Ein praktisches Tutorial

(Cyrus Behroozi) (21. Oktober 2020)

Ich hatte kürzlich die Gelegenheit, mich beim Venice Computer Vision -Treffen zu präsentieren. Wenn Sie nicht vertraut sind, handelt es sich um eine Veranstaltung, die von Trueface gesponsert wird. Hier können Entwickler und Enthusiasten von Computer Vision die neuesten Forschungsergebnisse, Anwendungen und praktischen Informationen zu Computer Vision präsentieren Tutorials.

In diesem Artikel werde ich meine Tutorial-Präsentation zum Entwerfen eines sprachunabhängigen Computer Vision Software Developer Kits (SDK) für plattformübergreifende Bereitstellung und maximale Erweiterbarkeit durchgehen. Wenn Sie die Live-Aufzeichnung der Präsentation anzeigen möchten, können Sie dies tun hier . Ich habe auch das gesamte Projekt als Open Source erstellt. Sie können es also gerne als Vorlage für Ihr nächstes Computer Vision-Projekt verwenden.

cyrusbehr / sdk_design

Entwerfen eines sprachunabhängigen SDK für plattformübergreifende Bereitstellung und maximale Erweiterbarkeit. Eine Computer Vision in Venedig…

github.com

Warum dieses Tutorial wichtig ist

Nach meiner Erfahrung habe ich nie einen umfassenden Leitfaden dafür gefunden fasst alle relevanten Schritte zusammen, die zum Erstellen eines sprachunabhängigen, plattformübergreifenden SDK erforderlich sind. Ich musste die unterschiedliche Dokumentation nach genau den richtigen Informationen durchsuchen, jede Komponente einzeln lernen und dann alles selbst zusammenfügen. Es war frustrierend. Es hat viel Zeit gekostet. Und jetzt können Sie, lieber Leser, von all meiner Arbeit profitieren. Im Voraus lernen Sie, wie Sie ein sprachunabhängiges, plattformübergreifendes SDK erstellen. Alles Nötige ist da. Keiner der Flusen, außer ein paar Memen. Viel Spaß.

In diesem Tutorial erfahren Sie, wie Sie:

  • Erstellen einer grundlegenden Computer Vision-Bibliothek in C ++
  • Kompilieren und Cross-Cross. Kompilieren Sie die Bibliothek für AMD64, ARM64 und ARM32.
  • Packen Sie die Bibliothek und alle Abhängigkeiten als eine einzige statische Bibliothek.
  • Automatisieren Sie Unit-Tests.
  • Richten Sie eine kontinuierliche Bibliothek ein Integrations (CI) -Pipeline
  • Schreiben Sie Python-Bindungen für unsere Bibliothek
  • Generieren Sie Dokumentation direkt aus unserer API

Für diese Demo haben wir erstellt ein SDK zur Erkennung von Gesichtern und Orientierungspunkten mithilfe eines Open-Source-Gesichtsdetektors namens MTCNN.

Beispiel von Gesichtsbegrenzungsrahmen und Gesichtsmarkierungen

Unsere API-Funktion erstellt einen Bildpfad und gibt dann die Koordinaten des Gesichtsbegrenzungsrahmens und der Gesichtsmarkierungen zurück. Die Fähigkeit, Gesichter zu erkennen, ist in der Bildverarbeitung sehr nützlich, da dies der erste Schritt in vielen Pipelines ist, einschließlich Gesichtserkennung, Altersvorhersage und automatisierter Gesichtsunschärfe.

Hinweis: Hierzu Tutorial, ich werde an Ubuntu 18.04 arbeiten.

Warum C ++ für unsere Bibliothek verwenden?

Das Ausführen von effizientem C ++ – Code kann sich so anfühlen

Der Großteil unserer Bibliothek wird in C ++ geschrieben, einer kompilierten und statisch typisierten Sprache. Es ist kein Geheimnis, dass C ++ eine sehr schnelle Programmiersprache ist. Es ist niedrig genug, um uns die gewünschte Geschwindigkeit zu geben, und hat nur einen minimalen zusätzlichen Laufzeitaufwand.

In Computer-Vision-Anwendungen bearbeiten wir im Allgemeinen viele Bilder, führen Matrixoperationen durch und führen Inferenzen für maschinelles Lernen durch, die alle dazu führen eine enorme Menge an Computer. Die Ausführungsgeschwindigkeit ist daher kritisch. Dies ist besonders wichtig in Echtzeitanwendungen, in denen Sie die Latenz reduzieren müssen, um die gewünschte Bildrate zu erzielen. Oft haben wir nur Millisekunden Zeit, um unseren gesamten Code auszuführen.

Ein weiterer Vorteil von C ++ ist, dass wir Kompilieren Sie für eine bestimmte Architektur und verknüpfen Sie alle Abhängigkeiten statisch. Dann können wir sie auf dieser Hardware ausführen, ohne zusätzliche Interpreter oder Bibliotheken zu benötigen. Ob Sie es glauben oder nicht, wir können sogar auf einem eingebetteten Bare-Metal-Gerät ohne Betriebssystem ausgeführt werden!

Verzeichnisstruktur

Wir werden die folgende Verzeichnisstruktur für unser Projekt verwenden.

3rdparty enthält die für unser Projekt erforderlichen Abhängigkeitsbibliotheken von Drittanbietern.

dist enthält die Dateien, die an die Endbenutzer des SDK verteilt werden. In unserem Fall ist dies die Bibliothek selbst und die zugehörige Header-Datei.

docker enthält die Docker-Datei, die zum Generieren eines Docker-Images verwendet wird für die CI-Builds.

docs enthält die Build-Skripte, die zum Generieren der Dokumentation direkt aus unserer Header-Datei erforderlich sind.

include enthält alle Include-Dateien für die öffentliche API.

models enthält die Deep Learning-Modelldateien für die Gesichtserkennung.

python enthält den Code, der zum Generieren von Python-Bindungen erforderlich ist.

src enthält alle CPP-Dateien, die kompiliert werden. und auch alle Header-Dateien, die nicht mit dem SDK verteilt werden (interne Header-Dateien).

test enthalten unsere Unit-Tests.

tools enthält unsere CMake-Toolchain-Dateien, die für das Cross-Compilieren erforderlich sind.

Installieren der Abhängigkeitsbibliotheken

Für dieses Projekt der Drittanbieter Erforderliche Abhängigkeitsbibliotheken sind ncnn , eine einfache Inferenzbibliothek für maschinelles Lernen, OpenCV , eine Bildvergrößerungsbibliothek, Catch2 , eine Unit-Testing-Bibliothek, und schließlich pybind11 , eine Bibliothek wird zum Generieren von Python-Bindungen verwendet. Die ersten beiden Bibliotheken müssen als eigenständige Bibliotheken kompiliert werden, während die beiden letzteren nur Header sind und wir daher nur die Quelle benötigen.

Eine Möglichkeit, diese Bibliotheken zu unseren Projekten hinzuzufügen, sind Git-Submodule . Obwohl dieser Ansatz funktioniert, bin ich persönlich ein Fan von Shell-Skripten, die den Quellcode abrufen und dann für die gewünschten Plattformen erstellen: in unserem Fall AMD64, ARM32 und ARM64.

Hier ist ein Beispiel dafür Diese Build-Skripte sehen folgendermaßen aus:

Das Skript ist ziemlich einfach. Zunächst wird der gewünschte Release-Quellcode aus dem Git-Repository abgerufen. Als Nächstes wird CMake verwendet, um den Build vorzubereiten, und anschließend wird make aufgerufen, um den Compiler zum Erstellen des Quellcodes anzutreiben.

Sie werden feststellen, dass der Hauptunterschied zwischen dem AMD64-Build und den ARM-Builds darin besteht Die ARM-Builds übergeben einen zusätzlichen CMake-Parameter mit dem Namen CMAKE_TOOLCHAIN_FILE. Dieses Argument wird verwendet, um CMake anzugeben, dass sich die Build-Zielarchitektur (ARM32 oder ARM64) von der Host-Architektur (AMD64 / x86_64) unterscheidet. CMake wird daher angewiesen, den in der ausgewählten Toolchain-Datei angegebenen Cross-Compiler zum Erstellen der Bibliothek zu verwenden (mehr zu Toolchain-Dateien weiter unten in diesem Lernprogramm). Damit dieses Shell-Skript funktioniert, müssen die entsprechenden Cross-Compiler auf Ihrem Ubuntu-Computer installiert sein. Diese können einfach mit apt-get installiert werden. Anweisungen dazu finden Sie unter hier .

Unsere Bibliotheks-API

Unsere Bibliotheks-API sieht folgendermaßen aus:

Da ich sehr kreativ bin, habe ich beschlossen, mein SDK MySDK zu nennen. In unserer API haben wir eine Aufzählung mit dem Namen ErrorCode, eine Struktur mit dem Namen Point und schließlich eine Funktion mit öffentlichen Mitgliedern getFaceBoxAndLandmarks. Im Rahmen dieses Tutorials werde ich nicht näher auf die Implementierung des SDK eingehen. Das Wesentliche ist, dass wir das Bild mit OpenCV in den Speicher einlesen und dann mithilfe von ncnn mit Open-Source-Modellen eine Inferenz zum maschinellen Lernen durchführen, um den Begrenzungsrahmen und die Orientierungspunkte zu erkennen. Wenn Sie in die Implementierung eintauchen möchten, können Sie dies hier tun.

Ich möchte jedoch, dass Sie darauf achten Designmuster, das wir verwenden. Wir verwenden eine Technik namens Zeiger auf die Implementierung, oder pImpl , die im Grunde die Implementierungsdetails einer Klasse entfernt, indem sie in eine separate Klasse eingefügt werden. Im obigen Code wird dies erreicht, indem die Klasse Impl vorwärts deklariert wird und dieser Klasse dann eine unique_ptr als private Mitgliedsvariable zugewiesen wird. Auf diese Weise verbergen wir nicht nur die Implementierung vor den neugierigen Blicken des Endbenutzers (was in einem kommerziellen SDK sehr wichtig sein kann), sondern reduzieren auch die Anzahl der Header, von denen unser API-Header abhängt (und verhindern so unsere API-Header von #include Abhängigkeitsbibliotheks-Headern).

Ein Hinweis zu Modelldateien

Ich sagte, wir würden nicht darüber nachdenken die Details der Implementierung, aber es gibt etwas, das ich für erwähnenswert halte. Standardmäßig lädt der von uns verwendete Open-Source-Gesichtsdetektor MTCNN die Modelldateien für maschinelles Lernen zur Laufzeit. Dies ist nicht ideal, da wir die Modelle an den Endbenutzer verteilen müssen. Dieses Problem ist bei kommerziellen Modellen noch schwerwiegender, bei denen Benutzer keinen freien Zugriff auf diese Modelldateien haben sollen (denken Sie an die unzähligen Stunden, die für das Training dieser Modelle aufgewendet wurden). Eine Lösung besteht darin, die Dateien dieser Modelle zu verschlüsseln, was ich unbedingt empfehlen möchte.Dies bedeutet jedoch weiterhin, dass wir die Modelldateien zusammen mit dem SDK versenden müssen. Letztendlich möchten wir die Anzahl der Dateien reduzieren, die wir einem Benutzer senden, um ihm die Verwendung unserer Software zu erleichtern (weniger Dateien bedeuten weniger Fehlerquellen). Wir können daher die unten gezeigte Methode verwenden, um die Modelldateien in Header-Dateien zu konvertieren und sie tatsächlich in das SDK selbst einzubetten.

Der Befehl xdd bash wird zum Generieren von Hex-Dumps verwendet und kann zum Generieren einer Header-Datei aus einer Binärdatei verwendet werden. Wir können daher die Modelldateien wie normale Header-Dateien in unseren Code aufnehmen und direkt aus dem Speicher laden. Eine Einschränkung dieses Ansatzes besteht darin, dass er bei sehr großen Modelldateien nicht praktikabel ist, da er beim Kompilieren zu viel Speicher benötigt. Stattdessen können Sie ein Tool wie ld verwenden, um diese großen Modelldateien direkt in Objektdateien zu konvertieren.

CMake und Kompilieren unserer Bibliothek

Wir können jetzt CMake verwenden, um die Build-Dateien für unser Projekt zu generieren. Falls Sie nicht vertraut sind, ist CMake ein Build-System-Generator, mit dem der Build-Prozess verwaltet wird. Unten sehen Sie, wie ein Teil des Stamms CMakeLists.txt (CMake-Datei) aussieht.

Grundsätzlich erstellen wir eine statische Bibliothek mit dem Namen my_sdk_static mit den beiden Quelldateien, die unsere Implementierung enthalten, my_sdk.cpp und mtcnn.cpp. Der Grund, warum wir eine statische Bibliothek erstellen, ist, dass es meiner Erfahrung nach einfacher ist, eine statische Bibliothek an Benutzer zu verteilen, und dass sie gegenüber eingebetteten Geräten benutzerfreundlicher ist. Wie oben erwähnt, kann eine ausführbare Datei, die mit einer statischen Bibliothek verknüpft ist, auf einem eingebetteten Gerät ausgeführt werden, auf dem noch nicht einmal ein Betriebssystem vorhanden ist. Dies ist mit einer dynamischen Bibliothek einfach nicht möglich. Darüber hinaus müssen wir uns bei dynamischen Bibliotheken um Abhängigkeitsversionen kümmern. Möglicherweise benötigen wir sogar eine Manifestdatei, die unserer Bibliothek zugeordnet ist. Statisch verknüpfte Bibliotheken haben auch ein etwas besseres Leistungsprofil als ihre dynamischen Gegenstücke.

Als Nächstes teilen wir CMake in unserem CMake-Skript mit, wo sich die erforderlichen Include-Header-Dateien befinden, die für unsere Quelldateien erforderlich sind. Zu beachten: Obwohl unsere Bibliothek zu diesem Zeitpunkt kompiliert wird, erhalten wir beim Versuch, eine Verknüpfung zu unserer Bibliothek herzustellen (z. B. mit einer ausführbaren Datei), eine absolute Menge undefinierter Verweise auf Symbolfehler. Dies liegt daran, dass wir keine unserer Abhängigkeitsbibliotheken verknüpft haben. Wenn wir also eine ausführbare Datei erfolgreich mit libmy_sdk_static.a verknüpfen möchten, müssen wir auch alle Abhängigkeitsbibliotheken (OpenCV-Module, ncnn usw.) aufspüren und verknüpfen. Im Gegensatz zu dynamischen Bibliotheken können statische Bibliotheken ihre eigenen Abhängigkeiten nicht auflösen. Sie sind im Grunde genommen nur eine Sammlung von Objektdateien, die in ein Archiv gepackt sind.

Später in diesem Lernprogramm werde ich zeigen, wie wir alle Abhängigkeitsbibliotheken in unserer statischen Bibliothek bündeln können, damit der Benutzer dies nicht muss Sorgen Sie sich um die Verknüpfung mit einer der Abhängigkeitsbibliotheken.

Kompilieren unserer Bibliotheks- und Toolchain-Dateien

Edge-Computing ist so… nervös

Viele Computer-Vision-Anwendungen werden am Rand bereitgestellt. Dies im Allgemeinen Der Code wird auf eingebetteten Geräten mit geringem Stromverbrauch ausgeführt, die normalerweise über ARM-CPUs verfügen. Da C ++ eine kompilierte Sprache ist, müssen wir unseren Code für die CPU-Architektur kompilieren, auf der die Anwendung ausgeführt wird (jede Architektur verwendet unterschiedliche Montageanweisungen).

Bevor wir darauf eingehen, wollen wir auch auf die eingehen Unterschied zwischen ARM32 und ARM64, auch AArch32 und AArch64 genannt. AArch64 bezieht sich auf die 64-Bit-Erweiterung der ARM-Architektur und ist sowohl von der CPU als auch vom Betriebssystem abhängig. Obwohl der Raspberry Pi 4 über eine 64-Bit-ARM-CPU verfügt, ist das Standardbetriebssystem Raspbian beispielsweise 32-Bit. Daher benötigt ein solches Gerät eine AArch32-kompilierte Binärdatei. Wenn wir auf diesem Pi-Gerät ein 64-Bit-Betriebssystem wie Gentoo ausführen würden, benötigen wir eine kompilierte AArch64-Binärdatei. Ein weiteres Beispiel für ein beliebtes eingebettetes Gerät ist der NVIDIA Jetson, der über eine integrierte GPU verfügt und AArch64 ausführt.

Um eine Cross-Kompilierung durchzuführen, müssen Sie CMake angeben, dass wir nicht für die Architektur der kompilieren Maschine, auf der wir gerade bauen. Daher müssen wir den Cross-Compiler angeben, den CMake verwenden soll. Für AArch64 verwenden wir den Compiler aarch64-linux-gnu-g++ und für AArch32 den Compiler arm-linux-gnuebhif-g++ (hf steht für hard float ).

Das Folgende ist ein Beispiel für eine Toolchain-Datei. Wie Sie sehen können, geben wir an, den AArch64-Cross-Compiler zu verwenden.

Zurück an unserer Wurzel CMakeLists.txt können wir Fügen Sie den folgenden Code oben in die Datei ein.

Grundsätzlich fügen wir CMake-Optionen hinzu, die kann über die Befehlszeile aktiviert werden, um eine Cross-Kompilierung durchzuführen. Durch Aktivieren der Optionen BUILD_ARM32 oder BUILD_ARM64 wird die entsprechende Toolchain-Datei ausgewählt und der Build für eine Kreuzkompilierung konfiguriert.

Packen unseres SDK mit Abhängigkeitsbibliotheken

Wenn ein Entwickler an dieser Stelle eine Verknüpfung zu unserer Bibliothek herstellen möchte, muss er, wie bereits erwähnt, auch eine Verknüpfung zu allen Abhängigkeitsbibliotheken herstellen, um alle Symbole aus aufzulösen Abhängigkeitsbibliotheken. Obwohl unsere App ziemlich einfach ist, haben wir bereits acht Abhängigkeitsbibliotheken! Das erste ist ncnn, dann haben wir drei OpenCV-Modulbibliotheken, dann haben wir vier Dienstprogrammbibliotheken, die mit OpenCV erstellt wurden (libjpeg, libpng, zlib, libtiff). Wir könnten vom Benutzer verlangen, dass er die Abhängigkeitsbibliotheken selbst erstellt oder sie sogar zusammen mit unserer Bibliothek versendet, aber letztendlich erfordert dies mehr Arbeit für den Benutzer und es geht uns darum, die Barriere für die Verwendung zu senken. Die ideale Situation ist, wenn wir dem Benutzer eine einzelne Bibliothek senden können, die unsere Bibliothek zusammen mit allen Abhängigkeitsbibliotheken von Drittanbietern außer den Standardsystembibliotheken enthält. Es stellt sich heraus, dass wir dies mit etwas CMake-Magie erreichen können.

Wir fügen zuerst ein benutzerdefiniertes Ziel hinzu Unsere CMakeLists.txt führen Sie dann ein sogenanntes MRI-Skript aus. Dieses MRI-Skript wird an den Befehl ar -M bash übergeben, der im Grunde alle statischen Bibliotheken in einem einzigen Archiv zusammenfasst. Das Coole an dieser Methode ist, dass sie überlappende Mitgliedsnamen aus den Originalarchiven ordnungsgemäß verarbeitet, sodass wir uns dort keine Gedanken über Konflikte machen müssen. Wenn Sie dieses benutzerdefinierte Ziel erstellen, wird libmy_sdk.a erstellt, das unser SDK zusammen mit allen Abhängigkeitsarchiven enthält.

Warten Sie eine Sekunde: Lassen Sie uns eine Bestandsaufnahme dessen machen, was wir tun. Bisher getan.

Atmen Sie ein. Schnapp dir einen Snack. Rufen Sie Ihre Mutter an.

Zu diesem Zeitpunkt haben wir eine statische Bibliothek namens libmy_sdk.a, die unser SDK und enthält Alle Abhängigkeitsbibliotheken, die wir in ein einziges Archiv gepackt haben. Wir haben auch die Möglichkeit, für alle unsere Zielplattformen zu kompilieren und zu kompilieren (unter Verwendung von Befehlszeilenargumenten).

Unit-Tests

Wenn Sie Ihre Komponententests zum ersten Mal ausführen

muss ich das wahrscheinlich nicht Erklären Sie, warum Komponententests wichtig sind, aber im Grunde genommen sind sie ein entscheidender Bestandteil des SDK-Designs, mit dem der Entwickler sicherstellen kann, dass das SDK wie eingerückt funktioniert. Wenn später Änderungen vorgenommen werden, ist es außerdem hilfreich, diese aufzuspüren und Korrekturen schneller zu veröffentlichen.

In diesem speziellen Fall bietet das Erstellen einer ausführbaren Unit-Test-Datei auch die Möglichkeit, eine Verknüpfung mit der zu erstellen Die kombinierte Bibliothek, die wir gerade erstellt haben, um sicherzustellen, dass wir wie beabsichtigt korrekt verknüpfen können (und wir erhalten keinen dieser bösen, undefinierten Fehler beim Verweisen auf Symbole).

Wir verwenden Catch2 als unser Unit-Test-Framework . Die Syntax ist unten beschrieben:

Wie Catch2 funktioniert, ist, dass wir dieses Makro namens TEST_CASE und ein weiteres Makro namens SECTION. Für jede SECTION wird die TEST_CASE von Anfang an ausgeführt. In unserem Beispiel wird also mySdk zuerst initialisiert und dann der erste Abschnitt mit dem Namen „Non face image“ ausgeführt. Als nächstes wird mySdk dekonstruiert, bevor es rekonstruiert wird. Anschließend wird der zweite Abschnitt mit dem Namen „Gesichter im Bild“ ausgeführt. Dies ist großartig, da dadurch sichergestellt wird, dass für jeden Abschnitt ein neues MySDK -Objekt zur Verfügung steht. Wir können dann Makros wie REQUIRE verwenden, um unsere Aussagen zu treffen.

Mit CMake können wir eine ausführbare Unit-Test-Datei mit dem Namen . Wie wir im Aufruf von target_link_libraries in Zeile 3 unten sehen können, ist die einzige Bibliothek, gegen die wir eine Verknüpfung herstellen müssen, unsere libmy_sdk.a und keine andere Abhängigkeitsbibliotheken.

Dokumentation

Wenn nur die Benutzer die verdammte Dokumentation lesen würden.

Wir werden doxygen , um Dokumentation direkt aus unserer Header-Datei zu generieren. Wir können alle unsere Methoden und Datentypen in unserem öffentlichen Header mit der im folgenden Code-Snippet gezeigten Syntax dokumentieren.Stellen Sie sicher, dass Sie alle Eingabe- und Ausgabeparameter für alle Funktionen angeben.

Um tatsächlich Dokumentation zu generieren Wir brauchen eine sogenannte Doxy-Datei, die im Grunde genommen eine Blaupause ist, um doxygen anzuweisen, wie die Dokumentation erstellt wird. Wir können eine generische Doxy-Datei generieren, indem wir doxygen -g in unserem Terminal ausführen, vorausgesetzt, Sie haben Sauerstoff auf Ihrem System installiert. Als nächstes können wir die Doxy-Datei bearbeiten. Zumindest müssen wir das Ausgabeverzeichnis und auch die Eingabedateien angeben.

In unserem In diesem Fall möchten wir nur Dokumentation aus unserer API-Header-Datei generieren. Aus diesem Grund haben wir das Include-Verzeichnis angegeben. Schließlich verwenden Sie CMake, um die Dokumentation tatsächlich zu erstellen. Dies kann wie folgt erfolgen.

Python-Bindungen

Sind Sie es leid, semi-relevante Gifs schon zu sehen? Ja, ich auch nicht.

Seien wir ehrlich. C ++ ist nicht die einfachste oder benutzerfreundlichste Sprache, in der entwickelt werden kann. Daher möchten wir unsere Bibliothek erweitern, um Sprachbindungen zu unterstützen und die Verwendung für Entwickler zu vereinfachen. Ich werde dies mit Python demonstrieren, da es eine beliebte Computer Vision-Prototyping-Sprache ist, aber andere Sprachbindungen sind genauso einfach zu schreiben. Wir verwenden pybind11, um dies zu erreichen:

Wir beginnen mit der Verwendung von PYBIND11_MODULE Makro, das eine Funktion erstellt, die aufgerufen wird, wenn eine Importanweisung aus Python ausgegeben wird. Im obigen Beispiel lautet der Name des Python-Moduls also mysdk. Als Nächstes können wir unsere Klassen und ihre Mitglieder mithilfe der pybind11-Syntax definieren.

Beachten Sie Folgendes: In C ++ werden Variablen häufig mit veränderlichen Referenzen übergeben, die sowohl Lese- als auch Schreibzugriff ermöglichen. Genau das haben wir mit unserer API-Member-Funktion mit den Parametern faceDetected und fbAndLandmarks getan. In Python werden alle Argumente als Referenz übergeben. Bestimmte grundlegende Python-Typen sind jedoch unveränderlich, einschließlich bool. Zufälligerweise ist unser Parameter faceDetected ein Bool, der als veränderbare Referenz übergeben wird. Wir müssen daher die im obigen Code in den Zeilen 31 bis 34 gezeigte Problemumgehung verwenden, in der wir den Bool in unserer Python-Wrapper-Funktion definieren und ihn dann an unsere C ++ – Funktion übergeben, bevor wir die Variable als Teil eines Tupels zurückgeben.

Sobald wir die Python-Bindungsbibliothek erstellt haben, können wir sie mit dem folgenden Code problemlos verwenden:

Kontinuierliche Integration

Für unsere Pipeline für die kontinuierliche Integration verwenden wir ein Tool namens CircleCI, das mir sehr gefällt, da es direkt in Github integriert ist. Jedes Mal, wenn Sie ein Commit ausführen, wird automatisch ein neuer Build ausgelöst. Wechseln Sie zunächst zur CircleCI-Website , verbinden Sie sie mit Ihrem Github-Konto und wählen Sie das Projekt aus, das Sie hinzufügen möchten. Nach dem Hinzufügen müssen Sie im Stammverzeichnis Ihres Projekts ein .circleci -Verzeichnis erstellen und in diesem Verzeichnis eine Datei mit dem Namen config.yml erstellen.

Für alle, die nicht vertraut sind, ist YAML eine Serialisierungssprache, die häufig für Konfigurationsdateien verwendet wird. Wir können es verwenden, um anzuweisen, welche Operationen CircleCI ausführen soll. Im folgenden YAML-Snippet können Sie sehen, wie wir zuerst eine der Abhängigkeitsbibliotheken erstellen, dann das SDK selbst erstellen und schließlich die Komponententests erstellen und ausführen.

Wenn wir intelligent sind (und ich nehme an, Sie sind es, wenn Sie es bis hierher geschafft haben), können wir das Caching verwenden, um die Erstellungszeiten erheblich zu verkürzen. In der obigen YAML zwischenspeichern wir beispielsweise den OpenCV-Build mit dem Hash des Build-Skripts als Cache-Schlüssel. Auf diese Weise wird die OpenCV-Bibliothek nur neu erstellt, wenn das Build-Skript geändert wurde. Andernfalls wird der zwischengespeicherte Build verwendet. Eine andere zu beachtende Sache ist, dass wir den Build in einem Docker-Image unserer Wahl ausführen. Ich habe ein benutzerdefiniertes Docker-Image ausgewählt ( hier ist die Docker-Datei), in dem ich alle Systemabhängigkeiten installiert habe.

Fin.

Und da haben Sie es. Wie jedes gut gestaltete Produkt möchten wir die gefragtesten Plattformen unterstützen und die Verwendung für die meisten Entwickler vereinfachen. Mit dem obigen Tutorial haben wir ein SDK erstellt, auf das in mehreren Sprachen zugegriffen werden kann und das auf mehreren Plattformen bereitgestellt werden kann. Und Sie mussten nicht einmal die Dokumentation für pybind11 selbst lesen. Ich hoffe, Sie fanden dieses Tutorial nützlich und unterhaltsam. Fröhliches Bauen.

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.