ショートストーリー: 「コードの中のロボット」
東京の高層ビルが立ち並ぶ街角、主人公のタケシはプログラマとして忙しい日々を送っていた。彼の最新のプロジェクトは、自ら設計した3Dロボットのプログラミングだった。このロボットは、彼の手元にあるコンピュータとカメラを使って、人間の動作を学習し、再現することができるのだ。
タケシは、昼間の忙しい業務を終えた後、自宅のスタジオで作業に没頭する。彼の机の上には、ノートパソコンと数冊の技術書、そして最新の3Dロボットが待っている。彼はプログラムを立ち上げ、まずはマニュアル操作モードに切り替えた。
「よし、やってみよう」とタケシは心の中で呟き、ロボットに自分の動作を教え始める。彼は手を伸ばし、ジャンプし、左右に体を振る。ロボットはその動きをカメラでキャッチし、タケシの動作を忠実に模倣していく。
「うん、いい感じだ」とタケシは微笑む。ロボットは彼の指示に従って、ジャンプを繰り返す。マニュアル操作モードでの学習は、彼にとって非常に楽しいひとときだった。ロボットが彼の動きを真似る様子は、まるで自分の分身を見ているかのようだった。
そのうちに、タケシはAIコントロールモードへの切り替えを思いつく。「今度はAIに任せてみるか」と彼はAIコントロールボタンを押した。すると、ロボットは彼の動作を分析し、学習した内容を基に新たな動きを生成し始めた。タケシは目を丸くして、その様子を見守る。
AIはタケシが教えた動きを超えて、独自のリズムで動き始めた。ジャンプの高さや動きの滑らかさは、まるで本物のダンサーのようだった。タケシは驚きと感動の入り混じった気持ちで見守る。
「すごい! これがAIの力か!」タケシは感嘆しながら、ロボットの動きを見つめる。AIモデルが彼の教えた動作をもとに新しい動きの組み合わせを創り出す様子は、まるで一つのアート作品のようだった。
ロボットは、タケシの指示に従いながらも、自らの個性を表現していた。彼はこの瞬間、テクノロジーと創造性が融合する瞬間を感じた。プログラムの背後にある計算やアルゴリズムが、ただの数字や文字ではなく、彼の心を動かす作品へと変わっていくのを実感した。
数時間後、タケシは画面の前でほっと一息つきながら、ロボットが動いている様子を眺めていた。「このロボットは、ただの機械じゃない。僕の仲間だ」と心の中で思った。
AIコントロールロボット
マニュアルモードボタンでトレーニング(60秒以上必要)です。AIコントロールボタンでAI制御(簡易的トランスフォーマーモデル)になります。
コードをメモ帳などのテキストエディタに貼り付け、ファイル名を「index.html」として保存します。その後、保存したファイルをブラウザで開けば、コードが実行されます。
かっこいいカラー: 関節は金色(ゴールド)に、関節間はメタリックな青色に設定して、よりロボットらしい見た目にしました。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>かっこいい3Dロボット描画</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; } /* 背景を緑色に設定 */
#control-buttons { position: absolute; top: 10px; left: 10px; z-index: 10; }
</style>
</head>
<body>
<div id="control-buttons">
<button id="manual-control">マニュアルコントロール</button>
<button id="ai-control">AIコントロール</button>
</div>
<video id="video" width="640" height="480" autoplay style="display:none;"></video>
<script>
let scene, camera, renderer;
let isAIControl = false;
let motionData = [];
const maxSteps = 500;
// 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.z = 5;
// ライトの追加
const light = new THREE.DirectionalLight(0xffffff, 1);
light.position.set(0, 1, 1).normalize();
scene.add(light);
// オービットコントロールの追加
const controls = new THREE.OrbitControls(camera, renderer.domElement);
}
// ロボットの肉付けと描画
function drawRobot(keypoints) {
// 既存のロボットの部品を削除
const existingMeshes = scene.children.filter(child => child.type === 'Mesh' || child.type === 'Line');
existingMeshes.forEach(mesh => scene.remove(mesh));
const adjacentKeyPoints = posenet.getAdjacentKeyPoints(keypoints, 0.5);
// 関節ごとに球を描画
keypoints.forEach(keypoint => {
if (keypoint.score > 0.5) {
const { y, x } = keypoint.position;
const color = 0xffd700; // 金色に設定
const geometry = new THREE.SphereGeometry(0.15, 32, 32); // 関節用の球
const material = new THREE.MeshStandardMaterial({ color: color, metalness: 0.8, roughness: 0.3 });
const sphere = new THREE.Mesh(geometry, material);
// 球の位置設定
sphere.position.set((x - video.width / 2) / 100, -(y - video.height / 2) / 100, 0);
scene.add(sphere);
}
});
// 関節間のシリンダーを描画
adjacentKeyPoints.forEach(keypointsPair => {
const [start, end] = keypointsPair;
if (start.score > 0.5 && end.score > 0.5) {
const startVector = new THREE.Vector3((start.position.x - video.width / 2) / 100, -(start.position.y - video.height / 2) / 100, 0);
const endVector = new THREE.Vector3((end.position.x - video.width / 2) / 100, -(end.position.y - video.height / 2) / 100, 0);
const direction = new THREE.Vector3().subVectors(endVector, startVector);
const length = direction.length();
const axis = new THREE.Vector3(0, 1, 0).cross(direction).normalize();
const angle = Math.acos(new THREE.Vector3(0, 1, 0).dot(direction.normalize()));
const geometry = new THREE.CylinderGeometry(0.05, 0.05, length, 32); // 細いシリンダー
const material = new THREE.MeshStandardMaterial({ color: 0x1e90ff, metalness: 0.8, roughness: 0.3 }); // メタリックな青色
const cylinder = new THREE.Mesh(geometry, material);
cylinder.position.copy(startVector.clone().add(direction.multiplyScalar(0.5)));
cylinder.quaternion.setFromAxisAngle(axis, angle);
scene.add(cylinder);
}
});
}
// コサイン類似度を確率分布に変換
function softmax(similarities) {
const maxSim = Math.max(...similarities); // オーバーフロー防止のため最大値を引く
const exps = similarities.map(sim => Math.exp(sim - maxSim));
const sumExps = exps.reduce((a, b) => a + b, 0);
return exps.map(exp => exp / sumExps);
}
// 最もコサイン類似度の高いフレームを確率に基づいて選択
function findNextFrameWithProbability(currentFrame) {
let similarities = [];
motionData.forEach(data => {
let similarity = cosineSimilarity(currentFrame, data.current);
similarities.push(similarity);
});
// コサイン類似度をソフトマックスで確率分布に変換
const probabilities = softmax(similarities);
// 確率分布に基づいて次のフレームをランダムに選択
let random = Math.random();
let cumulativeProbability = 0;
for (let i = 0; i < probabilities.length; i++) {
cumulativeProbability += probabilities[i];
if (random < cumulativeProbability) {
return motionData[i].next;
}
}
return null; // フレームが見つからない場合のフォールバック
}
// コサイン類似度の計算
function cosineSimilarity(frameA, frameB) {
let dotProduct = 0;
let magnitudeA = 0;
let magnitudeB = 0;
frameA.forEach((pointA, index) => {
const pointB = frameB[index];
dotProduct += (pointA.position.x * pointB.position.x) + (pointA.position.y * pointB.position.y);
magnitudeA += (pointA.position.x * pointA.position.x) + (pointA.position.y * pointA.position.y);
magnitudeB += (pointB.position.x * pointB.position.x) + (pointB.position.y * pointB.position.y);
});
magnitudeA = Math.sqrt(magnitudeA);
magnitudeB = Math.sqrt(magnitudeB);
return dotProduct / (magnitudeA * magnitudeB);
}
// アニメーションループ
function animate() {
requestAnimationFrame(animate);
if (!isAIControl) {
renderer.render(scene, camera);
} else if (motionData.length > 0) {
const currentPose = motionData[motionData.length - 1].current;
const nextFrame = findNextFrameWithProbability(currentPose);
if (nextFrame) {
drawRobot(nextFrame);
}
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();
video.onloadeddata = async () => {
// 骨格推定と描画を繰り返し実行
setInterval(async () => {
const pose = await net.estimateSinglePose(video, {
flipHorizontal: false
});
motionData.push({ current: pose.keypoints, next: pose.keypoints }); // モーションデータを保存
if (!isAIControl) {
drawRobot(pose.keypoints); // 肉付けされたロボットを描画
}
}, 100);
};
}
// コントロールボタンの設定
document.getElementById('manual-control').addEventListener('click', () => {
isAIControl = false;
alert("マニュアルコントロールに切り替えました。");
});
document.getElementById('ai-control').addEventListener('click', () => {
isAIControl = true;
alert("AIコントロールに切り替えました。");
});
// 初期化関数を実行
initThreeJS();
startPoseNet();
animate();
</script>
</body>
</html>