Spaces:
Running
Running
import DeviceDetector from "https://cdn.skypack.dev/[email protected]"; | |
// ์ฌ์ฉ ๋ฐฉ๋ฒ: testSupport({client?: string, os?: string}[]) | |
// client์ os๋ ์ ๊ท ํํ์์ ๋๋ค. | |
// ์ฐธ๊ณ : https://cdn.jsdelivr.net/npm/[email protected]/README.md | |
// client์ os์ ์ ํจ ๊ฐ ํ์ธ | |
// ํ์ํ ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์ํฌํธ | |
// ์๋์ ๊ฐ์๋ ์ฐจํธ๋ฅผ ์ด๊ธฐํ | |
let speedChart, accelerationChart; | |
let previousPoseData = null; | |
let lastTimestamp = 0; | |
testSupport([ | |
{ client: 'Chrome' }, | |
]); | |
// ์ฐจํธ ๊ด๋ จ ์์ ์ค์ | |
const CHART_CONFIG = { | |
maxDataPoints: 50, | |
updateInterval: 100, // ์ฐจํธ ์ ๋ฐ์ดํธ ๊ฐ๊ฒฉ(ms) | |
colors: { | |
speed: { | |
primary: 'rgba(75, 192, 192, 1)', | |
background: 'rgba(75, 192, 192, 0.1)' | |
}, | |
acceleration: { | |
primary: 'rgba(255, 99, 132, 1)', | |
background: 'rgba(255, 99, 132, 0.1)' | |
} | |
} | |
}; | |
// ์ฐจํธ ์ด๊ธฐํ | |
function initCharts() { | |
// ์ฐจํธ๋ฅผ ๋ด์ ์ปจํ ์ด๋ ์์ฑ | |
const chartsContainer = document.createElement('div'); | |
chartsContainer.className = 'charts-container'; | |
document.querySelector('.container').appendChild(chartsContainer); | |
// ์๋ ์ฐจํธ ์ปจํ ์ด๋ | |
const speedChartContainer = document.createElement('div'); | |
speedChartContainer.className = 'chart-card'; | |
const speedCanvas = document.createElement('canvas'); | |
speedCanvas.id = 'speedChart'; | |
speedChartContainer.appendChild(speedCanvas); | |
chartsContainer.appendChild(speedChartContainer); | |
// ๊ฐ์๋ ์ฐจํธ ์ปจํ ์ด๋ | |
const accelerationChartContainer = document.createElement('div'); | |
accelerationChartContainer.className = 'chart-card'; | |
const accelerationCanvas = document.createElement('canvas'); | |
accelerationCanvas.id = 'accelerationChart'; | |
accelerationChartContainer.appendChild(accelerationCanvas); | |
chartsContainer.appendChild(accelerationChartContainer); | |
// ์๋ ์ฐจํธ ์ค์ | |
speedChart = new Chart(speedCanvas.getContext('2d'), { | |
type: 'line', | |
data: { | |
labels: [], | |
datasets: [{ | |
label: '์ด๋ ์๋ (ํฝ์ /์ด)', | |
data: [], | |
borderColor: CHART_CONFIG.colors.speed.primary, | |
backgroundColor: CHART_CONFIG.colors.speed.background, | |
tension: 0.4, | |
borderWidth: 2, | |
fill: true, | |
pointRadius: 0, | |
pointHitRadius: 10 | |
}] | |
}, | |
options: { | |
responsive: true, | |
maintainAspectRatio: false, | |
animation: { | |
duration: 0 | |
}, | |
interaction: { | |
intersect: false, | |
mode: 'index' | |
}, | |
plugins: { | |
legend: { | |
position: 'top', | |
labels: { | |
font: { | |
family: '"Titillium Web", sans-serif', | |
size: 14 | |
}, | |
color: '#333' | |
} | |
}, | |
tooltip: { | |
enabled: true, | |
backgroundColor: 'rgba(0, 0, 0, 0.7)', | |
titleFont: { | |
family: '"Titillium Web", sans-serif' | |
}, | |
bodyFont: { | |
family: '"Titillium Web", sans-serif' | |
} | |
} | |
}, | |
scales: { | |
y: { | |
beginAtZero: true, | |
grid: { | |
color: 'rgba(0, 0, 0, 0.1)' | |
}, | |
ticks: { | |
font: { | |
family: '"Titillium Web", sans-serif' | |
} | |
} | |
}, | |
x: { | |
grid: { | |
display: false | |
}, | |
ticks: { | |
maxRotation: 0, | |
maxTicksLimit: 5, | |
font: { | |
family: '"Titillium Web", sans-serif' | |
} | |
} | |
} | |
} | |
} | |
}); | |
// ๊ฐ์๋ ์ฐจํธ ์ค์ | |
accelerationChart = new Chart(accelerationCanvas.getContext('2d'), { | |
type: 'line', | |
data: { | |
labels: [], | |
datasets: [{ | |
label: '๊ฐ์๋ (ํฝ์ /์ดยฒ)', | |
data: [], | |
borderColor: CHART_CONFIG.colors.acceleration.primary, | |
backgroundColor: CHART_CONFIG.colors.acceleration.background, | |
tension: 0.4, | |
borderWidth: 2, | |
fill: true, | |
pointRadius: 0, | |
pointHitRadius: 10 | |
}] | |
}, | |
options: { | |
responsive: true, | |
maintainAspectRatio: false, | |
animation: { | |
duration: 0 | |
}, | |
interaction: { | |
intersect: false, | |
mode: 'index' | |
}, | |
plugins: { | |
legend: { | |
position: 'top', | |
labels: { | |
font: { | |
family: '"Titillium Web", sans-serif', | |
size: 14 | |
}, | |
color: '#333' | |
} | |
}, | |
tooltip: { | |
enabled: true, | |
backgroundColor: 'rgba(0, 0, 0, 0.7)', | |
titleFont: { | |
family: '"Titillium Web", sans-serif' | |
}, | |
bodyFont: { | |
family: '"Titillium Web", sans-serif' | |
} | |
} | |
}, | |
scales: { | |
y: { | |
beginAtZero: true, | |
grid: { | |
color: 'rgba(0, 0, 0, 0.1)' | |
}, | |
ticks: { | |
font: { | |
family: '"Titillium Web", sans-serif' | |
} | |
} | |
}, | |
x: { | |
grid: { | |
display: false | |
}, | |
ticks: { | |
maxRotation: 0, | |
maxTicksLimit: 5, | |
font: { | |
family: '"Titillium Web", sans-serif' | |
} | |
} | |
} | |
} | |
} | |
}); | |
// ๋ฐ์ํ ์ฒ๋ฆฌ | |
window.addEventListener('resize', () => { | |
speedChart.resize(); | |
accelerationChart.resize(); | |
}); | |
} | |
// ์์ธ ๋ณํ์ ์๋์ ๊ฐ์๋๋ฅผ ๊ณ์ฐ | |
function calculateMotionMetrics(results, timestamp) { | |
// ๊ธฐ๋ณธ ๊ฒ์ฆ | |
if (!results || !results.poseLandmarks || !Array.isArray(results.poseLandmarks)) { | |
return { speed: 0, acceleration: 0 }; | |
} | |
// ์ด๊ธฐ ์ํ | |
if (!previousPoseData || !previousPoseData.poseLandmarks) { | |
previousPoseData = { | |
poseLandmarks: [...results.poseLandmarks] | |
}; | |
lastTimestamp = timestamp; | |
return { speed: 0, acceleration: 0 }; | |
} | |
const deltaTime = (timestamp - lastTimestamp) / 1000; // ์ด ๋จ์๋ก ๋ณํ | |
if (deltaTime === 0) { | |
return { speed: 0, acceleration: 0 }; | |
} | |
// ํคํฌ์ธํธ ํ๊ท ์ด๋๊ฑฐ๋ฆฌ ๊ณ์ฐ | |
let totalDisplacement = 0; | |
let validPoints = 0; | |
try { | |
// ์ ํจํ ํคํฌ์ธํธ๋ง ์ฌ์ฉ | |
results.poseLandmarks.forEach((landmark, index) => { | |
const prevLandmark = previousPoseData.poseLandmarks[index]; | |
if ( | |
landmark && prevLandmark && | |
typeof landmark.x === 'number' && | |
typeof landmark.y === 'number' && | |
typeof prevLandmark.x === 'number' && | |
typeof prevLandmark.y === 'number' && | |
// ์ ํ ์ฌํญ: ๊ฐ์์ฑ(visibility) ๊ฐ์ด ์ผ์ ๊ธฐ์ค ์ด์์ผ ๋๋ง | |
(!landmark.visibility || landmark.visibility > 0.5) && | |
(!prevLandmark.visibility || prevLandmark.visibility > 0.5) | |
) { | |
const dx = landmark.x - prevLandmark.x; | |
const dy = landmark.y - prevLandmark.y; | |
const displacement = Math.sqrt(dx * dx + dy * dy); | |
// ๋น์ ์์ ์ผ๋ก ํฐ ์ด๋ ๊ฑฐ๋ฆฌ๋ ํํฐ๋ง | |
if (displacement < 1.0) { | |
totalDisplacement += displacement; | |
validPoints++; | |
} | |
} | |
}); | |
} catch (error) { | |
console.warn('์ด๋ ๊ฑฐ๋ฆฌ ๊ณ์ฐ ์ค ์ค๋ฅ:', error); | |
return { speed: 0, acceleration: 0 }; | |
} | |
// ์ ํจ ํฌ์ธํธ๊ฐ ์๋ค๋ฉด 0 ๋ฐํ | |
if (validPoints === 0) { | |
return { speed: 0, acceleration: 0 }; | |
} | |
// ํ๊ท ์ด๋๊ฑฐ๋ฆฌ ๋ฐ ์๋ ๊ณ์ฐ | |
const averageDisplacement = totalDisplacement / validPoints; | |
const currentSpeed = averageDisplacement / deltaTime; | |
// ์ด์ ์๋๊ฐ ์์ผ๋ฉด 0 | |
let previousSpeed = 0; | |
try { | |
previousSpeed = speedChart.data.datasets[0].data[ | |
speedChart.data.datasets[0].data.length - 1 | |
] || 0; | |
} catch (error) { | |
console.warn('์ด์ ์๋ ์ ๊ทผ ์ค ์ค๋ฅ:', error); | |
} | |
// ๊ฐ์๋ ๊ณ์ฐ | |
const acceleration = (currentSpeed - previousSpeed) / deltaTime; | |
// ๋ค์ ํ๋ ์ ๊ณ์ฐ์ ์ํด ์ด์ ๋ฐ์ดํฐ ์ ๋ฐ์ดํธ | |
previousPoseData = { | |
poseLandmarks: [...results.poseLandmarks] | |
}; | |
lastTimestamp = timestamp; | |
// ๊ฐ ๊ฒ์ฆ ๋ฐ ์ ํ | |
const metrics = { | |
speed: isFinite(currentSpeed) ? Math.min(Math.max(currentSpeed, 0), 1000) : 0, | |
acceleration: isFinite(acceleration) ? Math.min(Math.max(acceleration, -1000), 1000) : 0 | |
}; | |
return metrics; | |
} | |
// ์ฐจํธ ๋ฐ์ดํฐ๋ฅผ ์ ๋ฐ์ดํธํ๋ ํจ์ (์ค๋ฅ ์ฒ๋ฆฌ ํฌํจ) | |
function updateCharts(metrics) { | |
if (!metrics || typeof metrics.speed !== 'number' || typeof metrics.acceleration !== 'number') { | |
console.warn('์๋ชป๋ metrics ๋ฐ์ดํฐ:', metrics); | |
return; | |
} | |
try { | |
const timestamp = new Date().toLocaleTimeString('ko-KR', { | |
hour: '2-digit', | |
minute: '2-digit', | |
second: '2-digit' | |
}); | |
// ์ฐจํธ ๊ฐ์ฒด๊ฐ ์ ๋๋ก ์ด๊ธฐํ๋์๋์ง ํ์ธ | |
if (!speedChart || !speedChart.data || !speedChart.data.labels) { | |
console.warn('์๋ ์ฐจํธ๊ฐ ์ ์์ ์ผ๋ก ์ด๊ธฐํ๋์ง ์์์ต๋๋ค.'); | |
return; | |
} | |
if (!accelerationChart || !accelerationChart.data || !accelerationChart.data.labels) { | |
console.warn('๊ฐ์๋ ์ฐจํธ๊ฐ ์ ์์ ์ผ๋ก ์ด๊ธฐํ๋์ง ์์์ต๋๋ค.'); | |
return; | |
} | |
// ์๋ ์ฐจํธ ์ ๋ฐ์ดํธ | |
speedChart.data.labels.push(timestamp); | |
speedChart.data.datasets[0].data.push(metrics.speed); | |
if (speedChart.data.labels.length > CHART_CONFIG.maxDataPoints) { | |
speedChart.data.labels.shift(); | |
speedChart.data.datasets[0].data.shift(); | |
} | |
// ๊ฐ์๋ ์ฐจํธ ์ ๋ฐ์ดํธ | |
accelerationChart.data.labels.push(timestamp); | |
accelerationChart.data.datasets[0].data.push(metrics.acceleration); | |
if (accelerationChart.data.labels.length > CHART_CONFIG.maxDataPoints) { | |
accelerationChart.data.labels.shift(); | |
accelerationChart.data.datasets[0].data.shift(); | |
} | |
// requestAnimationFrame์ ์ฌ์ฉํด ์ฐจํธ ์ ๋ฐ์ดํธ ์ต์ ํ | |
requestAnimationFrame(() => { | |
try { | |
speedChart.update('none'); | |
accelerationChart.update('none'); | |
} catch (error) { | |
console.warn('์ฐจํธ ์ ๋ฐ์ดํธ ์ค ์ค๋ฅ:', error); | |
} | |
}); | |
} catch (error) { | |
console.warn('updateCharts์์ ์ค๋ฅ ๋ฐ์:', error); | |
} | |
} | |
function testSupport(supportedDevices) { | |
const deviceDetector = new DeviceDetector(); | |
const detectedDevice = deviceDetector.parse(navigator.userAgent); | |
let isSupported = false; | |
for (const device of supportedDevices) { | |
if (device.client !== undefined) { | |
const re = new RegExp(`^${device.client}$`); | |
if (!re.test(detectedDevice.client.name)) { | |
continue; | |
} | |
} | |
if (device.os !== undefined) { | |
const re = new RegExp(`^${device.os}$`); | |
if (!re.test(detectedDevice.os.name)) { | |
continue; | |
} | |
} | |
isSupported = true; | |
break; | |
} | |
if (!isSupported) { | |
alert(`์ด ๋ฐ๋ชจ๋ ${detectedDevice.client.name}/${detectedDevice.os.name} ์์ ์คํ๋ ๋ ` + | |
`์์ ํ ์ง์๋์ง ์์ต๋๋ค. ๊ณ์ ์ฌ์ฉํ ๊ฒฝ์ฐ ๋ณธ์ธ ์ฑ ์ ํ์ ์งํํ์๊ธฐ ๋ฐ๋๋๋ค.`); | |
} | |
} | |
const controls = window; | |
const mpHolistic = window; | |
const drawingUtils = window; | |
const config = { locateFile: (file) => { | |
return `https://cdn.jsdelivr.net/npm/@mediapipe/holistic@` + | |
`${mpHolistic.VERSION}/${file}`; | |
} }; | |
// ์ ๋ ฅ ํ๋ ์์ ์ฌ๊ธฐ์ ๊ฐ์ ธ์ต๋๋ค. | |
const videoElement = document.getElementsByClassName('input_video')[0]; | |
const canvasElement = document.getElementsByClassName('output_canvas')[0]; | |
const controlsElement = document.getElementsByClassName('control-panel')[0]; | |
const canvasCtx = canvasElement.getContext('2d'); | |
// ์ดํ tick()์ด ํธ์ถ๋ ๋๋ง๋ค ์ฐธ์กฐํ FPS ์ปจํธ๋กค | |
const fpsControl = new controls.FPS(); | |
// ๋ก๋ฉ ์คํผ๋๋ฅผ ์จ๊ธฐ๋ ์ต์ ํ | |
const spinner = document.querySelector('.loading'); | |
spinner.ontransitionend = () => { | |
spinner.style.display = 'none'; | |
}; | |
function removeElements(landmarks, elements) { | |
for (const element of elements) { | |
delete landmarks[element]; | |
} | |
} | |
function removeLandmarks(results) { | |
if (results.poseLandmarks) { | |
removeElements(results.poseLandmarks, [ | |
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, | |
15, 16, 17, 18, 19, 20, 21, 22 | |
]); | |
} | |
} | |
function connect(ctx, connectors) { | |
const canvas = ctx.canvas; | |
for (const connector of connectors) { | |
const from = connector[0]; | |
const to = connector[1]; | |
if (from && to) { | |
if (from.visibility && to.visibility && | |
(from.visibility < 0.1 || to.visibility < 0.1)) { | |
continue; | |
} | |
ctx.beginPath(); | |
ctx.moveTo(from.x * canvas.width, from.y * canvas.height); | |
ctx.lineTo(to.x * canvas.width, to.y * canvas.height); | |
ctx.stroke(); | |
} | |
} | |
} | |
let activeEffect = 'mask'; | |
function onResults(results) { | |
// ๋ก๋ฉ ์คํผ๋ ์จ๊ธฐ๊ธฐ | |
document.body.classList.add('loaded'); | |
// ๊ทธ๋ฆด ํ์ ์๋ ๋๋๋งํฌ ์ ๊ฑฐ | |
removeLandmarks(results); | |
// FPS ์ ๋ฐ์ดํธ | |
fpsControl.tick(); | |
// ์บ๋ฒ์ค์ ๊ทธ๋ฆฌ๊ธฐ | |
canvasCtx.save(); | |
canvasCtx.clearRect(0, 0, canvasElement.width, canvasElement.height); | |
if (results.segmentationMask) { | |
canvasCtx.drawImage( | |
results.segmentationMask, | |
0, 0, canvasElement.width, canvasElement.height | |
); | |
// ๊ธฐ์กด ํฝ์ ๋ง ๋ฎ์ด์ฐ๊ธฐ | |
if (activeEffect === 'mask' || activeEffect === 'both') { | |
canvasCtx.globalCompositeOperation = 'source-in'; | |
canvasCtx.fillStyle = '#00FF007F'; | |
canvasCtx.fillRect(0, 0, canvasElement.width, canvasElement.height); | |
} else { | |
canvasCtx.globalCompositeOperation = 'source-out'; | |
canvasCtx.fillStyle = '#0000FF7F'; | |
canvasCtx.fillRect(0, 0, canvasElement.width, canvasElement.height); | |
} | |
// ๋๋ฝ๋ ํฝ์ ๋ง ๋ฎ์ด์ฐ๊ธฐ | |
canvasCtx.globalCompositeOperation = 'destination-atop'; | |
canvasCtx.drawImage(results.image, 0, 0, canvasElement.width, canvasElement.height); | |
canvasCtx.globalCompositeOperation = 'source-over'; | |
} else { | |
canvasCtx.drawImage(results.image, 0, 0, canvasElement.width, canvasElement.height); | |
} | |
// ๋์ ์งํ ๊ณ์ฐ ํ ์ฐจํธ ์ ๋ฐ์ดํธ | |
const metrics = calculateMotionMetrics(results, performance.now()); | |
updateCharts(metrics); | |
// ์์ ๊ฒ์ฌ | |
if (!results.poseLandmarks || !mpHolistic.POSE_LANDMARKS) { | |
canvasCtx.restore(); | |
return; | |
} | |
// ํ๊ฟ์น์์ ์๊น์ง ์ฐ๊ฒฐ | |
canvasCtx.lineWidth = 5; | |
if (results.poseLandmarks) { | |
if (results.rightHandLandmarks && results.poseLandmarks[mpHolistic.POSE_LANDMARKS.RIGHT_ELBOW]) { | |
canvasCtx.strokeStyle = 'white'; | |
connect(canvasCtx, [[ | |
results.poseLandmarks[mpHolistic.POSE_LANDMARKS.RIGHT_ELBOW], | |
results.rightHandLandmarks[0] | |
]]); | |
} | |
if (results.leftHandLandmarks && results.poseLandmarks[mpHolistic.POSE_LANDMARKS.LEFT_ELBOW]) { | |
canvasCtx.strokeStyle = 'white'; | |
connect(canvasCtx, [[ | |
results.poseLandmarks[mpHolistic.POSE_LANDMARKS.LEFT_ELBOW], | |
results.leftHandLandmarks[0] | |
]]); | |
} | |
} | |
// ์ ์ ์์ธ ์ฐ๊ฒฐ | |
if (results.poseLandmarks && mpHolistic.POSE_CONNECTIONS) { | |
drawingUtils.drawConnectors( | |
canvasCtx, | |
results.poseLandmarks, | |
mpHolistic.POSE_CONNECTIONS, | |
{ color: 'white' } | |
); | |
} | |
// ์ผ์ชฝ ์์ธ ๋๋๋งํฌ | |
if (results.poseLandmarks && mpHolistic.POSE_LANDMARKS_LEFT) { | |
const leftLandmarks = Object.values(mpHolistic.POSE_LANDMARKS_LEFT) | |
.map(index => results.poseLandmarks[index]) | |
.filter(landmark => landmark !== undefined); | |
if (leftLandmarks.length > 0) { | |
drawingUtils.drawLandmarks(canvasCtx, leftLandmarks, { | |
visibilityMin: 0.65, | |
color: 'white', | |
fillColor: 'rgb(255,138,0)' | |
}); | |
} | |
} | |
// ์ค๋ฅธ์ชฝ ์์ธ ๋๋๋งํฌ | |
if (results.poseLandmarks && mpHolistic.POSE_LANDMARKS_RIGHT) { | |
const rightLandmarks = Object.values(mpHolistic.POSE_LANDMARKS_RIGHT) | |
.map(index => results.poseLandmarks[index]) | |
.filter(landmark => landmark !== undefined); | |
if (rightLandmarks.length > 0) { | |
drawingUtils.drawLandmarks(canvasCtx, rightLandmarks, { | |
visibilityMin: 0.65, | |
color: 'white', | |
fillColor: 'rgb(0,217,231)' | |
}); | |
} | |
} | |
// ์ ๋๋๋งํฌ | |
if (results.rightHandLandmarks && mpHolistic.HAND_CONNECTIONS) { | |
drawingUtils.drawConnectors( | |
canvasCtx, | |
results.rightHandLandmarks, | |
mpHolistic.HAND_CONNECTIONS, | |
{ color: 'white' } | |
); | |
drawingUtils.drawLandmarks(canvasCtx, results.rightHandLandmarks, { | |
color: 'white', | |
fillColor: 'rgb(0,217,231)', | |
lineWidth: 2, | |
radius: (data) => { | |
return drawingUtils.lerp(data.from.z, -0.15, .1, 10, 1); | |
} | |
}); | |
} | |
if (results.leftHandLandmarks && mpHolistic.HAND_CONNECTIONS) { | |
drawingUtils.drawConnectors( | |
canvasCtx, | |
results.leftHandLandmarks, | |
mpHolistic.HAND_CONNECTIONS, | |
{ color: 'white' } | |
); | |
drawingUtils.drawLandmarks(canvasCtx, results.leftHandLandmarks, { | |
color: 'white', | |
fillColor: 'rgb(255,138,0)', | |
lineWidth: 2, | |
radius: (data) => { | |
return drawingUtils.lerp(data.from.z, -0.15, .1, 10, 1); | |
} | |
}); | |
} | |
// ์ผ๊ตด ๋๋๋งํฌ | |
if (results.faceLandmarks && mpHolistic.FACEMESH_TESSELATION) { | |
drawingUtils.drawConnectors( | |
canvasCtx, | |
results.faceLandmarks, | |
mpHolistic.FACEMESH_TESSELATION, | |
{ color: '#C0C0C070', lineWidth: 1 } | |
); | |
if (mpHolistic.FACEMESH_RIGHT_EYE) { | |
drawingUtils.drawConnectors( | |
canvasCtx, | |
results.faceLandmarks, | |
mpHolistic.FACEMESH_RIGHT_EYE, | |
{ color: 'rgb(0,217,231)' } | |
); | |
} | |
if (mpHolistic.FACEMESH_RIGHT_EYEBROW) { | |
drawingUtils.drawConnectors( | |
canvasCtx, | |
results.faceLandmarks, | |
mpHolistic.FACEMESH_RIGHT_EYEBROW, | |
{ color: 'rgb(0,217,231)' } | |
); | |
} | |
if (mpHolistic.FACEMESH_LEFT_EYE) { | |
drawingUtils.drawConnectors( | |
canvasCtx, | |
results.faceLandmarks, | |
mpHolistic.FACEMESH_LEFT_EYE, | |
{ color: 'rgb(255,138,0)' } | |
); | |
} | |
if (mpHolistic.FACEMESH_LEFT_EYEBROW) { | |
drawingUtils.drawConnectors( | |
canvasCtx, | |
results.faceLandmarks, | |
mpHolistic.FACEMESH_LEFT_EYEBROW, | |
{ color: 'rgb(255,138,0)' } | |
); | |
} | |
if (mpHolistic.FACEMESH_FACE_OVAL) { | |
drawingUtils.drawConnectors( | |
canvasCtx, | |
results.faceLandmarks, | |
mpHolistic.FACEMESH_FACE_OVAL, | |
{ color: '#E0E0E0', lineWidth: 5 } | |
); | |
} | |
if (mpHolistic.FACEMESH_LIPS) { | |
drawingUtils.drawConnectors( | |
canvasCtx, | |
results.faceLandmarks, | |
mpHolistic.FACEMESH_LIPS, | |
{ color: '#E0E0E0', lineWidth: 5 } | |
); | |
} | |
} | |
canvasCtx.restore(); | |
} | |
// ์์ ์ ๋ก๋ ์ฒ๋ฆฌ | |
function handleVideoUpload(file) { | |
// ๋น๋์ค URL ์์ฑ | |
const videoUrl = URL.createObjectURL(file); | |
// ์ฐจํธ ๋ฐ์ดํฐ ์ด๊ธฐํ | |
speedChart.data.labels = []; | |
speedChart.data.datasets[0].data = []; | |
accelerationChart.data.labels = []; | |
accelerationChart.data.datasets[0].data = []; | |
previousPoseData = null; | |
lastTimestamp = 0; | |
// ์์ธ ๊ฐ์ง ๋ฆฌ์ | |
holistic.reset(); | |
// ๋น๋์ค ์์ค ์ ๋ฐ์ดํธ | |
videoElement.src = videoUrl; | |
// ๋น๋์ค ๋ก๋ ์๋ฃ ํ ์ฒ๋ฆฌ | |
videoElement.onloadedmetadata = () => { | |
// ๋น๋์ค ๋น์จ์ ๋ง์ถฐ ์บ๋ฒ์ค ์ฌ์ด์ฆ ์กฐ์ | |
const aspect = videoElement.videoHeight / videoElement.videoWidth; | |
let width, height; | |
if (window.innerWidth > window.innerHeight) { | |
height = window.innerHeight; | |
width = height / aspect; | |
} else { | |
width = window.innerWidth; | |
height = width * aspect; | |
} | |
canvasElement.width = width; | |
canvasElement.height = height; | |
// ์์์ ์ฒ๋ฆฌํ๊ธฐ ์ํ animation frame ์์ฑ | |
let animationId; | |
async function processFrame() { | |
if (videoElement.paused || videoElement.ended) { | |
cancelAnimationFrame(animationId); | |
return; | |
} | |
// ํ์ฌ ํ๋ ์์ ์์ธ ๊ฐ์ง์ ์ ์ก | |
await holistic.send({ | |
image: videoElement | |
}); | |
animationId = requestAnimationFrame(processFrame); | |
} | |
// ๋น๋์ค ์ฌ์ ์ฒ๋ฆฌ | |
videoElement.onplay = () => { | |
processFrame(); | |
}; | |
// ์ฌ์/์ผ์์ ์ง ๋ฒํผ | |
const playPauseBtn = document.createElement('button'); | |
playPauseBtn.textContent = '์ฌ์/์ผ์์ ์ง'; | |
playPauseBtn.className = 'control-button'; | |
playPauseBtn.onclick = () => { | |
if (videoElement.paused) { | |
videoElement.play(); | |
} else { | |
videoElement.pause(); | |
} | |
}; | |
// ๋ค์ ์์ ๋ฒํผ | |
const restartBtn = document.createElement('button'); | |
restartBtn.textContent = '์ฒ์๋ถํฐ ์ฌ์'; | |
restartBtn.className = 'control-button'; | |
restartBtn.onclick = () => { | |
videoElement.currentTime = 0; | |
if (videoElement.paused) { | |
videoElement.play(); | |
} | |
}; | |
// ๋ฒํผ์ ํ๋ฉด์ ์ถ๊ฐ | |
const controlsContainer = document.createElement('div'); | |
controlsContainer.className = 'video-controls'; | |
controlsContainer.appendChild(playPauseBtn); | |
controlsContainer.appendChild(restartBtn); | |
// ์ ์ ํ ์์น์ ๋ฒํผ ์ฝ์ | |
const container = document.querySelector('.container') || document.body; | |
container.appendChild(controlsContainer); | |
}; | |
// ์๋ฌ ์ฒ๋ฆฌ | |
videoElement.onerror = () => { | |
console.error('๋น๋์ค ๋ก๋ ์คํจ'); | |
alert('๋น๋์ค ๋ก๋์ ์คํจํ์ต๋๋ค. ๋ค๋ฅธ ๋น๋์ค ํ์ผ์ ์๋ํด๋ณด์ธ์.'); | |
}; | |
} | |
// ์ผ๋ถ ๊ธฐ๋ณธ ์คํ์ผ ์ถ๊ฐ | |
const style = document.createElement('style'); | |
style.textContent = ` | |
.video-controls { | |
position: fixed; | |
bottom: 20px; | |
left: 50%; | |
transform: translateX(-50%); | |
z-index: 1000; | |
display: flex; | |
gap: 10px; | |
} | |
.control-button { | |
padding: 10px 20px; | |
background-color: rgba(0, 0, 0, 0.7); | |
color: white; | |
border: none; | |
border-radius: 5px; | |
cursor: pointer; | |
font-size: 14px; | |
} | |
.control-button:hover { | |
background-color: rgba(0, 0, 0, 0.9); | |
} | |
`; | |
document.head.appendChild(style); | |
const holistic = new mpHolistic.Holistic(config); | |
holistic.onResults(onResults); | |
// ์ปจํธ๋กค ํจ๋์ ๊ทธ๋ ค์ ์ฌ์ฉ์์๊ฒ ์ต์ ์ ์ด ์ ๊ณต | |
new controls | |
.ControlPanel(controlsElement, { | |
selfieMode: true, | |
modelComplexity: 1, | |
smoothLandmarks: true, | |
enableSegmentation: false, | |
smoothSegmentation: true, | |
minDetectionConfidence: 0.5, | |
minTrackingConfidence: 0.5, | |
effect: 'background', | |
}) | |
.add([ | |
new controls.StaticText({ title: 'MediaPipe ์ ์ ์์ธ ๊ฐ์ง' }), | |
fpsControl, | |
new controls.Toggle({ title: '์ ํผ ๋ชจ๋', field: 'selfieMode' }), | |
new controls.SourcePicker({ | |
onSourceChanged: () => { | |
// ์์ค ๋ณ๊ฒฝ ์ ๋ฆฌ์ . ๋ฆฌ์ ํ ์์ธ๋ฅผ ๋ ์ ๊ฐ์งํ ์ ์์ | |
holistic.reset(); | |
}, | |
onFrame: async (input, size) => { | |
const aspect = size.height / size.width; | |
let width, height; | |
if (window.innerWidth > window.innerHeight) { | |
height = window.innerHeight; | |
width = height / aspect; | |
} else { | |
width = window.innerWidth; | |
height = width * aspect; | |
} | |
canvasElement.width = width; | |
canvasElement.height = height; | |
await holistic.send({ image: input }); | |
}, | |
}), | |
new controls.Slider({ | |
title: '๋ชจ๋ธ ๋ณต์ก๋', | |
field: 'modelComplexity', | |
discrete: ['๊ฒฝ๋', '์์ ', '๊ณ ๊ธ'], | |
}), | |
new controls.Toggle({ title: '๋๋๋งํฌ ํํํ', field: 'smoothLandmarks' }), | |
new controls.Toggle({ title: '์ธ๊ทธ๋จผํ ์ด์ ์ฌ์ฉ', field: 'enableSegmentation' }), | |
new controls.Toggle({ title: '์ธ๊ทธ๋จผํ ์ด์ ํํํ', field: 'smoothSegmentation' }), | |
new controls.Slider({ | |
title: '์ต์ ๊ฐ์ง ์ ๋ขฐ๋', | |
field: 'minDetectionConfidence', | |
range: [0, 1], | |
step: 0.01 | |
}), | |
new controls.Slider({ | |
title: '์ต์ ์ถ์ ์ ๋ขฐ๋', | |
field: 'minTrackingConfidence', | |
range: [0, 1], | |
step: 0.01 | |
}), | |
new controls.Slider({ | |
title: 'ํจ๊ณผ', | |
field: 'effect', | |
discrete: { 'background': '๋ฐฐ๊ฒฝ', 'mask': '์ ๊ฒฝ' }, | |
}), | |
]) | |
.on(x => { | |
const options = x; | |
videoElement.classList.toggle('selfie', options.selfieMode); | |
activeEffect = x['effect']; | |
holistic.setOptions(options); | |
}); | |
// ์ด๊ธฐํ ํจ์ | |
function initialize() { | |
// ์ฐจํธ ์ด๊ธฐํ | |
initCharts(); | |
// ๋น๋์ค ์ ๋ก๋ ์ฒ๋ฆฌ | |
const videoUploadInput = document.querySelector('#video-upload'); | |
if (videoUploadInput) { | |
videoUploadInput.addEventListener('change', (e) => { | |
if (e.target.files.length > 0) { | |
handleVideoUpload(e.target.files[0]); | |
} | |
}); | |
} | |
// ์์ธ ๊ฐ์ง ์ด๊ธฐํ | |
const holistic = new mpHolistic.Holistic(config); | |
holistic.onResults(onResults); | |
// ... ๊ธฐ์กด ์ด๊ธฐํ ๋ก์ง์ ์ ์ง ... | |
} | |
// ์ ํ๋ฆฌ์ผ์ด์ ์์ | |
window.addEventListener('load', initialize); | |
// ์ฐฝ ํฌ๊ธฐ ๋ณ๊ฒฝ ์ฒ๋ฆฌ | |
window.addEventListener('resize', () => { | |
const aspect = videoElement.videoHeight / videoElement.videoWidth; | |
let width, height; | |
if (window.innerWidth > window.innerHeight) { | |
height = window.innerHeight; | |
width = height / aspect; | |
} else { | |
width = window.innerWidth; | |
height = width * aspect; | |
} | |
canvasElement.width = width; | |
canvasElement.height = height; | |
// ์ฐจํธ ํฌ๊ธฐ ์ฌ์กฐ์ | |
speedChart.resize(); | |
accelerationChart.resize(); | |
}); | |