Moving Poster: Big Brother

Dec 12, 2017

During our Graphic Design Basics course in my first semester at ZHdK I designed this moving poster. It was illustrated in Adobe Illustrator, layouted and typeset in Adobe InDesign and animated using Processing.

Progress Presentation on slides.com

Illustrations in Adobe Illustrator

The illustrations of the eyes were done in Adobe Illustration. I'm used to this software from my apprenticeship as a Polygraf EFZ.

Layout and type setting in Adobe InDesign

The layout and typography were done in Adobe InDesign. I'm used to this software from my apprenticeship as a Polygraf EFZ as well and am quite proficient in using it.

Animations in Processing

The code for animating the moving poster was written in Processing. I used OpenCV for the computer vision feature. The poster was displayed on a large vertical monitor featuring a Cinnect camera. When people moved in front of the poster, the eyes followed the person standing closest and the text changed. I used facial image recognition for finding peoples faces. This was the first time I used OpenCV. It was a lot of fun.

Repository on Github

moving-poster.pde

int posterBackground = 255;
int textId = 1;
float actorX;              
float actorY;              
float eyeW;                
float eyeH;                
float margin;              
float gutter;              

Eye eye1;                  
Eye eye2;                  
Eye eye3;                  
Eye eye4;                  
Eye eye5;                  
Eye eye6;                  
Eye eye7;                  
Eye eye8;                  
Eye eye9;                  

Text text;                 

void setup() {             
  fullScreen(2);          
  setupScreen();           
  shapeMode(CENTER);       
  setupOpenCV();           
  eyeW = width * 0.25;     
  eyeH = width * 0.25;     
  margin = width * 0.05;   
  gutter = width * 0.075;  

  eye1 = new Eye(eyeW * 0.5 + margin, eyeH * 0.5 + margin, eyeW, eyeH);
  eye2 = new Eye(eyeW * 1.5 + margin + gutter, eyeH * 0.5 + margin, eyeW, eyeH);
  eye3 = new Eye(eyeW * 2.5 + margin + gutter * 2, eyeH * 0.5 + margin, eyeW, eyeH);
  eye4 = new Eye(eyeW * 0.5 + margin, eyeH * 1.5 + margin + gutter, eyeW, eyeH);
  eye5 = new Eye(eyeW * 1.5 + margin + gutter, eyeH * 1.5 + margin + gutter, eyeW, eyeH);
  eye6 = new Eye(eyeW * 2.5 + margin + gutter * 2, eyeH * 1.5 + margin + gutter, eyeW, eyeH);
  eye7 = new Eye(eyeW * 0.5 + margin, eyeH * 2.5 + margin + gutter * 2, eyeW, eyeH);
  eye8 = new Eye(eyeW * 1.5 + margin + gutter, eyeH * 2.5 + margin + gutter * 2, eyeW, eyeH);
  eye9 = new Eye(eyeW * 2.5 + margin + gutter * 2, eyeH * 2.5 + margin + gutter * 2, eyeW, eyeH);

  text = new Text(width / 2, height * 0.9);
};                        

void draw() {             
  background(posterBackground);
  PVector heapPoint = faceLocation();                    
  eye1.setActorPos(heapPoint.x, heapPoint.y);
  eye2.setActorPos(heapPoint.x, heapPoint.y);
  eye3.setActorPos(heapPoint.x, heapPoint.y);
  eye4.setActorPos(heapPoint.x, heapPoint.y);
  eye5.setActorPos(heapPoint.x, heapPoint.y);
  eye6.setActorPos(heapPoint.x, heapPoint.y);
  eye7.setActorPos(heapPoint.x, heapPoint.y);
  eye8.setActorPos(heapPoint.x, heapPoint.y);
  eye9.setActorPos(heapPoint.x, heapPoint.y);

  eye1.draw();            
  eye2.draw();            
  eye3.draw();            
  eye4.draw();            
  eye5.draw();            
  eye6.draw();            
  eye7.draw();            
  eye8.draw();            
  eye9.draw();            

  if (faces.length > 0) {
    eye1.move();            
    eye2.move();            
    eye3.move();            
    eye4.move();            
    eye5.move();            
    eye6.move();            
    eye7.move();            
    eye8.move();            
    eye9.move();
  } else if (faces.length == 0) {
    eye1.idle();            
    eye2.idle();            
    eye3.idle();            
    eye4.idle();            
    eye5.idle();            
    eye6.idle();            
    eye7.idle();            
    eye8.idle();            
    eye9.idle();
  }

  text.draw(textId);            

  if (faces.length > 0) {
    textId = 2;
  } else if (faces.length == 0) {
    textId = 1;
  }
};

opencv.pde

import gab.opencv.*;
import processing.video.*;
import java.awt.Rectangle;

// resolution for webcamVideo
int camWidth = 320;
int camHeight = 180;
//
boolean displayWindow = false;
Rectangle[] faces = new Rectangle[0];
Rectangle ActiveFace;
PVector[] headPosition = new PVector[15]; // a longer array means more references for calculating average
int readIndex = 0;             
PVector total = new PVector();    
PVector average  = new PVector(); 
PVector heapPoint = new PVector();

Capture video;
OpenCV opencv;

void setupOpenCV() {

  String[] cameras = Capture.list();
  ActiveFace = new Rectangle();
  if (cameras.length == 0) {
    println("There are no cameras available for capture.");
    exit();
  } else {
    println("Available cameras:");
    for (int i = 0; i < cameras.length; i++) {
      println(cameras[i]);
    }
  }
  //openCV
  for (int i = 0; i<headPosition.length; i++) {
    headPosition[i] = new PVector();
  }
  // video information
  video = new Capture(this, camWidth, camHeight, cameras[1]);
  opencv = new OpenCV(this, camWidth, camHeight);
  opencv.loadCascade(OpenCV.CASCADE_FRONTALFACE);  
  video.start();
}

PVector faceLocation() {

  opencv.loadImage(video);
  faces = opencv.detect();

  if (faces.length == 0) {
    ActiveFace.width = 1;
    ActiveFace.height = 1;
    ActiveFace.x = camWidth/2;
    ActiveFace.y = camHeight/2;
  } else if (faces.length>1) {
    int index = 0;
    float record = camWidth*2;
    for (int i = 0; i < faces.length; i++) {
      float d = dist(faces[i].x, faces[i].y, ActiveFace.x, ActiveFace.y);
      if (d < record) {
        record = d;
        index = i;
      }
    }
    ActiveFace = faces[index];
  } else  if (faces.length == 1) {
    ActiveFace = faces[0];
  }
  heapPoint.x = ActiveFace.x + ActiveFace.width/2;
  heapPoint.y = ActiveFace.y + ActiveFace.height/2;
  heapPoint.x = map(heapPoint.x, ActiveFace.width/2, camWidth-(ActiveFace.width/2), 0, width);
  heapPoint.y = map(heapPoint.y, ActiveFace.height/2, camHeight-(ActiveFace.height/2), 0, height);
  heapPoint.z = map(ActiveFace.width, 30, camWidth, 100, 0);

  // smooth readings out to avoid any shake
  total = total.sub(headPosition[readIndex]);
  headPosition[readIndex] = heapPoint.copy();
  total = total.add(headPosition[readIndex]);
  readIndex = readIndex + 1;
  if (readIndex >= headPosition.length) {
    readIndex = 0;
  }
  // calculate average
  average.x = total.x /  headPosition.length;
  average.x =  -average.x + width; 
  average.y = total.y /  headPosition.length;
  average.z = total.z /  headPosition.length;
  
  return average;
}


//2nd window for debuging 
void captureEvent(Capture c) {
  c.read();
}

public class SecondApplet extends PApplet {
  public void settings() {
    size(320, 180);
  }

  public void draw() {
    image(video, 0, 0);

    pushStyle();
    noFill();
    stroke(255, 0, 0);
    rect(ActiveFace.x, ActiveFace.y, ActiveFace.width, ActiveFace.height);
    popStyle();
  }
}

// activate 2nd window 
void keyPressed() {
  if (key == ' ') {
    if (!displayWindow) {
      String[] args = {"video"};
      SecondApplet sa = new SecondApplet();
      PApplet.runSketch(args, sa);
      displayWindow = true;
    }
  }
}