웹 AI 모델로 피코 제어하기
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<title>손 제스처 + 블루투스</title>
<style>
body {
margin: 0;
overflow: hidden;
background: #000;
}
#video, #canvas {
position: absolute;
top: 0; left: 0;
width: 100vw;
height: 100vh;
object-fit: cover;
transform: scaleX(-1);
}
#gestureNumber {
position: absolute;
top: 40px;
left: 50%;
transform: translateX(-50%);
color: white;
font-size: 6rem;
font-weight: bold;
text-shadow: 2px 2px 10px #000;
z-index: 10;
}
#connectButton {
position: absolute;
top: 10px;
left: 10px;
z-index: 20;
padding: 10px 20px;
font-size: 16px;
}
</style>
</head>
<body>
<video id="video" autoplay playsinline muted></video>
<canvas id="canvas"></canvas>
<div id="gestureNumber">-</div>
<button id="connectButton">블루투스 연결</button>
<!-- MediaPipe Hands -->
<script src="https://cdn.jsdelivr.net/npm/@mediapipe/hands/hands.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/@mediapipe/drawing_utils/drawing_utils.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/@mediapipe/camera_utils/camera_utils.min.js"></script>
<script>
const video = document.getElementById('video');
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
const gestureDisplay = document.getElementById('gestureNumber');
const connectButton = document.getElementById('connectButton');
let lastGesture = null;
let device, server, writeCharacteristic;
let isConnected = false;
const NUS_SERVICE_UUID = '6e400001-b5a3-f393-e0a9-e50e24dcca9e';
const NUS_TX_CHAR_UUID = '6e400002-b5a3-f393-e0a9-e50e24dcca9e';
connectButton.addEventListener('click', async () => {
try {
device = await navigator.bluetooth.requestDevice({
filters: [{ services: [NUS_SERVICE_UUID] }]
});
server = await device.gatt.connect();
const service = await server.getPrimaryService(NUS_SERVICE_UUID);
writeCharacteristic = await service.getCharacteristic(NUS_TX_CHAR_UUID);
isConnected = true;
alert('블루투스 연결 완료');
} catch (error) {
alert('연결 실패: ' + error);
}
});
function countFingers(landmarks) {
let count = 0;
const tips = [8, 12, 16, 20];
for (let tip of tips) {
if (landmarks[tip].y < landmarks[tip - 2].y) count++;
}
if (landmarks[4].x > landmarks[2].x) count++;
return count;
}
function isHandOpen(fingerCount) {
return fingerCount >= 4;
}
function detectGesture(results) {
const hands = results.multiHandLandmarks;
if (!hands || hands.length === 0) {
updateGesture("-");
return;
}
const states = hands.map(lm => isHandOpen(countFingers(lm)));
if (states.length === 1) {
if (states[0]) updateGesture("1");
else updateGesture("2");
} else if (states.length === 2) {
if (states[0] && states[1]) updateGesture("3");
else if (!states[0] && !states[1]) updateGesture("4");
else updateGesture("-");
}
}
function updateGesture(gesture) {
if (gesture !== lastGesture) {
gestureDisplay.textContent = gesture;
lastGesture = gesture;
if (isConnected && gesture !== "-") {
sendData(gesture);
}
}
}
async function sendData(data) {
const encoded = new TextEncoder().encode(data);
try {
await writeCharacteristic.writeValue(encoded);
console.log("전송됨:", data);
} catch (err) {
console.error("전송 실패:", err);
}
}
const hands = new Hands({
locateFile: file => `https://cdn.jsdelivr.net/npm/@mediapipe/hands/${file}`
});
hands.setOptions({
maxNumHands: 2,
modelComplexity: 1,
minDetectionConfidence: 0.7,
minTrackingConfidence: 0.7
});
hands.onResults(results => {
canvas.width = video.videoWidth;
canvas.height = video.videoHeight;
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.drawImage(results.image, 0, 0, canvas.width, canvas.height);
if (results.multiHandLandmarks) {
for (let lm of results.multiHandLandmarks) {
drawConnectors(ctx, lm, HAND_CONNECTIONS, { color: '#00FF00', lineWidth: 2 });
drawLandmarks(ctx, lm, { color: '#FF0000', lineWidth: 2 });
}
}
detectGesture(results);
});
async function startCamera() {
const stream = await navigator.mediaDevices.getUserMedia({ video: true });
video.srcObject = stream;
video.onloadedmetadata = () => {
const camera = new Camera(video, {
onFrame: async () => await hands.send({ image: video }),
width: 640,
height: 480
});
camera.start();
};
}
startCamera().catch(err => alert("카메라 접근 실패: " + err.message));
</script>
</body>
</html>
Python
복사
미디어파이프로 웹에서 불러오기
코드펜으로 나만의 웹사이트 제작하기
https://codepen.io/leeun/full/XJbqNgZ
Python
복사
[과제명][학교][이름] 바꿔주세요, 과제태그를 선생님 설명 듣고 넣어주세요.