2025-07-31 18:20:14 +02:00
|
|
|
|
import cv2
|
|
|
|
|
|
import requests
|
|
|
|
|
|
import numpy as np
|
|
|
|
|
|
from buildhat import Motor
|
|
|
|
|
|
import time
|
|
|
|
|
|
from datetime import datetime
|
|
|
|
|
|
|
|
|
|
|
|
motorL = Motor('C')
|
|
|
|
|
|
motorR = Motor('D')
|
|
|
|
|
|
|
|
|
|
|
|
# Adres MJPEG streamu z VLC
|
|
|
|
|
|
url = "http://pilego.local:8080"
|
|
|
|
|
|
stream = requests.get(url, stream=True)
|
|
|
|
|
|
|
|
|
|
|
|
# Bufor bajtów, w którym będziemy szukać klatek JPEG
|
|
|
|
|
|
bytes_buffer = b""
|
|
|
|
|
|
tprev = datetime.now()
|
|
|
|
|
|
xp = 0
|
|
|
|
|
|
ip = 0
|
|
|
|
|
|
# Główna pętla
|
|
|
|
|
|
for chunk in stream.iter_content(chunk_size=1024):
|
|
|
|
|
|
bytes_buffer += chunk
|
|
|
|
|
|
|
|
|
|
|
|
# Szukamy pełnej klatki JPEG w bajtach
|
|
|
|
|
|
a = bytes_buffer.find(b'\xff\xd8') # początek JPEG
|
|
|
|
|
|
b = bytes_buffer.find(b'\xff\xd9') # koniec JPEG
|
|
|
|
|
|
|
|
|
|
|
|
if a != -1 and b != -1:
|
|
|
|
|
|
# Wycinamy JPEG z bufora
|
|
|
|
|
|
jpg = bytes_buffer[a:b + 2]
|
|
|
|
|
|
bytes_buffer = bytes_buffer[b + 2:]
|
|
|
|
|
|
|
|
|
|
|
|
# Dekodujemy JPEG do obrazu (OpenCV)
|
|
|
|
|
|
frame = cv2.imdecode(np.frombuffer(jpg, dtype=np.uint8), cv2.IMREAD_COLOR)
|
|
|
|
|
|
if frame is None:
|
|
|
|
|
|
continue
|
|
|
|
|
|
|
|
|
|
|
|
# Zmniejsz obraz (opcjonalnie) dla szybkości
|
|
|
|
|
|
frame = cv2.resize(frame, (320, 240))
|
|
|
|
|
|
# if frame is not None:
|
|
|
|
|
|
# cv2.imshow("MJPEG Stream", frame)
|
|
|
|
|
|
|
|
|
|
|
|
# ============================
|
|
|
|
|
|
# 👇 WYKRYWANIE NIEBIESKIEJ PIŁKI 👇
|
|
|
|
|
|
# ============================
|
|
|
|
|
|
|
|
|
|
|
|
# 1. Konwersja z BGR (OpenCV) do HSV (lepszy do filtracji koloru)
|
|
|
|
|
|
hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV)
|
|
|
|
|
|
|
|
|
|
|
|
# 2. Definiujemy zakres koloru niebieskiego w HSV
|
|
|
|
|
|
lower_blue = np.array([110, 240, 50]) # dolna granica niebieskiego
|
|
|
|
|
|
upper_blue = np.array([130, 255, 255]) # górna granica niebieskiego
|
|
|
|
|
|
|
|
|
|
|
|
# 3. Maska – gdzie kolor mieści się w podanym zakresie
|
|
|
|
|
|
mask = cv2.inRange(hsv, lower_blue, upper_blue)
|
|
|
|
|
|
|
|
|
|
|
|
# 4. Morfologia: usuwanie szumów (dylatacja, erozja)
|
|
|
|
|
|
mask = cv2.erode(mask, None, iterations=2)
|
|
|
|
|
|
mask = cv2.dilate(mask, None, iterations=2)
|
|
|
|
|
|
|
|
|
|
|
|
# 5. Szukamy konturów w masce
|
|
|
|
|
|
contours, _ = cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
|
|
|
|
|
|
|
|
|
|
|
|
# 6. Jeśli znaleziono jakiekolwiek kontury
|
|
|
|
|
|
if contours:
|
|
|
|
|
|
# Wybieramy największy (zakładamy, że to piłka)
|
|
|
|
|
|
largest_contour = max(contours, key=cv2.contourArea)
|
|
|
|
|
|
|
|
|
|
|
|
# Jeśli kontur jest wystarczająco duży
|
|
|
|
|
|
if cv2.contourArea(largest_contour) > 200:
|
|
|
|
|
|
# Wyznacz środek i promień otaczającego koła
|
|
|
|
|
|
((x, y), radius) = cv2.minEnclosingCircle(largest_contour)
|
|
|
|
|
|
center = (int(x), int(y))
|
|
|
|
|
|
radius = int(radius)
|
|
|
|
|
|
|
|
|
|
|
|
# Rysuj koło i środek na oryginalnym obrazie
|
|
|
|
|
|
cv2.circle(frame, center, radius, (255, 0, 0), 2)
|
|
|
|
|
|
cv2.circle(frame, center, 5, (0, 0, 255), -1)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if int(radius) >= 100:
|
|
|
|
|
|
motorL.stop()
|
|
|
|
|
|
motorR.stop()
|
|
|
|
|
|
|
|
|
|
|
|
# if 150 < int(x) < 170:
|
|
|
|
|
|
# motorL.stop()
|
|
|
|
|
|
# motorR.stop()
|
|
|
|
|
|
else:
|
|
|
|
|
|
tnow = datetime.now()
|
|
|
|
|
|
dtmicro = tprev - tnow
|
|
|
|
|
|
dtms = dtmicro.seconds * 1000 + dtmicro.microseconds // 1000
|
|
|
|
|
|
tprev = tnow
|
|
|
|
|
|
|
|
|
|
|
|
x = x - 160
|
|
|
|
|
|
|
|
|
|
|
|
wp = 0.8
|
2025-07-31 18:45:19 +02:00
|
|
|
|
wd = 0.3
|
2025-07-31 18:20:14 +02:00
|
|
|
|
wi = 0
|
|
|
|
|
|
mri = 0.5
|
|
|
|
|
|
|
2025-07-31 18:22:20 +02:00
|
|
|
|
v = 0.25
|
|
|
|
|
|
|
2025-07-31 18:35:05 +02:00
|
|
|
|
p = -x
|
2025-07-31 18:39:56 +02:00
|
|
|
|
d = (x - xp) * 1000 / dtms
|
2025-07-31 18:45:19 +02:00
|
|
|
|
i = mri * ip + x * dtms / 1000
|
2025-07-31 18:20:14 +02:00
|
|
|
|
|
|
|
|
|
|
k = wp * p + wi * i + wd * d
|
|
|
|
|
|
|
2025-07-31 18:37:06 +02:00
|
|
|
|
motorL.start(-v * min(100 + k, 100))
|
2025-07-31 18:35:05 +02:00
|
|
|
|
motorR.start(v * min(100 - k, 100))
|
2025-07-31 18:20:14 +02:00
|
|
|
|
xp = x
|
|
|
|
|
|
ip += i
|
|
|
|
|
|
|
2025-07-31 18:31:02 +02:00
|
|
|
|
# (Opcjonalnie) Wypisz pozycję
|
2025-07-31 18:32:27 +02:00
|
|
|
|
cv2.putText(frame, f"P:{int(p)} D:{int(d)} I:{int(i)}", (10, 30),
|
2025-07-31 18:31:02 +02:00
|
|
|
|
cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 2)
|
2025-07-31 18:50:21 +02:00
|
|
|
|
print("PDI: ", p, d, dtms, i)
|
2025-07-31 18:20:14 +02:00
|
|
|
|
# ============================
|
|
|
|
|
|
# 👆 KONIEC DETEKCJI PIŁKI 👆
|
|
|
|
|
|
# ============================
|
|
|
|
|
|
|
|
|
|
|
|
# Pokaż obraz z wykryciem
|
|
|
|
|
|
if frame is not None:
|
|
|
|
|
|
cv2.imshow("MJPEG Stream", frame)
|
|
|
|
|
|
|
|
|
|
|
|
# Wyjdź z pętli po wciśnięciu 'q'
|
|
|
|
|
|
if cv2.waitKey(1) & 0xFF == ord('q'):
|
|
|
|
|
|
break
|
|
|
|
|
|
# Posprzątaj okna po zakończeniu
|
|
|
|
|
|
cv2.destroyAllWindows()
|