Skip to content

Entrega final

s/c edited this page Jun 3, 2023 · 25 revisions

En loor de los cosmonautas...

Iteración final: Resultados escenografía interactiva para la obra "Los físicos", de Dürrenmatt.

Realizado por Mateo, Lore, Cris y Sofía.

https://github.com/sofiacastaneda/fundamentos-3/assets/47247552/4788ca9f-c2d8-4dff-b969-9331149f2974

Mesa de trabajo 3@2x-100 Mesa de trabajo 3_1@2x-100

Esta propuesta escenográfica conecta el sentimiento y la corporalidad de los personajes con el paisaje de la escena.

Overview: Buscamos que los movimientos corporales de los actores impacten la configuración del escenario. Con velos y aire componemos figuras abstractas en escena, que, de acuerdo a los inputs recibidos, se contorsionan y ajustan modificando su forma.

Overview técnico: Por medio de análisis de postura, se controla un sistema de poleas que hace reactivo el velo a los movimientos del actor. Un set de motores controla el velo, al enrollar y desenrollar carretes de hilos atados a sus esquinas.


Diagrama del sistema

Disposición
IMG_6415
Copia de IMG_6415

El setup final consiste en lo siguiente:

  • Tres proyectores y tres computadores para acompañar la proyección
  • Un ventilador
  • Sistema de velos
  • Soporte que integra ventilador & setup
  • Dos rieles de soporte
  • Acople arduino (véase la sección Componentes para más detalles)
  • Una webcam y un computador asociado para la lectura corporal
  • Parlante

¿Cómo se integran los diferentes elementos desarrollados y qué hace cada uno en el sistema?
Hay tres grandes partes dentro del montaje: (A) la sección asociada al análisis corporal, (B) la sección dedicada a controlar el movimiento de la tela y (3) los periféricos de video y audio. El flujo de la información es unidireccional, la secuencia ocurre de la siguiente manera: Ilustración_sin_título 3
Actor se mueve --> Webcam captura imagen --> Mediapipe analiza posición --> Arduino recibe str de secuencia corporal --> Motores responden --> El hilo se tensiona --> El velo se mueve.
Independientemente de estas acciones, hay proyecciones y flujo de aire en todo momento.

Componentes

Disposición
!IMG_6426
Captura de pantalla 2023-06-03 a la(s) 1 17 20 p m

Listado:

  • Arduino uno
  • 4 motores DC 12-24v
  • 2 puentes H
  • 2 cargadores de 24v, 2 amperios

Creación de piezas

Modificación eje motores
Los motores debieron ajustarse para poder adaptarse a la instalación. Creamos piezas en corte láser para soportar los carretes de hilo, e incorporamos un soporte rígido al motor usual para poder alargar su eje. Esto implicó modificar el tamaño total de cada motor, por lo que creamos un soporte (también en corte láser) para asegurarnos que cada motor estuviera lo suficientemente elevado y estable como para girar. Mesa de trabajo 3

Listado de componentes de cada motor:

  • Motor
  • Soporte rígido
  • Dos discos en madera
  • Soporte inferior y lateral en madera
  • Tiras de agarre a lámina inferior.

Soporte ventilador / setup
Creamos esta caja que rodea el ventilador para evitar que el vacío generado por el flujo de aire succionara la tela y se enredara con los componentes. Se añadieron piezas a lo largo del prototipado porque nos dimos cuenta que debíamos modificar el flujo de aire constantemente. El soporte fue realizado siguiendo las medidas del ventilador, con balso y cartón.
IMG_6419

Asegurar las piezas
Dado que el sistema estaba literalmente amarrado al velo (por ende, en riesgo de constante movimiento...) creamos una base rígida a la cual acoplamos pesas para mantenerla anclada al piso. Con ello evitamos que la fuerza del ventilador desarmara el setup en cada prueba.

Mesa de trabajo 2

Código

Esta sección describe cómo se logró la integración python - arduino para la lectura del movimiento corporal y la activación de los mecanismos en escena. IMG_6534

Análisis de imagen
El sistema de detección y seguimiento de postura corporal se creó utilizando la biblioteca de Python Mediapipe. El código utiliza la webcam para capturar imágenes de video en tiempo real y procesa cada cuadro para detectar y rastrear las diferentes partes del cuerpo humano, tomando como punto de referencia muñecas, tobillos, hombros y pelvis.

Después de detectar las landmarks (puntos de referencia) de las diferentes partes del cuerpo, el código realiza varias operaciones basadas en dos umbrales que elegimos: si las manos están por encima de los hombros, y si los pies están en una posición transversal a la cadera. Cada una de esas posturas se establece como el mecanismo principal de activación del sistema. Landmarks elegidos:

Extremidades superiores Extremidades inferiores
IMG_6531 IMG_6532

ezgif com-optimize

Comunicación de datos: Highs y Lows del código
En base a los comandos dados por las posiciones relativas de las diferentes partes del cuerpo se comienza a armar el mensaje que transmitimos con serial. Analizamos cuatro extremidades, por lo cual el mensaje se compone de 4 caracteres, que pueden ser H o L.

Mensaje: H/L + H/L + H/L + H/L

Si la posición de la mano izquierda está por encima de la posición del hombro izquierdo en un umbral determinado, se concatena al string H. Si está por debajo del umbral, se concatena L. Esto se repite para la mano derecha. En cuanto a los pies, H está dado si sobrepasa el límite que delimitan los puntos de la cadera.

Este es el código que recibe python para el análisis de imagen:

import cv2
import mediapipe as mp
import serial
import time

mp_drawing = mp.solutions.drawing_utils
mp_pose = mp.solutions.pose

cap = cv2.VideoCapture(0)

arduino = serial.Serial('COM13', 9600)
time.sleep(4)

with mp_pose.Pose(min_detection_confidence=0.5, min_tracking_confidence=0.5) as pose:
    while cap.isOpened():
        ret, frame = cap.read()

        # Recolor image to RGB
        image = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
        image.flags.writeable = False

        # Make detection
        results = pose.process(image)

        # Recolor back to BGR
        image.flags.writeable = True
        image = cv2.cvtColor(image, cv2.COLOR_RGB2BGR)

        # Extract landmarks
        try:
            landmarks = results.pose_landmarks.landmark
            # print(landmarks)
            # Get coordinates: Muñecas
            wrist_a = [landmarks[mp_pose.PoseLandmark.LEFT_WRIST.value].x,
                       landmarks[mp_pose.PoseLandmark.LEFT_WRIST.value].y]
            wrist_b = [landmarks[mp_pose.PoseLandmark.RIGHT_WRIST.value].x,
                       landmarks[mp_pose.PoseLandmark.RIGHT_WRIST.value].y]
            # Get coordinates: Tobillos
            ankle_a = [landmarks[mp_pose.PoseLandmark.LEFT_ANKLE.value].x,
                       landmarks[mp_pose.PoseLandmark.LEFT_ANKLE.value].y]
            ankle_b = [landmarks[mp_pose.PoseLandmark.RIGHT_ANKLE.value].x,
                       landmarks[mp_pose.PoseLandmark.RIGHT_ANKLE.value].y]
            # Get coordinates: Hombros
            shoulder_a = [landmarks[mp_pose.PoseLandmark.LEFT_SHOULDER.value].x,
                          landmarks[mp_pose.PoseLandmark.LEFT_SHOULDER.value].y]
            shoulder_b = [landmarks[mp_pose.PoseLandmark.RIGHT_SHOULDER.value].x,
                          landmarks[mp_pose.PoseLandmark.RIGHT_SHOULDER.value].y]
            # Get coordinates: Pelvis
            hip_a = [landmarks[mp_pose.PoseLandmark.LEFT_HIP.value].x,
                     landmarks[mp_pose.PoseLandmark.LEFT_HIP.value].y]
            hip_b = [landmarks[mp_pose.PoseLandmark.RIGHT_HIP.value].x,
                     landmarks[mp_pose.PoseLandmark.RIGHT_HIP.value].y]

            Umbral_Hands = 0
            Umbral_Shoes = 0

            # Detect commands: Mano izquierda
            if wrist_a[1] - shoulder_a[1] < Umbral_Hands:
                # print("Mano izquierda: High")
                Mensaje = "H"
            elif wrist_a[1] - shoulder_a[1] > Umbral_Hands:
                # print("Mano izquierda: Low")
                Mensaje = "L"

            # Detect commands: Mano derecha
            if wrist_b[1] - shoulder_b[1] < Umbral_Hands:
                # print("Mano derecha: High")
                Mensaje = Mensaje + "H"
            elif wrist_b[1] - shoulder_b[1] > Umbral_Hands:
                # print("Mano derecha: Low")
                Mensaje = Mensaje + "L"

            # Detect commands: Pie izquierdo
            if ankle_a[0] - hip_a[0] < Umbral_Shoes:
                # print("Mano izquierda: High")
                Mensaje = Mensaje + "H"
            elif ankle_a[0] - hip_a[0] > Umbral_Shoes:
                # print("Mano izquierda: Low")
                Mensaje = Mensaje + "L"

            # Detect commands: Pie derecho
            if ankle_b[0] - hip_b[0] > Umbral_Shoes:
                # print("Mano derecha: High")
                Mensaje = Mensaje + "H"
            elif ankle_b[0] - hip_b[0] < Umbral_Shoes:
                # print("Mano derecha: Low")
                Mensaje = Mensaje + "L"

            arduino.write(bytes(Mensaje, 'utf-8'))
            print(Mensaje)
            # arduino.write(b'\n')

        except:
            pass

        # Render detections
        mp_drawing.draw_landmarks(image, results.pose_landmarks, mp_pose.POSE_CONNECTIONS,
                                  mp_drawing.DrawingSpec(color=(245, 117, 66), thickness=2, circle_radius=2),
                                  mp_drawing.DrawingSpec(color=(245, 66, 230), thickness=2, circle_radius=2))

        cv2.imshow('Mediapipe Feed', image)

        if cv2.waitKey(10) & 0xFF == ord('q'):
            break

    cap.release()
    cv2.destroyAllWindows()

Una vez que se determinan los comandos, se envía el mensaje a través serial a Arduino, manejamos 9600 baudios en este caso.

En el bucle principal loop(), verificamos si hay datos disponibles en la comunicación serial. Si se reciben al menos 4 bytes, se leen los datos en el arreglo receivedValue. Python parsea los datos como str, y arduino los reinterpreta como char. char es un tipo de dato interesante, pues es utilizado para guardar letras, pero los valores que utiliza no son alfanuméricos. char almacena un valor entre -128 y +127, correspondiente al valor en la tabla ASCII de la letra recibida.

En el caso de Hy L, la lectura es la siguiente:

char h_upper = 'H';
// o, lo mismo que:
char h_upper = 72;

char l_upper = 'L';
// o, lo mismo que:
char l_upper = 76;

Este es un ejemplo de cómo estamos recibiendo los datos desde python.

char charArray[4] = { 'H', 'H', 'L', 'H' }; // or directly: = "HHLH";

A continuación, se procesan los valores recibidos para controlar cada motor individualmente. Para cada motor, se verifica el valor correspondiente en el arreglo receivedValue. Si es H, se establece la dirección del motor en un sentido, y si es L, se establece en el sentido opuesto. Los pines de control se establecen en consecuencia para cada motor.

#include <LEANTEC_ControlMotor.h>   //Incluimos la librería control de motores  

// Motor 1 + 2:
ControlMotor control(2,3,7,4,5,6);  
int MotorDer1 = 4;  
int MotorDer2 = 5;  
int MotorIzq1 = 6;  
int MotorIzq2 = 7;  

int PWM_1 = 2;  
int PWM_2 = 3;  

// Motor 3 + 4:
int MotorDer3 = 8;  
int MotorDer4 = 9;  
int MotorIzq3 = 10;  
int MotorIzq4 = 11;  

int PWM_3 = 12;  
int PWM_4 = 13; 

// Velocidad por defecto:

int velocidad = 150; 

char receivedValue[5]; 


void setup()  {    //Configuramos los pines como salida
  Serial.begin(9600);

   pinMode(MotorDer1, OUTPUT);    pinMode(MotorDer2, OUTPUT);
   pinMode(MotorIzq1, OUTPUT);    pinMode(MotorIzq2, OUTPUT);
   pinMode(PWM_2, OUTPUT);   pinMode(PWM_2, OUTPUT);

   pinMode(MotorDer3, OUTPUT);    pinMode(MotorDer4, OUTPUT);
   pinMode(MotorIzq3, OUTPUT);    pinMode(MotorIzq4, OUTPUT);
   pinMode(PWM_3, OUTPUT);   pinMode(PWM_4, OUTPUT); 
}  

void Stop()
{ 
  digitalWrite(MotorDer1,HIGH);   
  digitalWrite(MotorDer2,LOW);
  digitalWrite(MotorIzq1,HIGH);   
  digitalWrite(MotorIzq2,LOW);

  digitalWrite(MotorDer3,HIGH);   
  digitalWrite(MotorDer4,LOW);
  digitalWrite(MotorIzq3,HIGH);   
  digitalWrite(MotorIzq4,LOW);

  analogWrite(PWM_1,velocidad);
  analogWrite(PWM_2,velocidad); 
  analogWrite(PWM_3,velocidad);
  analogWrite(PWM_4,velocidad);
} 


void loop()  {   
  
  if (Serial.available() >= 4) {

    Serial.readBytes(receivedValue, 4);

    //Motor 1: 
    if (receivedValue[0] == 'H') {
      digitalWrite(MotorDer1,HIGH);   
      digitalWrite(MotorDer2,LOW); 
    }
    else if (receivedValue[0] == 'L') {
      digitalWrite(MotorDer1,LOW);   
      //digitalWrite(MotorDer2,HIGH); 
      digitalWrite(MotorDer2,LOW);
    } 

    //Motor 2: 
    if (receivedValue[1] == 'H') {
      digitalWrite(MotorIzq1,HIGH);   
      digitalWrite(MotorIzq2,LOW);
    }
    else if (receivedValue[1] == 'L') { 
      digitalWrite(MotorIzq1,LOW); 
      //digitalWrite(MotorIzq2,HIGH);   
      digitalWrite(MotorIzq2,LOW);
    } 

    //Motor 3: 
    if (receivedValue[2] == 'H') {
      digitalWrite(MotorDer3,HIGH);   
      digitalWrite(MotorDer4,LOW); 
    }
    else if (receivedValue[2] == 'L') {
      digitalWrite(MotorDer3,LOW);   
      //digitalWrite(MotorDer4,HIGH); 
      digitalWrite(MotorDer4,LOW);
    } 
    
    //Motor 4: 
    if (receivedValue[3] == 'H') {
      digitalWrite(MotorIzq3,HIGH);   
      digitalWrite(MotorIzq4,LOW);
    }
    else if (receivedValue[3] == 'L') { 
      digitalWrite(MotorIzq3,LOW); 
      //digitalWrite(MotorIzq4,HIGH);   
      digitalWrite(MotorIzq4,LOW);
    } 

    // Velocidad:
    analogWrite(PWM_1,velocidad);
    analogWrite(PWM_2,velocidad); 
    analogWrite(PWM_3,velocidad);
    analogWrite(PWM_4,velocidad);
  }
} 

Imágenes proyectadas

Link de youtube: https://www.youtube.com/watch?v=mm_rcNbbunM&list=PL8PGNtwJ8ED89OkEA6k3SqREj5iuPMTzy&index=2

Con el video buscábamos representar la mente del personaje principal de la obra, para lograr esto, hicimos un video-collage con imágenes de archivo, con la edición del video buscamos construir una narrativa abstracta de la mente de Moebius. Bajo esta premisa seleccionamos imágenes microscópicas y macroscópicas las cuales recorrimos con el ritmo que determinaba la música del video, la cual es autoría de Cosmin Cioca.