Stanowisko do grawerowania laserowego z wykorzystaniem robota edukacyjnego Astorino
Kontakt w sprawie artykułu: Kamila Piechocka - 2025-10-01

Przedstawiamy projekt opracowany przez studentów w ramach Koła Naukowego Robotyka Automatyka Informatyka (KN RAI) w Instytucie Automatyki i Robotyki na Politechnice Poznańskiej.
Projekt zrealizowali studenci Politechniki Poznańskiej: Jakub Sadurski, Wiktor Stachowski, Daniel Stasiak.
Opiekunem projektu był dr inż. Paweł Szulczyński, adiunkt Zakładu Sterowania i Robotyki (Z1).
Plan realizacji projektu

Plan stanowiska

1. Komputer PC wysyła komendy ruchu czasu rzeczywistego RTC, zawierające położenie następnego punktu, pozycje narzędzia, czas na wykonanie danego ruchu oraz sygnał sterujący laserem.
2. Astorino zwraca informacje o zakończeniu danego ruchu – funkcja blokująca.
3. Arduino Nano przelicza sygnały I/O z Astorino na wypełnienie PWM dla lasera.
4. Laser NEJE reguluje moc na podstawie sygnału PWM z Arduino.
Zastosowane mocowania
1. Holder Arduino.

2. Holder sterownika lasera.

3. Przejściówka robot – laser.

Rzeczywisty wygląd stanowiska

Algorytm programu

Wygląd aplikacji

Przetwarzanie obrazu – program w języku Python

Kod przetwarzania obrazu
# --- Parametry ---
img_path = sys.argv[1]
target_width = 2000 # px
target_height = 2000 # px
scale = 200.0 / 2000.0 # skalowanie (mm/px)
step_contour_mm = 0.1 # odstęp punktów wewnątrz konturu (mm)
step_between_mm = 0.2 # odstęp punktów między konturami (mm)
# --- Wczytanie i przygotowanie obrazu ---
img_og = cv2.imread(img_path)
img = cv2.resize(img_og, (target_width, target_height))
img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
_, thresh = cv2.threshold(img_gray, 192, 255, cv2.THRESH_BINARY)
thresh_invert = cv2.bitwise_not(thresh)
contours, hierarchy = cv2.findContours(thresh_invert, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)
Generowanie trajektorii – program w języku Python
Przykładowe wygenerowane ścieżki robota


Fragment kodu Python odpowiedzialnego za trajektorię
path = []
prev_last_point = None
for contour in contours:
# Skalowanie konturu do mm
contour_mm = contour.reshape(-1, 2).astype(float)
contour_mm[:, 0] *= scale # x
contour_mm[:, 1] *= -scale # y odwrócone
# Interpolacja punktów
resampled_contour = resample_path(contour_mm, step_contour_mm)
# Punkty między konturami
if prev_last_point is not None:
move_between = resample_path([prev_last_point, resampled_contour[0]], step_between_mm)
for pt in move_between:
path.append([round(pt[0], 1), round(pt[1], 1), 2]) # 2 = między konturami
# Punkty wewnątrz konturu
for pt in resampled_contour:
path.append([round(pt[0], 1), round(pt[1], 1), 1]) # 1 = kontur
prev_last_point = resampled_contour[-1]
# Dodanie tagów aktywacji/deaktywacji lasera
fix_tags(path)
print(f"Liczba punktów w ścieżce: {len(path)}")
Interpolacja punktów
def resample_path(points, step):
"""
Interpoluje punkty na ścieżce tak, aby odstępy między nimi były równe 'step' [mm].
points: lista punktów (x, y) [mm]
step: krok między punktami [mm]
return: lista punktów (x, y) w odstępach step [mm]
"""
points = np.array(points)
resampled = [points[0]]
acc_dist = 0.0
for i in range(1, len(points)):
p0 = points[i - 1]
p1 = points[i]
segment_vec = p1 - p0
segment_len = np.linalg.norm(segment_vec)
while acc_dist + segment_len >= step and segment_len > 1e-12:
ratio = (step - acc_dist) / segment_len
new_point = p0 + ratio * segment_vec
resampled.append(new_point)
p0 = new_point
segment_vec = p1 - p0
segment_len = np.linalg.norm(segment_vec)
acc_dist = 0.0
acc_dist += segment_len
# Dodaj ostatni punkt (koniec konturu)
if not np.allclose(resampled[-1], points[-1]):
resampled.append(points[-1])
return np.array(resampled)
Realizacja trajektorii na robocie – program w języku C#
Pętla realizująca trajektorię
// Pętla trajektorii
for (int i = 1; i < points.Count; i++)
{
if (token.IsCancellationRequested)
throw new OperationCanceledException("Użytkownik przerwał trajektorię.");
var pt = points[i];
double[] target = { pt.x + x_offset, pt.y + y_offset, tool_height, orientation[0], orientation[1], orientation[2] };
r.RTC_move(0x01, 10, target); // Nowy punkt wczytywany co 10ms
if (pt.tag == 3)
{
r.setOutput(5, 1);
Invoke(() => listBox1.Items.Add("Laser ON"));
}
if (pt.tag == 4)
{
r.setOutput(5, -1);
Invoke(() => listBox1.Items.Add("Laser OFF"));
}
Invoke(() =>
{
listBox1.Items.Add($"{i}. X: {pt.x:F1}, Y: {pt.y:F1}, tag: {pt.tag}");
listBox1.SelectedIndex = listBox1.Items.Count - 1; // autoscroll
listBox1.ClearSelected();
});
}
Invoke(() => MessageBox.Show("Trajektoria zakończona."));