Newer
Older
2025-Tsuji / system / demo / demo_vegetable.html
<!DOCTYPE html>
<html>
<head>
    <title>野菜に水をあげるゲーム</title>
    <style>
        body { margin: 0; overflow: hidden; background-color: #222; }
        #container {
            position: relative;
            width: 640px;
            height: 480px;
            margin: 20px auto;
            border: 4px solid #fff;
        }
        #video {
            transform: scaleX(1);
            position: absolute;
            top: 0;
            left: 0;
        }
        #canvas {
            transform: scaleX(-1);
            position: absolute;
            top: 0;
            left: 0;
        }
        #targetImage {
            position: absolute;
            top: 20px;
            left: 50%;
            transform: translateX(-50%);
            width: 120px;
            z-index: 10;
            filter: drop-shadow(0 0 10px rgba(255,255,255,0.8));
        }
        #status {
            position: absolute;
            top: 160px;
            left: 50%;
            transform: translateX(-50%);
            color: white;
            font-size: 24px;
            text-shadow: 2px 2px 4px black;
        }
        #zoneA, #zoneB {
            position: absolute;
            top: 0;
            width: 320px;
            height: 480px;
            pointer-events: none;
            opacity: 0.3;
        }
        #zoneA {
            left: 0;
            background-color: rgba(0, 255, 0, 0.5); /* 畑(緑) */
        }
        #zoneB {
            right: 0;
            background-color: rgba(0, 150, 255, 0.5); /* 水(青) */
        }
    </style>
</head>
<body>
    <div id="container">
        <video id="video" width="640" height="480" autoplay playsinline></video>
        <canvas id="canvas" width="640" height="480"></canvas>
        <img id="targetImage" src="circle.png" alt="状態表示">
        <div id="status">検出中...</div>
        <div id="zoneA"></div>
        <div id="zoneB"></div>
    </div>

    <script src="https://cdn.jsdelivr.net/npm/@tensorflow/tfjs"></script>
    <script src="https://cdn.jsdelivr.net/npm/@tensorflow-models/posenet"></script>

    <script>
        (async function main() {
            const video = document.getElementById('video');
            const canvas = document.getElementById('canvas');
            const ctx = canvas.getContext('2d');
            const targetImage = document.getElementById('targetImage');
            const status = document.getElementById('status');
            const zoneA = document.getElementById('zoneA');
            const zoneB = document.getElementById('zoneB');

            const waterImg = new Image();
            waterImg.src = "water.png";

            const vegetableImg = new Image();
            vegetableImg.src = "vegetable.png";

            let stream;
            try {
                stream = await navigator.mediaDevices.getUserMedia({
                    video: {
                        width: { ideal: 640 },
                        height: { ideal: 480 },
                        facingMode: 'user'
                    }
                });
                video.srcObject = stream;
            } catch (error) {
                alert('カメラの使用を許可してください');
                return;
            }

            const net = await posenet.load({
                architecture: 'MobileNetV1',
                outputStride: 16,
                inputResolution: 256,
                multiplier: 0.75,
                quantBytes: 2
            });

            let handInZoneA = false;
            let handInZoneB = false;
            let waterCount = 0;
            const waterGoal = 5;

            function isHandInZone(handX, zone, canvasWidth) {
                const flippedX = canvasWidth - handX;
                const rect = zone.getBoundingClientRect();
                const canvasRect = canvas.getBoundingClientRect();
                const zoneLeft = rect.left - canvasRect.left;
                const zoneRight = rect.right - canvasRect.left;
                return flippedX >= zoneLeft && flippedX <= zoneRight;
            }

            function drawWrist(x, y, ctx) {
                ctx.beginPath();
                ctx.arc(x, y, 10, 0, Math.PI * 2);
                ctx.fillStyle = 'rgba(255,0,0,0.7)';
                ctx.fill();
            }

            function processFrame() {
                net.estimateSinglePose(video, {
                    flipHorizontal: false,
                    decodingMethod: 'single-person',
                    scoreThreshold: 0.4
                }).then(pose => {
                    ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
                    const rightWrist = pose.keypoints.find(k => k.part === 'rightWrist');

                    if (rightWrist && rightWrist.score > 0.3) {
                        drawWrist(rightWrist.position.x, rightWrist.position.y, ctx);

                        const inZoneA = isHandInZone(rightWrist.position.x, zoneA, canvas.width);
                        const inZoneB = isHandInZone(rightWrist.position.x, zoneB, canvas.width);

                        if (inZoneB && !handInZoneB) {
                            handInZoneB = true;
                            handInZoneA = false;
                            status.textContent = 'ジョウロを取ったよ!';
                        }

                        if (inZoneA && handInZoneB) {
                            handInZoneB = false;
                            handInZoneA = true;
                            waterCount++;

                            targetImage.src = waterImg.src;
                            status.textContent = `水やり ${waterCount}回目`;

                            if (waterCount >= waterGoal) {
                                targetImage.src = vegetableImg.src;
                                status.textContent = '野菜が育ったよ!🎉';
                            }

                            setTimeout(() => {
                                if (waterCount < waterGoal) {
                                    status.textContent = '検出中...';
                                    targetImage.src = "circle.png";
                                }
                            }, 1500);
                        }
                    } else {
                        status.textContent = '右手首を映してください';
                        handInZoneA = false;
                        handInZoneB = false;
                    }

                    requestAnimationFrame(processFrame);
                });
            }

            video.onloadeddata = () => {
                canvas.width = video.videoWidth;
                canvas.height = video.videoHeight;
                processFrame();
            };
        })();
    </script>
</body>
</html>