原文链接为 JavaScript Quickies: Controlling 3D Objects with Hands, 由 Team XenoX 发表在 DEV 社区并由本人进行翻译转载. 本篇文章其实并无太大的难点和亮点, 但鉴于项目的娱乐性和趣味性, 并且作者今后也可能改进, 因此在此介绍给大家.
嘿, 伙计们, 我们 Team XenoX 的全体成员很高兴地宣布我们将开始一个新的系列文章: JavaScript Quickies. 在这里, 你能够快速体验到 JavaScript 领域的最新科技实验. 感谢 JavaScript, 我们可以插入各种模块并创建任何东西. 唯一的局限在于我们的想象力.
想法
我们都能在科幻电影中找到我们喜欢的场景, 特别对我们开发者而言, 看到这些场景的时候我们不禁会思考要怎么做才能让这些炫酷的科幻场景在现实中落地. 每当看到这样的场景, 我的大脑就会疯狂地运转起来, 想象着所有的技术可能性. 对此有着犹如孩子般的迷恋, 我喜欢这种感觉.
我依然记得十几岁刚看到钢铁侠的时候, 完全被他在实验室里与全息物体互动的场景所震撼. 当我回忆起那一幕时, 我开始思考我是否可以创造一些类似的, 能够带给我们同样乐趣的东西.
当然, 我们并没有那么厉害的技术来实现完全相同的效果, 至少目前还没有. 但是我们可以用现有的技术做一些一样酷炫的东西. 所我在周末做了这个很酷的小项目来和你们分享.
系好安全带, 复仇者们! 我们将开始探索 The Hand Trick 的旅程.
立即尝试
必备前提
我使用原生 JS 实现了此项目, 所以你需要具备基本的 JavaScript 知识才能够理解这篇教程. 此外, 我在本项目中引入了两个第三方库:
- Three.JS link
- Handtrack.JS link
开始编码
html 部分的代码非常简单, 我们仅仅引入相关库并且添加 div 用来渲染摄像头.
- <!DOCTYPE HTML>
- <HTML lang="en">
- <head>
- <meta charset="UTF-8" />
- <meta name="viewport" content="width=device-width, initial-scale=1.0"
- />
- <meta http-equiv="X-UA-Compatible" content="ie=edge" />
- <title>
- The Hand Trick
- </title>
- <link rel="stylesheet" href="CSS/style.css" />
- </head>
- <body>
- <!-- Video for handtracker -->
- <div class="tracker">
- <video id="myvideo">
- </video>
- <canvas id="canvas" class="border">
- </canvas>
- <button id="trackbutton" disabled onclick="toggleVideo()">
- Button
- </button>
- <div id="updatenote">
- hello
- </div>
- </div>
- <div class="data">
- <div class="hand-1">
- <p id="hand-x">
- X:
- <span>
- 0
- </span>
- </p>
- <p id="hand-y">
- Y:
- <span>
- 0
- </span>
- </p>
- </div>
- </div>
- <script src="js/three.js">
- </script>
- <script src="https://cdn.jsdelivr.net/npm/handtrackjs/dist/handtrack.min.js">
- </script>
- <script src="js/scene.js">
- </script>
- </body>
- </HTML>
完成以上部分后, 我们来快速看下 JavaScript 部分做了什么, 如果你了解 Three.JS, 你可以跳过这部分. 这里, 我们创建了一个场景并配置了相关的细节.
- // 设置 3D 物体所属的场景
- var scene = new THREE.Scene();
- var camera = new THREE.PerspectiveCamera(
- 75,
- Windows.innerWidth / Windows.innerHeight,
- 0.1,
- 1000
- );
- var vector = new THREE.Vector3();
- var renderer = new THREE.webGLRenderer();
- renderer.setSize(Windows.innerWidth, Windows.innerHeight);
- document.body.appendChild(renderer.domElement);
之后, 我们创建了一个将在此场景中进行渲染的 3D 物体. 我们定义了箱体的几何形体及材质.
- // 创建 3D 物体对象
- var geometry = new THREE.BoxGeometry(1, 2, 1);
- var material = new THREE.MeshBasicMaterial({
- color: "rgba(3, 197, 221, 0.81)",
- wireframe: true,
- wireframeLinewidth: 1
- });
- var cube = new THREE.Mesh(geometry, material);
- scene.add(cube);
- camera.position.z = 5;
如果你想要以 3D 模式旋转创对象, 这一步骤是可选的. 这一步只是使得效果看起来更酷炫.
- // 旋转元素的动画 (可选的)
- var animate = function() {
- requestAnimationFrame(animate);
- cube.rotation.x += 0.01;
- cube.rotation.y += 0.01;
- renderer.render(scene, camera);
- };
- animate();
上面就是我们使用 three.JS 所做的所有事情. 接下来我们来看看 Handtrack.JS.
- // 创建 canvas 渲染视频输入流
- const video = document.getElementById("myvideo");
- const handimg = document.getElementById("handimage");
- const canvas = document.getElementById("canvas");
- const context = canvas.getContext("2d");
- let trackButton = document.getElementById("trackbutton");
- let updateNote = document.getElementById("updatenote");
- let imgindex = 1;
- let isVideo = false;
- let model = null;
- // 初始化 Handtracking JS 的参数
- const modelParams = {
- flipHorizontal: true,
- maxNumBoxes: 1,
- iouThreshold: 0.5,
- scoreThreshold: 0.7
- };
- handTrack.load(modelParams).then(lmodel => {
- model = lmodel;
- updateNote.innerText = "Loaded Model!";
- trackButton.disabled = false;
- });
虽然我们在此定义了一些加载 Handtrack JS 的参数, 但是这个参数是可选的, 你也可以传入一个空的模型参数. handTrack.load() 方法帮助我们加载手势检测模型. Handtrack JS 加载完毕之后, 我们编写了一个函数将视频流加载到 HTML 中定义的 canvas 中. 为此, 我们使用了 handTrack.startVideo() 方法.
- // 开启视频
- function startVideo() {
- handTrack.startVideo(video).then(function(status) {
- if (status) {
- updateNote.innerText = "Video started. Now tracking";
- isVideo = true;
- runDetection();
- } else {
- updateNote.innerText = "Please enable video";
- }
- });
- }
- // 开启 / 关闭视频
- function toggleVideo() {
- if (!isVideo) {
- updateNote.innerText = "Starting video";
- startVideo();
- } else {
- updateNote.innerText = "Stopping video";
- handTrack.stopVideo(video);
- isVideo = false;
- updateNote.innerText = "Video stopped";
- }
- }
现在我们可以编写代码从 Handtrack.JS 获取到检测的数据.
- // 手势移动检测
- function runDetection() {
- model.detect(video).then(predictions => {
- model.renderPredictions(predictions, canvas, context, video);
- if (isVideo) {
- requestAnimationFrame(runDetection);
- }
- });
- }
关键步骤 ♂
上面的所有代码基本上都可以从库文档中复制 - 粘贴. 真正的挑战在于将两者集成起来以获取想要的数据.
诀窍在于捕捉跟踪视频上的手势, 并对 three.JS 中的对象物体进行相应的变换.
model.detect() 方法的 prediction 对象返回以下数据:
- {
- "bbox": [x, y, width, height],
- "class": "hand",
- "score": 0.8380282521247864
- }
bbox 定义了手部周围边框的坐标, 宽度和高度. 这个坐标并非中心点坐标, 我们将通过以下这个简单的公式来计算中心点坐标:
- let midvalX = value[0] + value[2] / 2;
- let midvalY = value[1] + value[3] / 2;
另一个问题是 3D 物体对象所在的 canvas(画布) 和追踪器所在的画布之间的比例比较大. 此外, 两者的源数据的原点都非中心点. 为了解决这个问题, 我们首先转换坐标以使视频画布点是居中的.
完成上述操作之后, 比例问题就变得容易解决. 所以最终的结果就像这样:
- // 手势移动检测
- function runDetection() {
- model.detect(video).then(predictions => {
- model.renderPredictions(predictions, canvas, context, video);
- if (isVideo) {
- requestAnimationFrame(runDetection);
- }
- if (predictions.length> 0) {
- changeData(predictions[0].bbox);
- }
- });
- }
- // 将手势检测的数据变为对我们有用的信息
- function changeData(value) {
- let midvalX = value[0] + value[2] / 2;
- let midvalY = value[1] + value[3] / 2;
- document.querySelector(".hand-1 #hand-x span").innerHTML = midvalX;
- document.querySelector(".hand-1 #hand-y span").innerHTML = midvalY;
- moveTheBox({ x: (midvalX - 300) / 600, y: (midvalY - 250) / 500 });
- }
- // 改变并渲染立方体
- function moveTheBox(value) {
- cube.position.x = ((Windows.innerWidth * value.x) / Windows.innerWidth) * 5;
- cube.position.y = -((Windows.innerHeight * value.y) / Windows.innerHeight) * 5;
- renderer.render(scene, camera);
- }
到此, 我们已经完成了所有步骤. 你现在可以用手来控制 3D 对象. 我已经在 GitHub 开源了代码, 所以去克隆下来看一看并运行起来, 希望你喜欢它!
GitHub 仓库
总结
故事才刚刚开始, 这是本系列的第一个教程, 我计划着进一步这个实验. 我希望有更多的人能够参与进来, 如果你希望参与此项目, 只要提交 PR 到 XenoX Multiverse 即可, 我将会联系你.
Team XenoX 最初是一个由一群为了乐趣而从事开源项目的开发人员组成的小团队. 但是过去的几个月, 这个团队在不断地壮大. 这也是我创建 XenoX Multiverse 的原因, 那里是 Team XenoX 所有开源计划的基地. 如果你想要成为我们的一员, 只要写下你的名字, 并参与到代码贡献中来.
我们下次见!
来源: http://www.jianshu.com/p/865b80e12291