2026-08-23

System Zervi: lokalna rozmowa, rozpoznawanie twarzy i pierwszy ruch

Notatki z łączenia rozmowy głosowej, lokalnego rozpoznawania twarzy, syntezy mowy oraz pierwszych reakcji szczęki i napędu przyszłej nogi.

system Zervi / głos / wzrok / reakcja fizyczna

Chciałem, żeby rozmowa kończyła się czymś więcej niż dźwiękiem

Od początku nie chciałem budować kolejnego asystenta zamkniętego w oknie komputera. Zervidjanna ma kiedyś patrzeć na rozmówcę, odpowiadać własnym głosem i reagować całym mechanicznym ciałem. Na tym etapie postanowiłem więc połączyć elementy, które wcześniej rozwijałem osobno: mikrofon, kamerę, rozmowę, syntezę mowy, prototyp szczęki i jeden napęd przyszłej nogi.

Największym wyzwaniem nie było samo uruchomienie kolejnego modelu AI. Musiałem zbudować sensowny przepływ informacji i zdecydować, która warstwa może tylko coś zaproponować, a która naprawdę ma prawo poruszyć silnikiem. Uznałem, że model może mieć pomysł na reakcję, ale ostatnie słowo zawsze powinien mieć przewidywalny kontroler — zwłaszcza gdy modelowi trafi się bardziej poetycki dzień.

Ekran uruchamiania Systemu Zervi podczas inicjalizacji lokalnego modułu STT
Podczas uruchamiania chciałem widzieć, który moduł jest właśnie przygotowywany. Tutaj startuje lokalne rozpoznawanie mowy.

Najpierw musiałem uporządkować przepływ danych

Mikrofon i kamera dostarczają zupełnie innych informacji, dlatego nie chciałem wrzucać wszystkiego do jednego wielkiego modułu. Tor głosowy wykrywa wypowiedź i zamienia ją na tekst, a tor wizyjny niezależnie sprawdza, kto znajduje się przed kamerą. Dopiero krótki wynik obu procesów spotyka się w rdzeniu rozmowy.

Świadomie zrezygnowałem z przekazywania do modelu surowego obrazu. Do rozmowy wystarcza prosty kontekst tekstowy, na przykład informacja, że kamera widzi konkretną osobę albo jedną nieznaną twarz. Jest to lżejsze, łatwiejsze do kontrolowania i nie miesza danych biometrycznych z samą historią rozmowy.

Schemat przepływu danych w Systemie Zervi
Tak uporządkowałem główne tory systemu: mikrofon i kamera pracują osobno, a rdzeń łączy ich wyniki z głosem i reakcją fizyczną.

Najważniejsze decyzje na tym etapie

Problem Moja decyzja Dlaczego
Rozpoznawanie mowy Lokalny Whisper Chciałem, aby podstawowa rozmowa nie zależała od połączenia z internetem.
Model rozmowy Domyślnie lokalny model GGUF Tryb offline daje mi większą kontrolę nad działaniem i przepływem danych.
Rozpoznawanie osób InsightFace i lokalna baza Obraz oraz embeddingi twarzy nie muszą opuszczać komputera.
Ruch szczęki Sterowanie amplitudą głosu Proste rozwiązanie już daje czytelny efekt bez budowania pełnej analizy fonemów.
Sterowanie napędem Mały zestaw walidowanych poleceń Model nie powinien wysyłać dowolnych wartości bezpośrednio do silnika.

Domyślny tryb offline

Zależało mi, aby Zervi mogła działać lokalnie nawet wtedy, gdy warsztat nie ma dostępu do sieci albo po prostu nie chcę wysyłać rozmowy na zewnątrz. Dlatego rozpoznawanie mowy, model rozmowy, rozpoznawanie twarzy i synteza głosu mogą tworzyć jeden lokalny tor, a domyślna konfiguracja korzysta z modelu GGUF uruchamianego przez llama.cpp.

Pozostawiłem możliwość użycia zewnętrznego modelu jako wariant opcjonalny, bo może przydać się do porównań albo zadań wymagających większego modelu. Nie jest on jednak potrzebny do podstawowego działania. Ceną trybu lokalnego są większe wymagania sprzętowe i konieczność dobrania modelu tak, aby odpowiedź nie pojawiała się dopiero wtedy, gdy rozmówca zdąży zapomnieć pytanie.

Dlaczego lokalnie

Najważniejsze były dla mnie prywatność, niezależność od internetu i pełna kontrola nad tym, które dane trafiają do poszczególnych modułów. Lokalność nie rozwiązuje automatycznie wszystkich problemów bezpieczeństwa, ale znacznie upraszcza odpowiedź na pytanie: gdzie właściwie znajduje się głos i obraz użytkownika.

Chciałem, żeby Zervi uczyła się imienia podczas normalnej rozmowy

Osobny formularz do dodawania każdej osoby byłby poprawny technicznie, ale zupełnie nie pasował do charakteru robota. Spróbowałem więc powiązać naturalne przedstawienie się — na przykład „mam na imię Anna” — z twarzą widoczną w danej chwili przed kamerą.

Szybko zauważyłem oczywisty problem: jeśli kamera widzi kilka nieznanych osób, samo imię nie mówi jeszcze, do kogo należy. Dlatego zapis profilu dopuszczam tylko wtedy, gdy w kadrze znajduje się dokładnie jedna nieznana twarz. W pozostałych przypadkach system woli niczego nie zapisać, zamiast z pełnym przekonaniem zapamiętać niewłaściwą osobę.

Schemat lokalnego rozpoznawania i uczenia twarzy
Naturalne przedstawienie się może utworzyć profil, ale tylko wtedy, gdy system potrafi jednoznacznie połączyć imię z jedną nieznaną twarzą.

Lokalnie nie znaczy bez zabezpieczeń

Nie zapisuję surowych klatek z kamery, ale imię i embedding twarzy nadal są danymi biometrycznymi. Obecna baza jest zwykłym lokalnym plikiem JSON i nie jest jeszcze szyfrowana. Docelowo chcę dodać wygodne usuwanie profili oraz lepszą ochronę samego pliku.

Sam głos wydawał się trochę zbyt nieruchomy

Kiedy lokalna synteza mowy zaczęła działać, od razu było widać, że dźwięk dochodzący z nieruchomej głowy wygląda dość sztucznie. Zamiast od razu budować dokładną analizę fonemów, wykorzystałem amplitudę aktualnie odtwarzanego fragmentu głosu.

Po wygładzeniu wartości RMS mogłem zamienić ją na pozycję serwa szczęki. Rozwiązanie jest proste i nie udaje pełnej mimiki, ale dobrze oddaje rytm wypowiedzi. Na tym etapie efekt okazał się znacznie ważniejszy niż komplikowanie systemu tylko po to, aby szczęka wymawiała wzorowe samogłoski.

Prototyp głowy Zervidjanny podczas testu rozmowy i ruchu szczęki
Pierwsze próby połączenia lokalnego głosu z ruchem szczęki sterowanym amplitudą odtwarzanego dźwięku.

Podłączenie silnika wymagało znacznie mniej zaufania do AI

Gdy rozmowa i głos zaczęły działać razem, chciałem sprawdzić, czy wypowiedź może wywołać prawdziwy ruch przyszłej nogi. Nie chciałem jednak, aby tekst wygenerowany przez model trafiał bezpośrednio do ODrive. Wydzieliłem więc niewypowiadany znacznik komendy, który jest usuwany przed syntezą mowy i trafia do osobnego dekodera.

Dekoder rozumie tylko kilka przewidzianych przeze mnie działań: mały ruch względny, kilka kontrolowanych wychyleń i zatrzymanie. Dopiero później kontroler sprawdza limity, stan osi, kalibrację, prąd, prędkość i timeout. Dzięki temu model może zaproponować gest, ale nie może samodzielnie wymyślić sobie nowego zakresu ruchu.

Schemat oddzielenia mowy od sterowania prototypową nogą i szczęką
Rozdzieliłem tekst wypowiadany przez Zervi od komendy sprzętowej. Do napędu trafia wyłącznie znane i zwalidowane polecenie.

Najważniejsza obserwacja ze stanowiska napędowego

Pozycja enkodera z chwili uruchomienia jest tylko programowym zerem, a nie pewną pozycją mechaniczną nogi. To wystarcza do kontrolowanego demonstratora, ale nie do kompletnego robota. Pełna konstrukcja będzie potrzebowała krańcówek, watchdoga, fizycznego E-stopu i niezależnego potwierdzania intencji ruchu.

Co wyniosłem z tego etapu

Najwięcej pracy wymagało nie samo AI, lecz połączenie wielu małych elementów tak, aby żaden z nich nie przejmował zbyt dużej odpowiedzialności. System nadal jest demonstratorem uruchamianym na komputerze, ale po raz pierwszy rozmowa, wzrok, głos i fizyczna reakcja tworzą jeden ciąg.

  • lokalny tryb offline daje niezależność, ale wymaga rozsądnego dobrania modelu do sprzętu;
  • krótki kontekst tekstowy z kamery jest wystarczający do rozmowy i nie wymaga przekazywania obrazu dalej;
  • prosty ruch szczęki zsynchronizowany z amplitudą daje dobry efekt bez nadmiernej komplikacji;
  • model może proponować reakcję, lecz o wykonaniu i granicach ruchu musi decydować osobny kontroler;
  • pełne HMI, nawigacja, równowaga i sterowanie całym ciałem pozostają kolejnymi etapami.

Mały ruch dla robota, całkiem duży dla projektu

Na razie Zervi ma prototypową szczękę, kamerę stojącą obok komputera i jedno mechaniczne „biodro”. Do spaceru po warsztacie jeszcze daleko, ale rozmowa po raz pierwszy nie kończy się na głośniku — może już poruszyć fragmentem przyszłego ciała.