# pip install opencv-contrib-python scipy # 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 matplotlib.pyplot as plt # Importamos matplotlib para graficar import numpy as np # Importamos NumPy para trabajar con arreglos numéricos from scipy.ndimage import center_of_mass # Importamos la función center_of_mass para calcular el centro de masa de un arreglo # ===== 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) # ==================================== # 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") # Configuramos matplotlib para modo interactivo plt.ion() # Creamos una figura y un eje para graficar fig, ax = plt.subplots() # Ajustamos el tamaño de la figura ax.figure.set_size_inches(5, 5) ax.set_title('Intensidad de la línea horizontal central') ax.set_xlim(0, 480) # Asumiendo un ancho de imagen de 480 píxeles ax.set_ylim(0, 255) # Valores de intensidad en escala de grises intensity_plot = ax.plot([], 'r-')[0] center_x_plot = ax.plot([], 'ko')[0] fig.canvas.draw() # 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 de BGR (Azul, Verde, Rojo) a escala de grises gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) # Obtenemos las dimensiones de la imagen en escala de grises height, width = gray.shape # Estraemos el vector de valores de intensidad de la línea horizontal central horizontal_line = gray[height // 2, :] horizontal_line_mean = horizontal_line.mean() umbral = horizontal_line_mean * 0.7 pesos = np.maximum(0, umbral - horizontal_line) centro_x = center_of_mass(pesos)[0] centro_x = centro_x if not np.isnan(centro_x) else width // 2 # Ploteamos la intensidad de la línea horizontal central en la imagen intensity_plot.set_xdata(range(len(horizontal_line))) intensity_plot.set_ydata(horizontal_line) center_x_plot.set_xdata([centro_x]) center_x_plot.set_ydata([horizontal_line[int(centro_x)]]) fig.canvas.flush_events() # Dibujamos una línea horizontal en el centro de la imagen cv2.line(frame, (0, height // 2), (width, height // 2), (0, 0, 255), 2) # Dibujamos un círculo en el centro de masa calculado cv2.circle(frame, (int(centro_x), height // 2), 8, (0, 0, 255), -1) cv2.circle(frame, (int(centro_x), height // 2), 5, (0, 0, 0), -1) # Mostramos el frame en una ventana llamada 'Camara' # cv2.resize() redimensiona la imagen a 480x640 píxeles cv2.imshow('Camara', 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()