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:

  1. Malen: Mache mit der rechten Hand eine Geste als würdest du einen Stift halten

  2. Größe einstellen: Lege die Größe deines Pinselns zwischen Zeigefinger und Daumen deiner linken Hand fest

  3. 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.


© 0t1

Cookies

0t1 mag keine Kekse (und kein Tracking). Wir verwenden lediglich notwendige Cookies für essentielle Funktionen.

Wir verwenden Schriftarten von Adobe Fonts. Dafür stellt dein Browser eine Verbindung zu den Servern von Adobe in den USA her. Wenn du unsere Seite nutzen möchtest, musst du dich damit einverstanden erklären.

Weitere Informationen in unserer Datenschutzerklärung.