Strona główna ASTOR
Automatyka w praktyce

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.

Rys 1. Przykładowy wynik zdjęcia poddanego progowaniu, z uwzględnionymi konturami. Źródło: ASTOR

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)
Rys. 2. Podgląd wygenerowanej ścieżki robota. Źródło: ASTOR

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:

Rys. 3. Schemat połączeń elektrycznych robota Astorino Kawasaki. Źródło: ASTOR

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

Rys. 4. Widok zakładki Control w AstorinoIDE. Źródło: ASTOR

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:

Rys. 5. Konfiguracja śledzenia trajektorii w aplikacji Astorino. Źródło: ASTOR

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:

Rys. 6. Przykładowy wynik symulacji programu w aplikacji Astorino. Źródlo: ASTOR

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:

Rys. 7. Przykładowy efekt malowania przez robota Astorino Kawasaki. Źródło: ASTOR
Rys. 8. Przykładowy efekt malowania przez robota Astorino Kawasaki. Źródło: ASTOR

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

Newsletter Poradnika Automatyka

Czytaj trendy i inspiracje, podstawy automatyki, automatykę w praktyce

Please wait...

Dziękujemy za zapis do newslettera!

Czy ten artykuł był dla Ciebie przydatny?

Średnia ocena artykułu: 0 / 5. Ilość ocen: 0

Ten artykuł nie był jeszcze oceniony.

Zadaj pytanie

Twój adres e-mail nie zostanie opublikowany. Wymagane pola są oznaczone *