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.

https://slides.com/mala23/be-right-bro

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;
    }
  }
}
To embed a website or widget, add it to the properties panel.