diff --git a/roshambo/index.html b/roshambo/index.html new file mode 100644 index 0000000..4434803 --- /dev/null +++ b/roshambo/index.html @@ -0,0 +1,34 @@ + + + + + + handtracking + + + + + + + +

+ + + + \ No newline at end of file diff --git a/roshambo/sketch.js b/roshambo/sketch.js new file mode 100644 index 0000000..6ae51a6 --- /dev/null +++ b/roshambo/sketch.js @@ -0,0 +1,266 @@ +let video; +let handPose; +let hands = []; +let hand; +let handVals; +let fingers; +let handBounds; +let folders = {}; +let painting; +let connections = [ + [0, 1], + [1, 2], + [2, 3], + [3, 4], + [1, 5], + [5, 6], + [6, 7], + [7, 8], + [13, 9], + [9, 5], + [2, 5], + [9, 10], + [10, 11], + [11, 12], + [17, 13], + [13, 14], + [14, 15], + [15, 16], + [0, 17], + [17, 18], + [18, 19], + [19, 20] +]; +let clearStart = 0; +let videoScale; + +let conf = { + hands: { + palm_kps: [0, 1, 5, 9, 13, 17], + palm_kps_str: null, + Left: "#ff0000", + Right: "#0000ff", + show: true, + paint: true, + }, + canvas: { + bg: "#00000000" + }, + kps: { + size: 8, + color: "#000000", + draw: false, + }, + kp_conn: { + connect: true, + stroke: 1, + }, + kp_text: { + show: false, + size: 8, + offx: 10, + offy: 5, + color: "#000000ff" + }, + camera: { + show: true, + }, + hand_bounds: { + show: true, + stroke: 2 + } + +} + +let palm_kps = [0, 1, 5, 17]; + +function preload() { + handPose = ml5.handPose({ flipped: true }); +} + +function setup() { + createCanvas(window.innerWidth, window.innerHeight); + video = createCapture({ + video: { + }, + audio: false, + flipped: true, + }); + videoScale = Math.min(window.innerWidth / 640, window.innerHeight / 480); + video.size(640 * videoScale, 480 * videoScale); + handPose.detectStart(video, (results) => { hands = results; }); + + video.hide(); + +} + +function draw() { + clear(); + strokeWeight(1); + stroke(255); + fill(conf.canvas.bg); + rect(0, 0, width, height); + rect(0, 0, video.width, video.height); + + image(video, 0, 0); + + handBounds = {}; + + if (hands.length > 0) { + hand = hands[0]; + let index = { + x: hand.index_finger_tip.x, + y: hand.index_finger_tip.y + } + let middle = { + x: hand.middle_finger_tip.x, + y: hand.middle_finger_tip.y + } + let ring = { + x: hand.ring_finger_tip.x, + y: hand.ring_finger_tip.y + } + let pinky = { + x: hand.pinky_finger_tip.x, + y: hand.pinky_finger_tip.y + } + let thumb = { + x: hand.thumb_tip.x, + y: hand.thumb_tip.y + } + fingers = [thumb, index, middle, ring, pinky]; + + handVals = { + fingers: fingers, + handBounds: getBounds(hand.keypoints), + handRad: null, + palmBounds: getBounds(palm_kps.map(i => hand.keypoints[i])), + palmRad: null, + fingersBounds: getBounds(fingers), + fingersRad: null, + + + fingersInPalm: null, + fingersOnBounds: 0, + handedness: hand.handedness, + pose: "none", + } + + handVals.palmRad = dist(handVals.palmBounds.min.x, handVals.palmBounds.min.y, handVals.palmBounds.max.x, handVals.palmBounds.max.y) / 2; + handVals.handRad = dist(handVals.handBounds.min.x, handVals.handBounds.min.y, handVals.handBounds.max.x, handVals.handBounds.max.y) / 2; + handVals.fingersRad = dist(handVals.fingersBounds.min.x, handVals.fingersBounds.min.y, handVals.fingersBounds.max.x, handVals.fingersBounds.max.y) / 2; + + handVals.fingersInPalm = fingers.filter(f => dist(f.x, f.y, handVals.palmBounds.c.x, handVals.palmBounds.c.y) < handVals.palmRad * 1.2).length; + handVals.fingersOnBounds = fingers.filter(f => dist(f.x, f.y, handVals.fingersBounds.c.x, handVals.fingersBounds.c.y) < handVals.fingersRad * 1.2).length; + + loopKP(hand, handVals); + // drawConnections(hand); + // drawHandText(hand); + drawBounds(handVals); + detectGestures(handVals); + + debug.innerHTML = ` + Hands detected: ${hands.length}
+ Palm radius: ${handVals.palmRad.toFixed(2)}
+ Fingers in palm: ${handVals.fingersInPalm}
+ Hand radius: ${handVals.handRad.toFixed(2)}
+ Fingers on edge: ${handVals.fingersOnBounds}
+ Pose: ${handVals.pose}
+ `; + + } + +} + +function drawConnections(hand) { + for (let j = 0; j < connections.length; j++) { + let pointAIndex = connections[j][0]; + let pointBIndex = connections[j][1]; + let pointA = hand.keypoints[pointAIndex]; + let pointB = hand.keypoints[pointBIndex]; + stroke(conf.hands[hand.handedness]); + strokeWeight(conf.kp_conn.stroke); + line(pointA.x, pointA.y, pointB.x, pointB.y); + } +} + +function detectGestures(handVals) { + if (handVals.fingersInPalm >= 4) { + stroke(0, 255, 0); + fill(0, 255, 0, 100); + // circle(handVals.palmBounds.c.x, handVals.palmBounds.c.y, handVals.palmRad * 2); + handVals.pose = "🪨"; + } else if (handVals.fingersOnBounds == 2 && handVals.fingersInPalm >= 2) { + // && dist(handVals.fingers[0].x, handVals.fingers[0].y, handVals.palmBounds.c.x, handVals.palmBounds.c.y) < handVals.palmRad * 1.2 + stroke(255, 0, 0); + fill(255, 0, 0, 100); + + handVals.pose = "✂️"; + } else if (handVals.fingersInPalm == 0 && handVals.fingersOnBounds >= 3) { + handVals.pose = "📄"; + } + + textSize(64); + fill(conf.hands[handVals.handedness]); + text(handVals.pose, handVals.palmBounds.c.x - textWidth(handVals.pose) / 2, handVals.palmBounds.c.y); +} + +function drawBounds(handVals) { + let x = handVals.handBounds.min.x; + let y = handVals.handBounds.min.y; + let w = handVals.handBounds.max.x - x; + let h = handVals.handBounds.max.y - y; + + noFill(); + stroke(conf.hands[handVals.handedness]); + strokeWeight(conf.hand_bounds.stroke); + // rect(x, y, w, h); + + // circle(handVals.palmBounds.c.x, handVals.palmBounds.c.y, handVals.palmRad * 2);// Palm center + // circle(handVals.fingersBounds.c.x, handVals.fingersBounds.c.y, handVals.fingersRad * 2);// Finger center + // // rect(handVals.fingersBounds.min.x, handVals.fingersBounds.min.y, handVals.fingersBounds.max.x - handVals.fingersBounds.min.x, handVals.fingersBounds.max.y - handVals.fingersBounds.min.y);// Finger bounds + // // rect(handVals.palmBounds.min.x, handVals.palmBounds.min.y, handVals.palmBounds.max.x - handVals.palmBounds.min.x, handVals.palmBounds.max.y - handVals.palmBounds.min.y);// Palm bounds +} + +function loopKP(hand, handVals) { + for (let j = 0; j < hand.keypoints.length; j++) { + const kp = hand.keypoints[j]; + fill(conf.kps.color); + noStroke(); + if (conf.kps.draw) circle(kp.x, kp.y, conf.kps.size); + + fill(conf.kp_text.color); + textSize(conf.kp_text.size); + // text(`[${j}]`, kp.x + conf.kp_text.offx, kp.y - conf.kp_text.offy); + + + } + handVals.fingersOnBounds = fingers.filter(f => f.x == handVals.handBounds.min.x || f.x == handVals.handBounds.max.x || f.y == handVals.handBounds.min.y || f.y == handVals.handBounds.max.y).length; + +} + +function drawHandText(hand) { + fill(conf.hands[hand.handedness]); + textSize(conf.kp_text.size * 1.5); + + let avgX = conf.hands.palm_kps.reduce((sum, i) => sum + hand.keypoints[i].x, 0) / conf.hands.palm_kps.length; + let avgY = conf.hands.palm_kps.reduce((sum, i) => sum + hand.keypoints[i].y, 0) / conf.hands.palm_kps.length; + let offset = (conf.kp_text.size * 1.5) / 2; + text(hand.handedness, avgX - textWidth(hand.handedness) / 2, avgY + offset); +} +function getBounds(arr) { + let b = { min: { x: Infinity, y: Infinity }, max: { x: -Infinity, y: -Infinity }, c: { x: 0, y: 0 } }; + for (let i of arr) { + if (i.x < b.min.x) b.min.x = i.x + if (i.y < b.min.y) b.min.y = i.y + if (i.x > b.max.x) b.max.x = i.x + if (i.y > b.max.y) b.max.y = i.y + } + b.c.x = (b.min.x + b.max.x) / 2; + b.c.y = (b.min.y + b.max.y) / 2; + return b; +} + +const clamp = (min, num, max) => Math.min(Math.max(num, min), max); + +