The human motion captured by the webcam is mapped onto a dog and drawn. The dog's head, legs, and tail are properly drawn to recreate the shape of the dog's body.
The webcam movement can be used to simulate the dog's skeleton.
The dog will appear to be standing on the ground, and the dog robot's movements will be simulated based on the skeleton estimation based on the webcam footage.
Paste the code into a text editor such as Notepad, save the file as "index.html", then open the saved file in a browser to run the code.
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>犬型ロボットの骨格描画(前足を地面に近づけて固定)</title>
<!-- TensorFlow.jsとPoseNetの読み込み -->
<script src="https://cdn.jsdelivr.net/npm/@tensorflow/tfjs"></script>
<script src="https://cdn.jsdelivr.net/npm/@tensorflow-models/posenet"></script>
<!-- Three.jsの読み込み -->
<script src="https://cdn.jsdelivr.net/npm/three@0.135.0/build/three.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/three@0.135.0/examples/js/controls/OrbitControls.js"></script>
<style>
body { margin: 0; overflow: hidden; }
canvas { display: block; background-color: green; }
</style>
</head>
<body>
<!-- カメラ映像を非表示で取得するビデオ要素 -->
<video id="video" width="640" height="480" autoplay style="display:none;"></video>
<script>
let scene, camera, renderer;
// Three.jsの初期化
function initThreeJS() {
// シーンの作成
scene = new THREE.Scene();
// カメラの設定 (視野角, アスペクト比, 近接クリップ距離, 遠方クリップ距離)
camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
// レンダラーの設定
renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
// カメラの位置を設定し、犬型ロボットが見えるようにカメラの視点を設定
camera.position.set(0, 3, 5);
camera.lookAt(0, 1, 0);
// 照明の設定
const light = new THREE.DirectionalLight(0xffffff, 1);
light.position.set(0, 5, 5).normalize();
scene.add(light);
// マウスで視点を回転できるようにするコントロールの追加
const controls = new THREE.OrbitControls(camera, renderer.domElement);
}
// 犬型骨格を描画(前足を地面に近づけるように)
function drawDogSkeleton(keypoints) {
// keypoints(関節座標)を部位ごとにアクセスしやすい形式に変換
const keypointsMap = keypoints.reduce((map, kp) => (map[kp.part] = kp, map), {});
// 骨格ラインのペア定義(接続する部位の組み合わせ)
const limbPairs = [
['leftShoulder', 'leftElbow'], ['leftElbow', 'leftWrist'], // 左前足
['rightShoulder', 'rightElbow'], ['rightElbow', 'rightWrist'], // 右前足
['leftHip', 'leftKnee'], ['leftKnee', 'leftAnkle'], // 左後足
['rightHip', 'rightKnee'], ['rightKnee', 'rightAnkle'], // 右後足
['leftShoulder', 'leftHip'], ['rightShoulder', 'rightHip'] // 胴体
];
// スケールとオフセット(全体の表示位置を調整)
const scale = 0.02;
const yOffset = -1.5;
// 前足の手首の位置をさらに下に設定
const frontFootY = -2.0;
// 前足の手首のY位置を地面に近づけるための関数
const adjustYPosition = (point, isFrontLeg) => {
// 前足の場合は固定高さをさらに下げて表示
return isFrontLeg ? frontFootY : yOffset - (point.position.y - video.height / 2) * scale;
};
// 前のラインと点をすべて削除
const existingObjects = scene.children.filter(child => child.type === 'Line' || (child.type === 'Mesh' && child.geometry.type === 'SphereGeometry'));
existingObjects.forEach(obj => scene.remove(obj));
// 各部位間のラインを描画
limbPairs.forEach(([partA, partB]) => {
const start = keypointsMap[partA];
const end = keypointsMap[partB];
if (start && end && start.score > 0.5 && end.score > 0.5) {
// 太い青いラインで描画
const material = new THREE.LineBasicMaterial({ color: 0x0000ff, linewidth: 5 });
// 各ラインの始点と終点を設定
const geometry = new THREE.BufferGeometry().setFromPoints([
new THREE.Vector3(
(start.position.x - video.width / 2) * scale,
adjustYPosition(start, partA.includes('Wrist')),
0
),
new THREE.Vector3(
(end.position.x - video.width / 2) * scale,
adjustYPosition(end, partB.includes('Wrist')),
0
)
]);
const line = new THREE.Line(geometry, material);
scene.add(line);
}
});
// 鼻の位置に赤い球を描画(頭の目印)
const nose = keypointsMap['nose'];
if (nose && nose.score > 0.5) {
const { x, y } = nose.position;
const geometry = new THREE.SphereGeometry(0.15, 32, 32);
const material = new THREE.MeshBasicMaterial({ color: 0xff0000 });
const sphere = new THREE.Mesh(geometry, material);
sphere.position.set((x - video.width / 2) * scale, yOffset - (y - video.height / 2) * scale, 0);
scene.add(sphere);
}
}
// アニメーションループ(再描画を行う)
function animate() {
requestAnimationFrame(animate);
renderer.render(scene, camera);
}
// カメラの映像を取得してPoseNetで骨格推定を行う
async function startPoseNet() {
const video = document.getElementById('video');
// ユーザーのカメラ映像を取得
navigator.mediaDevices.getUserMedia({
video: true
}).then(stream => {
video.srcObject = stream;
});
// PoseNetモデルの読み込み
const net = await posenet.load();
// カメラ映像の準備ができたらPoseNetで定期的に骨格推定を行う
video.onloadeddata = async () => {
setInterval(async () => {
// 現在のフレームの骨格を推定
const pose = await net.estimateSinglePose(video, {
flipHorizontal: false
});
// 推定結果の骨格を描画
drawDogSkeleton(pose.keypoints);
}, 100); // 0.1秒間隔で更新
};
}
// 初期化とアニメーション開始
initThreeJS();
animate();
startPoseNet();
</script>
</body>
</html>