Robot Astorino Kawasaki malarzem. Analiza zdjęć i malowanie obrazów
Kontakt w sprawie artykułu: Łukasz Giza - 2025-12-17

Dzięki temu poradnikowi dowiesz się, jak przy użyciu języka programowania Python napisać program generujący trajektorię robota edukacyjnego Astorino Kawasaki na podstawie zadanych mu obrazów. Napisany kod komunikuje się z robotem korzystając z RTC (Real Time Control), pozwalając na sterowanie w czasie rzeczywistym. Aplikacja ma za zadanie namalować kontury analizowanego zdjęcia. Po przetestowaniu w warunkach bezpiecznych stanowisko zostanie zaimplementowane w rzeczywistości i poddane weryfikacji.
Przybliżony czas wykonywania aplikacji:
Symulacja: 45 min
Robot: 30 min
1. Potrzebne urządzenia oraz oprogramowanie
W celu wykonania aplikacji potrzebne elementy to:
- Robot Astorino Kawasaki wraz z oprogramowaniem.
- AstorinoIDE.
- SimBox.
- Aerograf.
- Farby odpowiednie do malowania aerografem.
- Wydrukowany uchwyt na aerograf.
- Rozcieńczalnik do farb (najlepiej nie toksyczny).
- Kompresor z przewodem pneumatycznym.
- Komputer PC lub laptop z systemem operacyjnym Windows.
- IDE (zintegrowane środowisko) obsługujące język programowania Python (w tym projekcie został użyty program PyCharm Community Edition).
- Biblioteki:
- Astorino-python-lib
- Matplotlib
- Opencv-python
- Numpy
2. Analiza obrazów
2.1 Zapis i odczyt obrazu
Na potrzeby wygodnego korzystania ze stanowiska, została zaimplementowana możliwość zapisywania zdjęć prosto z kamery urządzenia. Kod funkcji pomocniczej realizującej zapis obrazu:
def capture_webcam_image(output_filename="captured_image.jpg"):
try:
# Dostęp do kamerki
cap = cv2.VideoCapture(0) # 0 lub 1 - wbudowana kamerka
# 1,2... - dodatkowe kamerki podpięte do urządzenia
if not cap.isOpened():
raise IOError("Nie można połączyć się z kamerką.")
while True:
ret, frame = cap.read()
if not ret:
raise IOError("Błąd odczytu z kamerki.")
# podgląd obrazu na żywo
cv2.imshow("Spacja - zapis obrazu, ESC - anulowanie zapisu", frame)
key = cv2.waitKey(1) & 0xFF
if key == 27: # klawisz ESC
print("Nie zapisano obrazu")
break
elif key == 32: # spacja
cv2.imwrite(output_filename, frame)
print(f"Obraz zapisany jako: '{output_filename}'")
break
cap.release()
cv2.destroyAllWindows()
except Exception as e:
print(f"Error: {e}")W przypadku gdy nie chcemy użyć naszej kamery do zapisywania obrazu, możemy bezpośrednio do folderu z naszym projektem wgrać plik graficzny z rozszerzeniem .jpg. Ważne jest jednak, żeby pamiętać jego nazwę w późniejszym etapie programu.
2.2 Przygotowanie obrazu pod analizę
W pierwszej kolejności potrzebujemy przewymiarować nasze zdjęcie, ponieważ w projekcie założono, że jeden piksel odpowiada 1mm w rzeczywistości. Z tego powodu potrzebne zadanie obrazowi nowych wymiarów, zależnych od tego, jak duży ma być obraz namalowany przez robota. Dodatkowo, aby zmniejszyć poziom skomplikowania analizy obrazów, należy zmienić paletę kolorów z RGB na skalę szarości (wartości poziomu szarości od 0 do 255).
image = cv2.imread(input_path)
image_resized = cv2.resize(image, (target_width, target_height))
image_gray = cv2.cvtColor(image_resized, cv2.COLOR_BGR2GRAY)2.3 Wstępna analiza obrazu
Warto zaznaczyć, że istnieje możliwość namalowania obrazu przy pomocy robota w kolorze, jednakże byłby to proces niezwykle czasochłonny, skomplikowany i kosztowny. Z tego też powodu w naszym przypadku użyjemy tylko jednego koloru, aby przenieść zdjęcie na płótno. Odbędzie się to przy malowaniu jedynie konturów (trochę jak szkic bez cieniowania). Trzeba więc w jakiś sposób wykryć wszystkie interesujące nas kontury na obrazie.
Aby było to możliwe, konieczne jest progowanie. Polega ono na przyjęciu dla konkretnego zdjęcia progu jasności, a następnie przypisaniu konkretnym pikselom wartości 0 lub 1, zależnie od tego, czy są one jaśniejsze, czy ciemniejsze od zadanego progu (threshold). Na takim zdjęciu jesteśmy w stanie w prosty sposób wykryć poszczególne kontury, a następnie odfiltrować te niechciane (pojedyncze losowe kontury wynikające z szumów).
thresh = cv2.adaptiveThreshold(
image_gray, 255,
cv2.ADAPTIVE_THRESH_GAUSSIAN_C,
cv2.THRESH_BINARY_INV,
block_size, C
)
contours, _ = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
filtered = [cnt for cnt in contours if cv2.contourArea(cnt) > max(1, min_area)]
clean = np.zeros_like(image_gray)
cv2.drawContours(clean, filtered, -1, 255, 1)
cv2.imshow("Edges", clean)Wyjaśnienie współczynników:
block_size – rozmiar obszaru wokół piksela branego do obliczania wyniku dla poszczególnych pikseli.
C – stała wartość dodawana do obliczanej średniej.
min_area – minimalny rozmiar (liczba elementów) konturu wymagana, aby wykryć go w finalnym zestawie konturów. Pomaga w usuwaniu niechcianych konturów z tła.
Uwaga: większe wartości min_area powodują usunięcie detali takich, jak rysy twarzy i włosy.

2.4 Wykrywanie ścieżek robota
Uzyskane w poprzednim punkcie kontury w teorii umożliwiają nam zadanie konkretnych pikseli jako współrzędnych dla robota, który w ich miejscu mógłby aktywować aerograf z farbą. Byłoby to jednak niezwykle czasochłonne oraz nieefektywne – chcemy przecież, aby robot malował kontury konkretnymi dłuższymi ruchami, a nie punktowo. W tym celu musimy w jakiś sposób pogrupować wszystkie piksele w grupy – linie, po których robot będzie się poruszać z stale włączonym aerografem, oraz w linie – miejsca przejazdu robota z wyłączonym aerografem z końca jednego konturu do początku drugiego. Do wygenerowania ścieżki robota został wykorzystany kod:
def visualize_path(path):
if not path:
print("⚠️ Brak elementu w ścieżce; nie ma co pokazywać :(.")
return
xs = [p[0] for p in path]
zs = [p[1] for p in path]
plt.figure()
prev = None
for xz_mode in path:
if prev is None:
prev = xz_mode
continue
x0, z0, m0 = prev
x1, z1, m1 = xz_mode
style = '-,g' if m1 == 1 else '-,r'
plt.plot([x0, x1], [z0, z1], style, linewidth=1)
prev = xz_mode
plt.gca().invert_yaxis()
plt.axis('equal')
plt.title("Podgląd ścieżki robota, aby kontynuować zamknij okno")
plt.xlabel("X [mm]")
plt.ylabel("Z [mm]")
plt.grid(True, alpha=0.3)
plt.show()
path = []
prev_last_point = None
for contour in scaled_contours:
contour_mm = contour.reshape(-1, 2).astype(float)
# mapowanie obrazu na współrzędne X/Z
contour_mm[:, 0] *= scale # X
contour_mm[:, 1] *= scale # Z
resampled_contour = resample_path(contour_mm, step_contour_mm)
if len(resampled_contour) == 0:
continue
if prev_last_point is not None:
# przejazd robota z wyłączonym aerografem (mode 2)
move_between = resample_path([prev_last_point, resampled_contour[0]], step_between_mm)
for pt in move_between:
path.append([pt[0], pt[1], 2])
# przejazd robota z włączonym aerografem, "malowanie" (mode 1)
for pt in resampled_contour:
path.append([pt[0], pt[1], 1])
prev_last_point = resampled_contour[-1]
# długość ścieżki + offsety
total = len(path)
if total == 0:
raise ValueError("Nie wygenerowano ścieżki")
xs = [p[0] for p in path]
zs = [p[1] for p in path]
min_x = min(xs)
min_z = min(zs)
print("Min X:", min_x)
print("Min Z:", min_z)
# Wizualizacja ścieżki
visualize_path(path)
Należy zaznaczyć, że im większa rozdzielczość oryginalnego zdjęcia, tym łatwiej będzie zastosować identyfikacje konturów. Dodatkowo w przypadku zdjęć o różnych formatach (16:9, 3:2, 1:1, itp.) trzeba zastosować odpowiednio pasujący format współrzędnych wyjściowych, aby uniknąć niechcianego zaburzenia proporcji (rozciągnięcie bądź ściśnięcie grafiki, w którymś z wymiarów).
3. Komunikacja z robotem Astorino Kawasaki
3.1 Ustanowienie połączenia
Chcąc uniknąć przepisywania wartości ogromnej liczby punktów ścieżki robota do pamięci robota, użyć można w tym celu ponownie języka Python. Po wgraniu do naszego projektu specjalnej biblioteki Astorino-python-lib otrzymujemy możliwość wysyłania poleceń do robota bez wychodzenia poza skrypt w Pythonie.
Pierwszym krokiem jest nawiązanie połączenia z robotem Astorino Kawasaki. Do tego potrzebne będzie podłączenie się do robota za pomocą kabla Ethernet (RJ45), zaznaczonego na poniższym schemacie:

Do poprawnego połączenia potrzebujemy też adres IP naszego robota. Znaleźć go można w aplikacji AstorinoIDE w zakładce Control.

UWAGA – Wykonanie poleceń ruchu zdefiniowanych w języku Python wymaga przełączenia robota w tryb REPEAT! Należy zachować szczególną ostrożność podczas wykonywania testów i uruchamianiu gotowych aplikacji.
Przy testowaniu podobnych aplikacji zaleca się korzystanie z SimBoxa, aby w pełni zasymulować działanie robota bez ryzyka uszkodzenia fizycznego modelu. W celu sprawdzenia, czy wszystkie poprzednie kroki zostały poprawnie wykonane, można teraz wykorzystać język Python, aby przesłać do robota proste polecenia. Kod do testu połączenia:
import asyncio
from astorino import Astorino # Importowanie biblioteki
async def main():
astorino = Astorino()
astorino.connect(IP) # Połączenie się z Astorino
astorino.set_motor_on() # Włączenie silników
astorino.Zero() # Zerowanie
astorino.reset()
astorino.set_repeat_once()
astorino.run()
astorino.HOME(80,40,40) # Przejazd do pozycji domowej
safety_check = astorino.is_in_motion() # Sprawdzenie czy robot
print(safety_check.i_val) # zakończył ruch
astorino.disconnect() # Zakończenie połączenia
# z Astorino
if __name__ == "__main__":
asyncio.run(main())3.2 Symulacja malowania
Dla przejrzystej wizualizacji symulacji w aplikacji Astorino istnieje możliwość śledzenia trajektorii robota. Zapis punktów trajektorii warto przypisać pod konkretny sygnał, który w skrypcie będzie aktywowany w trybie „malowania”. Konfiguracje śledzenia ścieżki można edytować w ustawieniach wizualizacji robota:

Dla celów weryfikacji istnieje również możliwość zapisu punktów wygenerowanej trajektorii po zakończeniu programu.
Przykład efektów poprawnie opracowanego programu:

Uwaga: należy mieć na uwadze, że duża liczba wygenerowanych punktów może mocno obciążyć kartę graficzną. Zaleca się ustawić widok w wizualizacji tak, aby widzieć efekty pracy robota (jak powyżej) i pozostawić go tak do końca trwania programu. Obracając widokiem z wieloma nałożonymi punktami w trakcie trwania procesu malowania ryzykujemy wysokim obciążeniem systemu i utratą wyników.
4. Wykonanie aplikacji z użyciem robota Astorino Kawasaki
4.1 Test na fizycznym robocie
Po potwierdzeniu, że przygotowany skrypt działa w sposób bezpieczny oraz z założonymi efektami, można przystąpić do testu z aerografem. Należy mieć na uwadze zasady bezpieczeństwa opisane w instrukcji użytkowania Astorino Kawasaki, oraz zasady użytkowania aerografu opracowane przez producenta.
Przykładowe efekty działania aplikacji:


5. Błędy i rozwiązania
Błąd wykrycia kamery
Jeśli nasz program nie zapisuje zdjęć z kamery, bądź nie jest w stanie się z nią połączyć („Camera index out of range”), należy zmienić wartość indeksu dla funkcji OpenCV VideoCaputre. Najczęstsza numeracja urządzeń:
0,1 – kamery wbudowane w urządzenie,
1,2… – zewnętrzne kamery podpięte do urządzenia.
Błąd wczytywania zdjęcia
Jeśli program nie otwiera naszego zdjęcia, należy sprawdzić, czy wprowadziliśmy poprawną ścieżkę do pliku, oraz czy jego rozszerzenie jest kompatybilne z aktualną wersją OpenCV.
Robot nie reaguje na skrypt w języku Python
Jeśli robot nie reaguje na skrypt, należy upewnić się, czy odpowiednio łączymy się z robotem, mając w szczególności na uwadze adres IP oraz poprawne użycie komend.
Błędy robota
W celu wyeliminowania typowych błędów, które mogą się pojawić w tej aplikacji upewnij się, że firmware robota Astorino Kawasaki został zaktualizowany do najnowszej wersji.
Pozostałe błędy
W przypadku innych błędów, należy skontaktować się z pomocą techniczną ASTOR:
- telefonicznie: 12 424 00 88
- mailowo: support@astor.com.pl
- lub przez stronę internetową: https://www.astor.com.pl/wsparcie.html
6. Dodatkowe materiały
Sprawdź nasz bezpłatny kurs:
Robot edukacyjny Astorino – kurs dla początkujących.
Autor artykułu:
Konrad Mędroń
Praktykant ASTOR