Skip to content

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

3 Commits

Folders and files

Repository files navigation

NexArm — śledzenie dłoni (Jetson Orin Nano)

Kontynuacja projektu nexarm_block_following (ten sam Jetson, ta sama kamera Aurora930, to samo ramię NexArm), ale jako osobne repozytorium: zamiast śledzić kolorowy klocek, ramię ma śledzić dłoń, wykrywaną siecią YOLO, z realnym pomiarem odległości z kamery głębi (zamiast proxy z pola powierzchni bboxa). Pełny plan i uzasadnienie decyzji: dokument planu w projekcie na claude.ai ("NexArm — plan: śledzenie dłoni (YOLO) + realny odczyt głębi").

Stan: STAGE 3

Stage 1 (kamera + detekcja dłoni + odległość z głębi, bez ramienia) i stage 2 (manualne sterowanie ramieniem + chwytakiem z panelu WWW) — potwierdzone działające na sprzęcie.

Stage 3 (ten etap) dodaje autonomiczne śledzenie dłoni: regulator P (app/servoing.py, HandFollower) czyta gotową detekcję + realny dystans z ProcessingLoop (bez ponownego odpalania YOLO — patrz komentarz w servoing.py) i sam wysyła mikroruchy do ramienia, próbując wyśrodkować dłoń w kadrze i utrzymać stałą odległość (target_distance_mm). Pan/tilt przeniesione ze wzoru BlockFollower z nexarm_block_following; regulator dystansu przeliczony pod realną głębię w mm zamiast proxy z pola powierzchni bboxa.

Bezpiecznik: śledzenie startuje WYŁĄCZONE przy każdym uruchomieniu main.py, niezależnie od arm_backend. Włączasz je świadomie przyciskiem "Śledzenie" w panelu WWW (obok podglądu, przy sterowaniu ręcznym). Każdy ręczny jog albo powrót do pozycji bazowej automatycznie wyłącza śledzenie z powrotem — ręczne i automatyczne komendy nigdy nie idą jednocześnie.

Model detekcji: YOLOv8n

Detektor (app/detector.py) używa YOLOv8n przez ultralytics (models/hand_yolov8n.pt, Bingsu/adetailer na Hugging Face) — zgodnie z planem i celem "wykorzystać wydajność Nvidii na maksa". Pierwszy szkic używał tymczasowo YOLOv4-tiny/Darknet przez cv2.dnn (cross-hands-yolov4-tiny, projekt cansik/yolo-hand-detection), bo z sandboxa, w którym ten szkic powstał, nie dało się pobrać wag z Hugging Face — te pliki (models/cross-hands-yolov4-tiny.*) zostały w repo jako fallback/porównanie, ale nie są już domyślnie używane.

Do zrobienia przed pierwszym uruchomieniem tej wersji:

  1. Pobierz hand_yolov8n.pt z https://huggingface.co/Bingsu/adetailer/blob/main/hand_yolov8n.pt (z Twojej sieci powinno być dostępne bez problemu) i zapisz jako models/hand_yolov8n.pt.
  2. Zainstaluj/zaktualizuj zależności (ultralytics jest już w requirements.txt) — koniecznie przeczytaj sekcję "Instalacja na Jetsonie" niżej, punkt o torch/CUDA, bo samo pip install ultralytics NIE da Ci przyspieszenia GPU.
  3. Eksport do TensorRT (opcjonalnie, po potwierdzeniu że detekcja działa na .pt): model.export(format="engine") na samym Jetsonie — silnik TensorRT jest specyficzny dla konkretnego sprzętu/wersji TensorRT, więc eksportuj bezpośrednio na docelowym Jetsonie, nie gdzie indziej. Potem podmień model_path w config.yaml na wygenerowany plik .engine.

Sprzęt

Ten sam co w nexarm_block_following:

  • NVIDIA Jetson Orin Nano, 192.168.2.75. Potwierdzone na sprzęcie: /etc/nv_tegra_release → R39 (release), REVISION: 2.1 = L4T R39.2.1 = JetPack 7.2.1, Ubuntu 24.04, Python 3.12.3. To bardzo świeży JetPack (Orin Nano + AGX Thor na wspólnej bazie L4T R39, premiera ok. połowy 2026) — nie standardowy JetPack 6.x (L4T 36.x, Ubuntu 22.04/Python 3.10), z którym większość poradników w sieci wciąż jest pisana. To ma znaczenie przy instalacji torch/CUDA, patrz niżej.
  • Kamera Hiwonder Aurora930 (Deptrum, structured light), eye-in-hand.
  • Ramię Hiwonder NexArm — sterowanie manualne z panelu WWW od stage 2 (patrz "Sterowanie ramieniem" niżej). SDK (app/nexarm_sdk.py) przeniesiony bez zmian z nexarm_block_following (to samo ramię, protokół UART, /dev/ttyCH341USB0, 1 000 000 bps).

Jak to działa

Aurora930 --(Deptrum Stream SDK)--> aurora_bridge (podproces C++) --(stdout: ramki RGB + głębi)-->
  app/camera.py --(BGR + mapa głębi mm)--> app/detector.py (YOLO) --(bbox dłoni + pewność)-->
  app/web_server.py (Flask: pomiar odległości pod bboxem, MJPEG + status) --> panel WWW

aurora_bridge.cc to rozszerzona wersja mostka z projektu klocka: oprócz strumienia RGB otwiera (jeśli sprzęt to wspiera w wybranym trybie) strumień głębi wyrównanej do RGB (Aurora900::SwitchAlignedMode(true)), żeby bbox dłoni policzony na obrazie RGB można było bezpośrednio nałożyć na mapę głębi. Kod obsługuje trzy warianty w zależności od tego, co kamera faktycznie zgłosi na tym sprzęcie (nieznane, dopóki nie uruchomisz --list-modes — patrz niżej): jeden połączony strumień kRgbd, dwa oddzielne strumienie kRgb+kDepth, albo — jeśli głębia w ogóle niedostępna w żadnym trybie — fallback do samego RGB (wtedy panel WWW po prostu nie pokaże odległości, reszta działa normalnie).

Instalacja na Jetsonie

1. SDK Deptrum (nagłówki + biblioteka)

Nie jest częścią repo (licencja) — skopiuj z projektu klocka, patrz aurora_bridge/SDK_DEPTRUM.md.

2. Zależności Pythona (+ torch z CUDA dla Jetsona — WAŻNE)

sudo apt-get update && sudo apt-get install -y python3-pip
pip3 install --break-system-packages -r requirements.txt

(Ubuntu 24.04 wymaga --break-system-packages — PEP 668. Na Ubuntu 22.04 zwykle nie jest to konieczne.)

To zainstaluje ultralytics, który po drodze pociągnie zwykły torch z PyPI — a ten na Jetsonie nie ma wsparcia CUDA (inna architektura niż desktop/serwer x86, Jetson ma własny stos CUDA/cuDNN/TensorRT wbudowany w L4T/JetPack). Bez kolejnego kroku detektor będzie działał, ale wyłącznie na CPU — czyli dokładnie NIE to, po co kupiłeś Jetsona zamiast RPI5.

Krok 2b — podmień torch na wersję z CUDA dla Jetsona (JetPack 7.2.1 / L4T R39.2.1, Python 3.12, CUDA 13.2 — potwierdzone na tym sprzęcie):

JetPack 7 jest na tyle świeży, że nie ma jeszcze dojrzałych, powszechnie sprawdzonych paczek pod Jetsona z tej wersji (stare wheele Ultralytics/Jetson AI Lab dla JetPack 6 są zbudowane pod Python 3.10 — cp310 — i nie zainstalują się pod Twoim 3.12/cp312). Do wypróbowania, w tej kolejności:

  1. Oficjalny indeks PyTorcha, prerelease dla CUDA 13.2 (najbardziej wiarygodne źródło, bo to sam PyTorch, nie build community):
    pip3 install --break-system-packages --pre torch torchvision \
      --extra-index-url https://download.pytorch.org/whl/cu132
  2. Jeśli krok 1 nie znajdzie pasującego wheela (cp312+aarch64+CUDA 13.2 bywa wąskim przekrojem) — alternatywa to nowy, ujednolicony indeks Jetson AI Lab dla architektury SBSA (od Thor Jetsony korzystają ze standardowego ARM Server-Base-System-Architecture, więc wheele nie są już per-JetPack tylko wspólne):
    pip3 install --break-system-packages torch torchvision \
      --extra-index-url https://pypi.jetson-ai-lab.io/sbsa/cu130
    Może wymagać doinstalowania nvpl-blas, nvpl-lapack, nvidia-cudss-cu13 jako dodatkowych zależności, jeśli import torch się wywali na brakującej bibliotece.
  3. Zweryfikuj, że CUDA faktycznie widoczna:
    python3 -c "import torch; print(torch.__version__, torch.cuda.is_available())"
    Musi wypisać True. Jeśli False mimo wykonania kroku 1/2, albo żaden z wheeli się nie zainstaluje — wklej mi pełny błąd, to bardzo świeży teren (JetPack 7 miał premierę dopiero w 2026) i część powyższego może wymagać korekty pod Twoją dokładną kombinację wersji CUDA/sterowników.
  4. main.py i tak wystartuje nawet jeśli ten krok pominiesz albo się nie uda — app/detector.py przy starcie loguje, na jakim urządzeniu faktycznie pracuje ("CPU" albo numer GPU), więc zawsze widać stan faktyczny bez zgadywania. dusty-nv/jetson-containers (Docker, alternatywne podejście) na razie nie wspiera jeszcze JetPack 7 — to nie jest opcja awaryjna na dziś.

3. Zbuduj aurora_bridge

cd aurora_bridge
bash build.sh

4. Sprawdź tryby kamery i wsparcie głębi — ZANIM uruchomisz całą aplikację

./build/aurora_bridge --list-modes

To pokaże listę trybów (rgb=... depth=...) oraz surowe wartości StreamType, jakie zgłasza kamera — to jest kluczowy, jeszcze niezweryfikowany punkt tego etapu: czy kRgbd (połączony strumień) jest wspierany, czy trzeba dwóch oddzielnych strumieni, czy któryś tryb w ogóle ma depth != invalid. Jeśli żaden tryb nie ma głębi, aplikacja i tak zadziała (samo RGB + detekcja), tylko bez odczytu odległości — do zdiagnozowania dopiero na sprzęcie.

5. Uruchom aplikację

python3 main.py

Otwórz http://192.168.2.75:8080 z dowolnego urządzenia w tej samej sieci. Zobaczysz: podgląd kamery z bboxem dłoni (jeśli wykryta), pewność detekcji w %, oraz odległość w mm pod bboxem (jeśli strumień głębi zadziałał).

Sterowanie ramieniem (stage 2)

Bezpieczeństwo — WAŻNE, przeczytaj przed testem na prawdziwym ramieniu

config.yaml → arm.arm_backend ma dwie wartości:

  • mock — nic fizycznie się nie rusza. Panel WWW, wszystkie przyciski/suwaki i telemetria działają normalnie, ale sterują tylko symulacją w pamięci (MockArmController). Użyj tego trybu, żeby sprawdzić cały panel (layout, suwaki, odczyty) zanim podłączysz prawdziwe ramię.
  • hiwonder (domyślnie od momentu potwierdzenia ręcznego sterowania na sprzęcie) — prawdziwe sterowanie przez UART (app/nexarm_sdk.py, HiwonderArmController). Jeśli kiedyś znów potrzebujesz bezpiecznego trybu bez fizycznego ruchu (np. testując nowy kod zdalnie), przełącz z powrotem na mock w config.yaml.

Wartości axis_sign/claw_sign w config.yaml są przeniesione z już fizycznie zweryfikowanych wartości w nexarm_block_following (to samo ramię/Jetson), więc powinny być od razu poprawne — ale to nowy kod (nowa ścieżka sterowania), nie ten sam plik co tam. Przy pierwszym teście po przejściu na hiwonder:

  1. Zacznij od małych kroków — suwak "krok ręcznego ruchu" (move_duration_ms w panelu "Ramię — kalibracja") na niskiej wartości.
  2. Miej rękę w pobliżu wyłącznika/zasilania ramienia.
  3. Sprawdź najpierw pojedyncze naciśnięcia pada pan/tilt/dystans osobno, zanim użyjesz suwaka pozycji chwytaka.
  4. Jeśli ramię rusza się w złą stronę po naciśnięciu konkretnego kierunku — odwróć znak (1.0 ↔ -1.0) odpowiedniej osi w axis_sign/claw_sign w config.yaml i zrestartuj main.py.

Zakres chwytaka (claw_min/claw_max w config.yaml, domyślnie −150…150) NIE jest fizycznie zweryfikowany w tym projekcie (manual Hiwondera nie podaje liczbowego zakresu dla naszego SDK) — testuj suwak absolutnej pozycji chwytaka ostrożnie, małymi krokami, obserwując ramię.

Przycisk EMERGENCY STOP

Duży czerwony przycisk na samej górze panelu (przyklejony do góry strony, zawsze widoczny) — kliknięcie ALBO wciśnięcie spacji (działa niezależnie od tego, co jest aktualnie w fokusie na stronie) natychmiast:

  1. wyłącza autonomiczne śledzenie, jeśli było włączone,
  2. blokuje ramię na WSZYSTKIE kolejne komendy ruchu (ręczne i automatyczne) — dopóki świadomie nie klikniesz "Wznów pracę ramienia", które się wtedy pojawia obok.

Ważne ograniczenie, żeby nie było złudzeń: to NIE jest twardy, natychmiastowy stop w trakcie już wykonywanego ruchu — SDK NexArm po UART nie ma komendy przerywającej wysłaną już komendę set_pose/ move_increment. Pojedynczy mikroruch (domyślnie move_duration_ms = 200 ms) dokończy się na ESP32, zanim zadziała blokada. W praktyce, przy ruchu ciągłym (śledzenie automatyczne albo trzymanie przycisku jog), to efektywnie natychmiastowe zatrzymanie — kolejny mikroruch po prostu nigdy nie zostanie wysłany. Jeśli kiedykolwiek potrzebujesz GWARANTOWANEGO zatrzymania w trakcie pojedynczego ruchu — to wymaga fizycznego wyłącznika zasilania ramienia, nie da się tego zrobić samym UART z tym SDK. Szczegóły implementacji: docstring app/arm_controller.py, sekcja "EMERGENCY STOP".

Co jest w panelu WWW

Layout: przycisk EMERGENCY STOP na samej górze (przyklejony do góry strony), poniżej obiekt kontroli ramienia obok okna podglądu (pad pan/tilt, przyciski dystansu, powrót do home, przełącznik śledzenia, wskazówka trybu backendu), poniżej reszta paneli w trzech kolumnach (jedna kolumna na szerokich ekranach poniżej 900px):

  1. Status ogólny — połączenie kamery/ramienia, wiek ostatniej klatki, FPS przetwarzania, czy dłoń wykryta.
  2. Detekcja dłoni (YOLOv8n) — confidence_threshold/nms_threshold jako suwaki, zmiana na żywo bez restartu.
  3. Kamera / głębia (debug) — rozdzielczość RGB, czy strumień głębi dostępny, jego wiek, mode_index, swap_rb, flip_horizontal.
  4. Ramię — telemetria — bieżąca pozycja (x/y/z/pitch/roll/claw), surowe pozycje serw, wersja firmware, błąd ostatniego polecenia, przycisk odczytu napięcia baterii (na żądanie, nie w każdym cyklu odświeżania — dodatkowy round-trip po porcie szeregowym).
  5. Ramię — kalibracja — podgląd axis_map/axis_sign/claw_sign z config.yaml (tylko odczyt — do zmiany trzeba edytować plik i zrestartować), suwak move_duration_ms (czas trwania mikroruchu, edytowalny na żywo).
  6. Chwytak (dłoń robota) — przyciski otwórz/zamknij (ruch względny, jak pad pan/tilt) ORAZ suwak pozycji absolutnej (wysyła bezpośrednio wartość docelową, nie deltę) w zakresie claw_min…claw_max.

Endpointy API (nowe w tym etapie)

  • POST /api/arm/home — powrót do home_pose z config.yaml.
  • POST /api/arm/jog — ruch względny: {dpan, dtilt, ddist, dclaw} (wszystko delty, jak stary panel klocka).
  • POST /api/arm/claw — pozycja chwytaka ABSOLUTNA: {"value": ...}. W przeciwieństwie do dclaw w /api/arm/jog, to nie jest delta — SDK nie ma osobnej komendy "tylko chwytak", więc set_claw() liczy różnicę względem wewnętrznie śledzonej pozycji chwytaka i wysyła ją jako move_increment() z zerową deltą na x/y/z/pitch/roll (patrz docstring app/arm_controller.py — wcześniejsza wersja przez set_pose() z odczytaną pozycją potrafiła nieoczekiwanie ruszyć całym ramieniem, jeśli firmware nie zwrócił pitch/roll w odpowiedzi).
  • POST /api/arm/battery — odczyt napięcia baterii na żądanie.
  • POST /api/tracking — włącz/wyłącz autonomiczne śledzenie dłoni: {"enabled": true|false} (patrz sekcja "Autonomiczne śledzenie dłoni" niżej).
  • POST /api/arm/jog, POST /api/arm/home — jak wyżej, ale teraz też automatycznie wyłączają śledzenie, jeśli było włączone (odpowiedź niesie tracking_disabled: true/false, UI synchronizuje po tym przełącznik).
  • GET/POST /api/params — rozszerzone o arm.move_duration_ms i sekcję servoing (parametry regulatora P śledzenia) obok istniejących progów detekcji.
  • POST /api/emergency_stop — EMERGENCY STOP (patrz sekcja niżej): wyłącza śledzenie i blokuje ramię na kolejne komendy ruchu.
  • POST /api/emergency_clear — świadome wznowienie po emergency stop. /api/status niesie stan arm.estopped (true/false).

Autonomiczne śledzenie dłoni (stage 3)

Regulator P (app/servoing.py, HandFollower) — pan/tilt centruje dłoń w kadrze, regulator dystansu utrzymuje target_distance_mm (realny pomiar z kamery głębi, nie proxy z pola bboxa jak w nexarm_block_following). Panel WWW: przycisk "Śledzenie" obok sterowania ręcznego (włącza/wyłącza), oraz panel "Śledzenie dłoni (auto)" w trzech kolumnach z podglądem stanu (dystans surowy i wygładzony, błąd dystansu, ostatni wysłany mikroruch) i suwakami do strojenia na żywo: target_distance_mm, distance_deadband_mm, center_deadband_px, kp_pan, kp_tilt, kp_distance, max_step_mm, distance_smoothing_alpha (wygładzanie EMA sygnału dystansu — patrz sekcja niżej "Śledzenie dystansu 'nie reaguje'..."), min_safe_distance_mm i max_plausible_jump_mm (bezpiecznik minimalnego dystansu — patrz sekcja niżej "⚠️ BEZPIECZNIK MINIMALNEGO DYSTANSU", przeczytaj przed pierwszym testem automatycznego śledzenia blisko dłoni).

Bezpiecznik: śledzenie startuje WYŁĄCZONE przy każdym uruchomieniu main.py — włączasz świadomie przyciskiem w panelu, PO sprawdzeniu, że ręczne sterowanie działa poprawnie (dobre znaki osi, sensowny zakres chwytaka). Każdy ręczny jog/home automatycznie je wyłącza z powrotem.

Naprawiony błąd: dystans nie reagował wcale, pan/tilt tak. Przyczyna: SDK koduje ruch jako całe milimetry (round() przed wysłaniem po UART w nexarm_sdk.py). Pan/tilt praktycznie zawsze saturuje do max_step_mm (≥1 mm po zaokrągleniu), więc zawsze coś realnie leci po drucie. Dystans, z mniejszym kp_distance, potrafił wyliczyć np. 0.375 mm dla błędu tuż za martwą strefą — round(0.375) = 0, czyli ramię dostawało komendę zerowego ruchu i efektywnie nic nie robiło na tej osi, mimo że regulator "chciał" się ruszyć. Naprawione: app/servoing.py gwarantuje teraz co najmniej 1 mm (najmniejszy krok, jaki protokół w ogóle umie przekazać) za każdym razem, gdy regulator jest poza martwą strefą, niezależnie od tego, jak mały wyszedłby wynik z samego mnożenia przez kp_distance. Przy okazji podniosłem domyślne kp_distance z 0.015 na 0.02, żeby średnie błędy nie były zawsze przypięte do tego minimum.

Strojenie na pierwszy test:

  1. Zacznij z domyślnymi wzmocnieniami z config.yaml (servoing:) — przeniesione strukturalnie z nexarm_block_following, ale kp_distance jest NOWĄ wartością (przeliczoną pod mm zamiast pod px² pola powierzchni) — nie ma gwarancji, że są od razu dobrze dobrane na Twoim sprzęcie.
  2. Włącz śledzenie z dłonią w spokojnej, znanej pozycji, obserwuj — jeśli ramię "goni" zbyt wolno, zwiększaj kp_pan/kp_tilt/kp_distance stopniowo; jeśli oscyluje/drga, zmniejszaj.
  3. max_step_mm to twardy limit pojedynczego mikroruchu — trzymaj nisko (domyślnie 3 mm) podczas pierwszych testów, niezależnie od wzmocnień.
  4. target_distance_mm ustaw na wygodny dla Ciebie dystans trzymania ręki od kamery — regulator będzie do niego "dociągał" ramię.

Śledzenie dystansu "nie reaguje" / "błąd dystansu" skacze -1000..1000mm

Zgłoszony objaw: niezależnie od kp_distance (nawet na maksa), regulator dystansu w większości przypadków się nie rusza, a pole "Błąd dystansu" w panelu potrafi skoczyć do ok. ±1000mm, mimo że "Odległość" w panelu "Status ogólny" wygląda stabilnie. To NIE był problem strojenia kp — dwie osobne przyczyny, obie naprawione w kodzie:

  1. "Zamrożony" odczyt błędu dystansu (app/servoing.py) — gdy dłoń na chwilę znika z kadru albo śledzenie jest wyłączone, pole "Ostatni ruch" zerowało się poprawnie, ale "Błąd dystansu" zostawało zamrożone na wartości z ostatniej udanej detekcji — czyli mogło pokazywać liczbę policzoną dla zupełnie innej pozycji dłoni niż ta widoczna właśnie na podglądzie. Teraz oba pola resetują się razem.
  2. Pojedyncze zaszumione klatki pomiaru głębi trafiały SUROWE do regulatora. "Odległość" w panelu odświeża się szybko (~9 klatek/s), więc pojedynczy skok jest dla oka praktycznie niewidoczny — ale wpadał nieprzetworzony do wzoru błędu, dając skokowe ddist (stąd wrażenie "nie reaguje": błąd regulatora migał między dodatnim a ujemnym zamiast płynnie przechodzić przez martwą strefę). Dwie niezależne warstwy naprawy:
    • app/camera.py, get_depth_roi_mean(): dłoń nie jest prostokątem, więc narożniki bboxa z YOLO regularnie łapały kawałek tła za dłonią — teraz próbkujemy tylko wyśrodkowany fragment bboxa (60%) i liczymy MEDIANĘ zamiast średniej (odporna na mniejszościowe wartości odstające, w przeciwieństwie do średniej, którą potrafi zepsuć nawet kilka pikseli tła).
    • app/servoing.py: nowy parametr distance_smoothing_alpha (config.yaml → servoing:, domyślnie 0.3) wygładza dystans wykładniczo (EMA) PRZED policzeniem błędu regulatora — surowy odczyt nadal widać osobno w "Odległość" (bez zmian), a nowe pole "Dystans (wygładzony)" w panelu "Śledzenie dłoni (auto)" pokazuje, co faktycznie widzi regulator. 1.0 = brak wygładzania (zachowanie jak przed tą zmianą); jeśli po tej zmianie regulator dalej wydaje się "szarpać", obniż to jeszcze (np. do 0.15–0.2) suwakiem w panelu — działa na żywo, bez restartu.

Ważne zastrzeżenie: te poprawki są oparte na analizie kodu (nie mam dostępu do Twojego sprzętu, żeby to zweryfikować na żywo) — są to principled fixes pod konkretne, prawdopodobne przyczyny zgłoszonych objawów, nie potwierdzony na 100% root cause. Po zaktualizowaniu, obserwuj nowe pole "Dystans (wygładzony)" obok "Błąd dystansu": jeśli po tej zmianie nadal widzisz nagłe skoki rzędu setek/tysięcy mm w "Odległość" (surowej, nie wygładzonej) — to znak, że sam pomiar głębi z kamery (aurora_bridge) miewa poważniejsze, jednorazowe błędy odczytu, nie tylko kontaminację tła w rogach bboxa, i wymaga dalszej diagnozy (np. logowanie histogramu wartości w ROI). Daj znać, jak zachowa się po tej zmianie.

⚠️ BEZPIECZNIK MINIMALNEGO DYSTANSU — przeczytaj przed kolejnym testem autonomicznego śledzenia

Zgłoszony objaw: przy bardzo bliskiej dłoni i wysoko ustawionym target_distance_mm, ramię czasem jechało do przodu i dotykało ręki — nie za każdym razem, więc niereprodukowalne w 100%. To realny problem bezpieczeństwa, więc potraktowałem go priorytetowo.

Sprawdziłem cały łańcuch znaków algebraicznie (błąd regulatora → ddist → axis_sign → dx wysyłane po UART) i wychodzi on spójny: dla dłoni bliżej niż target_distance_mm, regulator poprawnie liczy ujemne ddist, co przy niezmienionym axis_sign["distance"] (przeniesionym z już fizycznie zweryfikowanego nexarm_block_following) powinno oznaczać cofanie, nie dojazd do przodu. Innymi słowy — nie znalazłem błędu w samej matematyce regulatora P, co zgadza się z Twoją obserwacją, że samo dokręcanie/luzowanie kp czy wygładzanie nic tu nie zmienia. Najbardziej prawdopodobne (ale NIEPOTWIERDZONE bez testu na sprzęcie) wytłumaczenie: kamery structured-light typu Aurora930 mają fizyczne minimum zasięgu — poniżej niego promiennik IR i odbiornik "widzą" inny fragment sceny (okluzja paralaksy) i pomiar potrafi się zachować nieprzewidywalnie, nie tylko zwracając 0 (już filtrowane), ale czasem zwracając wiarygodnie wyglądającą, lecz BŁĘDNĄ dużą wartość, jakby dłoń była daleko. To pasowałoby zarówno do "nie zawsze się powtarza" (zależy od kąta/odbicia dłoni z bliska), jak i do Twojej własnej diagnozy ("coś nie tak z przekazywaniem/konwersją danych o odległości").

Niezależnie od dokładnej przyczyny, dodałem twardy bezpiecznik niezależny od reszty regulatora (app/servoing.py, docstring modułu, punkt 5):

  1. min_safe_distance_mm (domyślnie 150mm, config.yaml → servoing:) — jeśli SUROWY zmierzony dystans spadnie poniżej tej wartości, regulator ignoruje target_distance_mm i cały P-controller i wymusza pełny krok cofnięcia w każdym cyklu, dopóki dłoń się nie oddali. Panel WWW pokazuje to na żywo jako "Bezpiecznik dystansu: AKTYWNY" (czerwony badge) w panelu "Śledzenie dłoni (auto)".
  2. max_plausible_jump_mm (domyślnie 150mm) — jeśli surowy dystans skoczy w górę (dalej) o więcej niż tyle w jednym cyklu (fizycznie niemożliwe dla ręki w ~100ms), a regulator na tej podstawie chciałby jechać do przodu — ta klatka jest ignorowana. Celowo asymetryczne: skok w dół (bliżej) nigdy nie jest filtrowany, bo cofanie jest zawsze bezpieczne.

⚠️ WARTOŚĆ min_safe_distance_mm=150mm JEST NIEZWERYFIKOWANYM ZGADYWANIEM — nie znam offsetu kamera→chwytak na Twoim montażu (eye-in-hand). Zanim zaufasz temu bezpiecznikowi blisko dłoni:

  1. Trzymaj rękę blisko wyłącznika zasilania ramienia.
  2. Powoli przybliżaj dłoń do ramienia (ręcznie, bez włączonego śledzenia automatycznego), obserwując w panelu "Status ogólny" pole "Odległość".
  3. Zanotuj, przy jakiej wartości chwytak faktycznie dotyka/prawie dotyka dłoni.
  4. Ustaw min_safe_distance_mm w panelu (suwak w "Śledzenie dłoni (auto)") albo w config.yaml na liczbę wyraźnie wyższą niż to, z zapasem bezpieczeństwa (np. 1.5–2×).

To jest DODATKOWA warstwa NAD enabled/EMERGENCY STOP, nie zamiast nich — oba nadal w pełni obowiązują i mają pierwszeństwo (patrz sekcja "Przycisk EMERGENCY STOP" wyżej, łącznie z jej ograniczeniem co do już wysłanej komendy).

Rozwiązywanie problemów

Większość błędów SDK/USB/ABI (crash przy starcie, Device::Open() zwraca ret=1, błędy pip3) jest identyczna jak w nexarm_block_following — patrz tamto README, sekcja "Rozwiązywanie problemów", te same przyczyny i poprawki dotyczą tego projektu.

Nowe, specyficzne dla tego etapu:

  • Brak odczytu odległości / głębi — na tym sprzęcie pole depth w trybach z --list-modes zawsze pokazuje invalid, mimo że kamera faktycznie wspiera głębię (GetSupportedStreamType to potwierdza) — to już uwzględnione, kod nie blokuje się na tym polu. Były już dwie poprawki w aurora_bridge.cc: (1) SwitchAlignedMode(true) musi być wywołane PO udanym SetMode(), nie zaraz po Open() (SDK tego pilnuje i inaczej zwraca błąd), (2) wybór trybu/strumienia nie patrzy już na niewiarygodne pole depth z GetSupportedFrameMode(). Jeśli mimo to nadal brak odległości — upewnij się najpierw, że aurora_bridge został PRZEBUDOWANY po tych zmianach (cd aurora_bridge && bash build.sh — sama podmiana pliku .cc nic nie da bez rebuildu), a potem sprawdź na stderr python3 main.py (linie z prefiksem aurora_bridge:) który wariant strumienia został wybrany — "POLACZONY strumień kRgbd", "DWA oddzielne strumienie" czy fallback do samego RGB — oraz czy w logu Pythona pojawia się ostrzeżenie "Rozmiar ramki głębi... nie zgadza się" (oznaczałoby to, że payload głębi ma inny format niż zakładane 16-bit/piksel w mm).
  • Rozmiar ramki głębi nie zgadza się z cols*rows*2 (log w app/camera.py: "Rozmiar ramki głębi... nie zgadza się") — założenie o 16-bit/piksel w milimetrach z nagłówków SDK może nie pasować do tego, co faktycznie wysyła ten sprzęt/tryb; wymaga sprawdzenia realnego rozmiaru payloadu na żywo (dodaj tymczasowy log w _decode_depth) i poprawki interpretacji formatu.
  • Detekcja dłoni słaba/niestabilna (pewność, rozmiar bboxa z bliska) — te objawy były zaobserwowane z poprzednim modelem (YOLOv4-tiny). Nowy hand_yolov8n.pt ma inną architekturę (anchor-free head), która generalnie lepiej radzi sobie ze zmienną skalą obiektu (dłoń wypełniająca cały kadr z bliska) — ale to trzeba zweryfikować na sprzęcie, nie zakładać z góry. Jeśli po przejściu na YOLOv8n nadal słabo:
    1. Dostrój confidence_threshold/nms_threshold w config.yaml (start: 0.5/0.3) — obniż próg pewności, jeśli detektor jest zbyt "nieśmiały" i traci dłoń, podnieś jeśli łapie fałszywe wykrycia.
    2. Sprawdź, czy detektor faktycznie pracuje na GPU (log przy starcie main.py) — CPU nie tylko wolniej liczy, ale u niektórych buildów ultralytics/torch bywa mniej dokładny numerycznie niż ścieżka CUDA (rzadkie, ale warto to wykluczyć, jeśli coś jest nie tak).
    3. Wyższa rozdzielczość kamery — spróbuj mode_index: 1 albo 2 w config.yaml (sprawdź dokładne rozdzielczości przez --list-modes na Jetsonie). Więcej pikseli źródłowych na dłoni przed przeskalowaniem do wejścia sieci (640×640) może pomóc, bez gwarancji.

Wydania (releases)

Projekt jest w repozytorium git (lokalnie zainicjowanym w tym katalogu). Duże pliki binarne (wagi modeli models/*.pt / models/*.weights, zbudowany aurora_bridge/build/, itp.) są celowo wykluczone przez .gitignore — pobierz je ręcznie wg sekcji "Instalacja na Jetsonie" wyżej.

Publikacja repozytorium na GitHubie

To repozytorium powstało lokalnie (na tym Macu, nie w chmurowej sandboksie Claude) — założenie konta zdalnego i pierwszy push trzeba wykonać z własnego terminala, tam gdzie masz zalogowane gh/klucze SSH:

cd ~/PycharmProjects/nexarm_hand_tracking
gh repo create radoslawo81/nexarm-hand-tracking --public --source=. --remote=origin --push
git push origin v1.0.0   # wypchnij też tag wydania

(Konwencja nazewnictwa i konto — radoslawo81 — jak w siostrzanym repozytorium nexarm-block-following. Zmień --public na --private, jeśli wolisz repo prywatne.)

Pakowanie paczki wydania

scripts/make_release.sh buduje archiwum .tar.gz z aktualnego stanu repozytorium przez git archive — czyli tylko pliki śledzone przez git, automatycznie z pominięciem wszystkiego z .gitignore (modele, __pycache__, .venv, dist/ itd.). Wynik trafia do dist/ (katalog sam jest zignorowany przez git, więc nie zaśmieca repo).

# Zbuduj paczkę dla wskazanej wersji (z aktualnego HEAD):
scripts/make_release.sh v1.0.0

# Albo dla konkretnego tagu/gałęzi:
scripts/make_release.sh v1.0.0 v1.0.0

Wynik: dist/nexarm-hand-tracking-v1.0.0.tar.gz + suma kontrolna .sha256 obok. Skrypt na końcu wypisuje gotowe polecenia do otagowania i utworzenia wydania na GitHubie.

Otagowanie i utworzenie GitHub Release

# Jeśli tag jeszcze nie istnieje:
git tag -a v1.0.0 -m "Wydanie v1.0.0"
git push origin v1.0.0

# Zbuduj paczkę (patrz wyżej), a potem:
gh release create v1.0.0 dist/nexarm-hand-tracking-v1.0.0.tar.gz \
  --title "v1.0.0" --generate-notes

Aktualny stan repozytorium (ten commit) jest już otagowany jako v1.0.0 — to punkt zamknięcia etapu 3 (kamera + detekcja + głębia, sterowanie ręczne, autonomiczne śledzenie P, EMERGENCY STOP, bezpiecznik minimalnego dystansu). Kolejne wydania: podnieś numer wersji (np. v1.1.0 dla nowych funkcji, v1.0.1 dla poprawek) i powtórz powyższe kroki.

Następne kroki (po potwierdzeniu stage 3 na sprzęcie)

  1. Eksport hand_yolov8n.pt do TensorRT (.engine) na Jetsonie, po potwierdzeniu że detekcja na .pt działa dobrze (patrz wyżej) — nieblokujące, można zrobić w dowolnym momencie.
  2. Strojenie regulatora P śledzenia na sprzęcie (patrz sekcja "Autonomiczne śledzenie dłoni" wyżej) — wartości kp_pan/kp_tilt/ kp_distance/target_distance_mm w config.yaml są startowe, niezweryfikowane fizycznie na tym konkretnym ramieniu/kamerze.
  3. Jeśli po strojeniu regulator P nadal "goni się w kółko" albo za bardzo spóźnia za szybkim ruchem dłoni — naturalny następny krok to dodanie członu D (na pochodną błędu) do HandFollower, tak jak było zasygnalizowane już w oryginalnym BlockFollower.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages