Graffiti - Ambientes Interactivos

  • Este proyecto, titulado "Graffiti", fue realizado para la entrega final del curso Ambientes Interactivos de la Universidad de los Andes. El objetivo del mismo es permitir la "libertad" de rayar en un contexto en el que esto no está permitido, con la excepción de que en este caso no podría generar reclamos de la autoridad en dicho espacio, dado que el rayón es sólo una proyección. Este primer acercamiento permitió el conocimiento de las tecnologías por las cuales este objetivo sería posible, las cuales serán expuestas a continuación.
  • ¿Qué se utilizó?

    Para la realización del proyecto se requirió de los siguientes componentes: Un proyector de largo alcance, un Kinect (éste sólo debe tener habilitada la cámara infrarroja, por lo que se debe tapar el proyector láser y la cámara RGB), un emisor de luz infrarroja (Lata con LEDs) y, finalmente, un computador con Processing. 
  • Proyector de largo alcance
  • Kinect
  • Kinect cubierto, únicamente visible la cámara infrarroja
  • Lata emisora de luz infrarroja
  • ¿Cómo funcionaba?

    En la siguiente imagen se muestra cómo iban organizados los componentes expuestos anteriormente para el funcionamiento del mecanismo. Así, en principio se requería un buen posicionamiento del computador para que pudiera tener conexión, tanto con el Kinect, como con el proyector. El Kinect debía encontrarse a una altura donde la cámara infrarroja pudiera detectar la luz infrarroja -que en este caso es emitida por la lata- y que la persona tendría a la altura de la mano. Adicionalmente, el proyector debe estar ubicado en dirección contraria al Kinect para que pueda funcionar como si la persona pintara hacia donde apunta. La persona debía estar ubicada a cierta distancia (que variaba según la luz exterior) para que el Kinect alcanzara a leer la luz infrarroja sin saltarse.
  • Código

    Para llevar a cabo el código en processing que permitía que el computador utilizara la cámara infrarroja del Kinect, detectara la luz y dibujara a partir de eso, se utilizaron librerías disponibles en processing. De igual forma, para saber el mecanismo de lectura de la luz para emitir un trazo, se utilizaron las referencias de la página de processing,  
  • import org.openkinect.freenect.*;
    import org.openkinect.freenect2.*;
    import org.openkinect.processing.*;
    import org.openkinect.tests.*;
    int fondo = 0;
    int brillo= 0;
    int llave= 0;
    boolean off= false;
    color sprayColor; //Color del spray
    int sprayWidth; //Ancho del spray
    int sprayTravel; // Distancia recorrida por el mouse

    final int MAX_SPRAY_WIDTH = 20; 
    final int MIN_SPRAY_WIDTH  = 1; 
    int memX;
    int memY;
    Kinect kinect;
    int brightestX = 0; 
    int brightestY = 0; 
    boolean start = false;
    void setup() { 
      size(1920, 1080);
      background(fondo);
      //Inicializar el Kinect
      kinect = new Kinect(this);
      kinect.initVideo();
      kinect.enableIR(true);
      kinect.enableMirror(true);
      
      colorMode(HSB);
      smooth();
      reset();
    }
    void draw() {
      scale(3);
      
      if (llave ==1 && off == false) {
        off =true;
        
      } 
      //background(0);
      //image(kinect.getVideoImage(), 0, 0);
      float brightestValue = 0; 
      kinect.getVideoImage().loadPixels();
      int index = 0;
      for (int y = 0; y < kinect.getVideoImage().height; y++) {
        for (int x = 0; x < kinect.getVideoImage().width; x++) {
          int pixelValue = kinect.getVideoImage().pixels[index];
          float pixelBrightness = brightness(pixelValue);
          if (start==true && pixelBrightness>40) {
            if (frameCount % 4 == 0) {
              if (start== true) {
                sprayTravel = floor(dist(memX, memY, brightestX, brightestY));
                if (sprayTravel >= 1) {
                  int oldWidth = sprayWidth;        
                  sprayWidth = (oldWidth > sprayTravel ? sprayWidth-1 : sprayWidth+1);
                  sprayWidth = constrain(sprayWidth, MIN_SPRAY_WIDTH, MAX_SPRAY_WIDTH);
                }
              }
            }
          }
          
          if (pixelBrightness > brightestValue) {
            if (pixelBrightness>40) {
              //start = true;
              brightestValue = pixelBrightness;
              brightestY = y;
              brightestX = x;
              arrastre();
            }
          }
          index++;
        }
      }
      if (start == true) {
        strokeWeight(10);
        drip();
        spray();
      }
      memX= brightestX; 
      memY= brightestY;
    }

    void keyPressed() {
      // Guardar el graffiti
      if (key =='s') {
        save( "graffiti.png");
      } 
      // LLama la funcion reset() con la tecla "r"
      if (key == 'r') {
        reset();
        start =false;
      }
      // Cambiar colores aleatoriamente
      if (key == 't') {
        sprayColor = color(random(150, 255), random(150, 255), random(50, 255));
      }
    }
    // Limpia la pintura
    void reset() {
      background(0);
      sprayColor = color(random(150, 255), random(150, 255), random(50, 255));  
      sprayWidth = 5;
      strokeCap(ROUND);
      strokeJoin(ROUND);
      stroke(sprayColor);
    }
    // Pintar
    void arrastre() {
      llave =1;
      stroke(sprayColor);      
      strokeWeight(sprayWidth);
      line(memX, memY, brightestX, brightestY);
      if (random(1) < 0.1) 
      {
        drip();
      }
      if (random(1) > 0.5) {
        spray();
      }
    }

    void drip() {
      int dripLength = ceil(random(sprayWidth, 10 * sprayWidth));
      int dripWidth  = floor(random(sprayWidth/10, sprayWidth/2));
      strokeWeight(dripWidth);
      stroke(sprayColor, random(128, 255));
      line(memX, memY, memX, memY + dripLength);
    }

    void spray() {
      float spotX = memX + 6.0 * random(-sprayWidth, sprayWidth);
      float spotY = memY + 6.0 * random(-sprayWidth, sprayWidth);
      int spotWidth = floor(random(sprayWidth / 6, sprayWidth));
      fill(sprayColor, random(64, 204));
      strokeWeight(random(0));
      ellipse(spotX, spotY, spotWidth, spotWidth);
    }
  • Implementación

    La implementación del último prototipo se llevó a cabo en un lugar de la universidad donde hay bastante flujo de personas, de igual forma, fue en horas de la noche para que la proyección fuera visible en una pared grande que se encuentra entre dos edificios y pudiera ser fácilmente visto por las personas que pasaran y pudieran probarlo por sí mismos. 
  • Resultados

    En principio, se obtuvo el resultado esperado y se logró entender la forma de plantear un mecanismo que permitiera la sensación de estar rayando una pared a gran escala, también se pudo configurar el código para cambiar colores y reiniciar el sketch sin tener que detener la visualización.  Sin embargo, luego de múltiples pruebas, no se hizo posible amplificar de forma suficiente la intensidad de los LEDs infrarrojos en la lata para un mayor alcance de la luz en la cámara del Kinect, por lo que éste no se pudo alejar tanto del montaje como era esperado, por lo que finalmente fue cubierto como se mostró inicialmente para que no fuera obvia su forma de funcionar para el usuario.