“Dlaczego”, a dokładniej, “do czego”?
AR i VR to pośrednia i bezpośrednia interakcja sprzętu i oprogramowania z człowiekiem. O różnicach między technologiami AR i VR pisaliśmy już na naszym blogu (tutaj). Ponieważ każdy z nas ludzi jest nieco inny, a urządzenia produkowane są masowo, pojawia się problem indywidualnego dostosowywania interakcji między sprzętem a użytkownikiem. Wbrew pozorom nie jest to takie proste, ponieważ klasyczne algorytmy mają bardzo ograniczone zdolności adaptacyjne, nie mówiąc już o sprzęcie. Oczywiście nie mamy tutaj na myśli dostosowywania grubości gąbki zamontowanej w okularach VR do kształtu twarzy :-). Chodzi o interakcję i pracę ze złożonym systemem na poziomie oprogramowania. Idealnie byłoby, gdyby algorytmy dostosowywały się same do użytkownika lub też, wzorem ludzi, potrafiły się uczyć, korzystając z obserwacji i wykazując przy tym dużą tolerancję. W końcu każdy z nas bez problemu potrafi rozpoznać gest kozakiewicza 🙂 Wskazówka dla młodszych czytelników: poszukajcie, co to oznacza, w Wikipedii, np. tu. W takich sytuacjach, gdzie wymagana jest adaptacja, a informacje nie są jednoznaczne, AI z powodzeniem zastępuje klasyczne algorytmy.
Planując kolejny projekt, zdecydowaliśmy się na wykorzystanie elementów sztucznej inteligencji. Ponieważ integracja AI i VR oraz AR w jednym projekcie nadal należy do rzadkości, postanowiliśmy podzielić się naszymi rozwiązaniami, uwagami i spostrzeżeniami.
Etap entuzjazmu
Zadanie, które postawiliśmy przed naszym zespołem, wyglądało dość prozaicznie: rozpoznawanie (dynamiczne) gestów wykonywanych przez użytkownika z wykorzystaniem rąk (interesuje nas pojedyncza ręka, przy czym nie ma dla nas znaczenia czy lewa, czy prawa), z minimalnymi opóźnieniami. Dzięki temu nasz system potrafiłby automatycznie weryfikować intencje i poprawność akcji wykonywanych przez użytkownika w wirtualnym świecie. Z naszego punktu widzenia był to element niezbędny w systemach szkoleniowych, w których użytkownicy ćwiczą w wirtualnym świecie interakcję z maszynami (budowlanymi, kopalnianymi czy jakimikolwiek innymi). Na początek skupiliśmy się na typowej obsłudze, czyli: złapanie czegoś (drążka, przełącznika, uchwytu), obrocie w lewo lub w prawo, naciśnięciu (przycisku) i tym podobnych prostych i jednocześnie najczęściej wykonywanych interakcjach manualnych ze sprzętem. Temat nie wyglądał zbyt “groźnie” – w końcu wiele rozwiązań ma wbudowane takie funkcje, tyle że często w mocno okrojonym zakresie.
Etap zaciekawienia
Ponieważ założyliśmy dodatkowo, że system musi działać z różnymi urządzeniami AR, VR i innymi oferującymi interakcję za pomocą dłoni (począwszy od Microsoft Hololens, a skończywszy na Leap Motion), ideałem byłoby, gdybyśmy mieli coś w rodzaju HAL (Hardware Abstraction Layer), który powoduje, że nie musimy przygotowywać rozwiązań pod konkretny sprzęt. Z pomocą przyszedł nam MRTK (Mixed Reality Toolkit) od Microsoft, gdzie dane na temat położenia dłoni (palców, stawów) są przekazywane w ujednolicony sposób, niezależnie od tego, jakim sprzętem dysponujemy. Microsoft odrobił lekcję z czasu sterowników dla MS DOS i Windows 95, gdzie przekleństwem deweloperów była konieczność tworzenia szeregu wersji oprogramowania tak, aby działało ono z różnymi konfiguracjami sprzętowymi.
OK – prawdą jest, że niektóre urządzenia nie przekazują kompletu danych, np. ze względu na ograniczenia sprzętowe, niemniej jednak te dane, które są przekazywane nawet przy najbardziej “ograniczonych” urządzeniach, okazały się wystarczające. Prawdziwym problemem stał się natomiast nie tyle brak danych, co raczej ich nadmiar, o czym za chwilę.
MRTK przekazuje dane w postaci położenia oraz obrotu wszystkich elementów składowych pojedynczej dłoni, a jest ich łącznie 25. Dane o obrocie są przekazywane za pomocą kwaternionu. Z grubsza można przyjąć, że odpowiadają one stawom czy też popularnym “kostkom”, a więc miejscom, gdzie zginają się palce. Dane te są przekazywane w postaci bezwzględnej, a zatem położenie i obrót określane są względem początkowego położenia w układzie współrzędnych wirtualnego świata. Więcej o tym rozwiązaniu można poczytać tutaj: https://docs.microsoft.com/en-us/windows/mixed-reality/mrtk-unity/features/input/hand-tracking
Etap powrotu do szkoły
Nasza analiza gestów ma charakter lokalny, a zatem nie interesuje nas położenie dłoni w przestrzeni. W związku z tym skupiliśmy się na wykorzystaniu informacji o obrocie. Pojawił się tutaj jednak jeden problem: jak zmienić obroty globalne na lokalne i to zapisane w postaci kwaternionów. Krótka analiza dostępnych informacji w literaturze naukowej i popularnej wskazała, że nie powinno to być trudne. A zatem przygotowaliśmy wzory (teoria), opracowaliśmy oprogramowanie wraz z wizualizacją (praktyka) i … połączyliśmy teorię z praktyką, co okazało się wcale nie takie proste: w pierwszej chwili okazało się, że nic nie działa i nikt nie wie dlaczego, a dłonie po przekształceniu wyglądają jak w kiepskim horrorze. Ostatecznie jednak udało się poskromić matematykę.
Strumień danych płynący z MRTK i przekształcony przez nasze oprogramowanie tworzy coś, co nazywamy szeregiem czasowym i to właśnie poddawane jest analizie za pomocą naszego mechanizmu sztucznej inteligencji. Jeśli pojęcie szeregu czasowego jest dla Ciebie mocno abstrakcyjne, to spróbuj wyobrazić sobie kolejne klatki filmu, na którym znajduje się dłoń wykonująca ruch: tutaj jest dokładnie tak samo, tyle że zamiast pikseli mamy dane numeryczne.
Etap paniki
Rozpoznanie pola bitwy (czyt. artykułów naukowych oraz bieżącego stanu wiedzy) wskazało, że… nikt przed nami tego jeszcze nie robił! Poważnie. Zupełnie nikt. Wszyscy bawiący się w rozpoznawanie gestów wykorzystują strumień wideo i ewentualnie kamery głębi. I na dodatek robią to na klastrach obliczeniowych wykorzystujących zaawansowane karty graficzne (GPU) i mocne procesory. Tymczasem my musimy to zrobić na urządzeniach mobilnych i za pomocą dość ograniczonych danych. Co więcej, nawet po odrzuceniu informacji o położeniu, nasz strumień danych nadal był “ogromny” jak na ograniczone zasoby urządzeń mobilnych AR i VR: 25 kwaternionów (kwaternion to jak nazwa wskazuje 4 dane zmiennoprzecinkowe) dostarczanych do systemu kilkanaście czy kilkadziesiąt razy na sekundę. Może to zatkać nawet wydajny system obliczeniowy, a co dopiero urządzenie klasy telefonu komórkowego.
Etap kombinowania
Do analizy szeregów czasowych nadaje się logika rozmyta (Fuzzy Logic) oraz regułowe systemy wnioskowania rozmytego (Fuzzy Inference Systems), które są już dość długo obecne w nauce, niemniej jednak ze względu na złożoność obliczeniową i kłopoty implementacyjne są rzadko spotykane w rozwiązaniach przemysłowych. Natomiast wraz z rozwojem uczenia głębokiego (Deep Learning), coraz popularniejsze stały się rekurencyjne sieci neuronowe RNN (Recurrent Neural Networks), a zwłaszcza ich szczególna postać, sieci typu LSTM (Long-Short Term Memory) oraz ich pochodne, tzw Transformers (na moment pisania tego tekstu temat był na tyle nowy, że nie doczekał się jeszcze odpowiednika w polskim słownictwie).
Pierwotnie planowaliśmy zastosowanie wyłącznie złożonej, wielowarstwowej sieci typu LSTM do rozwiązania całości zagadnienia w jednym kroku.
Niestety sieci LSTM mają jednak to do siebie, że wymagają dość dużych zasobów obliczeniowych i to nie tylko na etapie uczenia, ale też na etapie ich używania. Choć są to zasoby zdecydowanie mniejsze niż porównywalne modele w logice rozmytej. Niemniej jednak niezbędna była zaawansowana optymalizacja danych, zmniejszenie ich wymiarowości, a finalnie zupełna zmiana podejścia, o czym przeczytasz poniżej, ponieważ przeniesienie “wprost” nauczonej sieci na platformę mobilną spowodowało nieakceptowalne “lagi”, a “grywalność” naszego rozwiązania umiejscawiała nas w ogonie najgorszych VRek, jakie kiedykolwiek powstały ręką programisty.
Etap euforii
Nie wchodząc w przydługie rozważania co do tego, ile czasu, środków i sił przeznaczyliśmy na znalezienie optymalnego rozwiązania, możemy z dumą napisać: tak, to działa! I do tego płynnie. Niemniej jednak konieczne było zastosowanie podejścia hybrydowego: szereg czasowy jest analizowany pod kątem statycznych gestów za pomocą sieci konwolucyjnej, a zatem modelu, który jest znacznie szybszy i wprowadza minimalne opóźnienia, ponieważ korzysta tylko z jednej “klatki” danych. Podobne podejście stosuje się np. do rozpoznawania obiektów w popularnych modelach AI do rozpoznawania obrazów, np. Inception czy Yolo, które również wykorzystują warstwy konwolucyjne. Gdy model bazujący na sieci konwolucyjnej rozpozna charakterystyczny układ dłoni, który potencjalnie może rozpoczynać interesującą nas sekwencję, do akcji wkracza drugi model wykorzystujący LSTM. Pracuje on na bardzo ograniczonym zestawie danych z powodów wydajnościowych. Taka hybryda dobrze sprawdza się na urządzeniach AR i VR (m.in. na Oculus Quest oraz na Hololens 2), które mają ograniczone zasoby obliczeniowe i w trakcie używania sieci korzystają głównie lub tylko z CPU. Aktualne frameworki AI nie udostępniają obliczeń dla GPU zintegrowanych z platformą ARM.
Ciekawostki techniczne
W przypadku obu modeli, zarówno konwolucyjnego jak i LSTM, konieczne było uczenie maszynowe. Do tego celu planowaliśmy wykorzystać gotowe frameworki dla komputerów PC, m.in. Keras oraz PyTorch czy Caffee. Finalnie zdecydowaliśmy się na Keras ze względu na jego dojrzałość, sporą liczbę potwierdzonych zastosowań komercyjnych oraz support dla urządzeń mobilnych (m.in. Tensorflow Lite oraz możliwość konwersji modelu do innych formatów). Ponadto Keras po jego integracji z Tensorflow wydaje się być rozwiązaniem, której najstabilniej jest wspierane przez nVidia CUDA, czyli obliczenia z wykorzystaniem GPU.
Przenoszenie nauczonego modelu z platformy uczącej (PC) do rozwiązania docelowego (urządzenia AR/VR) w teorii jest dosyć proste, w praktyce bywa jednak kłopotliwe. W naszym przypadku mieliśmy zasadniczo jedynie dwa możliwe rozwiązania: eksport wyuczonego modelu do TFLite (dedykowany format dla frameworku Tensorflow Lite) lub do otwartego modelu w formacie ONNX (Open Neural Network Exchange). Podejście pierwsze sprawdza się dla platform, dla których dostępny jest Tensorflow Lite, niestety nie dla wszystkich (np. nie jest dostępny dla Hololens). Natomiast sama biblioteka Tensorflow Lite ma tę zaletę, że jest napisana niskopoziomowo w C++ i nawet w przypadku tworzenia aplikacji w językach skryptowych czy interpretowanych rdzeń obliczeniowy działa bezpośrednio na procesorze. To oznacza też, że potrzebne są biblioteki binarne dedykowane dla każdej platformy. W przypadku eksportu i późniejszego importu na urządzenie mobilne w formacie ONNX w większości przypadków możemy korzystać z biblioteki, która jest uniwersalna, ponieważ jest napisana w C#, Java (JavaScript) lub Pythonie i dostępna w postaci kodu źródłowego. Niestety to drugie rozwiązanie jest zdecydowanie bardziej powolne, jak na języki interpretowane przystało. Na dodatek, stosując cały łańcuch deweloperski, trzeba mieć na uwadze, że występuje sporo niekompatybilności pomiędzy poszczególnymi wersjami bibliotek, zarówno po stronie “uczącej” (PC), jak i po stronie “korzystającej” (urządzenia mobilne), jak również między nimi. Przykładowo, uczenie wykonanie za pomocą biblioteki Tensorflow w wersji 2.4.0 lub nowszej nie pozwala na wyeksportowanie w tym samym kodzie naszego modelu do formatu TFLite (tak, TFLite!), ponieważ deweloperzy w Google odpowiedzialni za mechanizm eksportujący najwyraźniej zaspali i nie zdążyli z “dociągnięciem” eksportera i przystosowaniem go do najnowszych wersji bibliotek. Bez problemu natomiast eksportujemy w wersji 2.4.0 do formatu ONNX ale… bez zmiany domyślnych ustawień w tak “zaawansowanej” wersji ONNX nie da się wczytać modelu do oprogramowania dla okularów VR/AR praktycznie w żadnej bibliotece ponieważ… znów trafiamy na niekompatybilność wersji, tym razem na poziomie “zbyt nowych ficzerów” po stronie formatu przenośnego, który nota bene reklamowany jest jako otwarty i uniwersalny.
A zatem jak widzicie, mielimy do rozwiązania niezłą zagadkę, a całość projektu na tym etapie bardziej przypominała zadanie ucieczki z Escape Room, niż klasyczną, solidną deweloperkę. Ale nie ukrywamy, że tego typu wyzwania napędzają nasz zespół do pracy.
Etap podium
Finalnie mamy ścieżkę i rozwiązanie, które pozwala na wykonanie uczenia na platformie PC, mamy modele, które z jednej strony zapewniają wysoką (przekraczającą 90%) jakość rozpoznawania gestów, z drugiej strony działają na tyle szybko, że użytkownik nawet nie zdaje sobie sprawy ze złożoności mechanizmów stojących za zaawansowaną analizą jego gestów – całość działa praktycznie w czasie rzeczywistym z opóźnieniami poniżej 100ms (a najczęściej znacznie szybciej).