Hoe ontwerp je een Language-Agnostic Cross-Platform Computer Vision SDK: A Hands-On Tutorial

(Cyrus Behroozi) (21 oktober 2020)

Ik had onlangs de gelegenheid om te presenteren op de Venice Computer Vision -bijeenkomst. Als u niet bekend bent, is het een evenement dat wordt gesponsord door Trueface , waar ontwikkelaars en enthousiastelingen van computervisie geavanceerd computervisieonderzoek, toepassingen en hands-on kunnen demonstreren tutorials.

In dit artikel zal ik mijn tutorialpresentatie bespreken over het ontwerpen van een taalonafhankelijke computer vision software developer kit (SDK) voor platformonafhankelijke implementatie en maximale uitbreidbaarheid. Als je de live opname van de presentatie wilt bekijken, kun je dat doen hier . Ik heb ook het volledige project open source gemaakt, dus gebruik het gerust als een sjabloon voor je volgende computer vision-project.

cyrusbehr / sdk_design

Hoe ontwerp je een taal-agnostische SDK voor cross-platform implementatie en maximale uitbreidbaarheid. A Venice Computer Vision …

github.com

Waarom deze tutorial belangrijk is

In mijn ervaring heb ik nooit een allesomvattende gids gevonden die vat alle relevante stappen samen die nodig zijn om een ​​taalonafhankelijke, platformonafhankelijke SDK te maken. Ik moest door ongelijksoortige documentatie kammen om precies de juiste stukjes informatie te zoeken, elk onderdeel afzonderlijk leren en het vervolgens allemaal zelf samenvoegen. Het was frustrerend. Het kostte veel tijd. En nu kunt u, beste lezer, profiteren van al mijn werk. Verderop leert u hoe u een taalonafhankelijke, platformonafhankelijke SDK kunt bouwen. Alle benodigdheden zijn aanwezig. Geen van de pluisjes, behalve een paar memes. Veel plezier.

In deze tutorial kun je verwachten te leren hoe je:

  • Een basisbibliotheek voor computervisie bouwt in C ++
  • Compileren en cross- compileer de bibliotheek voor AMD64, ARM64 en ARM32
  • Verpak de bibliotheek en alle afhankelijkheden als een enkele statische bibliotheek
  • Automatiseer het testen van eenheden
  • Stel een doorlopende integratie (CI) pijplijn
  • Schrijf python-bindingen voor onze bibliotheek
  • Genereer documentatie rechtstreeks vanuit onze API

In het belang van deze demo, zal een SDK voor gezichts- en herkenningsdetectie bouwen met behulp van een open-source gezichtsdetector genaamd MTCNN.

Voorbeeld van gezichtskaders en gezichtsoriëntatiepunten

Onze API-functie neemt een afbeeldingspad en retourneert vervolgens de coördinaten van het gezichtskader en gezichtsoriëntatiepunten. De mogelijkheid om gezichten te detecteren is erg handig bij computervisie, aangezien het de eerste stap is in veel pijplijnen, waaronder gezichtsherkenning, leeftijdsvoorspelling en geautomatiseerde gezichtsvervaging.

Opmerking: Hiervoor tutorial, zal ik aan Ubuntu 18.04 werken.

Waarom C ++ gebruiken voor onze bibliotheek?

Het uitvoeren van efficiënte C ++ – code kan zo aanvoelen

Het grootste deel van onze bibliotheek zal worden geschreven in C ++, een gecompileerde en statisch getypte taal. Het is geen geheim dat C ++ een zeer snelle programmeertaal is; het is laag genoeg om ons de gewenste snelheid te geven en heeft minimale extra runtime-overhead.

In computer vision-applicaties manipuleren we over het algemeen veel afbeeldingen, voeren matrixbewerkingen uit, voeren machine learning-gevolgtrekkingen uit, allemaal met een enorme hoeveelheid computergebruik. De uitvoeringssnelheid is daarom van cruciaal belang. Dit is vooral belangrijk in real-time applicaties waar je de latentie moet verminderen om een ​​gewenste framesnelheid te bereiken – vaak hebben we maar milliseconden om al onze code uit te voeren.

Een ander voordeel van C ++ is dat als we compileer voor een bepaalde architectuur en koppel alle afhankelijkheden statisch, dan kunnen we het op die hardware draaien zonder dat er extra interpreters of bibliotheken nodig zijn. Geloof het of niet, we kunnen zelfs draaien op een bare metal embedded apparaat zonder besturingssysteem!

Directorystructuur

We zullen de volgende directorystructuur gebruiken voor ons project.

3rdparty bevat de afhankelijkheidsbibliotheken van derden die vereist zijn voor ons project.

dist bevat de bestanden die worden gedistribueerd naar de eindgebruikers van de SDK. In ons geval is dat de bibliotheek zelf en het bijbehorende headerbestand.

docker bevat het docker-bestand dat zal worden gebruikt om een ​​docker-afbeelding te genereren voor de CI-builds.

docs bevat de build-scripts die nodig zijn om documentatie rechtstreeks vanuit ons headerbestand te genereren.

include bevat alle include-bestanden voor de openbare API.

models bevat de modelbestanden voor deep learning voor gezichtsdetectie.

python bevat de code die nodig is om python-bindingen te genereren.

src bevat alle cpp-bestanden die worden gecompileerd, en ook alle header-bestanden die niet met de SDK worden verspreid (interne header-bestanden).

test zullen onze unit tests bevatten.

tools zal onze CMake toolchain-bestanden bevatten die nodig zijn voor cross-compileren.

De afhankelijkheidsbibliotheken installeren

Voor dit project, de derde partij afhankelijkheidsbibliotheken die vereist zijn, zijn ncnn , een lichtgewicht machine learning-inferentiebibliotheek, OpenCV , een bibliotheek voor beeldvergroting, Catch2 , een bibliotheek voor het testen van eenheden en tot slot pybind11 , een bibliotheek gebruikt voor het genereren van python-bindingen. De eerste twee bibliotheken zullen als zelfstandige bibliotheken moeten worden gecompileerd, terwijl de laatste twee alleen header zijn en daarom hebben we alleen de broncode nodig.

Een manier om deze bibliotheken aan onze projecten toe te voegen is via git-submodules . Hoewel deze aanpak werkt, ben ik persoonlijk een fan van het gebruik van shell-scripts die de broncode ophalen en vervolgens bouwen voor de gewenste platforms: in ons geval AMD64, ARM32 en ARM64.

Hier is een voorbeeld van wat een van deze build-scripts ziet er als volgt uit:

Het script is vrij eenvoudig. Het begint met het ophalen van de gewenste release-broncode uit de git-repository. Vervolgens wordt CMake gebruikt om de build voor te bereiden, en vervolgens wordt make aangeroepen om de compiler de broncode te laten bouwen.

Wat je opvalt is dat het belangrijkste verschil tussen de AMD64-build en de ARM-build is dat de ARM-builds geven een extra CMake-parameter door met de naam CMAKE_TOOLCHAIN_FILE. Dit argument wordt gebruikt om aan CMake te specificeren dat de build-doelarchitectuur (ARM32 ofARM64) verschilt van de hostarchitectuur (AMD64 / x86_64). CMake krijgt daarom de opdracht om de cross-compiler te gebruiken die is gespecificeerd in het geselecteerde toolchain-bestand om de bibliotheek te bouwen (meer over toolchain-bestanden verderop in deze tutorial). Om dit shellscript te laten werken, moet u de juiste cross-compilers op uw Ubuntu-machine hebben geïnstalleerd. Deze kunnen eenvoudig worden geïnstalleerd met apt-get en instructies over hoe u dit moet doen, worden hier weergegeven.

Onze bibliotheek-API

Onze bibliotheek-API ziet er als volgt uit:

Omdat ik super creatief ben, heb ik besloten mijn SDK MySDK te noemen. In onze API hebben we een opsomming met de naam ErrorCode, we hebben een structuur met de naam Point, en tot slot hebben we een functie voor openbaar leden genaamd getFaceBoxAndLandmarks. Voor de reikwijdte van deze tutorial zal ik niet ingaan op de details van de implementatie van de SDK. De essentie is dat we de afbeelding in het geheugen lezen met behulp van OpenCV en vervolgens machine learning-inferentie uitvoeren met behulp van ncnn met open source-modellen om het grensvak en oriëntatiepunten van het gezicht te detecteren. Als u in de implementatie wilt duiken, kunt u dat hier doen.

Waar ik echter op moet letten, is de ontwerppatroon dat we gebruiken. We gebruiken een techniek genaamd Pointer to implementatie, of pImpl in het kort, die in feite de implementatiedetails van een klasse verwijdert door ze in een aparte klasse te plaatsen. In de bovenstaande code wordt dit bereikt door de Impl klasse voorwaarts te declareren, en vervolgens een unique_ptr aan deze klasse te geven als een privé-lidvariabele. Door dit te doen, verbergen we niet alleen de implementatie voor nieuwsgierige blikken van de eindgebruiker (wat behoorlijk belangrijk kan zijn in een commerciële SDK), maar verminderen we ook het aantal headers waarvan onze API-header afhankelijk is (en dus voorkomen dat onze API-header van #include ing dependency library headers).

Een opmerking over modelbestanden

Ik zei dat we niet verder zouden gaan de details van de implementatie, maar er is iets dat volgens mij het vermelden waard is. Standaard laadt de open source gezichtsdetector die we gebruiken, genaamd MTCNN, de machine learning-modelbestanden tijdens runtime. Dit is niet ideaal omdat het betekent dat we de modellen naar de eindgebruiker moeten distribueren. Dit probleem is zelfs nog belangrijker bij commerciële modellen waarvan u niet wilt dat gebruikers gratis toegang hebben tot deze modelbestanden (denk aan de talloze uren die zijn besteed aan het trainen van deze modellen). Een oplossing is om de bestanden van deze modellen te versleutelen, wat ik absoluut aanraad.Dit betekent echter nog steeds dat we de modelbestanden samen met de SDK moeten verzenden. Uiteindelijk willen we het aantal bestanden dat we een gebruiker sturen, verminderen om het voor hem gemakkelijker te maken om onze software te gebruiken (minder bestanden is gelijk aan minder plaatsen waar fout kan gaan). We kunnen daarom de hieronder getoonde methode gebruiken om de modelbestanden naar headerbestanden te converteren en ze daadwerkelijk in de SDK zelf in te sluiten.

Het xdd bash-commando wordt gebruikt voor het genereren van hex-dumps en kan worden gebruikt om een ​​headerbestand te genereren vanuit een binair bestand. We kunnen daarom de modelbestanden in onze code opnemen zoals normale headerbestanden en ze rechtstreeks uit het geheugen laden. Een beperking van deze benadering is dat het niet praktisch is met zeer grote modelbestanden, aangezien het te veel geheugen verbruikt tijdens het compileren. In plaats daarvan kunt u een tool zoals ld gebruiken om deze grote modelbestanden rechtstreeks naar objectbestanden te converteren.

CMake and Compiling our Library

We kunnen nu CMake gebruiken om de build-bestanden voor ons project te genereren. Voor het geval u niet bekend bent, CMake is een build-systeemgenerator die wordt gebruikt om het buildproces te beheren. Hieronder ziet u welk deel van de root CMakeLists.txt (CMake-bestand) eruitziet.

In feite maken we een statische bibliotheek met de naam my_sdk_static met de twee bronbestanden die onze implementatie bevatten, my_sdk.cpp en mtcnn.cpp. De reden dat we een statische bibliotheek maken, is dat het, naar mijn ervaring, gemakkelijker is om een ​​statische bibliotheek onder gebruikers te distribueren en vriendelijker is voor embedded apparaten. Zoals ik hierboven al zei, als een uitvoerbaar bestand is gekoppeld aan een statische bibliotheek, kan het worden uitgevoerd op een ingebed apparaat dat niet eens een besturingssysteem heeft. Dit is simpelweg niet mogelijk met een dynamische bibliotheek. Bovendien moeten we ons bij dynamische bibliotheken zorgen maken over afhankelijkheidsversies. We hebben misschien zelfs een manifestbestand nodig dat is gekoppeld aan onze bibliotheek. Statisch gekoppelde bibliotheken hebben ook een iets beter prestatieprofiel dan hun dynamische tegenhangers.

Het volgende dat we in ons CMake-script doen, is CMake vertellen waar de benodigde include-headerbestanden kunnen worden gevonden die onze bronbestanden nodig hebben. Iets om op te merken: hoewel onze bibliotheek op dit punt zal compileren, zullen we, wanneer we proberen te linken tegen onze bibliotheek (bijvoorbeeld met een uitvoerbaar bestand), een absolute massa ongedefinieerde verwijzingen naar symboolfouten krijgen. Dit komt doordat we geen van onze afhankelijkheidsbibliotheken hebben gekoppeld. Dus als we een uitvoerbaar bestand met succes willen linken tegen libmy_sdk_static.a, dan zouden we ook alle afhankelijkheidsbibliotheken moeten opsporen en koppelen (OpenCV-modules, ncnn, enz.). In tegenstelling tot dynamische bibliotheken kunnen statische bibliotheken hun eigen afhankelijkheden niet oplossen. Ze zijn eigenlijk gewoon een verzameling objectbestanden die in een archief zijn verpakt.

Later in deze tutorial zal ik demonstreren hoe we alle afhankelijkheidsbibliotheken kunnen bundelen in onze statische bibliotheek, zodat de gebruiker dit niet hoeft te doen maak je zorgen over het linken met een van de afhankelijkheidsbibliotheken.

Cross-compileren van onze bibliotheek- en Toolchain-bestanden

Edge computing is zo… edgy

Veel computer vision-applicaties worden aan de rand geïmplementeerd. Dit is over het algemeen omvat het uitvoeren van de code op ingebedde apparaten met een laag vermogen die meestal ARM-CPUs hebben. Aangezien C ++ een gecompileerde taal is, moeten we onze code compileren voor de CPU-architectuur waarop de applicatie zal worden uitgevoerd (elke architectuur gebruikt verschillende montage-instructies).

Voordat we erin duiken, laten we ook de verschil tussen ARM32 en ARM64, ook wel AArch32 en AArch64 genoemd. AArch64 verwijst naar de 64-bits extensie van de ARM-architectuur en is zowel afhankelijk van de CPU als het besturingssysteem. Dus hoewel de Raspberry Pi 4 een 64 bit ARM CPU heeft, is het standaard besturingssysteem Raspbian 32 bit. Daarom vereist een dergelijk apparaat een AArch32 gecompileerd binair bestand. Als we een 64-bits besturingssysteem zouden draaien, zoals Gentoo op dit Pi-apparaat, dan zouden we een AArch64 gecompileerd binair bestand nodig hebben. Een ander voorbeeld van een populair ingebed apparaat is de NVIDIA Jetson die een ingebouwde GPU heeft en AArch64 draait.

Om cross-compileren te maken, moeten we CMake specificeren dat we niet compileren voor de architectuur van de machine waarop we momenteel bouwen. Daarom moeten we de cross-compiler specificeren die CMake moet gebruiken. Voor AArch64 gebruiken we de aarch64-linux-gnu-g++ compiler, en voor AArch32 gebruiken we de arm-linux-gnuebhif-g++ compiler (hf staat voor hard float ).

Het volgende is een voorbeeld van een toolchain-bestand. Zoals u kunt zien, specificeren we om de AArch64-crosscompiler te gebruiken.

Terug bij onze root CMakeLists.txt kunnen we voeg de volgende code toe aan de bovenkant van het bestand.

In feite voegen we CMake-opties toe die kan worden ingeschakeld vanaf de opdrachtregel om cross-compileren. Als u de opties BUILD_ARM32 of BUILD_ARM64 inschakelt, wordt het juiste toolchain-bestand geselecteerd en wordt de build geconfigureerd voor een kruiscompilatie.

Onze SDK verpakken met afhankelijkheidsbibliotheken

Zoals eerder vermeld, als een ontwikkelaar op dit punt wil linken met onze bibliotheek, moet hij ook linken tegen alle afhankelijkheidsbibliotheken om alle symbolen van afhankelijkheidsbibliotheken. Ook al is onze app vrij eenvoudig, we hebben al acht afhankelijkheidsbibliotheken! De eerste is ncnn, daarna hebben we drie OpenCV-modulebibliotheken en vervolgens hebben we vier hulpprogramma-bibliotheken die zijn gebouwd met OpenCV (libjpeg, libpng, zlib, libtiff). We zouden van de gebruiker kunnen verlangen dat hij de afhankelijkheidsbibliotheken zelf bouwt of ze zelfs naast onze bibliotheek verzendt, maar uiteindelijk kost dat meer werk voor de gebruiker en willen we de drempel voor gebruik verlagen. De ideale situatie is als we de gebruiker een enkele bibliotheek kunnen sturen die onze bibliotheek bevat samen met alle afhankelijkheidsbibliotheken van derden, behalve de standaard systeembibliotheken. Het blijkt dat we dit kunnen bereiken met wat CMake-magie.

We voegen eerst een aangepast doel toe aan our CMakeLists.txt en voer vervolgens een zogenaamd MRI-script uit. Dit MRI-script wordt doorgegeven aan het ar -M bash-commando, dat in feite alle statische bibliotheken in één archief combineert. Wat cool is aan deze methode, is dat deze netjes omgaat met overlappende ledennamen uit de oorspronkelijke archieven, dus we hoeven ons geen zorgen te maken over conflicten daar. Het bouwen van dit aangepaste doel zal libmy_sdk.a produceren dat onze SDK zal bevatten samen met alle afhankelijkheidsarchieven.

Wacht even: laten we eens kijken wat we tot nu toe gedaan.

Haal diep adem. Pak een snack. Bel je moeder.

Op dit moment hebben we een statische bibliotheek met de naam libmy_sdk.a die onze SDK en alle afhankelijkheidsbibliotheken, die we in een enkel archief hebben verpakt. We hebben ook de mogelijkheid om te compileren en cross-compileren (met behulp van opdrachtregelargumenten) voor al onze doelplatforms.

Unit-tests

Wanneer u uw unit-tests voor de eerste keer uitvoert

Ik hoef waarschijnlijk niet leg uit waarom unit-tests belangrijk zijn, maar in feite zijn ze een cruciaal onderdeel van het SDK-ontwerp waarmee de ontwikkelaar ervoor kan zorgen dat de SDK werkt zoals ingesprongen. Bovendien, als er onderweg belangrijke wijzigingen worden aangebracht, helpt het om ze op te sporen en sneller reparaties uit te voeren.

In dit specifieke geval geeft het maken van een uitvoerbaar unittest ons ook de mogelijkheid om te linken met de gecombineerde bibliotheek die we zojuist hebben gemaakt om ervoor te zorgen dat we correct kunnen linken zoals bedoeld (en we krijgen geen van die vervelende ongedefinieerde referentie-naar-symboolfouten).

We gebruiken Catch2 als ons testraamwerk voor eenheden . De syntaxis wordt hieronder beschreven:

Hoe Catch2 werkt, is dat we deze macro hebben genaamd TEST_CASE en een andere macro genaamd SECTION. Voor elke SECTION wordt de TEST_CASE vanaf het begin uitgevoerd. Dus in ons voorbeeld wordt mySdk eerst geïnitialiseerd, waarna de eerste sectie met de naam “Non face image” wordt uitgevoerd. Vervolgens wordt mySdk gedeconstrueerd voordat het wordt gereconstrueerd, waarna het tweede gedeelte met de naam “Gezichten in afbeelding” wordt uitgevoerd. Dit is geweldig omdat het ervoor zorgt dat we voor elke sectie een nieuw MySDK -object hebben om mee te werken. We kunnen dan macros gebruiken zoals REQUIRE om onze beweringen te doen.

We kunnen CMake gebruiken om een ​​uitvoerbaar bestand voor het testen van eenheden uit te bouwen met de naam run_tests. Zoals we kunnen zien in de aanroep van target_link_libraries op regel 3 hieronder, is de enige bibliotheek waarnaar we moeten linken onze libmy_sdk.a en geen andere afhankelijkheidsbibliotheken.

Documentatie

Als alleen de gebruikers de verdomde documentatie zouden lezen.

We gebruiken doxygen om documentatie rechtstreeks vanuit ons headerbestand te genereren. We kunnen doorgaan en al onze methoden en gegevenstypen in onze openbare koptekst documenteren met behulp van de syntaxis die wordt weergegeven in het onderstaande codefragment.Zorg ervoor dat u alle invoer- en uitvoerparameters voor alle functies opgeeft.

Om daadwerkelijk documentatie te genereren , we hebben iets nodig dat een doxyfile wordt genoemd, wat in feite een blauwdruk is om doxygen te instrueren hoe de documentatie moet worden gegenereerd. We kunnen een generiek doxyfile genereren door doxygen -g in onze terminal uit te voeren, ervan uitgaande dat doxygen op uw systeem is geïnstalleerd. Vervolgens kunnen we het doxyfile. We moeten minimaal de uitvoermap specificeren en ook de invoerbestanden.

In onze In dat geval willen we alleen documentatie genereren uit ons API-headerbestand, daarom hebben we de include-directory gespecificeerd. Ten slotte gebruik je CMake om de documentatie daadwerkelijk te bouwen, wat zoals zo kan worden gedaan.

Python-bindingen

Ben je het al beu om semi-relevante gifs te zien? Ja, ik ook niet.

Laten we eerlijk zijn. C ++ is niet de gemakkelijkste of meest vriendelijke taal om in te ontwikkelen. Daarom willen we onze bibliotheek uitbreiden om taalbindingen te ondersteunen, zodat het gemakkelijker te gebruiken is voor ontwikkelaars. Ik zal dit demonstreren met python, omdat het een populaire prototypetaal voor computervisie is, maar bindingen in andere talen zijn net zo gemakkelijk te schrijven. We gebruiken pybind11 om dit te bereiken:

We beginnen met het gebruik van de PYBIND11_MODULE macro die een functie maakt die wordt aangeroepen wanneer een importinstructie wordt uitgegeven vanuit python. Dus in het bovenstaande voorbeeld is de naam van de python-module mysdk. Vervolgens kunnen we onze klassen en hun leden definiëren met behulp van de pybind11-syntaxis.

Hier is iets om op te merken: in C ++ is het vrij gebruikelijk om variabelen door te geven met behulp van veranderlijke referentie die zowel lees- als schrijftoegang toestaat. Dit is precies wat we hebben gedaan met onze API-ledenfunctie met de parameters faceDetected en fbAndLandmarks. In python worden alle argumenten door middel van verwijzing doorgegeven. Bepaalde basistypen python zijn echter onveranderlijk, inclusief bool. Toevallig is onze parameter faceDetected een bool die wordt doorgegeven via een veranderlijke referentie. We moeten daarom de tijdelijke oplossing gebruiken die wordt weergegeven in de bovenstaande code op regels 31 tot 34, waar we de bool definiëren binnen onze python-wrapper-functie, en deze vervolgens doorgeven aan onze C ++ -functie voordat de variabele wordt geretourneerd als onderdeel van een tupel.

Zodra we de python-bindingsbibliotheek hebben gebouwd, kunnen we deze gemakkelijk gebruiken met de onderstaande code:

Continue integratie

Voor onze continue integratiepijplijn zullen we een tool gebruiken genaamd CircleCI die ik erg leuk vind omdat het rechtstreeks met Github kan worden geïntegreerd. Elke keer dat je een commit pusht, wordt automatisch een nieuwe build geactiveerd. Om te beginnen, ga naar de CircleCI website en verbind deze met je Github-account en selecteer vervolgens het project dat je wilt toevoegen. Eenmaal toegevoegd, moet u een .circleci directory aanmaken in de root van uw project en een bestand maken met de naam config.yml binnen die directory.

Voor iedereen die niet bekend is, is YAML een serialisatietaal die vaak wordt gebruikt voor configuratiebestanden. We kunnen het gebruiken om te instrueren welke bewerkingen we willen dat CircleCI uitvoert. In het YAML-fragment hieronder kun je zien hoe we eerst een van de afhankelijkheidsbibliotheken bouwen, vervolgens de SDK zelf bouwen en ten slotte de unit-tests bouwen en uitvoeren.

Als we intelligent zijn (en ik neem aan dat u dat bent als u zover bent gekomen), kunnen we caching gebruiken om de build-tijd aanzienlijk te verkorten. In de YAML hierboven cachen we bijvoorbeeld de OpenCV-build met behulp van de hash van het build-script als de cachesleutel. Op deze manier wordt de OpenCV-bibliotheek alleen opnieuw opgebouwd als het build-script is gewijzigd – anders wordt de build in de cache gebruikt. Een ander ding om op te merken is dat we de build uitvoeren in een docker-afbeelding van onze keuze. Ik heb een aangepaste docker-afbeelding geselecteerd ( hier is de Dockerfile) waarin ik alle systeemafhankelijkheden heb geïnstalleerd.

Fin.

En daar heb je het. Zoals elk goed ontworpen product, willen we de meest gevraagde platforms ondersteunen en het gebruiksvriendelijk maken voor het grootste aantal ontwikkelaars. Met behulp van de bovenstaande tutorial hebben we een SDK gebouwd die toegankelijk is in verschillende talen en die op meerdere platforms kan worden geïmplementeerd. En je hoefde de documentatie voor pybind11 niet eens zelf te lezen. Ik hoop dat je deze tutorial nuttig en vermakelijk hebt gevonden. Gelukkig bouwen.

Geef een reactie

Het e-mailadres wordt niet gepubliceerd. Vereiste velden zijn gemarkeerd met *