× Haoshu Project #1 Project #2 Project #3 Project #4 Project #5 Project #6 Project #7 Project #8 Project #9 Project #10
☰ Menu

Project #6 Experimental Camera | Put your mask on!

This project is improved with consentful interface in Project 8.

Please allow camera access if it's continuously loading.

Designed by Haoshu Yang.

Description

I started my concept by reflecting this year, this COVID year.

As far as I know, the pandemic began from Wuhan, China. Then, it spread across China rapidly because of the Spring Festival Holiday that most of the people worked in another city got back to their hometown.Then, the coronavirus spread globally, from the East Asian countries like Japan and South Korea, to European countries , especially from Italy. Following, a large number of cases imported from the Europe to America, who blocked traveling from China at the very beginning but left the door open to Europe. From this on, the COVID became a really global pandemic that nearly crashed most countries medical system.

Since it became a global issue, many unfriendly and racist actions towards Asians revealed again to the public.

A student from Singapore was beaten up in Oxford Street by a group of men who told him that they didn’t want his coronavirus in their country. Also, a lot of Anti-Asian prejudice appeared from California to New York. And for those Asians who know how to protect themselves from being infected and know how to reduce the spread, they wear a mask, which made them more vulnerable to be attacked. Since many people just regarded an asian with a mask as the virus carrier.

But I just find it hilarious that people in asian countries, who are first exposed to the COVID, who had a much worse pandemic situation, get back to almost normal now, while in the US, the number are still increasing dramatically. Every one wore a mask and had a quarantine in Asian countries. While in America, with Trump blaming China for everything and calling it the Chinese virus again and again, the masks are not recommended until the very late.

So here I made this camera that no matter whom use it, it will put a changeable mask on the face. And after changing mask styles for few times, user can also trigger the scenario of being beaten up like the Singaporean student I mention above.

Hope it could emphasize the importance of wearing a mask during the COVID pandemic and reveal one aspect of Asian discrimination.

Reference:

Coronavirus: Student from Singapore hurt in Oxford Street attack
A student from Singapore has said he was beaten up by a group of men who told him: "I don't want your coronavirus in my country."

America’s long history of scapegoating its Asian citizens
When leaders call COVID-19 the “China virus,” it harkens back to decades of state-sanctioned discrimination against Asian Americans.

The Slur I Never Expected to Hear in 2020
As an Asian-American, I’ve been conditioned to a certain kind of unspoken racism. This pandemic has unmasked how vicious it really is.

Attacked for wearing face mask: Anti-Asian prejudice bubbles up in California over coronavirus fears
On one occasion when she wore a mask outside her office she was accosted by a woman. "She started yelling, 'Are you crazy? Get the heck out of here," said Ms Yu, 34. "I realised it was because I was wearing a mask."

Design Process

The first thing that came to mind about the camera and p5.js was to have some fun with the image input, like redrawing human faces into low-poly style with triangles, or decomposing and reconstructing the parts in the picture to create a unique feeling of the world.

Then, I looked into things around me and the reality of the COVID pandemic pulled me out of the artistic fantasy. What are the situations and impacts revealed by the epidemic?

Online could be one of the keywords in this year. I came up with an idea of projecting user‘s face to a virtual character on the canvas. Then the user could take a virtual photo in the digital world like a virtual gallery or virtual campus.

The other keyword I found out is the Asian discrimination, which was revealed again when the pandemic started, with every Asian wearing a mask being regarded as Coronavirus carrier and being cursed or even being attacked.

While the first keyword has already been part of the life for many people, the second was only wide-spread across asian community. Hence, I decided to design a camera that aligns with the topic about COVID-19, masks and asian discrimination.

I first imported clmtrackr.js to track user’s face. Then, according to the tracker array, different styles of masks were drawn on user’s face. An ellipse was also created from face location to mask the video so that only user’s head area was shown on the canvas. To add more fun, a cap emoji and coat emoji were added to the head. What’s more, virus emojis were sparkling and shaking in the background to indicate this COVID situation. Finally, I used pixel array and changed the color of the certain area on the user face to represent being beaten up like the Singaporean student, which was triggered by few times of changing mask styles.

Reflection

Through this design, I have learned more characteristics and functions in p5.js like pixel array, createCapture from camera video, createGraphics to customize a vector graph whose alpha channel is used to mask another element by function mask, and import the clmtrackr.js to track the user’s face. There were lots of trials and errors and I came up with some tricky ways to solve the unexpected problems, for example, I found out when loading the video pixels, all of them are loaded to the canvas and cannot be masked, which could be solved by changing alpha values of the unneeded pixels into 0. It is necessary to understand how program “seeing” us from camera by loading the pixels with R, G, B and alpha value.

Code

//clmtrackr reference: https://github.com/auduno/clmtrackr

let vid, Mask, shape, ctracker, htracker, detector;

let start;

let masknum;

let beat;

let c;

 

class Virus {

constructor() {

this.x = random(width);

this.y = random(height);

this.size = random(10, 30);

this.speed = 5;

}

 

move() {

this.x += random(-this.speed, this.speed);

this.y += random(-this.speed, this.speed);

}

 

display() {

push();

if (random([0, 1])) {

textSize(this.size);

text('🦠', this.x, this.y);

} else {

fill(10, 200, 30, random(0, 50));

noStroke();

circle(this.x, this.y, this.size);

}

pop();

}

}

 

function preload() {

img = loadImage('Jonathan.png');

}

 

function setup() {

createCanvas(400, 300);

 

// initialize 30 virus illustrations

let cnt = 30;

V = new Array();

while (cnt--) {

V.push(new Virus());

}

 

button = createButton('change');

button.position(width - button.width, height - button.height);

 

Mask = createGraphics(width, height);

Mask.ellipseMode(CORNERS);

shape = createGraphics(width, height);

shape.ellipseMode(CORNERS);

 

vid = createCapture(VIDEO);

vid.size(width, height);

vid.hide();

 

ctracker = new clm.tracker();

ctracker.init();

ctracker.start(vid.elt);

 

start = false;

masknum = 1;

beat = 0;

 

pixelDensity(1);

textAlign(CENTER);

angleMode(DEGREES);

}

 

function drawMask(pos) {

let x1 = pos[2][0],

y1 = pos[41][1],

x2 = pos[12][0],

y2 = pos[7][1];

if (masknum) {

Mask.clear();

Mask.noStroke();

Mask.fill(c);

Mask.ellipse(x1, y1, x2, y2);

Mask.stroke(c);

Mask.strokeWeight(3);

Mask.line(pos[1][0], pos[1][1], pos[3][0], pos[3][1]);

Mask.line(pos[13][0], pos[13][1], pos[11][0], pos[11][1]);

 

} else {

Mask.clear();

Mask.fill(c);

Mask.noStroke();

Mask.beginShape();

Mask.vertex(pos[62][0], pos[62][1]);

Mask.vertex(pos[2][0], pos[2][1]);

Mask.vertex(pos[5][0], y2);

Mask.vertex(pos[9][0], y2);

Mask.vertex(pos[12][0], pos[12][1]);

Mask.endShape(CLOSE);

Mask.stroke(c);

Mask.strokeWeight(3);

Mask.line(pos[1][0], pos[1][1], pos[3][0], pos[3][1]);

Mask.line(pos[13][0], pos[13][1], pos[11][0], pos[11][1]);

}

 

image(Mask, 0, 0);

}

 

// elliptical shape is used to mask the video and demonstrate only the head area

function Shape(pos) {

let x1 = pos[1][0],

y1 = pos[33][1],

x2 = pos[13][0],

y2 = pos[7][1];

shape.clear();

shape.noStroke();

shape.fill(0);

shape.ellipse(x1 - 30, y1 - (y2 - y1) / 3 - 30, x2 + 30, y2 + 30);

}

 

function loading() {

fill(10, 100, 200);

textSize(60);

text('Loading...', width / 2, height / 2);

c = color(random([0, 1]) ? color(random(0, 200), random(200, 255)) : color(random(69, 79), random(166, 176), random(234, 244), random(200, 255)));

button.hide();

}

 

function noface() {

fill(10, 200, 100);

textSize(60);

text('Wait for a face', width / 2, height / 2);

button.hide();

}

 

function inEllipse(x, y, x1, x2, y1, y2) {

let r1 = abs(x1 - x2);

let r2 = abs(y1 - y2);

if (sq(x - x2) / sq(r1) + sq(y - 2 * y1 + y2) / sq(r2) <= 1) return true;

else return false;

}

 

function inCircle(x, y, x1, x2, y1, y2) {

let r = sqrt(sq(x1 - x2) + sq(y1 - y2)) / 2;

let xc = (x1 + x2) / 2;

let yc = (y1 + y2) / 2;

if (sq(x - xc) + sq(y - yc) <= sq(r)) return true;

else return false;

}

 

function Beat(pos) {

let x1_start = int(pos[66][0]);

let y1_start = int(pos[26][1]);

let x1_end = int(pos[25][0]);

let y1_end = int(pos[34][1]);

let x2_start = int(pos[67][0]);

let y2_start = int(pos[67][1]);

let x2_end = int(pos[28][0]);

let y2_end = int(pos[28][1]);

vid.loadPixels();

for (let y = 0; y < height; y++)

for (let x = 0; x < width; x++) {

let index = (x + y * width) * 4;

if (inCircle(x, y, x1_start, x1_end, y1_start, y1_end)) {

vid.pixels[index] -= 73;

vid.pixels[index + 1] -= 68;

vid.pixels[index + 2] -= 36;

} else if (inEllipse(x, y, x2_start, x2_end, y2_start, y2_end)) {

vid.pixels[index] -= 42;

vid.pixels[index + 1] -= 23;

vid.pixels[index + 2] -= 13;

} else vid.pixels[index + 3] = 0;

}

vid.updatePixels();

image(vid, 0, 0, width, height);

}

 

function draw() {

background('rgba(255,255,255,20)');

// move background viruses

V.forEach(function(v) {

v.move();

v.display()

});

// change facemask type and color

button.mousePressed(function() {

masknum = !masknum;

beat++;

c = color(random([0, 1]) ? color(random(0, 200), random(200, 255)) : color(random(69, 79), random(166, 176), random(234, 244), random(200, 255)));

});

let pos = ctracker.getCurrentPosition();

if (!pos && !start) loading();

else if (!pos && start) noface();

else {

start = true;

button.show();

if (beat >= 4) {

push();

fill(10, 100, 200, 200);

textFont('Impact');

textAlign(CENTER);

textSize(40);

text('YOU ARE BEATEN UP\nFOR WEARING A MASK', width / 2, 40);

pop();

 

push();

fill(10);

textFont('Arial');

textAlign(LEFT);

textSize(10);

text('This is exactly\nwhat happened to\nsome Asians\nat the beginning of\nthe COVID-19\nin western countries.', width - 100, 150);

image(img, 40, 150, 50, 62);

pop();

}

 

push();

let ts = ((pos[13][0] + 30) - (pos[1][0] - 30)) * 2.5;

textSize(ts);

text('🥼', pos[7][0], pos[7][1] + pos[7][1] - pos[37][1] + 60 + ts / 2);

pop();

 

image(vid, 0, 0, width, height);

if (beat >= 4) Beat(pos);

 

drawMask(pos);

 

Shape(pos);

vid.mask(shape);

 

if (beat < 4) {

push();

let ts2 = ((pos[13][0] + 30) - (pos[1][0] - 30)) * 1.1;

textSize(ts2);

translate((pos[7][0] + pos[0][0]) / 2, pos[41][1]);

rotate(-20);

text('🧢', 0, 0);

pop();

} else {

if (random([0, 1, 1, 1, 1, 1, 1, 1, 1])) {

push();

let ts2 = ((pos[13][0] + 30) - (pos[1][0] - 30)) * 0.7;

textSize(ts2);

translate((pos[7][0] + pos[0][0]) / 2 - ts2 * 0.2, pos[41][1] - ts2 * 0.2);

rotate(10);

text('💥', 0, 0);

pop();

}

}

}

}