jonathan damian peña
Miguel Palacios Banda
jonathan damian peña
Francisco López Velázquez
Mauricio Peñuela Aristizábal
Francisco López Velázquez
Iván Alexis Nocua Benitez
Carlos Bustillo
ProfesorIván Alexis Nocua Benitez
aplicaría esto en un gimnasio por ej para poder checar las maquinas mas utilizadas y adquirir mas de esas y descartar las menos utilizadas
Aporte: Detección de Personas Corriendo y Conteo con YOLO + SORT
Quiero compartir una solución funcional para detectar y contar personas corriendo en video, utilizando el modelo YOLO de Ultralytics junto al algoritmo de seguimiento SORT. El sistema detecta personas que cruzan líneas virtuales (por ejemplo, entradas a diferentes secciones) y lleva un conteo preciso de cada cruce, evitando duplicadas gracias al tracking por ID.
Requisitos
Descargar SORT: Descarga el archivo sort.py desde el repositorio oficial de GitHub: https://raw.githubusercontent.com/abewley/sort/master/sort.py
Luego, crea una carpeta llamada sort en tu proyecto y guarda allí el archivo sort.py
Guarda sort.py dentro de esta carpeta
Instalar dependencias necesarias:Ejecuta los siguientes comandos para instalar las bibliotecas requeridas:```js %pip install ultralytics %pip install git+https://github.com/abewley/sort.git %pip install filterpy %pip install scikit-image
Características de la solución
Detección precisa de personas (class_id = 0) usando el modelo YOLOv8.
Seguimiento de cada persona con SORT y asignación de un ID único.
Conteo de cruces mediante la comparación de lados en relación con líneas virtuales usando distancia firmada.
Evita duplicados gracias a un sistema de memoria por ID (track_memory y already_counted).
Código con la solución:```js from ultralytics import YOLO import cv2 import numpy as np from sort.sort import Sort # Algoritmo de tracking (seguimiento de objetos)
Función que calcula la distancia firmada entre un punto y una línea
Esto nos ayuda a saber si un objeto ha cruzado la línea (cambia de lado)
def signed_distance(point, line): x, y = point (x1, y1), (x2, y2) = line num = (y2 - y1) * x - (x2 - x1) * y + x2 * y1 - y2 * x1 den = np.sqrt((y2 - y1) ** 2 + (x2 - x1) ** 2) return num / den if den != 0 else 0
Definimos las dos líneas de conteo (puedes ajustar según el video)
line1 = ((130, 120), (25, 300)) # Línea para la sección de fútbol line2 = ((650, 175), (720, 275)) # Línea para la sección de tenis
Contadores para cada línea
count_line1 = 0 count_line2 = 0
Conjunto para registrar IDs ya contados y evitar duplicados
already_counted = set()
Cargar modelo YOLO para detección de objetos
model = YOLO("yolo11n.pt")
Inicializar el tracker (seguimiento de objetos)
tracker = Sort()
Abrir el video de entrada
cap = cv2.VideoCapture(video_path) if not cap.isOpened(): raise ValueError("No se pudo abrir el video.")
Obtener parámetros del video
fps = cap.get(cv2.CAP_PROP_FPS) width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH)) height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT)) fourcc = cv2.VideoWriter_fourcc(*'XVID') writer = cv2.VideoWriter(output_path, fourcc, fps, (width, height))
Diccionario para guardar el historial de cada objeto trackeado
track_memory = {}
Bucle principal de procesamiento por frame
while True: ret, frame = cap.read() if not ret: break # Salir si ya no hay más frames
# Detectar objetos en el frame con YOLO results = model(frame, conf=0.5) boxes_obj = results[0].boxes detections = [] if boxes_obj is not None and len(boxes_obj) > 0: # Extraer coordenadas, clases y confianza bboxes = boxes_obj.xyxy.cpu().numpy() classes = boxes_obj.cls.cpu().numpy() confs = boxes_obj.conf.cpu().numpy() # Filtrar solo personas (clase 0 en COCO) for i in range(len(bboxes)): if int(classes[i]) == 0: x1, y1, x2, y2 = map(int, bboxes[i]) conf = confs[i] detections.append([x1, y1, x2, y2, conf]) # Aplicar el tracker a las detecciones detections = np.array(detections).reshape(-1, 5) tracks = tracker.update(detections) for track in tracks: x1, y1, x2, y2, track_id = map(int, track) centroid = ((x1 + x2) // 2, (y1 + y2) // 2) # Centro del objeto # Dibujar caja, centro y texto con ID cv2.rectangle(frame, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.circle(frame, centroid, 4, (0, 255, 0), -1) cv2.putText(frame, f"ID: {track_id}", (x1, y1 - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 255, 0), 1) # Registrar o actualizar la trayectoria del objeto if track_id not in track_memory: track_memory[track_id] = { "initial": centroid, "last": centroid } else: prev_centroid = track_memory[track_id]["last"] init_centroid = track_memory[track_id]["initial"] # Verificar si ha cruzado la línea 1 prev_side1 = signed_distance(prev_centroid, line1) curr_side1 = signed_distance(centroid, line1) init_side1 = signed_distance(init_centroid, line1) if prev_side1 * curr_side1 < 0 or init_side1 * curr_side1 < 0: if f"l1_{track_id}" not in already_counted: count_line1 += 1 already_counted.add(f"l1_{track_id}") # Verificar si ha cruzado la línea 2 prev_side2 = signed_distance(prev_centroid, line2) curr_side2 = signed_distance(centroid, line2) init_side2 = signed_distance(init_centroid, line2) if prev_side2 * curr_side2 < 0 or init_side2 * curr_side2 < 0: if f"l2_{track_id}" not in already_counted: count_line2 += 1 already_counted.add(f"l2_{track_id}") # Actualizar último centroide track_memory[track_id]["last"] = centroid # Dibujar líneas de conteo y mostrar contadores cv2.line(frame, line1[0], line1[1], (255, 0, 0), 2) cv2.line(frame, line2[0], line2[1], (0, 0, 255), 2) cv2.putText(frame, f"Seccion Futbol: {count_line1}", (10, 30), cv2.FONT_HERSHEY_SIMPLEX, 1, (255, 0, 0), 2) cv2.putText(frame, f"Seccion Tenis: {count_line2}", (10, 70), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 255), 2) # Guardar el frame procesado en el video de salida writer.write(frame)
Liberar recursos
cap.release() writer.release() print(f"Video procesado con tracking y guardado en: {output_path}")
me encanto la técnica de encontrar las coordenadas con photopea, muy ingenioso jejejej
para encontrar las coodenadas, aqui el script del profe pero dinamico
import cv2import tkinter as tkfrom tkinter import filedialog
# Configurar tkinter (no mostraremos la ventana principal)root = tk.Tk()root.withdraw()
# Abrir el explorador de archivos para seleccionar una imagenprint("Por favor, selecciona una imagen en el explorador de archivos")imagen_path = filedialog.askopenfilename( title="Selecciona una imagen", filetypes=[("Imágenes", "*.jpg *.jpeg *.png *.bmp *.tif *.tiff"), ("Todos los archivos", "*.*")])
if not imagen_path: print("No se seleccionó ninguna imagen. Saliendo...") exit()
# Cargar imagenimagen = cv2.imread(imagen_path)
if imagen is None: print(f"No se pudo cargar la imagen desde {imagen_path}. Verifica la ruta y el formato.") exit()
# Función que se llama al hacer clicdef mostrar_coordenadas(event, x, y, flags, param): if event == cv2.EVENT_LBUTTONDOWN: print(f"Coordenadas: x={x}, y={y}") # Dibujar un círculo en la posición del clic cv2.circle(imagen, (x, y), 5, (0, 255, 0), -1) cv2.putText(imagen, f"({x},{y})", (x+10, y-10), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0,255,0), 1) cv2.imshow("Imagen interactiva", imagen)
# Mostrar imagen e iniciar escucha de eventoscv2.imshow("Imagen interactiva", imagen)cv2.setMouseCallback("Imagen interactiva", mostrar_coordenadas)
# Esperar hasta que se presione una teclacv2.waitKey(0)cv2.destroyAllWindows()
Para solucionar el reto y contar la última persona que esta corriendo debemos bajar el umbral del confidence. El problema es que el modelo Yolo, no esta asignando el bounding box de la categoría persona al frame exacto cuando el cliente que esta corriendo pasa por la línea. No es capaz de asociar esa imagen a una persona. Entonces, como no hay bounding box, tampoco hay centroide, por lo que no se hace el conteo. Bajando el parámetro de confidence de 0.7 a 0.2, Yolo ya puede asignar el bounding box al frame, por lo que ya aparece el centróide y se realiza el conteo :)
Solo hay que modificar esta línea:
results = model(frame, conf=0.2)
si lo tendría que hacer esto en tiempo real, como seria?
¿De que otra manera, podriamos encontrar las coordenadas? , no se me es claro, puesto que en el programa que usamos las dimesiones sobrepasan los 1000 px de ancho, y esas no estan dentro de las coordenadas especificadas.
Un truco bastante sencillo para este tipo de tareas y ya que estas utilizando Python es hacer un pequeño script.
Te comparto uno que suelo utilizar para marcar en la imagen los puntos de interés con el mouse y este me muestra a qué coordenada corresponde:
import cv2 # Ruta a tu imagen imagen_path = "tu_imagen.jpg" # <-- cambia esto por tu imagen # Cargar imagen imagen = cv2.imread(imagen_path) # Función que se llama al hacer clic def mostrar_coordenadas(event, x, y, flags, param): if event == cv2.EVENT_LBUTTONDOWN: print(f"Coordenadas: x={x}, y={y}") # Dibujar un círculo en la posición del clic cv2.circle(imagen, (x, y), 5, (0, 255, 0), -1) cv2.putText(imagen, f"({x},{y})", (x+10, y-10), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0,255,0), 1) cv2.imshow("Imagen interactiva", imagen) # Mostrar imagen e iniciar escucha de eventos cv2.imshow("Imagen interactiva", imagen) cv2.setMouseCallback("Imagen interactiva", mostrar_coordenadas) # Esperar hasta que se presione una tecla cv2.waitKey(0) cv2.destroyAllWindows()
Genial ! Carlos, me gusto mucho esa implementacion.
Gracias.
Solo quisiera saber algo mas, al parecer una de mis imagenes es muy grande y solo puede mostrar un pedazo de la imagen, no la muestra completa, ¿como podria modificar este hecho?