P5
und ML5: Hand-Tracking
in Videos
Über ML5 kann P5 auf Machine-Learning-Modelle zugeifen, die im Bereich
der Mustererkennung Dinge ermöglichen, die vor einigen Jahren für
einfache Scriptsprachen wie Javascript oder P5 noch undenkbar waren. Einer
dieser Hauptbereiche ist die Erkennung von menschlichen Posen und Bewegungen,
so dass nun via Javascript/P5 Bewegungen und Posen erfasst und in Zahlen
umgesetzt werden können, die dann mit anderen Zahlenwerten (z.B.
aus physiologischen Messungen, Timbre Feature Analysen, EEG, Eye-Tracking
etc.) in Verbindung gebracht werden können.
Um auf diese
Möglichkeiten zugreifen zu können, muss im Header der HTML-Seite
neben der P5-Library
auch die ML5-Library
eingebunden werden, so
dass es im Header der Seite folgendermaßen heisst:
<script src="header/ml5.min_0.12.2.js"></script>
Im Script
wird nach der Deklarierung der für die Verbindung mit der Handerkennung
via handPose und für die Bilddarstellung notwendigen Variablen ...
var handpose; //
Array für das von handPose zurückgelieferte Ergebnis
var video; //
Variable für das zu analysierende Video
var videoIsPlaying = false; // Variable
für den Zustand des Videos
var hands = []; // Array für die erfassten
Handkonturen
var bildweite = 800; //
Weite des zu untersuchenden Bilds
var bildhoehe = 480; //
Höhe des zu untersuchenden Bilds
... die setup-Funktion
gestartet, in der sowohl der Canvas zur Darstellung von Video und Handerkennung
erstellt wird als auch die Verbindung mit dem Modell für die Handerkennung
gestartet wird Der Canvas sollte dabei mindestens so groß wie die
Maße des Videos sein:
function setup() {
var container = createCanvas(800,500);
// Canvas erstellen
container.parent('p5container'); // an
DIV-Container anhängen
container.mouseClicked(togglePlay); //
auf Klick auf Canvas reagieren
video = createVideo("bilder/bertsch.mp4");
// Video laden
video.size(bildweite, bildhoehe); // Videogröße
einstellen
handpose = ml5.handpose(video, modelReady); //
Verbindung zu handPose
// herstellen, um Hände zu erkennen und das Ergebnis in der Variablen
handpose zu speichern.
handpose.on("hand", results => { //
sobald eine Hand erkannt wird
hands = results; // fülle ein Array
mit einer zahlenmäßigen Beschreibung der
// erkannten Hand
});
video.hide(); // verstecke das Video (es
würde sonst neben dem Canvas zusätzlich zum
// verarbeiteten Video erscheinen)
}
Sobald die
Verbindung zu handPose hergestellt ist, können weitere Funktionen
gestartet werden
function modelReady()
{
console.log("Model ready!"); //
sende die Nachricht, dass das Modell geladen und bereit
// ist
}
In der draw-Funktion
werden dann die einzelnen markanten Punkte der Hand ausgegeben, die dann
beliebig weiter verarbeitet werden können, z.B.:
function draw() {
background(255,255,255, 125); // Hintergrund
halb durchsichtig gestalten, damit mit der
// Maus zwischen Handkontur und Video hin- und hergeblendet werden kann
cursor(HAND); // Mauszeiger in eine
Hand umwandeln
image(video, 0, 0, bildweite, bildhoehe);//
zeige das Video in seiner Höhe und
// Weite an
tint(255, 255, 255, map(mouseY, 0, height, 0, 255), ); //
Transparenz des
// Videos mit der Y-Position der Maus verknüpfen
drawKeypoints();
// einzelne Handpunkte ausgeben
}
Am Ende der
draw-Funktion werden in
der Funktion drawKeypoints()
die 21 Punkte (landmarks)
ausgegeben, in die die Hand für die Erkennung der Posen aufgelöst
wird (ML5 kann bis jetzt leider nur eine Hand erkennen), z.B.:
function drawKeypoints()
{
for (var i = 0; i < hands.length; i += 1) { //
sobald eine Hand erkannt wird...
handgelenk = hands[i].landmarks[0]; //
weise den nullten Landmark dem Handgelenk zu
daumen1 = hands[i].landmarks[1];
// weise die Landmarks 1-4 den Gliedern
des Daumens
// zu...
daumen2 = hands[i].landmarks[2]; //...
daumen3 = hands[i].landmarks[3]; //...
daumen4 = hands[i].landmarks[4]; //...
noStroke();fill(150, 50, 50); //...
wähle dunkelrot und ...
ellipse(daumen4[0], daumen4[1],15,15); //...
markiere die Daumenspitze mit einem
// roten Kreis
zeigefinger1 = hands[i].landmarks[5];
// weise die Landmarks 5-8 den Gliedern
des
// Zeigefingers zu...
zeigefinger2 = hands[i].landmarks[6]; //...
zeigefinger3 = hands[i].landmarks[7]; //...
zeigefinger4 = hands[i].landmarks[8]; //...
noStroke();fill(150, 150, 50); //...
wähle grüngelb und ...
ellipse(zeigefinger4[0], zeigefinger4[1],15,15); //...
markiere die
// Zeigefingerspitze mit einem grüngelben Kreis
mittelfinger1 = hands[i].landmarks[9];
// weise die Landmarks 9-12 den Gliedern
des
// Mittelfingers zu...
mittelfinger2 = hands[i].landmarks[10]; //...
mittelfinger3 = hands[i].landmarks[11]; //...
mittelfinger4 = hands[i].landmarks[12]; //...
noStroke();fill(50, 150, 50); //...
wähle grün und ...
ellipse(mittelfinger4[0], mittelfinger4[1],15,15); //...
markiere die
// Mittelfingerspitze mit einem grünen Kreis
ringfinger1 = hands[i].landmarks[13]; //
weise die Landmarks 13-16 den Gliedern des
// Mittelfingers zu...
ringfinger2 = hands[i].landmarks[14]; //...
ringfinger3 = hands[i].landmarks[15]; //...
ringfinger4 = hands[i].landmarks[16]; //...
noStroke();fill(50, 150, 150); //...
wähle türkis und ...
ellipse(ringfinger4[0], ringfinger4[1],15,15); //...
markiere die
// Mittelfingerspitze mit einem türkisen Kreis
kleinerfinger1 = hands[i].landmarks[17];
// weise die Landmarks 17-20 den Gliedern
// des kleinen Fingers zu...
kleinerfinger2 = hands[i].landmarks[18]; //...
kleinerfinger3 = hands[i].landmarks[19]; //...
kleinerfinger4 = hands[i].landmarks[20]; //...
noStroke();fill(50, 50, 150); //...
wähle dunkelblau und ...
ellipse(kleinerfinger4[0], kleinerfinger4[1],15,15); //...
markiere die
// Spitze des kleinen Fingers mit einem blauen Kreis
noStroke();fill(150,
150, 150); //...
wähle grau und ...
ellipse(handgelenk[0], handgelenk[1],15,15); //...
markiere das Handgelenk mit
// einem grauen Kreis
Wenn die
Punkte zur Verdeutlichung der Hand für eine Handkontur miteinander
verbunden werden sollen, kann man für jeden Finger via beginShape()
- vertex(x,y) - und
endShape() eine Form erstellen und diese entsprechend einfärben,
z.B.:
noFill(); strokeWeight(2);
// wähle als Strichstärke
2 Pixel und ...
stroke(150, 50, 50); //...
wähle dunkelrot als Strichfarbe und ...
beginShape(); // ... verbinde die Daumenglieder
miteinander
vertex(handgelenk[0], handgelenk[1]);
vertex(daumen1[0], daumen1[1]);
vertex(daumen2[0], daumen2[1]);
vertex(daumen3[0], daumen3[1]);
vertex(daumen4[0], daumen4[1]);
endShape(); // schließe die Form
und verbinde die Punkte mit Strichen.
stroke(150, 150, 50);
//...
wähle gelbgrün als Strichfarbe und ...
beginShape(); // ... verbinde die Zeigefingerglieder
miteinander
vertex(handgelenk[0], handgelenk[1]);
vertex(zeigefinger1[0], zeigefinger1[1]);
vertex(zeigefinger2[0], zeigefinger2[1]);
vertex(zeigefinger3[0], zeigefinger3[1]);
vertex(zeigefinger4[0], zeigefinger4[1]);
endShape(); // schließe die Form
und verbinde die Punkte mit Strichen.
stroke(50, 150, 50);
//...
wähle grün als Strichfarbe und ...
beginShape(); // ... verbinde die Mittelfingerglieder
miteinander
vertex(handgelenk[0], handgelenk[1]);
vertex(mittelfinger1[0], mittelfinger1[1]);
vertex(mittelfinger2[0], mittelfinger2[1]);
vertex(mittelfinger3[0], mittelfinger3[1]);
vertex(mittelfinger4[0], mittelfinger4[1]);
endShape(); // schließe die Form
und verbinde die Punkte mit Strichen.
stroke(50, 150, 150);
//...
wähle türkis als Strichfarbe und ...
beginShape(); // ... verbinde die Ringfingerglieder
miteinander
vertex(handgelenk[0], handgelenk[1]);
vertex(ringfinger1[0], ringfinger1[1]);
vertex(ringfinger2[0], ringfinger2[1]);
vertex(ringfinger3[0], ringfinger3[1]);
vertex(ringfinger4[0], ringfinger4[1]);
endShape(); // schließe die Form
und verbinde die Punkte mit Strichen.
stroke(50, 50, 150);
//...
wähle dunkelblau als Strichfarbe und ...
beginShape(); // ... verbinde die Glieder
des kleinen Fingers miteinander
vertex(handgelenk[0], handgelenk[1]);
vertex(kleinerfinger1[0], kleinerfinger1[1]);
vertex(kleinerfinger2[0], kleinerfinger2[1]);
vertex(kleinerfinger3[0], kleinerfinger3[1]);
vertex(kleinerfinger4[0], kleinerfinger4[1]);
endShape(); // schließe die Form
und verbinde die Punkte mit Strichen.
Insgesamt
sieht das Script dann folgendermaßen aus:
<script src="header/ml5.min_0.12.2.js"></script>
<script src="header/p5.js"></script>
<script>
var handpose;
var video;
var videoIsPlaying = false;
var hands = [];
var bildweite = 800;
var bildhoehe = 480;
function setup() {
var container = createCanvas(800,500);
container.parent('p5container');
container.mouseClicked(togglePlay);
video = createVideo("bilder/bertsch.mp4");
video.size(bildweite, bildhoehe);
handpose = ml5.handpose(video, modelReady);
handpose.on("hand", results => {
hands = results;
});
video.hide();
}
function modelReady()
{
console.log("Model ready!");
}
function draw() {
background(255,255,255);
cursor(HAND);
image(video, 0, 0, bildweite, bildhoehe);
tint(255, 255, 255, map(mouseY, 0, bildhoehe, 0, 255));
if (hands.length > 0) {
drawKeypoints();
}
}
function drawKeypoints() {
console.log("Keypoints zeichnen");
for (var i = 0; i < hands.length; i += 1) {
handgelenk = hands[i].landmarks[0];
daumen1 = hands[i].landmarks[1];
daumen2 = hands[i].landmarks[2];
daumen3 = hands[i].landmarks[3];
daumen4 = hands[i].landmarks[4];noStroke();fill(150, 50, 50); ellipse(daumen4[0],
daumen4[1],15,15);
zeigefinger1 = hands[i].landmarks[5];
zeigefinger2 = hands[i].landmarks[6];
zeigefinger3 = hands[i].landmarks[7];
zeigefinger4 = hands[i].landmarks[8];noStroke();fill(150, 150, 50);
ellipse(zeigefinger4[0], zeigefinger4[1],15,15);
mittelfinger1 = hands[i].landmarks[9];
mittelfinger2 = hands[i].landmarks[10];
mittelfinger3 = hands[i].landmarks[11];
mittelfinger4 = hands[i].landmarks[12];noStroke();fill(50, 150, 50);
ellipse(mittelfinger4[0], mittelfinger4[1],15,15);
ringfinger1 = hands[i].landmarks[13];
ringfinger2 = hands[i].landmarks[14];
ringfinger3 = hands[i].landmarks[15];
ringfinger4 = hands[i].landmarks[16];noStroke();fill(50, 150, 150);
ellipse(ringfinger4[0], ringfinger4[1],15,15);
kleinerfinger1 =
hands[i].landmarks[17];
kleinerfinger2 = hands[i].landmarks[18];
kleinerfinger3 = hands[i].landmarks[19];
kleinerfinger4 = hands[i].landmarks[20];noStroke();fill(50, 50, 150);
ellipse(kleinerfinger4[0], kleinerfinger4[1],15,15);
noFill();fill(150,
150, 150); ellipse(handgelenk[0], handgelenk[1],15,15);
noFill();
strokeWeight(2);
stroke(150, 50, 50);
beginShape();
vertex(handgelenk[0], handgelenk[1]);
vertex(daumen1[0], daumen1[1]);
vertex(daumen2[0], daumen2[1]);
vertex(daumen3[0], daumen3[1]);
vertex(daumen4[0], daumen4[1]);
endShape();
stroke(150, 150, 50);
beginShape();
vertex(handgelenk[0], handgelenk[1]);
vertex(zeigefinger1[0], zeigefinger1[1]);
vertex(zeigefinger2[0], zeigefinger2[1]);
vertex(zeigefinger3[0], zeigefinger3[1]);
vertex(zeigefinger4[0], zeigefinger4[1]);
endShape();
stroke(50, 150, 50);
beginShape();
vertex(handgelenk[0], handgelenk[1]);
vertex(mittelfinger1[0], mittelfinger1[1]);
vertex(mittelfinger2[0], mittelfinger2[1]);
vertex(mittelfinger3[0], mittelfinger3[1]);
vertex(mittelfinger4[0], mittelfinger4[1]);
endShape();
stroke(50, 150, 150);
beginShape();
vertex(handgelenk[0], handgelenk[1]);
vertex(ringfinger1[0], ringfinger1[1]);
vertex(ringfinger2[0], ringfinger2[1]);
vertex(ringfinger3[0], ringfinger3[1]);
vertex(ringfinger4[0], ringfinger4[1]);
endShape();
stroke(50, 50, 150);
beginShape();
vertex(handgelenk[0], handgelenk[1]);
vertex(kleinerfinger1[0], kleinerfinger1[1]);
vertex(kleinerfinger2[0], kleinerfinger2[1]);
vertex(kleinerfinger3[0], kleinerfinger3[1]);
vertex(kleinerfinger4[0], kleinerfinger4[1]);
endShape();
}
}
function togglePlay(){
if (videoIsPlaying==true)
{
video.pause(); videoIsPlaying = false;
} else {
video.loop(); videoIsPlaying = true;
}
}
</script>
<DIV id="p5container"
style="width:800;border: 1px solid #333;box-shadow: 8px 8px 5px
#444;padding: 8px 12px;background-color:ffffff"></DIV>
|