mediapipe实现实时摄像头手指把脸嘴巴拉长拉大橡胶特效互动代码
代码语言:html
所属分类:其他
代码描述:mediapipe实现实时摄像头手指把脸嘴巴拉长拉大橡胶特效互动代码
代码标签: mediapipe 实时 摄像头 手指 把脸 嘴巴 拉长 拉大 橡胶 特效 互动 代码
下面为部分代码预览,完整代码请点击下载或在bfwstudio webide中打开
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>橡胶果实实时拉脸特效</title>
<!-- 引入 Tailwind CSS 构建现代动漫风面板 -->
<script src="https://cdn.tailwindcss.com"></script>
<!-- 引入 MediaPipe 核心库 -->
<script src="https://cdn.jsdelivr.net/npm/@mediapipe/camera_utils/camera_utils.js" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/@mediapipe/face_mesh/face_mesh.js" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/@mediapipe/hands/hands.js" crossorigin="anonymous"></script>
<style>
body {
background-color: #1a1a2e;
font-family: 'PingFang SC', system-ui, -apple-system, sans-serif;
}
/* 动漫黑框描边风格 */
.anime-border {
border: 4px solid #000;
box-shadow: 6px 6px 0px #000;
}
.anime-btn {
border: 3px solid #000;
box-shadow: 3px 3px 0px #000;
transition: all 0.1s ease;
}
.anime-btn:active {
transform: translate(2px, 2px);
box-shadow: 1px 1px 0px #000;
}
</style>
</head>
<body class="min-h-screen flex flex-col items-center justify-center p-4 md:p-8 text-white">
<!-- 顶部标题 -->
<div class="text-center mb-6 max-w-lg">
<h1 class="text-3xl md:text-4xl font-extrabold text-[#FFD369] tracking-wider uppercase" style="text-shadow: 3px 3px 0px #000, -1px -1px 0px #000;">
GOMU GOMU NO...
</h1>
<p class="text-xs md:text-sm text-gray-400 mt-2 font-medium">
实时动作捕捉:用单手食指和拇指“捏住”嘴角或脸颊往外拉,松开瞬间触发橡胶回弹!
</p>
</div>
<!-- 核心区域 -->
<div class="grid grid-cols-1 lg:grid-cols-12 gap-8 max-w-6xl w-full items-center">
<!-- 左侧:画布与摄像头容器 -->
<div class="lg:col-span-8 flex flex-col items-center">
<div class="relative anime-border bg-black rounded-lg overflow-hidden w-[640px] max-w-full aspect-[4/3]">
<!-- 主渲染画面 -->
<canvas id="outputCanvas" width="640" height="480" class="w-full h-full block transform scale-x-1"></canvas>
<!-- 隐藏的原始视频流(用于 MediaPipe 帧捕获) -->
<video id="webcam" autoplay playsinline class="absolute top-0 left-0 w-0 h-0 opacity-0"></video>
<!-- 初始化遮罩/加载提示 -->
<div id="loader" class="absolute inset-0 bg-[#16213e]/95 flex flex-col items-center justify-center z-10 transition-opacity duration-500">
<div class="w-12 h-12 border-4 border-[#FF9F1C] border-t-transparent rounded-full animate-spin mb-4"></div>
<p class="text-sm font-semibold tracking-wider text-yellow-400">正在载入 MediaPipe 视觉模型与初始化相机...</p>
<p class="text-xs text-gray-400 mt-2">首次加载可能需要10-20秒,请在浏览器弹窗中允许相机权限</p>
</div>
</div>
</div>
<!-- 右侧:交互参数与指示说明 -->
<div class="lg:col-span-4 space-y-6">
<!-- 指导卡片 -->
<div class="bg-[#16213e] anime-border p-5 rounded-lg space-y-4">
<h3 class="text-lg font-bold text-[#FF9F1C] border-b-2 border-black pb-2">💡 动作秘籍</h3>
<ul class="text-xs space-y-3 text-gray-300 leading-relaxed">
<li class="flex items-start gap-2">
<span class="bg-[#FF9F1C] text-black font-extrabold text-[10px] px-1.5 py-0.5 rounded">步骤 1</span>
<span>正对摄像头,伸手靠近自己的<b class="text-[#FF9F1C]">左/右嘴角</b>或<b class="text-[#FF9F1C]">脸颊</b>。</span>
</li>
<li class="flex items-start gap-2">
<span class="bg-[#FF9F1C] text-black font-extrabold text-[10px] px-1.5 py-0.5 rounded">步骤 2</span>
<span>将拇指与食指捏合(Pinch 动作),此时画面会锁定锚点。</span>
</li>
<li class="flex items-start gap-2">
<span class="bg-[#FF9F1C] text-black font-extrabold text-[10px] px-1.5 py-0.5 rounded">步骤 3</span>
<span>按住不放往外拉,脸部像素会跟随手平滑变形。</span>
</li>
<li class="flex items-start gap-2">
<span class="bg-[#FF9F1C] text-black font-extrabold text-[10px] px-1.5 py-0.5 rounded">步骤 4</span>
<span>松开手指,体验弹性物理抖动回弹!</span>
</li>
</ul>
</div>
<!-- 参数微调 -->
<div class="bg-[#16213e] anime-border p-5 rounded-lg space-y-4">
<h3 class="text-md font-bold text-white">⚙️ 橡胶参数调节</h3>
<div class="space-y-1">
<div class="flex justify-between text-xs text-gray-400">
<span>橡胶回弹硬度 (Stiffness)</span>
<span id="stiffness-val">0.18</span>
</div>
<input type="range" id="stiffness-slider" min="0.05" max="0.4" step="0.01" value="0.18" class="w-full accent-yellow-400" />
</div>
<div class="space-y-1">
<div class="flex justify-between text-xs text-gray-400">
<span>回弹空气阻尼 (Damping)</span>
<span id="damping-val">0.72</span>
</div>
<input type="range" id="damping-slider" min="0.5" max="0.95" step="0.01" value="0.72" class="w-full accent-yellow-400" />
</div>
<div class="space-y-1">
<div class="flex justify-between text-xs text-gray-400">
<span>影响半径 (Influence Radius)</span>
<span id="radius-val">120px</span>
</div>
<input type="range" id="radius-slider" min="60" max="200" step="5" value="120" class="w-full accent-yellow-400" />
</div>
</div>
<div class="text-center">
<button onclick="toggleAudio()" id="audio-btn" class="anime-btn w-full bg-[#FF9F1C] text-black font-bold py-2.5 rounded-lg text-sm">
🔊 卡通音效: 已开启
</button>
</div>
</div>
</div>
<script>
// --- 页面配置与交互参数 ---
const width = 640;
const height = 480;
let stiffness = 0.18;
let damping = 0.72;
let influenceRadius = 120;
let audioEnabled = true;
// 滑块数据联动
document.getElementById('stiffness-slider').addEventListener('input', (e) => {
stiffness = parseFloat(e.target.value);
document.getElementById('stiffness-val').innerText = stiffness;
});
document.getElementById('damping-slider').addEventListener('input', (e) => {
damping = parseFloat(e.target.value);
document.getElementById('damping-val').innerText = damping;
});
document.getElementById('radius-slider').addEventListener('input', (e) => {
influenceRadius = parseInt(e.target.value);
document.getElementById('radius-val').innerText = influenceRadius + 'px';
});
function toggleAudio() {
audioEnabled = !audioEnabled;
document.getElementById('audio-btn').innerText = audioEnabled ? "🔊 卡通音效: 已开启" : "🔇 卡通音效: 已关闭";
}
// --- 仿射仿生网格(Grid Warp) 物理引擎设置 ---
const GRID_COLS = 16;
const GRID_ROWS = 12;
let srcGrid = [];
let dstGrid = [];
// 初始化坐标网格
for (let r = 0; r <= GRID_ROWS; r++) {
srcGrid[r] = [];
dstGrid[r] = [];
for (let c = 0; c <= GRID_COLS; c++) {
const gx = c * (width / GRID_COLS);
const gy = r * (height / GRID_ROWS);
srcGrid[r][c] = { x: gx, y: gy };
dstGrid[r][c] = { x: gx, y: gy };
}
}
// 重置变形目标网格
function resetDstGrid() {
for (let r = 0; r <= GRID_ROWS; r++) {
for (let c = 0; c <= GRID_COLS; c++) {
dstGrid[r][c].x = srcGrid[r][c].x;
dstGrid[r][c].y = srcGrid[r][c].y;
}
}
}
// --- 2D仿射三角形渲染贴图算法 ---
function drawTriangle(ctx, img, sx0, sy0, sx1, sy1, sx2, sy2, dx0, dy0, dx1, dy1, dx2, dy2) {
ctx.save();
ctx.beginPath();
ctx.moveTo(dx0, dy0);
ctx.lineTo(dx1, dy1);
ctx.lineTo(dx2, dy2);
ctx.closePath();
ctx.clip(); // 限制渲染输出为三角形闭合区间
// 求解单应/仿射变换矩阵系数 (基于三角形两组顶点的解析解)
const denom = sx0 * (sy1 - sy2) - sy0 * (sx1 - sx2) + sx1 * sy2 - sx2 * sy1;
if (Math.abs(denom) < 0.001) {
ctx.restore();
return;
}
const a = (dx0 * (sy1 - sy2) - sy0 * (dx1 - dx2) + dx1 * sy2 - dx2 * sy1) / denom;
const c = (sx0 * (dx1 - dx2) - dx0 * (sx1 - sx2) + sx1 * dx2 - sx2 * dx1) / denom;
const e = (sx0 * (sy1 * dx2 - sy2 * dx1) - sy0 * (sx1 * dx2 - sx2 * dx1) + (sx1 * sy2 - sx2 * sy1) * dx0) / denom;
const b = (dy0 * (sy1 - sy2) - sy0 * (dy1 - dy2) + dy1 * sy2 - dy2 * sy1) / denom;
const d = (sx0 * (dy1 - dy2) - dy0 * (sx1 - sx2) + sx1 * dy2 - sx2 * dy1) / denom;
const f = (sx0 * (sy1 * dy2 - sy2 * dy1) - sy0 * (sx1 * dy2 - sx2 * dy1) + (sx1 * sy2 - sx2 * sy1) * dy0) / denom;
ctx.transform(a, b, c, d, e, f);
ctx.drawImage(img, 0, 0);
ctx.restore();
}
// --- 卡通物理回弹音效 (Web Audio API 纯程序合成) ---
let audioCtx = null;
function playBoingSound() {
if (!audioEnabled) return;
try {
if (!audioCtx) audioCtx = new (window.AudioContext || window.webkitAudioContext)();
if (audioCtx.state === 'suspended') audioCtx.resume();
const now = audioCtx.currentTime;
const osc = audioCtx.createOscillator();
con.........完整代码请登录后点击上方下载按钮下载查看















网友评论0