多源异构数据采集与融合应用综合实践
以「指尖与光影」打造沉浸式非遗互动 —— 多源异构数据采集与融合应用综合实践
模块负责:虚拟剪纸(
virtual_cut.html)、皮影戏互动(shadow_puppetry.html)、濒危项目展示(endangered.html)
01 虚拟剪纸:让“剪刀手”剪进 3D 纸张
目标与体验设计
- 对着摄像头比剪刀手,在 3D 纸张上实时剪裁。
- 支持 对折 / 团花(四折) 镜像;折叠预览与展开切换。
- 颜色可选,剪裁轨迹实时映射到纸张纹理。
- 交互必须“顺手”:低延迟、低抖动、明显的状态反馈。
![image]()
技术方案
- 手势识别:MediaPipe Hand Landmarker(GPU),食指/中指距离判定剪刀手,阈值随手掌尺寸动态调整。
- 坐标映射:相机归一化(0~1)→ NDC(-1~1)→ 射线落到纸面 UV → 纹理像素。
- 剪裁实现:
CanvasTexture作为alphaMap,destination-out挖洞,needsUpdate控制刷新。 - 镜像策略:按折叠层级对 UV 做水平/垂直/中心对称镜像,多次绘制同轨迹。
- 视觉反馈:3D 光标(锥体+环)剪裁态变红收缩;GSAP 处理折叠/展开缩放。
- 平滑与性能:指尖坐标 Lerp 去抖,限制重绘和线宽,OrbitControls 禁止平移保持居中。
关键代码片段
// 剪刀手检测 + 动态阈值
const indexTip = landmarks[8];
const middleTip = landmarks[12];
const wrist = landmarks[0];
const handSize = Math.hypot(indexTip.x - wrist.x, indexTip.y - wrist.y);
const dist = Math.hypot(indexTip.x - middleTip.x, indexTip.y - middleTip.y);
const isScissors = dist > (handSize * 0.15); // 动态阈值
// 镜像剪裁:对折 / 团花
function performCut(uvX, uvY) {
const texX = uvX * cutTexture.repeat.x;
const texY = uvY * cutTexture.repeat.y;
const canvasX = texX * PAPER_RES;
const canvasY = (1 - texY) * PAPER_RES;
cutCtx.globalCompositeOperation = 'destination-out';
cutCtx.lineWidth = 30; cutCtx.lineCap = 'round'; cutCtx.lineJoin = 'round';
drawLine(canvasX, canvasY, lastCanvasPoint);
if (isFolded) {
const mirrorX = PAPER_RES - canvasX;
drawLine(mirrorX, canvasY, mirrorPointX(lastCanvasPoint));
if (foldLevel === 2) {
const mirrorY = PAPER_RES - canvasY;
drawLine(canvasX, mirrorY, mirrorPointY(lastCanvasPoint));
drawLine(mirrorX, mirrorY, mirrorPointXY(lastCanvasPoint));
}
}
lastCanvasPoint = { x: canvasX, y: canvasY };
cutTexture.needsUpdate = true;
}
// 折叠/展开的视觉缩放
function updateFoldVisuals() {
let tx = 1, ty = 1, repX = 1, repY = 1;
if (isFolded) {
btn.innerText = "展开预览"; btn.classList.remove('btn-action');
if (foldLevel === 1) { tx = 0.5; repX = 0.5; }
else if (foldLevel === 2) { tx = 0.5; ty = 0.5; repX = 0.5; repY = 0.5; }
} else { btn.innerText = "继续剪裁"; btn.classList.add('btn-action'); }
gsap.to(paperMesh.scale, { x: tx, y: ty, duration: 0.6, ease: "power2.inOut" });
cutTexture.repeat.set(repX, repY); cutTexture.offset.set(0, 0);
}
踩坑 & 解决
- 抖动锯齿 → Lerp 平滑 + 适度线宽。
- 远近误判 → 手掌尺度动态阈值。
- 纹理性能 → 控制
needsUpdate,必要时降分辨率。
02 皮影戏互动:指尖驱动的“幕后人偶”
目标与氛围
- 摄像头捕捉手势,驱动虚拟皮影人物表演。
- “幕布+背光”质感,呈现光影投射氛围。
![image]()
技术方案
- 人偶构建:纯代码拼平面网格(头/躯干/臂/腿/操控杆),半透明材质模拟皮影。
- 动作映射:食指/拇指/小指 → 身体/左右手;X 轴镜像适配摄像头。
- 简易 IK:仅 Z 轴插值并限幅,远伸近弯,避免折断。
- 光影与雾效:柔光 + 背光 + 指数雾 + 幕布噪声纹理。
- 循环解耦:渲染与手势检测分离,姿态插值降抖。
关键代码片段
// 坐标映射(含镜像)
const mapX = (val) => (0.5 - val) * 12;
const mapY = (val) => (0.5 - val) * 8;
targetPos.body.set(mapX(indexTip.x), mapY(indexTip.y), 0);
targetPos.lHand.set(mapX(thumbTip.x), mapY(thumbTip.y), 0);
targetPos.rHand.set(mapX(pinkyTip.x), mapY(pinkyTip.y), 0);
currentPos.body.lerp(targetPos.body, LERP_FACTOR);
currentPos.lHand.lerp(targetPos.lHand, LERP_FACTOR);
currentPos.rHand.lerp(targetPos.rHand, LERP_FACTOR);
// 简易 IK + 角度限幅
function updateArmIK(armObj, targetHandWorldPos) {
const shoulderWorldPos = new THREE.Vector3();
armObj.root.getWorldPosition(shoulderWorldPos);
const dir = new THREE.Vector3().subVectors(targetHandWorldPos, shoulderWorldPos);
const angle = Math.atan2(dir.y, dir.x);
const rotationZ = angle + Math.PI / 2;
armObj.root.rotation.z = THREE.MathUtils.lerp(armObj.root.rotation.z, rotationZ, 0.1);
const dist = dir.length(), armLen = 2.0;
armObj.foreArm.rotation.z = dist < armLen * 0.8 ? -0.5 : 0;
}
// MediaPipe 初始化 & 解耦循环
const vision = await FilesetResolver.forVisionTasks(".../wasm");
handLandmarker = await HandLandmarker.createFromOptions(vision, {
baseOptions: { modelAssetPath: "/static/models/hand_landmarker.task", delegate: "GPU" },
runningMode: "VIDEO", numHands: 1
});
function processVideo() {
const now = performance.now();
const result = handLandmarker.detectForVideo(video, now);
if (result.landmarks.length > 0) updatePuppet(result.landmarks[0]);
requestAnimationFrame(processVideo);
}
function animate() {
requestAnimationFrame(animate);
renderer.render(scene, camera);
}
踩坑 & 解决
- 镜像方向错位 → X 轴映射层统一反转。
- 抖动/折断 → 只插值 Z 轴并限幅,距离驱动前臂弯曲。
03 濒危项目展示:数据与情感的“小档案墙”
目标与体验
- 卡片化呈现濒危非遗,突出“极危/预警”与传承人信息。
- 友好加载、流畅分页,营造“档案调阅”仪式感。
![image]()
技术方案
- 数据加载:
/analysis/api/endangered_items;加载遮罩与错误兜底。 - 计数动画:固定时长/帧率计算步长,末帧矫正,1s 内完成。
- 卡片设计:状态角标、灰度悬停反差、传承人提示;分页滚顶。
- 动画节奏:卡片
fadeInUp阶梯延迟,列表fadeIn。
关键代码片段
// 计数动画(步长+定时)
const target = data.total_endangered;
let count = 0;
const duration = 1000, frameRate = 20;
const totalFrames = duration / frameRate;
const step = Math.max(1, Math.ceil(target / totalFrames));
const interval = setInterval(() => {
count += step;
if (count >= target) { this.totalCount = target; clearInterval(interval); }
else { this.totalCount = count; }
}, frameRate);
// 分页与滚动复位
get paginatedItems() {
const start = (this.currentPage - 1) * this.pageSize;
return this.items.slice(start, start + this.pageSize);
},
nextPage() {
if (this.currentPage < this.totalPages) {
this.currentPage++;
window.scrollTo({ top: 0, behavior: 'smooth' });
}
},
prevPage() {
if (this.currentPage > 1) {
this.currentPage--;
window.scrollTo({ top: 0, behavior: 'smooth' });
}
}
<!-- 状态角标与传承人提示 -->
<div class="absolute top-0 right-0 px-3 py-1 text-xs font-bold"
:class="item.level === 'critical' ? 'bg-cinnabar text-white' : 'bg-orange-600/10 text-orange-700'">
<span x-text="item.level === 'critical' ? '极危 · 断层' : '预警 · 单传'"></span>
</div>
<div class="text-sm font-serif">
<template x-if="item.inheritor_count === 0">
<span class="text-cinnabar/80 italic">已无在册传承人,急需抢救。</span>
</template>
<template x-if="item.inheritor_count > 0">
<span><span class="text-ink-light">仅存:</span><span x-text="item.inheritor_names.join('、')"></span></span>
</template>
</div>
踩坑 & 解决
- 计数过慢/不准 → 固定时长+步长,末帧校准。
- 加载/空态 → 失败立刻去除 loading;空列表提示“幸甚·暂无濒危项目”。
架构与技术选型小结
- 前端框架:模板继承 + Alpine 轻量状态;Three.js 负责 3D。
- 手势识别:MediaPipe Tasks Vision(Hand Landmarker)GPU 加速。
- 动画/过渡:GSAP(折叠/展开、光标反馈)、CSS Keyframes(列表入场)。
- 性能策略:渲染/推理解耦;指尖 Lerp;限制纹理
needsUpdate;减少阴影与重绘。
体验优化的细节清单
- 加载遮罩与主题文案(“正在后台起乐…/正在唤醒灵感…”)降低等待焦虑。
- 光标态(颜色+缩放)清晰区分“可剪/闲置”。
- 镜像/对称逻辑封装,便于扩展更多折叠模式。
- 分辨率与线宽平衡:保证边缘平滑又保帧率。
- 翻页滚顶、空态提示、错误兜底,覆盖完整路径。
可能的扩展方向
- 更多手势语义:长按=刻刀、圈选=区域裁剪、握拳=暂停等组合。
- 移动端适配:降纹理/简光影,兼容弱 GPU 与高 DPI。
- 录制与分享:输出剪纸/皮影短视频或 GIF。
- 多语言与无障碍:I18N、键盘/语音 fallback。
- 素材/皮肤:皮影剧目皮肤包,剪纸主题模板。
我的收获
- 手势到交互的映射:归一化→世界坐标,配平滑/镜像/反馈构建可信手感。
- 低成本沉浸感:材质、光影、雾效、纹理与文案即可营造戏台与纸感。
- 实时渲染取舍:纹理更新、手势推理、动画插值要解耦;谨慎
needsUpdate。 - 反馈决定可信度:颜色、缩放、过渡、灰度/彩色、加载态等微交互让体验顺手可感。
用指尖重新连接人和非遗技艺,让传统的美感在屏幕上继续发光。



浙公网安备 33010602011771号