# pip install opencv-contrib-python # Comando para instalar las librerías necesarias de visión por computadora import threading # Importamos threading para leer la cámara en segundo plano import time # Importamos time para medir cuánto tiempo pasa sin recibir imágenes import cv2 # Importamos la librería OpenCV, que nos permite trabajar con imágenes y video import numpy as np # Importamos la librería NumPy, que nos permite trabajar con arreglos y operaciones matem # ===== CONFIGURA AQUÍ TU CÁMARA ===== IP = '192.168.137.49' # dirección IP que muestra la aplicación del celular PUERTO = 8080 # 8080 en IP Webcam (Android), 8081 en IP Camera Lite (iPhone/iPad) IPHONE = False # True si usas IP Camera Lite (iPhone/iPad), False si usas IP Webcam (Android) ARUCO_ID = 47 # ID del ArUco que vamos a medir MARKER_SIZE = 0.0254 # Tamaño del marcador en metros (2.54 cm = 1 pulgada) # ==================================== # Cada aplicación usa un formato de dirección distinto. Usamos el video por HTTP # porque tiene mucho menos retardo que RTSP (medido: ~150 ms contra 0.4–1.2 s). if IPHONE: url = f'http://admin:admin@{IP}:{PUERTO}/' else: url = f'http://{IP}:{PUERTO}/video' class CamaraUltimoFrame: """Lee la cámara en segundo plano y entrega siempre la imagen más reciente. Un hilo lee las imágenes apenas llegan y solo guarda la última. Así el programa principal nunca espera ni procesa imágenes viejas acumuladas. """ def __init__(self, url): self.cap = cv2.VideoCapture(url) self.frame = None # la imagen más reciente self.t_ultimo = time.time() # cuándo llegó la última imagen self.activa = self.cap.isOpened() self.hilo = threading.Thread(target=self._leer, daemon=True) self.hilo.start() def _leer(self): # Este ciclo corre en paralelo con el programa principal while self.activa: ret, frame = self.cap.read() if ret: self.frame = frame self.t_ultimo = time.time() def leer(self): # Devuelve una copia de la última imagen recibida (None si aún no ha llegado ninguna). # Es una copia para que dibujar sobre ella no altere la imagen guardada. return None if self.frame is None else self.frame.copy() def cerrar(self): self.activa = False self.hilo.join(timeout=2) # esperamos a que el hilo termine de leer self.cap.release() # antes de soltar la cámara print("Conectando a", url) camara = CamaraUltimoFrame(url) # Creamos la cámara: abre la conexión y empieza a leer en segundo plano if not camara.activa: raise SystemExit("No se pudo abrir la cámara: revise la IP, el puerto y que el servidor de la aplicación esté encendido") # Obtenemos el diccionario de marcadores ArUco predefinido (4x4 con 50 códigos) aruco_dict = cv2.aruco.getPredefinedDictionary(cv2.aruco.DICT_4X4_50) # Establecemos los parámetros para la detección de marcadores ArUco parameters = cv2.aruco.DetectorParameters() # Creamos el detector de marcadores ArUco con el diccionario y parámetros detector = cv2.aruco.ArucoDetector(aruco_dict, parameters) # Iniciamos un bucle que procesa siempre la imagen más reciente while True: # Si pasan 5 segundos sin recibir imágenes, la transmisión se cortó if time.time() - camara.t_ultimo > 5: print("Stream ended...") break # Tomamos la imagen más reciente que recibió el hilo de la cámara frame = camara.leer() if frame is None: # Todavía no ha llegado ninguna imagen: esperamos un momento cv2.waitKey(1) continue # Rotamos el frame 90 grados en sentido horario frame = cv2.rotate(frame, cv2.ROTATE_90_CLOCKWISE) # Convertimos el frame a escala de grises para mejorar la detección gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) # Detectamos los marcadores ArUco en la imagen en escala de grises corners, ids, rejected = detector.detectMarkers(gray) # Si se detectaron marcadores, dibujamos los marcadores en el frame if ids is not None: cv2.aruco.drawDetectedMarkers(frame, corners, ids) for id in ids: if id in [ARUCO_ID]: index = list(ids).index(id) corner = corners[index][0] # Calculamos el centro del marcador center_x = int((corner[0][0] + corner[2][0]) / 2) center_y = int((corner[0][1] + corner[2][1]) / 2) # Calculamos la distancia desde el centro del frame frame_center_x = frame.shape[1] // 2 frame_center_y = frame.shape[0] // 2 distance_x = center_x - frame_center_x distance_y = center_y - frame_center_y # Calcular la relación de píxeles por metro (asumiendo que el marcador es cuadrado) pixel_per_meter = (corner[1][0] - corner[0][0]) / MARKER_SIZE # Mostramos la distancia en el frame cv2.putText(frame, f"Dist X: {distance_x}px \t {distance_x/pixel_per_meter:.2f}m", (10, 30), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 0), 2) cv2.putText(frame, f"Dist Y: {distance_y}px \t {distance_y/pixel_per_meter:.2f}m", (10, 60), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 0), 2) # Calcular la rotación del marcador vector_1 = corner[1] - corner[0] angle = cv2.fastAtan2(vector_1[1], vector_1[0]) cv2.putText(frame, f"Angle: {angle:.2f} deg", (10, 90), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 0), 2) # Calcular la distancia euclidiana desde el centro del frame al centro del marcador euclidean_distance = np.sqrt(distance_x**2 + distance_y**2) cv2.putText(frame, f"Euclidean Dist: {euclidean_distance:.2f}px \t {euclidean_distance/pixel_per_meter:.2f}m", (10, 120), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 0), 2) # Dibujamos un círculo en el centro del marcador cv2.circle(frame, (center_x, center_y), 5, (255, 0, 0), -1) # Dibujamos una línea desde el centro del frame hasta el centro del marcador cv2.line(frame, (frame_center_x, frame_center_y), (center_x, center_y), (0, 255, 0), 2) # Dibujamos una línea desde el centro del frame mostrando el angulo length = 100 # Longitud de la línea end_x = int(frame_center_x + length * np.sin(np.deg2rad(angle))) end_y = int(frame_center_y - length * np.cos(np.deg2rad(angle))) cv2.line(frame, (frame_center_x, frame_center_y), (end_x, end_y), (255, 0, 0), 2) # Mostramos el frame en una ventana llamada 'Detected Ids' # cv2.resize() redimensiona la imagen a 480x640 píxeles cv2.imshow('Detected Ids', cv2.resize(frame, (480, 640))) # Capturamos la entrada del teclado (espera 1ms por cada iteración) key = cv2.waitKey(1) # Si presionamos 'q', salimos del bucle if key == ord('q'): break # Cerramos la cámara y el hilo que la lee camara.cerrar() # Cerramos todas las ventanas creadas por OpenCV cv2.destroyAllWindows()