HandPose Rainbow Painting in p5.js
Mit dieser Übung lernst du, wie man mit p5.js eine Anwendung programmiert, die dich durch Handgesten zeichnen lässt. Durch verschiedene dieser Gesten kannst du dein Bild individuell gestalten, da du Größe und Farbe deines imaginären Pinsels verändern kannst.
Grundlagen
Hat dein Gerät eine Kamera? - Wenn ja, dann kannst du direkt loslegen!
Zuerst sollte ein p5.js Projekt geöffnet werden. Dies kannst du im p5.js Web-Editor tun oder mit den Visual Studio Code Plugins die auf der p5.js Webseite empfohlen werden.
Wir empfehlen den Web-Editor, da dieser von Anfang an alles beinhaltet was du benötigst.
1. Das passende HTML
Bevor du dich in Javascript-Action stürzen kannst, müssen die Grundlagen stimmen. Wir müssen ein paar Libraries importieren, welche die Grundlagen für unsere Modelle bieten, auf denen unsere Anwendung basiert.
Die erste Library ist eine p5.js Library, die zweite eine von Machine Learning, um das auf KI basierte Hand-Tracking zu importieren.
Wichtig ist hier die Machine Learning Library, sonst funktioniert die Anwendung nicht. In den Head fügst du also folgende zwei Libraries ein:
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.10.0/p5.js"></script>
<script src="https://unpkg.com/ml5@1/dist/ml5.min.js"></script>
2. Variablen initialisieren
Nun kannst du dein p5.js Projekt beginnen, in dem du im Javascript Teil der Datei die ersten Variablen als Grundlagen für den späteren Code initialisierst.
Zuerst entfernst du möglichen Code, der bereits automatisch generiert wurde. Diesen benötigen wir für dieses Projekt nicht. Dann beginnst du mit folgenden Variablen.
let video;
let handPose;
let hands = [];
let painting;
let px = 0;
let py = 0;
let sw = 8;
let hueVal = 0;
let currentColor;
let lastResetTime = 0;
let resetCooldown = 7000; // Millisekunden
let video;
initialisiert eine Video-Instanz um die Kameradaten zu verarbeiten.
let handPose;
und let hands = [];
verarbeitet die Handdaten.
let painting; sowie let px = 0;
und let py = 0;
verarbeiten das Gemalte und dessen Verhalten im Zusammenhang mit den Handdaten und dem Tracking.
let sw = 8;
und let hueVal = 0;
sowie let currentColor;
verarbeitet die Farbe selber und wie sie sich verhält.
let lastResetTime = 0;
und let resetCooldown = 7000; // Millisekunden
Initialisiert einen Reset und einen dazugehörigen Cooldown um das Bild zu löschen.
3. HandPose laden
Hier laden wir das Handtracking-Modell von ml5.js vor dem Start. Mit flipped: true
wird das Webcambild gespiegelt dargestellt - das ist intuitiver für den Nutzer.
function preload() {
handPose = ml5.handPose({ flipped: true });
}
Damit du die Handdaten verarbeiten kannst, brauchst du die Funktion gotHands(results)
. Jedes Mal, wenn eine neue Hand erkannt wird, wird sie in der hands
-Liste gespeichert.
function gotHands(results) {
hands = results;
}
4. Canvas und Video einrichten
Wir erstellen hier zwei Canvases: eines für die Anzeige der Webcam createCanvas()
und eines zum Zeichnen createGraphics()
. Letzteres ist durch painting.clear()
unsichtbar.
Nun aktivierst du das Webcam-Video mit createCapture(VIDEO)
, wobei das Bild durch flipped: true
gespiegelt wird. Das video.hide()
verhindert, dass p5.js das Video automatisch rendert.
function setup() {
createCanvas(640, 480);
painting = createGraphics(640, 480);
painting.clear();
video = createCapture(VIDEO, { flipped: true });
video.hide();
Um das Handtracking auf der Webcam zu starten brauchst du das handPose aus ml5.js
Den Farbmodus haben wir auf HSL gesetzt und mir currentColor
bestimmen wir die aktuelle Zeichenfarbe.
handPose.detectStart(video, gotHands);
colorMode(HSL, 360, 100, 100);
currentColor = color(0, 100, 50);
}
5. Fingererkennung zum Zeichnen
Mit der Funktion function draw()
legen wir fest, mit welcher Hand was gezeichnet wird. Zuerst initialisieren wir das Bild auf dem gezeichnet werden soll, also unser Video. Diesem geben wir keine Farbwerte, damit es unverändert bleibt.
function draw() {
image(video, 0, 0);
if (hands.length > 0) {
let now = millis();
let rightHand, leftHand;
for (let hand of hands) {
if (hand.handedness == 'Right') {
let index = hand.index_finger_tip;
let thumb = hand.thumb_tip;
rightHand = { index, thumb };
}
if (hand.handedness == 'Left') {
let index = hand.index_finger_tip;
let thumb = hand.thumb_tip;
leftHand = { index, thumb };
}
}
Mit den If-Statements deklarieren wir, wann die Anwendung unsere Finger zum Zeichnen erkennt und welchen Finger es dabei nutzen soll. Wir beziehen uns hier auf Daumen und Zeigefinger zum Zeichnen und Größe verändern.
Auch bringen wir der Anwendung hier bei, welche Hand welche ist, also was die Linke und was die Rechte Hand ist, da diese unterschiedliche Funktionen besitzen.
6. Canvas löschen mit Geste
Wenn der Daumen über dem Zeigefinger liegt (also „Daumen hoch“) und die letzte Löschung mehr als 7 Sekunden her ist, wird das Bild gelöscht. Den Cooldown haben wir bereits in den Variablen bestimmt.
if (leftHand && leftHand.thumb.y < leftHand.index.y && now - lastResetTime > resetCooldown) {
painting.clear();
lastResetTime = now;
}
7. Zeichnen mit rechter Hand
Bevor du mit dem Zeichnen loslegen kannst, prüft das Programm zunächst, ob eine rechte Hand erkannt wurde. Sobald das der Fall ist, wird der Mittelpunkt zwischen Daumen und Zeigefinger mit let x, let y
berechnet.
Die so ermittelten Koordinaten x und y dienen dann als aktuelle Position deines virtuellen Pinsels. Ist der Abstand zwischen Daumen und Finger gering ( in unserem Fall Abstand < 20 Pixel), wird losgezeichnet.
if (rightHand) {
let x = (index.x + thumb.x) * 0.5;
let y = (index.y + thumb.y) * 0.5;
let d = dist(index.x, index.y, thumb.x, thumb.y);
if (d < 20) {
hueVal = (hueVal + 2) % 360;
currentColor = color(hueVal, 100, 50);
painting.stroke(currentColor);
painting.strokeWeight(sw * 0.5);
painting.line(px, py, x, y);
}
px = x;
py = y;
}
Um den Regenbogen-Effekt zu erzeugen, wird bei jedem Frame der Farbwert hueVal
um einen kleinen Schritt erhöht. Mit der Funktion stroke()
wird dann die Farbe der Linie festgelegt. Um einen sanften Malverlauf zu erzeugen, wird schließlich eine Linie von der vorherigen Pinselposition (px
, py
) zur aktuellen Position (x
, y
) gezogen.
8. Flexible Pinselgröße einstellen
Nun initiieren wir eine Größeneinstellung unseres imaginären Pinsels durch eine Handgeste.
// Brush size from left hand gesture
if (leftHand) {
let { index, thumb } = leftHand;
sw = dist(index.x, index.y, thumb.x, thumb.y);
fill(currentColor);
noStroke();
circle((index.x + thumb.x) / 2, (index.y + thumb.y) / 2, sw);
}
}
image(painting, 0, 0);
}
Durch let {index,thumb} = leftHand;
greifen wir auf das Array zu, in welchem die Finger und deren Bedeutung gespeichert wurden. Dann geben wir ihnen durch sw = dist(index.x,index.y,thumb.x,thumb.y);
Distanzen und Größenordnungen mit, aus welchen sich die Anwendung dann die Größe für den Pinsel berechnet. Durch fill(currentColor);
initialisieren wir die aktuelle Farbe unseres Pinsels, diese ist auch durch das Nutzen der gleichen Variable die Selbe wie die, mit der wir Zeichnen.
noStroke();
sorgt dafür, dass wir keine Umrandung bei unserer Farbe haben und circle((index.x + thumb.x) / 2, (index.y + thumb.y) / 2, sw);
berechnet die Größe des Kreises innerhalb unserer Handgeste und setzt den Kreis an dieser Stelle ein.
Schlussendlich wird das Bild dann durch image(painting,0,0);
erstellt, aktualisiert und auf das Video gelegt, als wäre es ein klassisches Javascript Canvas.
Inspiration und Quellen
Unsere Anwendung kannst du hier finden, um sie nutzen zu können, musst du eine Kamera an deinem Gerät haben, sowie zustimmen, dass der Browser sie benutzen darf.
Mit diesen Gesten kannt du die Anwendung bedienen:
Malen: Mache mit der rechten Hand eine Geste als würdest du einen Stift halten
Größe einstellen: Lege die Größe deines Pinselns zwischen Zeigefinger und Daumen deiner linken Hand fest
Löschen: Lösche das gemalte Bild mit einem Daumen hoch deiner linken Hand
Unsere Inspiration waren das HandPose Tutorial von CodingTrain, das auf ml5.js, also Machine Learning basiert, sowie der Rainbow Paintbrush von Kelly Lougheed.
Wir haben die Grundlagen des HandPosings von Coding Train um eine Daumen-Hoch-Geste die ebenfalls von CodingTrain zur Verfügung gestellt wird, erweitert, sowie den Hue-Verlauf von Kelly Lougheed hinzugefügt, um ein für uns ansprechenderes Endprojekt zu erhalten.