xxxxxxxxxx
282
// ml5.js Real-Time Body Pose Detection with Smoothing
// https://thecodingtrain.com/tracks/ml5js-beginners-guide/ml5/7-bodypose/pose-detection
let video;
let bodyPose;
let connections;
let motionThreshold = 50;
let lastParticleTime = 0;
let sound;
let poses = [];
let prevPoses = [];
let particles = [];
let branches = [];
let excludeIndices = [0, 3, 4]; // 需要排除的点索引
// Variables for smoothing the nose position using linear interpolation
let lerpedX = 0;
let lerpedY = 0;
function preload() {
// Initialize MoveNet model with flipped video input
bodyPose = ml5.bodyPose("MoveNet", { flipped: true });
sound = loadSound("mySound.mp3");
}
function mousePressed() {
// Log detected pose data to the console when the mouse is pressed
console.log(poses.length);
}
function gotPoses(results) {
if (results.length > 0) {
if (prevPoses.length > 0) {
// 用上一帧的数据补全缺失的关键点
for (let i = 0; i < results[0].keypoints.length; i++) {
if (results[0].keypoints[i].confidence < 0.1) {
results[0].keypoints[i] = prevPoses[0].keypoints[i]; // 复制上一帧的数据
}
}
}
prevPoses = JSON.parse(JSON.stringify(poses)); // ✅ 先存当前的 poses
poses = results; // ✅ 更新新的 poses
} else {
poses = prevPoses; // 没有检测到就使用上次的数据
}
}
function setup() {
// Create canvas for displaying video feed
createCanvas(windowWidth, windowHeight);
frameRate(20);
// Capture live video with flipped orientation
video = createCapture(VIDEO, { flipped: true }).size(width, height);
//video.hide();
sound.loop();
// Start detecting poses from the video feed
bodyPose.detectStart(video, gotPoses);
}
function draw() {
background(0);
if (poses.length > 0) {
let pose = poses[0];
let bodyposeData = pose.keypoints; // ✅ 正确用法
let cTime = millis();
let motionDetected = detectMotion(bodyposeData);
//console.log(bodyposeData);
let filteredKeypoints = bodyposeData.filter(
(_, index) => !excludeIndices.includes(index)
);
if (motionDetected) {
// if (cTime - lastParticleTime > 300) {
lastParticleTime = cTime; //
let intersection = lineIntersection(
filteredKeypoints[2].x,
filteredKeypoints[2].y,
filteredKeypoints[9].x,
filteredKeypoints[9].y,
filteredKeypoints[3].x,
filteredKeypoints[3].y,
filteredKeypoints[8].x,
filteredKeypoints[8].y
);
if (intersection) {
// let cx1 = intersection.x; // 圆心的 x 坐标
// let cy1 = intersection.y; // 圆心的 y 坐标
// let r1 = random(30, 40); // 半径 r 是一个随机值
// let theta1 = random(TWO_PI); // 随机角度 θ,范围是 0 到 2π
// // 计算圆周上点的坐标
// let keyx1 = cx1 + r1 * cos(theta1);
// let keyy1 = cy1 + r1 * sin(theta1);
let cx2 = intersection.x; // 圆心的 x 坐标
let cy2 = intersection.y; // 圆心的 y 坐标
let r2 = random(100, 150); // 半径 r 是一个随机值
let theta2 = random(TWO_PI); // 随机角度 θ,范围是 0 到 2π
// 计算圆周上点的坐标
let keyx2 = cx2 + r2 * cos(theta2);
let keyy2 = cy2 + r2 * sin(theta2);
// let pointOnCircle1 = createVector(keyx1, keyy1);
// particles.push(new Particle(keyx1, keyy1));
let pointOnCircle2 = createVector(keyx2, keyy2);
particles.push(new Particle(keyx2, keyy2));
}
for (let j = 0; j < filteredKeypoints.length; j++) {
let keypoint = filteredKeypoints[j];
// 假设圆心 (cx, cy) 和半径 r 已定义
let cx = keypoint.x; // 圆心的 x 坐标
let cy = keypoint.y; // 圆心的 y 坐标
let r = random(60, 80); // 半径 r 是一个随机值
let theta = random(TWO_PI); // 随机角度 θ,范围是 0 到 2π
// 计算圆周上点的坐标
let keyx = cx + r * cos(theta);
let keyy = cy + r * sin(theta);
let pointOnCircle = createVector(keyx, keyy);
particles.push(new Particle(keyx, keyy));
// }
}
}
// else {
// // 没有检测到运动,清空所有粒子
// particles = [];
// }
}
for (let i = particles.length - 1; i >= 0; i--) {
particles[i].update();
particles[i].display();
//particles[i].applyForce();
if (particles[i].isDead()) particles.splice(i, 1);
}
let branchStartColor = color(220, 250, 122); // 绿色
let branchEndColor = color(240, 114, 2,80); // 红色
for (let i = branches.length - 1; i >= 0; i--) {
branches[i].lifespan -= 3;
let t = 1 - branches[i].lifespan / 255; // t 从 0 到 1
let currentBranchColor = lerpColor(branchStartColor, branchEndColor, t);
strokeWeight(2);
stroke(currentBranchColor);
line(
branches[i].start.x,
branches[i].start.y,
branches[i].end.x,
branches[i].end.y
);
if (branches[i].lifespan <= 0) branches.splice(i, 1);
}
}
function lineIntersection(x1, y1, x2, y2, x3, y3, x4, y4) {
let denom = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4);
if (denom == 0) {
return null; // 平行或重合,没有交点
}
let t = ((x1 - x3) * (y3 - y4) - (y1 - y3) * (x3 - x4)) / denom;
let u = -((x1 - x2) * (y1 - y3) - (y1 - y2) * (x1 - x3)) / denom;
// 交点坐标
let ix = x1 + t * (x2 - x1);
let iy = y1 + t * (y2 - y1);
return { x: ix, y: iy };
}
function detectMotion(bodyposeData) {
// 如果没有前一帧的数据,直接返回 false
if (!prevPoses || prevPoses.length === 0 || !prevPoses[0] || !prevPoses[0].keypoints) {
return false;
}
let motion = 0;
// 只检查重要关键点,比如手腕和脚踝
// 这些索引对应不同的身体部位,可以根据需要调整
let keyPointsToCheck = [0,1,2,3]; // 手肘、手腕、脚踝
for (let i of keyPointsToCheck) {
if (i >= bodyposeData.length) continue; // 确保索引有效
let kpoint = bodyposeData[i];
let prevKeypoint = prevPoses[0].keypoints[i];
if (kpoint && prevKeypoint && kpoint.confidence > 0.3 && prevKeypoint.confidence > 0.3) {
let dx = kpoint.x - prevKeypoint.x;
let dy = kpoint.y - prevKeypoint.y;
let distSq = dx * dx + dy * dy;
// 增加运动检测的阈值
if (distSq > 25) {
motion += distSq / 25; // 根据运动幅度增加motion值
}
}
}
// 调整阈值比例,使其更合理
return motion > motionThreshold / 25;
}
class Particle {
constructor(x, y) {
this.position = createVector(x, y);
this.velocity = p5.Vector.random2D().mult(random(1, 3));
this.size = random(1, 10);
let timeFactor = millis() * 0.0005; // 控制速度变化的时间因子
this.maxSpeed = map(sin(timeFactor), -1, 1, 0.8, 6); // 让 maxSpeed 在 2 到 4 之间循环
this.lifetime = 255;
// this.isOutside = false;
this.history = []; // 记录运动轨迹用于生成分支
this.angle = random(TWO_PI);
this.branchLength = 0;
this.noiseOffset = random(8000);
this.startColor = color(117, 250, 7,100); // 比如浅红色
this.endColor = color(245, 79, 7,150); // 比如蓝色
}
update() {
this.velocity.limit(this.maxSpeed);
this.position.add(this.velocity);
this.history.push(this.position.copy());
if (this.history.length > 5) this.history.shift();
// 使用L-system规则和噪声生成树枝
if (this.branchLength < 20 && this.lifetime > 50) {
let noiseX = (4 * noise(this.noiseOffset + frameCount * 0.1)) / 8;
let noiseY = 8 * noise(this.noiseOffset + frameCount * 0.1 + 1000);
// Add noise-based movement
this.velocity.x += map(noiseX, 0, 1, -50, 50);
this.velocity.y += map(noiseY, 0, 1, -50, 50);
// let n = noise(this.noiseOffset + frameCount * 8);
this.angle += map(noiseX, 0, 1, -PI / 8, PI / 8);
let newPos = this.position
.copy()
.add(p5.Vector.fromAngle(this.angle).mult(3));
branches.push({
start: this.position.copy(),
end: newPos,
lifespan: 200,
});
this.position = newPos;
this.branchLength++;
this.noiseOffset += 0.1;
}
this.lifetime -= 5;
}
display() {
let t = 1 - this.lifetime / 255; // t 从 0 到 1
let currentColor = lerpColor(this.startColor, this.endColor, t);
push();
noStroke();
fill(currentColor);
ellipse(this.position.x, this.position.y, this.size * 8, this.size * 2);
fill(currentColor);
ellipse(this.position.x, this.position.y, this.size * 4, this.size);
fill(currentColor);
ellipse(this.position.x, this.position.y, this.size * 2, this.size / 2);
pop();
// arc(this.position.x, this.position.y,this.size*20,this.size*10,PI,2*PI);
//arc(this.position.x, this.position.y,this.size*20,this.size*5,2*PI,PI);
}
isDead() {
return this.lifetime <= 0;
}
}