Project 2: New Faces – Mauricio Giraldo

by Mauricio @ 11:18 am 26 January 2011

Background

New Faces is a project that began in 2009 after I got fed up with Colombia’s online newspapers’ (one site, another site) high emphasis in sports and celebtrity-related news[1]. I decided to take a screenshot of the offending website and remove this information (along with advertising) in order to see what was left:

¿qué sería de las noticias sin la publicidad, el fútbol y la farándula?

Clearly sports, advertising and celebrity news have a relatively high importance for this colombian newspaper (at least in its online incarnation). This led me to wonder if this was the same across different countries. I decided to create a Python script that makes use of webkit2png-0.5 to take a screenshot of eleven news websites every night at 8pm. This script has been ran almost every night since November 8, 2009.

Snippet of the script used to take screenshots

New faces

After seeing Rutherford Chang’s work with the New York Times where he blacks-out every element in the page except faces (no link available, sorry) I thought it would be an interesting idea to execute this same process in my dataset of more than 3,000 files and 10 GB.

I tested various technical solution for this process and decided upon using the OpenCV library for Processing. I could not find a library that could do non-rectagular detection (this one seems to work only in Windows and I have a Mac).

OpenCV for Processing: Hello World!

After that it was a matter of applying it to my dataset and see if it worked:

Testing face recognition (click to view full size)

I tried several parameters for the detect() method since there were many false positives (text selected as face) and false negatives (face undetected when there should be) but could not get 100% accuracy. These errors provide an interesting outcome anyway.

The faces are extracted from the screenshots and saved as separate files along with their original location in x,y and number of total faces in the screenshot. There were 28,000+ faces deteced (less than 10 faces per screenshot):

OpenCV + Processing doing their magic on 3,000+ files

With this new subset I then created a Flash interface to view the faces in their original x,y coordinates with buttons to toggle the visibility of individual news sites. Next to the buttons a blue line indicates average face area, a green line indicates how many screenshots (individual days captured) and a red line indicates total amount of faces in that site.

View the Flash interface online in your browser. F for fullscreen (click the interface to give it focus). CAUTION: ~30,000 images files loaded might clog up your browser (although they’re about 2kb each only).

Some screenshots below:

All the site faces on top of each other

El Tiempo (Colombia) does not have many faces in its topmost part

El Espectador (Colombia) has a high face count with large size

Sydney Morning Herald - Australians seem to like big faces in their newspapers

The Age - Another australian site which also gives high importance to faces

The Guardian (UK) has a medium scale average face

Le Monde (France) - Ignoring the (huge) false positive, the french have a few midsize faces

New York Times - New Yorkers seem to prefer small faces

Wall Street Journal - Finance-oriented New Yorkers prefer hand-drawn faces

Future work

From this initial work some superficial conclusions could be made (see photo captions). Further analysis could provide some interesting information such as:

  • dominant race or skin color
  • gender distribution
  • dominant face expression (sad, happy, angry, etc.)
  • average face area
  • preferred location in the page
  • how the above change over time

The interface makes use of Bit-101’s Minimal Comps.

PDF Presentation (1.5MB)

For those so inclined, this is the code for detecting faces in Processing (assumes a folder with images and a text file listing the images to use):

import hypermedia.video.*;
import java.awt.Rectangle;
 
OpenCV opencv;
 
// contrast/brightness values
int contrast_value    = 0;
int brightness_value  = 0;
 
PImage img;
PImage maskimg;
 
boolean is_masking = false;
boolean has_started = false;
int current = 0;
String files[];
String current_file = "";
String current_file_no_suffix = "";
String path = "";
 
PFont font;
 
// ==================================================
// setup()
// ==================================================
void setup() {
  opencv = new OpenCV( this );
  size(300, 100);
  fill(255,255,255);
  font = createFont("Inconsolata", 12); 
  textFont(font); 
  path = selectFolder();
  files = loadStrings(selectInput());
}
// ==================================================
// draw()
// ==================================================
void draw() {
  if (!is_masking && files != null && files.length>0 && current<files.length) {
    is_masking = true;
    current_file = path + "/" + files[current];
    current_file_no_suffix = current_file.substring(0,current_file.indexOf("."));
    current_file_no_suffix = current_file_no_suffix.substring(33);
    processFile();
    current++;
  }
}
 
void clearScreen() {
  background(0);
}
 
void processFile() {
  img = loadImage(current_file);
  opencv.allocate(img.width, img.height);
  opencv.copy(img);
  opencv.cascade( OpenCV.CASCADE_FRONTALFACE_ALT );  // load detection description, here-> front face detection : "haarcascade_frontalface_alt.xml"
  // Size of applet
  saveFaces();
}
 
void saveFaces() {
    Rectangle[] faces = opencv.detect(1.3,3,opencv.HAAR_DO_CANNY_PRUNING,10,10);
    /**/
    color white = color(255, 255, 255);
    if (faces.length>0) {
      for( int i=0; i<faces.length; i++ ) {
        /**/
        maskimg = createImage(faces[i].width, faces[i].height, RGB);
        for (int j = 0; j < faces[i].width; j++) {
          for (int k = 0; k < faces[i].height; k++) {
            maskimg.set(j,k,img.get(faces[i].x+j,faces[i].y+k));
          }
        }
        maskimg.save("output/" + current_file_no_suffix + "_" + i + "-" + faces.length + "_" + faces[i].x + "-" + faces[i].y + "_" + faces[i].width + "-" + faces[i].height + ".jpg");
        /**/
        clearScreen();
        text("Done!\nProcessing file " + current + " of " + files.length + " with " + faces.length + " faces: " + current_file,10,10,280,80);
        is_masking = false;
      }
    } else {
      is_masking = false;
    }
}
 
public void stop() {
    opencv.stop();
    super.stop();
}

The code for the Flash interface:

package com.mga {
	import com.bit101.components.Label;
	import flash.events.KeyboardEvent;
 
	import com.bit101.components.HSlider;
 
	import flash.events.IOErrorEvent;
 
	import com.bit101.components.PushButton;
	import com.bit101.components.Style;
	import com.bit101.components.VSlider;
 
	import flash.display.LoaderInfo;
	import flash.events.ProgressEvent;
	import flash.display.Loader;
	import flash.net.URLLoader;
	import flash.net.URLRequest;
	import flash.display.StageDisplayState;
	import flash.display.StageScaleMode;
	import flash.display.StageAlign;
	import flash.events.Event;
	import flash.display.Sprite;
 
	/**
	 * @author mga
	 */
	public class FaceViewer extends Sprite {
		private var _filenames : Array;
		private var _imagesVector : Vector.<Loader>;
		private var _sites : Object;
		private var _baseFolder : String = "file:////Users/mga/Documents/Processing/face_opencv_03/output/";//
		private var _textLoader : URLLoader;
		private var _currentFile : int;
		private var _isLoading : Boolean;
		private var _loaderClip : LoaderClip;
		//private var _database : Vector.<Object>;
		public var _imageContainer : Sprite;
		private var _hScrollBar : HSlider;
		private var _vScrollBar : VSlider;
		private var _controlsClip : Sprite;
		private var _baseWidth : Number = 800;
		private var _scrollBarWidth : Number = 10;
		private var _buttonWidth : Number = 65;
		private var _buttonHeight : Number = 20;
		private var _maxWidth : Number = 1200;
		private var _labelWidth  : Number = 65;
		private var _graphWidth : Number = 60;
		private var _graphHeight : Number = 4;
 
		public function FaceViewer() {
			init();
		}
 
		private function init() : void {
			initProperties();
			initContainers();
			initControls();
			addListeners();
			processList();
		}
 
		private function initContainers() : void {
			// image container clip
			_imageContainer = new Sprite();
			_imageContainer.name = "_imageContainer";
			addChild(_imageContainer);
			var tmp:Sprite;
			var i:Number = 0;
			for (var site:String in _sites) {
				tmp = new Sprite();
				tmp.name = site;
				_imageContainer.addChild(tmp);
				i++;
			}
		}
 
		private function initControls() : void {
			// interface
			Style.setStyle(Style.DARK);
			_hScrollBar = new HSlider(this,_buttonWidth,0,onHScroll);
			_hScrollBar.setSize(_baseWidth - (_buttonWidth*2), _scrollBarWidth);
			_hScrollBar.setSliderParams(-(_baseWidth-_maxWidth)/2, (_baseWidth-_maxWidth)/2, 0);
			_hScrollBar.alpha = 0.5;
			addChild(_hScrollBar);
 
			_vScrollBar = new VSlider(this,_baseWidth - _scrollBarWidth,_scrollBarWidth,onVScroll);
			_vScrollBar.setSize(_scrollBarWidth, _baseWidth/ 2);
			_vScrollBar.alpha = 0.5;
			addChild(_vScrollBar);
 
			// base loading clip
			_loaderClip = new LoaderClip();
			addChild(_loaderClip);
			_loaderClip.visible = false;
			_loaderClip.y = stage.stageHeight - _loaderClip.height;
 
			_controlsClip = new Sprite();
			var site:String;
			var btn:PushButton;
			var lbl:Label;
			var i:Number = 0;
			var fSquare:Sprite;
			var aSquare:Sprite;
			var dSquare:Sprite;
			for (site in _sites) {
				btn = new PushButton(_controlsClip, 0, i * _buttonHeight, site, onSiteButtonPressed);
				btn.toggle = true;
				btn.name = site;
				btn.label = _sites[site].name;
				btn.alpha = 0.5;
				btn.setSize(_buttonWidth, _buttonHeight);
				_controlsClip.addChild(btn);
				/**
				lbl = new Label(_controlsClip, _buttonWidth, i * _buttonHeight, "F:0 A:0");
				lbl.name = site + "_lbl";
				_controlsClip.addChild(lbl);
				/**/
				fSquare = new Sprite();
				fSquare.x = _buttonWidth + 2;
				fSquare.y = _buttonHeight * i;
				fSquare.name = site + "f";
				fSquare.graphics.beginFill(0xFF0000);
				fSquare.graphics.moveTo(0, 0);
				fSquare.graphics.lineTo(5, 0);
				fSquare.graphics.lineTo(5, _graphHeight);
				fSquare.graphics.lineTo(0, _graphHeight);
				fSquare.graphics.lineTo(0, 0);
				fSquare.graphics.endFill();
				_controlsClip.addChild(fSquare);
				dSquare = new Sprite();
				dSquare.x = _buttonWidth + 2;
				dSquare.y = (_buttonHeight * i) + (_graphHeight)+2;
				dSquare.name = site + "d";
				dSquare.graphics.beginFill(0x00FF00);
				dSquare.graphics.moveTo(0, 0);
				dSquare.graphics.lineTo(5, 0);
				dSquare.graphics.lineTo(5, _graphHeight);
				dSquare.graphics.lineTo(0, _graphHeight);
				dSquare.graphics.lineTo(0, 0);
				dSquare.graphics.endFill();
				_controlsClip.addChild(dSquare);
				aSquare = new Sprite();
				aSquare.x = _buttonWidth + 2;
				aSquare.y = (_buttonHeight * i) + ((_graphHeight * 2))+4;
				aSquare.name = site + "a";
				aSquare.graphics.beginFill(0x0000FF);
				aSquare.graphics.moveTo(0, 0);
				aSquare.graphics.lineTo(5, 0);
				aSquare.graphics.lineTo(5, _graphHeight);
				aSquare.graphics.lineTo(0, _graphHeight);
				aSquare.graphics.lineTo(0, 0);
				aSquare.graphics.endFill();
				_controlsClip.addChild(aSquare);
				i++;
			}
			addChild(_controlsClip);
		}
 
		private function onSiteButtonPressed(event : Event) : void {
			_sites[event.target.name].show = !_sites[event.target.name].show;
			trace(_sites[event.target.name].show);
		}
 
		private function processList() : void {
			// loads the file
			var file:String = _baseFolder + "lista.txt";
			var request:URLRequest = new URLRequest(file);
			_textLoader.load(request);
		}
 
		private function loadImages(e:Event) : void {
			trace("loaded!");
			_filenames = String(_textLoader.data).split("\n");
			trace("files:",_filenames.length);
			_currentFile = 0;
			_isLoading = true;
			loadNextFile();
		}
 
		private function loadAllFiles() : void {
			var i:Number;
			for (i=0;i < _filenames.length;++i) {
				_imagesVector[i] = new Loader();
				//trace("loading:",_filenames[_currentFile]);
				_imagesVector[i].load(new URLRequest(_baseFolder + _filenames[i]));
				var o:Object = objectify(i);
				_imagesVector[i].x = (stage.stageWidth/2) + o.x - (_sites[o.site][1]/2);
				_imagesVector[i].y = o.y;
				Sprite(_imageContainer.getChildByName(o.site)).addChild(_imagesVector[i]);
			}
		}
 
		private function loadNextFile() : void {
			if (_isLoading && _currentFile < _filenames.length && _filenames[_currentFile]!="") {
				updateLoader(_loaderClip, (_currentFile + 1) / _filenames.length, (_currentFile+1) + "/" + _filenames.length);
				_imagesVector[_currentFile] = new Loader();
				//trace("loading:",_baseFolder + _filenames[_currentFile]);
				_imagesVector[_currentFile].load(new URLRequest(_baseFolder + _filenames[_currentFile]));
				_imagesVector[_currentFile].contentLoaderInfo.addEventListener(Event.COMPLETE, imageLoaded);
				_imagesVector[_currentFile].contentLoaderInfo.addEventListener(IOErrorEvent.IO_ERROR, imageLoadError);
			} else {
				trace("done!");
				_isLoading = false;
				hideLoaders();
			}
		}
 
		private function imageLoadError(event : IOErrorEvent) : void {
			trace("error loading: ", _baseFolder + _filenames[_currentFile]);
		}
 
		private function imageLoaded(event : Event) : void {
			LoaderInfo(event.target).removeEventListener(Event.COMPLETE, imageLoaded);
			LoaderInfo(event.target).removeEventListener(IOErrorEvent.IO_ERROR, imageLoadError);
			placeImage(_currentFile);
			_currentFile++;
			loadNextFile();
		}
 
		private function placeImage(num : int) : void {
			var o:Object = objectify(num);
			_sites[o.site].area = normalize(_sites[o.site].faces,_sites[o.site].area, o.width * o.height);
			if (_sites[o.site].lastday!=o.timestamp) {
				_sites[o.site].lastday = o.timestamp;
				_sites[o.site].days++;
			}
			_sites[o.site].faces++;
			updateGraphs();
			_imagesVector[num].x = (stage.stageWidth/2) + o.x - (_sites[o.site].width/2);
			_imagesVector[num].y = o.y;
			Sprite(_imageContainer.getChildByName(o.site)).addChild(_imagesVector[num]);
			//Label(_controlsClip.getChildByName(o.site+"_lbl")).text = "F:" + _sites[o.site].faces + " A:" + _sites[o.site].area;
		}
 
		private function updateGraphs() : void {
			var site:String;
			var gF:Sprite;
			var gA:Sprite;
			var gD:Sprite;
			var maxArea:Number = 0;
			var maxFaces:Number = 0;
			var maxDays:Number = 0;
			for (site in _sites) {
				if (_sites[site].area > maxArea) maxArea = _sites[site].area;
				if (_sites[site].faces > maxFaces) maxFaces = _sites[site].faces;
				if (_sites[site].days > maxDays) maxDays = _sites[site].days;
			}
			for (site in _sites) {
				gF = Sprite(_controlsClip.getChildByName(site+"f"));
				gF.width = _sites[site].faces / maxFaces * _graphWidth;
				gA = Sprite(_controlsClip.getChildByName(site+"a"));
				gA.width = _sites[site].area / maxArea * _graphWidth;
				gD = Sprite(_controlsClip.getChildByName(site+"d"));
				gD.width = _sites[site].days / maxDays * _graphWidth;
			}
		}
 
		private function normalize(faces : Number, oldarea : Number, newarea : Number) : Number {
			var a:Number = 0;
			if (!isNaN(faces) && !isNaN(oldarea) && !isNaN(newarea)) {
				a = ((faces * oldarea) + newarea) / (faces+1);
				return int(a);
			}
			return a;
		}
 
		private function objectify(num : int) : Object {
			// break up the name into its components
			var name:String = _filenames[num];
			//20091108190345clarin-full_1-17_715-3292_24-24.jpg
			//YYYYMMDDHHIISSsite-full_num-total_x-y_w-h.jpg
			var tmp:Object = {};
			tmp.timestamp = Number(name.substr(0,8));
			tmp.year = Number(name.substr(0,4));
			tmp.month = Number(name.substr(4,2));
			tmp.day = Number(name.substr(6,2));
			tmp.site = name.substr(14).substring(0,name.substr(14).indexOf("-"));
			var arr:Array = name.split("_");
			tmp.num = Number(arr[1].split("-")[0]);
			tmp.total = Number(arr[1].split("-")[1]);
			tmp.x = Number(arr[2].split("-")[0]);
			tmp.y = Number(arr[2].split("-")[1]);
			tmp.width = Number(arr[3].split("-")[0]);
			tmp.height = Number(arr[3].split("-")[1].split(".")[0]);
			//_database[num] = tmp;
			return tmp;//_database[num];
		}
 
		private function hideLoaders() : void {
			_loaderClip.visible = false;
		}
 
		private function draw(event : Event) : void {
			updateControls();
		}
 
		private function updateControls() : void {
			_hScrollBar.x = stage.stageWidth - _hScrollBar.width;
			_vScrollBar.x = stage.stageWidth-_scrollBarWidth;
			_vScrollBar.minimum = -_imageContainer.height;
			_vScrollBar.maximum = 0;
			var i:Number = 0;
			for (var site:String in _sites) {
				_imageContainer.getChildByName(site).x = (stage.stageWidth / 2) - (_sites[site].width / 2);
				_imageContainer.getChildByName(site).visible = _sites[site].show;
				i++;
			}
		}
 
		private function updateLoader(clip:LoaderClip, pct:Number, txt:String = "") : void {
			clip.visible = true;
			clip.bar_mc.width = clip.track_mc.width * pct;
			clip.status_txt.text = txt;
		}
 
		private function addListeners() : void {
			stage.addEventListener(KeyboardEvent.KEY_DOWN, keyDownHandler);
			_textLoader.addEventListener(Event.COMPLETE, loadImages);
			//Wait to be added to stage
            addEventListener(Event.ADDED_TO_STAGE, onAddedToStage);
		}
 
		private function onVScroll(event : Event) : void {
			_imageContainer.y = _vScrollBar.value;
		}
 
		private function onHScroll(event : Event) : void {
			_imageContainer.x = _hScrollBar.value;
		}
 
		private function imageLoadProgress(event : ProgressEvent) : void {
			var ldr:LoaderInfo = LoaderInfo(event.target);
			var pct:Number = ldr.bytesLoaded / ldr.bytesTotal;
			if (pct>=1) {
				ldr.removeEventListener(ProgressEvent.PROGRESS,	imageLoadProgress);
			}
		}
 
		private function initProperties() : void {
			// interface elements
			// variables
			_textLoader = new URLLoader();
			// arrays
			//_database = new Vector.<Object>();
			_imagesVector = new Vector.<Loader>();
			_filenames = [];
			_sites = {};
			_sites["clarin"] = {};
			_sites["clarin"].name = "el clarín";
			_sites["clarin"].width = 1000;
			_sites["clarin"].faces = 0;
			_sites["clarin"].area = 0;
			_sites["clarin"].shots = 0;
			_sites["clarin"].days = 0;
			_sites["clarin"].lastday = 0;
			_sites["clarin"].show = true;
			_sites["elpais"] = {};
			_sites["elpais"].name = "el país";
			_sites["elpais"].width = 996;
			_sites["elpais"].faces = 0;
			_sites["elpais"].area = 0;
			_sites["elpais"].shots = 0;
			_sites["elpais"].days = 0;
			_sites["clarin"].lastday = 0;
			_sites["elpais"].show = true;
			_sites["elespectador"] = {};
			_sites["elespectador"].name = "el espectador";
			_sites["elespectador"].width = 1000;
			_sites["elespectador"].faces = 0;
			_sites["elespectador"].area = 0;
			_sites["elespectador"].shots = 0;
			_sites["elespectador"].days = 0;
			_sites["elespectador"].lastday = 0;
			_sites["elespectador"].show = true;
			_sites["eltiempo"] = {};
			_sites["eltiempo"].name = "el tiempo";
			_sites["eltiempo"].width = 970;
			_sites["eltiempo"].faces = 0;
			_sites["eltiempo"].area = 0;
			_sites["eltiempo"].shots = 0;
			_sites["eltiempo"].days = 0;
			_sites["eltiempo"].lastday = 0;
			_sites["eltiempo"].show = true;
			_sites["guardian"] = {};
			_sites["guardian"].name = "the guardian";
			_sites["guardian"].width = 950;
			_sites["guardian"].faces = 0;
			_sites["guardian"].area = 0;
			_sites["guardian"].shots = 0;
			_sites["guardian"].days = 0;
			_sites["guardian"].lastday = 0;
			_sites["guardian"].show = true;
			_sites["huffingtonpost"] = {};
			_sites["huffingtonpost"].name = "the huffington post";
			_sites["huffingtonpost"].width = 1016;
			_sites["huffingtonpost"].faces = 0;
			_sites["huffingtonpost"].area = 0;
			_sites["huffingtonpost"].shots = 0;
			_sites["huffingtonpost"].days = 0;
			_sites["huffingtonpost"].lastday = 0;
			_sites["huffingtonpost"].show = true;
			_sites["lemonde"] = {};
			_sites["lemonde"].name = "le monde";
			_sites["lemonde"].width = 1026;
			_sites["lemonde"].faces = 0;
			_sites["lemonde"].area = 0;
			_sites["lemonde"].shots = 0;
			_sites["lemonde"].days = 0;
			_sites["lemonde"].lastday = 0;
			_sites["lemonde"].show = true;
			_sites["nytimes"] = {};
			_sites["nytimes"].name = "new york times";
			_sites["nytimes"].width = 972;
			_sites["nytimes"].faces = 0;
			_sites["nytimes"].area = 0;
			_sites["nytimes"].shots = 0;
			_sites["nytimes"].days = 0;
			_sites["nytimes"].lastday = 0;
			_sites["nytimes"].show = true;
			_sites["smh"] = {};
			_sites["smh"].name = "sydney morning herald";
			_sites["smh"].width = 990;
			_sites["smh"].faces = 0;
			_sites["smh"].area = 0;
			_sites["smh"].shots = 0;
			_sites["smh"].days = 0;
			_sites["smh"].lastday = 0;
			_sites["smh"].show = true;
			_sites["theage"] = {};
			_sites["theage"].name = "the age";
			_sites["theage"].width = 990;
			_sites["theage"].faces = 0;
			_sites["theage"].area = 0;
			_sites["theage"].shots = 0;
			_sites["theage"].days = 0;
			_sites["theage"].lastday = 0;
			_sites["theage"].show = true;
			_sites["wsj"] = {};
			_sites["wsj"].name = "wall street journal";
			_sites["wsj"].width = 992;
			_sites["wsj"].faces = 0;
			_sites["wsj"].area = 0;
			_sites["wsj"].shots = 0;
			_sites["wsj"].days = 0;
			_sites["wsj"].lastday = 0;
			_sites["wsj"].show = true;
			_sites["thesun"] = {};
			_sites["thesun"].name = "the sun";
			_sites["thesun"].width = 996;
			_sites["thesun"].faces = 0;
			_sites["thesun"].area = 0;
			_sites["thesun"].shots = 0;
			_sites["thesun"].days = 0;
			_sites["thesun"].lastday = 0;
			_sites["thesun"].show = true;
		}
 
		private function onAddedToStage(event : Event) : void {
            //If added to stage, set stage properties and start listening to the global enterframe.
            stage.frameRate = 120;
            stage.align = StageAlign.TOP_LEFT;
			stage.scaleMode = StageScaleMode.NO_SCALE;
            stage.addEventListener(Event.ENTER_FRAME, draw);
            stage.addEventListener(Event.RESIZE, resizeHandler);
		}
 
		private function resizeHandler(event : Event) : void {
			updateControls();
		}
 
		private function keyDownHandler(e:KeyboardEvent):void {
			// 32 = SPACE
			var code:uint = e.keyCode;
			if (code == 70) { 
				// F
				if (stage.displayState == "normal") {
					stage.displayState = "fullScreen";
				}
			}
		}
	}
}

1 While this might have changed since then (at least ine site suffered an important redesign, not for the better, in my opinion)

2 Comments

  1. Comments from PiratePad A:

    Stripping the news down to the news. Nice concept, too bad the actual companies don’t adopt this policy. This is like what would happen if you took all the flashy visuals and particle effects away from Fox. You’d come up with a very uninformative show without enough of the bells and whistles to keep the unintelligent interested in the programming.

    DOM replacement as a sketch is nice
    jesus christ 10 gb? of screenshots? compressed png? ffffuuuuuuuuu 10 gig
    link to artist who blacks out the news with face?

    Nice narrative presentation style. Presentation time is lost in a lot of places, including explaining the filenaming structure. However, the narrative skipped from taking out the “sports” to capturing faces. I got lost for a second. I liked how you showed your early prototypes and why you discarded them. Buttons for website on/off don’t have state feedback.

    Great to see all of those faces! It would be nice if there was some way to see images below when the overlap. Maybe stacks could explode when you mouse over.

    It wasn’t immediately obvious that this is mapping to positions on the original page. If the software lived within a web browser window it would instantly be easier to understand.

    Smart way of mapping the images with their positions and layering with the other pages.

    Totally agree – News would be so much more appealing if the amount of sport + celebrity coverage was toned down. >> But then again, everyone would rather see pictures of Ke$ha all day than pictures of Nancy Pelosi. >> This is true.

    The sheer amount of data makes this really compelling. Now curious to know what else you’ve been collecting in 10gb bundles.

    I like the face recognition idea ;) One thing you could about the duplicating portraits is just discard faces if they have a certain threshold of pixel overlap with another face. The flash visualization looks somewhat chaotic. Is there a way to organize the content? But it is interesting, just seeing the position ;) zooming would be nice.

    How long will this take to load?? I love the comparisons you can make between countries.

    Great idea, but I wonder if you could maybe present the data in a format that’s easier to view.

    Very cool that you have been collecting this data since 2009. Nice concept to look at faces. Visually the collection is very rich. Wow, the end result is very impressive! Nicely done! I like that all of your visual variables carry some significance, like the position that they originally appeared on the site. The ability to sort by site is a nice interactive feature to filter through the information and make some interesting comparisons. Impressive technical implementation.

    Very cool! Awesome it turned out well.

    Really interesting, really cool, massive data set. Maybe the ability to load/play over time. So you can hit play and see the faces move and jump as time passes. Really cool, nice presentation.

    It would be really interesting to try to use some identity detection to try to guess who all these people are. Also have you considered changing your cron so that it saves the html so you can better search it? (Or maybe a pdf or something) I wuold also like to see this plotted across time

    This is awesome! This simple plotting reveals so much – from grid structures, to kinds of faces the newspapers show. Navigation, like you said, it’s hard to tell what newspapers are on or off. Since the images are overlapping, it might be interesting to see a heat map of how many images are in one location.

    So many faces! I am also interested in the gender/age thing.. I think having this numbers could reveal a lot about the papers. I like the loading phase of the application because it shows how time passes and how news change.. what becomes important in a particular moment might be forgotten a few months later. Nice work!

    Comment by Golan Levin — 26 January 2011 @ 3:15 pm
  2. Comments from PiratePad B:

    Nice to have mentioned your inspiration from Ruthorford Chang’s NYT face project.
    Nice mentions of the technologies you mentioned. Truly freestyle computing.

    I was very interested by the finding that the Australian newspaper have more faces. I’m wondering if there are regional differences in facial expressions. Your ideas about time-based display are good.

    I like how you explain the multiple iterations you went through for this project in your presentation

    great data set…great interface, could be a little cleaner but this is a great project. Could be interesting to also have a toggle to spread them out on a grid according to size or something

    Interesting to see when clusters of photos appear – kinda gives insight into news page layout patterns.

    I liked the false positives of text that appear to be abstract faces.

    Those toggle buttons need indicators – as you showed us. The zoom-out that you mentioned would be nice – I know that can be done in Flash with a vCam as3 (Though I don’t know exactly how you implemented this project).

    Great idea, Agree w/the green above me; indicate those toggles!!! There’s too much going on not to have some kind of indicator of on or off, otherwise it’s very confusing as to where the faces are coming from.
    Side note: I find it really funny that The Huffington Post example that you showed us is just a false positive and a few pictures of Sarah Palin. Also interesting to see that the wall street journal’s faces are like 50% hand-drawn (again, from the small snippet of the bajillions of pixels you have available for viewing).

    I love this! I like how you can start to understand the grid structure and stategie of the image placement. The design is a bit cluncky but the outcome is still really interesting.

    Comment by Golan Levin — 26 January 2011 @ 3:15 pm

This work is licensed under a Creative Commons Attribution-Noncommercial-Share Alike 3.0 Unported License.
(c) 2019 Interactive Art & Computational Design / Spring 2011 | powered by WordPress with Barecity