Final Project: Something ml5.js

Progress:

As previously mentioned in my final project concept, I intend to create an “electronic buddy” or an “art robot” (which sounds better than my previous term). Thus far, I have successfully established communication between p5 and Arduino. Specifically, I utilized the handpose model from the ml5 library to detect if the hand is within specific areas on the screen and transmit a corresponding index to the Arduino.

As this is only a preliminary test, I have two motors connected to the Arduino, rotating in one direction if the hand is in the upper third of the screen, halting if the hand is in the middle portion, and rotating in the opposite direction if the hand is in the lower third. Currently, there is only unidirectional communication from p5 to Arduino, with p5 sending data to the latter. I hope to achieve meaningful communication from the Arduino to p5 as I progress. I have included the p5 sketch, Arduino sketch, and a video of the test run for reference.

p5 sketch:

let handpose;
let value = -1;
let video;
let predictions = [];

function setup() {
  createCanvas(640, 480);
  video = createCapture(VIDEO);
  video.size(width, height);

  handpose = ml5.handpose(video, modelReady);

  // This sets up an event that fills the global variable "predictions"
  // with an array every time new hand poses are detected
  handpose.on("predict", results => {
    predictions = results;
  });

  // Hide the video element, and just show the canvas
  video.hide();
}

function modelReady() {
  console.log("Model ready!");
}

function draw() {
  if (!serialActive) {
    text("Press Space Bar to select Serial Port", 20, 30);
  } else {
    image(video, 0, 0, width, height);

    // We can call both functions to draw all keypoints and the skeletons
    drawKeypoints();

    // draws sections on the screen
    for (let i = 0; i <= width / 2; i += (width / 2) / 3) {
      stroke(255, 0, 0);
      line(i , 0, i, height);
    }

    for (let i = 0; i <= height; i += height / 3) {
      stroke(255, 0, 0);
      line(0, i, width / 2, i);
    }

  }
  
}


function keyPressed() {
  if (key == " ") {
    // important to have in order to start the serial connection!!
    setUpSerial();
  }
}

function readSerial(data) {

  if (data != null) {

    //////////////////////////////////
    //SEND TO ARDUINO HERE (handshake)
    //////////////////////////////////
    let sendToArduino = value + "\n";
    writeSerial(sendToArduino);
  }
}

// A function to draw ellipses over the detected keypoints
function drawKeypoints() {
  for (let i = 0; i < predictions.length; i += 1) {
    const prediction = predictions[i];
    let area = [0, 0, 0, 0, 0, 0, 0, 0, 0];
    for (let j = 0; j < prediction.landmarks.length; j += 1) {
      const keypoint = prediction.landmarks[j];
      fill(0, 255, 0);
      noStroke();
      ellipse(keypoint[0], keypoint[1], 10, 10);
      
      // count number of trues
      // -- helps to detect the area the detected hand is in
      if (withinTopLeft(keypoint[0], keypoint[1])) {
        area[0] += 1;
      }
      if (withinTopCenter(keypoint[0], keypoint[1])) {
        area[1] += 1;
      }
      if (withinTopRight(keypoint[0], keypoint[1])) {
        area[2] += 1;
      }
      if (withinMiddleLeft(keypoint[0], keypoint[1])) {
        area[3] += 1;
      }
      if (withinMiddleCenter(keypoint[0], keypoint[1])) {
        area[4] += 1;
      }
      if (withinMiddleRight(keypoint[0], keypoint[1])) {
        area[5] += 1;
      }
      if (withinBottomLeft(keypoint[0], keypoint[1])) {
        area[6] += 1;
      }
      if (withinBottomCenter(keypoint[0], keypoint[1])) {
        area[7] += 1;
      }
      if (withinBottomRight(keypoint[0], keypoint[1])) {
        area[8] += 1;
      }
      // end of count
    }
    
    // print index
    for (let i = 0; i < area.length; i += 1) {
      if (area[i] == 21) {
        value = i;
      }
    }
  }
}

// returns true if a point is in a specific region
function withinTopLeft(x, y) {
  if (x >= 0 && x < (width / 2) / 3 && y >=0 && y < height / 3) {
    return true;
  }
  return false;
}

function withinTopCenter(x, y) {
  if (x > (width / 2) / 3  && x < 2 * (width / 2) / 3 && 
      y >=0 && y < height / 3) {
    return true;
  }
  return false;
}

function withinTopRight(x, y) {
  if (x > 2 * (width / 2) / 3 && x < (width / 2) && 
      y >=0 && y < height / 3) {
    return true;
  }
  return false;
}

function withinMiddleLeft(x, y) {
  if (x >= 0 && x < (width / 2) / 3 && y > height / 3 && y < 2 * height / 3) {
    return true;
  }
  return false;
}

function withinMiddleCenter(x, y) {
  if (x > (width / 2) / 3  && x < 2 * (width / 2) / 3 && 
      y > height / 3 && y < 2 * height / 3) {
    return true;
  }
  return false;
}

function withinMiddleRight(x, y) {
  if (x > 2 * (width / 2) / 3 && x < (width / 2) && 
      y > height / 3 && y < 2 * height / 3) {
    return true;
  }
  return false;
}

function withinBottomLeft(x, y) {
  if (x >= 0 && x < (width / 2) / 3 && y > 2 * height / 3 && y < height) {
    return true;
  }
  return false;
}

function withinBottomCenter(x, y) {
  if (x > (width / 2) / 3  && x < 2 * (width / 2) / 3 &&
      y > 2 * height / 3 && y < height) {
    return true;
  }
  return false;
}

function withinBottomRight(x, y) {
  if (x > 2 * (width / 2) / 3 && x < (width / 2) &&
      y > 2 * height / 3 && y < height) {
    return true;
  }
  return false;
}

Arduino sketch:

const int ain1Pin = 3;
const int ain2Pin = 4;
const int pwmAPin = 5;

const int bin1Pin = 8;
const int bin2Pin = 7;
const int pwmBPin = 6;


void setup() {
  // Start serial communication so we can send data
  // over the USB connection to our p5js sketch
  Serial.begin(9600);

  pinMode(LED_BUILTIN, OUTPUT);

  pinMode(ain1Pin, OUTPUT);
  pinMode(ain2Pin, OUTPUT);
  pinMode(pwmAPin, OUTPUT); // not needed really

  pinMode(bin1Pin, OUTPUT);
  pinMode(bin2Pin, OUTPUT);
  pinMode(pwmBPin, OUTPUT); // not needed really

  // TEST BEGIN
  // turn in one direction, full speed
  analogWrite(pwmAPin, 255);
  analogWrite(pwmBPin, 255);
  digitalWrite(ain1Pin, HIGH);
  digitalWrite(ain2Pin, LOW);
  digitalWrite(bin1Pin, HIGH);
  digitalWrite(bin2Pin, LOW);

  // stay here for a second
  delay(1000);

  // slow down
  int speed = 255;
  while (speed--) {
    analogWrite(pwmAPin, speed);
    analogWrite(pwmBPin, speed);
    delay(20);
  }

  // TEST END

  // start the handshake
  while (Serial.available() <= 0) {
    digitalWrite(LED_BUILTIN, HIGH); // on/blink while waiting for serial data
    Serial.println("0,0"); // send a starting message
    delay(300);            // wait 1/3 second
    digitalWrite(LED_BUILTIN, LOW);
    delay(50);
  }

}

void loop() {

  while (Serial.available()) {
    // sends dummy data to p5
    Serial.println("0,0");

    // led on while receiving data
    digitalWrite(LED_BUILTIN, HIGH); 

    // gets value from p5
    int value = Serial.parseInt();

    // changes brightness of the led
    if (Serial.read() == '\n') {
      
      if (value >= 0 && value <= 2) {
        digitalWrite(ain1Pin, HIGH);
        digitalWrite(ain2Pin, LOW);
        digitalWrite(bin1Pin, HIGH);
        digitalWrite(bin2Pin, LOW);

        analogWrite(pwmAPin, 255);
        analogWrite(pwmBPin, 255);

      }

      else if (value >= 3 && value <= 5) {
        analogWrite(pwmAPin, 0);
        analogWrite(pwmBPin, 0);
      }

      else {
        digitalWrite(ain1Pin, LOW);
        digitalWrite(ain2Pin, HIGH);
        digitalWrite(bin1Pin, LOW);
        digitalWrite(bin2Pin, HIGH);

        analogWrite(pwmAPin, 255);
        analogWrite(pwmBPin, 255);
      }
    }
  }
  // led off at end of reading
  digitalWrite(LED_BUILTIN, LOW);
}

Video:

Leave a Reply