Skip to content
Snippets Groups Projects
mapper2d.pde 5.72 KiB
Newer Older
  • Learn to ignore specific revisions
  • /*
     * Using a camera, approximate the 2-D location of the area illuminated by each LED.
     * Instead of illuminating each LED in sequence, this uses a binary pattern to quickly
     * identify large numbers of LEDs with few test patterns.
     *
     * This is still really experimental! Currently it's just a minimal proof-of-concept :)
     *
     * Micah Elizabeth Scott, 2013
     */
    
    import processing.video.*;
    
    //int MAX_PIXELS = 0xFFFF / 3;    // Maximum number of pixels
    int MAX_PIXELS = 16;
    int BRIGHTNESS = 130;           // How bright should our test pattern be?
    float MSE_THRESHOLD = 30;       // Mean squared error below which frames are "identical"
    
    int S_WAITING    = 0;
    int S_START_BIT  = 1;
    int S_SETTLE_BIT = 2;
    int S_FINALIZE = 3;
    
    int state;
    int bit;
    int numBitsInUse;
    OPCLowLevel opc;
    Capture video;
    int[][] bitImages;
    
    void setup()
    {
      size(640, 480);
      video = new Capture(this, width, height);
      video.start();
    
      bitImages = new int[16][width * height];
    
      opc = new OPCLowLevel("127.0.0.1", 7890, MAX_PIXELS);
      opc.firmwareConfig = 0x07;  // Disable dithering, interpolation, and status LED
    }
    
    void allPixelsOff()
    {
      for (int i = 0; i < MAX_PIXELS; i++) {
        opc.setPixel(i, 0, 0, 0);
      }
      opc.sendPixels();
    }  
    
    void sendBitPattern()
    {
      // Send an LED pattern in which red/blue correspond to 1/0 for the indicated bit position.
      for (int i = 0; i < MAX_PIXELS; i++) {
        boolean b = 0 != ((i >> bit) & 1);
        opc.setPixel(i, b ? BRIGHTNESS : 0, 0, b ? 0 : BRIGHTNESS);
      }
      opc.sendPixels();
    }
    
    void keyPressed()
    {
      if (key == ' ' && state == S_WAITING) {
        // Start!
        bit = 0;
        state = S_START_BIT;
      }
      if (keyCode == DELETE || keyCode == BACKSPACE) {
        // Start over
        state = S_WAITING;
      }
    }
    
    void sText(String s, int x, int y)
    {
      // Text with a shadow
      fill(0);
      text(s, x+2, y+2);
      fill(255);
      text(s, x, y);
    }
    
    float frameDifference(int[] a, int[] b)
    {
      // Mean squared distance between frames
      float total = 0;
      for (int i = 0; i < a.length; i++) {
        int aPixel = a[i];
        int bPixel = b[i];
        int diffR = ((aPixel >> 16) & 0xFF) - ((bPixel >> 16) & 0xFF);
        int diffG = ((aPixel >> 8) & 0xFF) - ((bPixel >> 8) & 0xFF);
        int diffB = (aPixel & 0xFF) - (bPixel & 0xFF);
        total += diffR*diffR + diffG*diffG + diffB*diffB;
      }
      return total / (width * height);
    }
    
    PVector bitSimilarityImage(int[] output, int pattern)
    {
      /*
       * Generate an image which maps how similar each pixel is, across all bits, to
       * the indicated bit pattern. One way to think of this: we're calculating a
       * probability that each pixel in question was in fact pointed at the pattern
       * we're looking for. Each bit gives us a single probability, and those probabilities
       * are multiplied together to give a pattern probability.
       *
       * Returns the weighted centroid of the image.
       */
    
      float xTotal = 0, xWeight = 0;
      float yTotal = 0, yWeight = 0;
    
      for (int i = 0; i < output.length; i++) {
        int x = i % width;
        int y = i / width;
        float probability = 1.0;
        
        for (int b = 0; b < numBitsInUse; b++) {
          int pixel = bitImages[b][i];
          boolean patternBit = 1 == ((pattern >> b) & 1);
          
          // We might want to do something smarter here, like histogram backprojection
          // based on actual colors sampled from the device. This is pretty stupid.
          
          int diffR = (patternBit ? 0xFF : 0x00) - ((pixel >> 16) & 0xFF);
          int diffB = (patternBit ? 0x00 : 0xFF) - (pixel & 0xFF);  
          probability /= 1 + (diffR*diffR + diffB*diffB) * 1e-4;
        }
    
        // Update weighted centroid
        float w = probability * probability;
        xTotal += x * w;
        xWeight += w;
        yTotal += y * w;
        yWeight += w;
    
        int gray = constrain(int(probability * 1e3), 0, 255);
        output[i] = gray | (gray << 8) | (gray << 16);
      }
      
      return new PVector(xTotal / xWeight, yTotal / yWeight);
    }
    
    void showFrameSequence()
    {
      // Show our captured frames in sequence
      int i = (millis() / 500) % numBitsInUse;
      loadPixels();
      arrayCopy(bitImages[i], pixels);
      updatePixels();
      textSize(25);
      sText("Bit " + i, 10, 30);
    }
    
    void showPatternSequence()
    {
      // Show pattern similarity measurements in sequence
      int i = (millis() / 500) % MAX_PIXELS;
      loadPixels();
      PVector peak = bitSimilarityImage(pixels, i);
      updatePixels();
      textSize(25);
      sText("Similarity to pattern " + i, 10, 30);
    
      // Draw crosshairs at the peak probability location
      stroke(128, 255, 128);
      int size = 100;
      line(peak.x, peak.y - size, peak.x, peak.y + size);
      line(peak.x - size, peak.y, peak.x + size, peak.y);
    }
    
    void draw()
    {
      if (state == S_FINALIZE) {
        if (mousePressed)
          showFrameSequence();
        else
          showPatternSequence();
    
        return;
      }
    
      if (!video.available())
        return;
      video.read();
      image(video, 0, 0, width, height);
      video.loadPixels();
    
      textSize(25);
      if (state == S_WAITING) {
        sText("Press [SPACE] to start mapping", 10, 30);
        allPixelsOff();
      } else {
        sText("Mapping bit " + bit + "...", 10, 30);
      }
      textSize(16);
    
      if (state == S_START_BIT) {
        // Update the LEDs, and discard this video frame.
        sendBitPattern();
        state = S_SETTLE_BIT;
      } else if (state == S_SETTLE_BIT) {
        // Keep capturing frames until the frames are similar enough.
        // This gives the camera's brightness control time to settle.
        float d = frameDifference(video.pixels, bitImages[bit]);
        sText("Settling... MSE: " + d, 10, 60);
        arrayCopy(video.pixels, bitImages[bit]);
    
        if (d < MSE_THRESHOLD) {
          // Settled enough! Ready to move on.
    
          if (((MAX_PIXELS - 1) >> (bit + 1) == 0)) {
            // No more pixels possible beyond this bit. We're done.
            numBitsInUse = bit + 1;
            state = S_FINALIZE;
    
          } else {
            // More bits
            bit++;
            state = S_START_BIT;
          }
        }     
      }
    }