P5
und ML5: Hand-Tracking
in Bildern
Ü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 bild; //
Variable für das zu analysierende Bild
var hands = []; // Array für die erfassten
Handkonturen
var bildweite = 550; //
Weite des zu untersuchenden Bilds
var bildhoehe = 384; //
Höhe des zu untersuchenden Bilds
... zunächst
das gewünschte Bild vorausgeladen ...
function preload(){
bild = loadImage("bilder/renaissance2.png");
//
gewünschtes Bild vorausladen
}
... und in
der setup-Funktion der
Canvas hergestellt, in dem das Bild dargestellt wird. Gleichzeitig wird
das Modell für die Handposen geladen und die Funktion modelReady()
aufgerufen, sobald das Modell fertig geladen ist. Der
Canvas sollte dabei mindestens so groß wie die Maße des Bilds
sein.
function setup() {
var container = createCanvas(800,600); //
Canvas erstellen
container.parent('p5container'); // an
DIV-Container anhängen
handpose = ml5.handpose(modelReady); //
Verbindung zu handPose herstellen, um
// Hände zu erkennen und das Ergebnis in der Variablen handpose zu
speichern.
}
Sobald das
handPose-Modell fertig geladen ist, wird es zur Erkennung der Hand eingesetzt
und speichert die erkannten Punkte im Array hands.
function modelReady()
{
handpose.on("predict", results => { //
sobald eine Hand erkannt wird
hands = results; // fülle ein Array
(results) mit einer zahlenmäßigen Beschreibung der erkannten
Hand und weise es der Variablen hands zu
});
handpose.predict(bild); // sobald das handPose-Modell
fertig geladen ist, beginne mit der Handposenerkennung
}
In der draw-Funktion
werden dann die einzelnen markanten Punkte der Hand (keypoints)
ausgegeben, die dann beliebig weiter verarbeitet werden können, z.B.:
function draw() {
background(255,255,255); // Hintergrund
halb durchsichtig gestalten, damit mit der
// Maus zwischen Handkontur und Bild hin- und hergeblendet werden kann
cursor(HAND); // Mauszeiger in eine Hand
umwandeln
image(bild, 0, 0, bildweite, bildhoehe); //
zeige das Bild in seiner Höhe und Weite
// an
tint(255, 255, 255, map(mouseY, 0, bildhoehe, 0, 255)); //
Transparenz des
// Bilds mit der Y-Position der Maus verknüpfen
if (hands.length > 0) { // Sobald eine
Hand erkannt wird
drawKeypoints(); //
einzelne Punkte der Hand 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 bild;
var hands = [];
var bildweite = 550;
var bildhoehe = 384;
function preload(){
bild = loadImage("bilder/renaissance2.png");
}
function setup() {
var container = createCanvas(800,500);
container.parent('p5container');
handpose = ml5.handpose(modelReady);
}
function modelReady()
{
console.log("Model ready!");
handpose.on("predict", results => {
hands = results;
});
handpose.predict(bild);
}
function draw() {
background(255,255,255);
cursor(HAND);
image(bild, 0, 0, bildweite, bildhoehe);
tint(255, 255, 255, map(mouseY, 0, bildhoehe, 0, 255));
if (hands.length > 0) {
drawKeypoints();
console.log("Keypoints zeichnen");
}
}
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();
}
}
</script>
<DIV id="p5container"
style="width:800;border: 1px solid #333;box-shadow: 8px 8px 5px
#444;padding: 8px 12px;background-color:ffffff"></DIV>
|