如何使用面部运动与排版交互

已发表: 2022-03-10
快速总结↬在本文中,我们将介绍如何结合机器学习、可变字体和 CSS 网格来创建响应接近度、倾斜度和用户面部数量的布局。

网页设计师一直在寻找新的方法来改进页面内容的呈现。 有时,这可能会导致巧妙的解决方案或与通常远离设计领域的技术进行交互。 在本文中,我们将把排版与人工智能联系起来,使用机器学习来检测用户面部的接近程度等,以提高文本的易读性。

我们将尝试如何使用 Tensorflow 进行人脸识别,以便从相机中提取一些信息,例如屏幕与用户面部之间的距离或阅读页面的人数。 然后,我们将这些数据传递给 CSS 以适应排版和调整页面布局。

什么是张量流?

Tensorflow 是谷歌机器学习的开源平台。 机器学习是计算机科学的一个领域,它研究学习从图像、音轨、时间序列、自然文本和一般数据中识别复杂关系和重复模式的算法。 这些算法生成数学模型(也称为训练模型),这是一种可用于根据输入数据做出决策的模式。 如果你想讨论这个话题,Charlie Gerard 在 Smashing Mag 上为前端开发人员写了一篇关于 ML 的文章。

Tensorflow 为 AI 开发人员、数据科学家、数学家提供了很多工具,但如果数据分析不是您的日常面包,请不要惊慌! 好消息是,您无需成为专家即可使用它,只要您使用预构建模型,就像我们将要使用的那样。

TensorFlow 模型可通过其 JavaScript SDK 在 Web 上使用。

设置

为了开始使用人脸识别算法,我们需要遵循几个步骤:

  • 加载 TensorFlow SDK。
  • 加载包含数学模型的 Facemesh 库。
  • 访问用户的相机并将其流式传输到 HTML 视频元素。 Facemesh 将分析来自视频标签的帧以检测人脸的存在。

在这个项目中,我们将通过 CDN 使用 Tensorflow,但如果您更喜欢 bundler 方式,它也可以在 NPM 上使用:

 <script src="https://cdn.jsdelivr.net/npm/@tensorflow/tfjs-core"></script> <script src="https://cdn.jsdelivr.net/npm/@tensorflow/tfjs-converter"></script> <script src="https://cdn.jsdelivr.net/npm/@tensorflow/tfjs-backend-webgl"></script>

Tensorflow 本身并不能做到这一点,因此我们需要添加 Facemesh,这是一个构建在 ML 框架之上并提供已经训练好的人脸识别模型的库:

 <script src="https://cdn.jsdelivr.net/npm/@tensorflow-models/facemesh"></script>

下一步是设置 Facemesh 库以加载经过训练的模型并定义将从视频流中评估人脸数据的函数:

 // create and place the video const video = document.createElement('video'); document.body.appendChild(video); // setup facemesh const model = await facemesh.load({ backend: 'wasm', maxFaces: 1, }); async function detectFaces() { const faces = await model.estimateFaces(video); console.log(faces); // recursively detect faces requestAnimationFrame(detectFaces); }

现在我们准备好使用视频标签询问用户访问其相机流的权限:

 // enable autoplay video.setAttribute('autoplay', ''); video.setAttribute('muted', ''); video.setAttribute('playsinline', ''); // start face detection when ready video.addEventListener('canplaythrough', detectFaces); // stream the camera video.srcObject = await navigator.mediaDevices.getUserMedia({ audio: false, video: { facingMode: 'user', }, }); // let's go! video.play();

navigator.mediaDevices.getUserMedia 方法将提示权限并将开始将相机流式传输到视频元素中。 一旦被接受,相机将开始流式传输到视频标签,而浏览器控制台将记录 Facemesh 检测到的面部信息。

请注意,相机权限需要安全的 https 连接或 localhost:您不能简单地打开 index.html 文件。 如果您不确定如何为 Node 设置本地服务器 checkout http-server 或遵循 Python 指南或 PHP 指南。

跳跃后更多! 继续往下看↓

案例 1. 使用智能手机摄像头调整排版

我们使用智能手机在任何地方浏览网络。 不久前,有一段时间,我们常常乘坐拥挤的火车或公共汽车,因为没有空间,我们将智能手机放在离眼睛很近的地方。 在我们一天中的许多时刻和地点,我们经常改变智能手机的位置和倾斜度,即使我们正在观看同一个网站。 眼睛和智能手机之间的距离会影响我们的阅读能力。 评估该距离,我们可以调整微排版,以优化字形以实现更近或更远的阅读。

人脸检测当然也意味着眼睛位置检测。 我们可以使用 Facemesh 提供的数据来计算我们的脸相对于相机拍摄的整张照片的大小。 我们可以假设我们的脸越大,我们离屏幕越近。 我们可以设置一个从 0(一只手臂相距很远——脸部大约占据相机的一半)到 1(粘在屏幕上)的比例,并通过分段检测当前值:

 async function detectFaces() { const faces = await model.estimateFaces(video); if (faces.length === 0) { // is somebody out there? return requestAnimationFrame(detectFaces); } const [face] = faces; // extract face surface corners let { bottomRight, topLeft} = face.boundingBox; // calculate face surface size let width = bottomRight[0] - topLeft[0]; let height = bottomRight[1] - topLeft[1]; let videoWidth = video.videoWidth; let videoHeight = video.videoHeight; let adjustWidth = videoWidth / 2; let adjustHeight = videoHeight / 2; // detect the ratio between face and full camera picture let widthRatio = Math.max(Math.min((width - adjustWidth) / (videoWidth - adjustWidth), 1), 0); let heightRatio = Math.max(Math.min((height - adjustHeight) / (videoHeight - adjustHeight), 1), 0); let ratio = Math.max(widthRatio, heightRatio); // recursively detect faces requestAnimationFrame(detectFaces); }
用户面部的分段表示被放置在智能手机的框架内,以指示面部占据了屏幕的多少区域。
Facemesh 检测到的两个特征示例,例如眼睛、鼻子和嘴巴的位置和倾斜度。 我们可以使用点之间的区域来计算与智能手机摄像头的距离。 (大预览)

现在我们已经计算了ratio ,是时候让一些神奇的事情发生了,将值传递给样式表:

 document.documentElement.style.setProperty('--user-distance', ratio);

有了这个值和一点计算,我们可以轻松地对字体粗细、大小甚至样式进行细微的更改,但我们可以做得更好。 使用可变字体,一种具有参数化形状和字形空间的字体,我们可以通过更新其光学尺寸变化来调整每个字形的感知。

由于每种可变字体都使用自己的光学尺寸值比例,因此我们需要将比率值与该比例相关联。 此外,我们可能只想在可用光学尺寸的子集之间移动,以便仅提供少量增强。

 .main-text { --min-opsz: 10; --max-opsz: 15; --opsz: calc(var(--min-opsz) + (var(--user-distance) * (var(--max-opsz) - var(--min-opsz)))); ... font-family: 'Amstelvar', serif; font-variation-settings: 'opsz' var(--opsz); }

你可以在这里看到它。 请注意,此示例仅演示该技术的工作原理。 为了真正提供更好的阅读体验,用户的眼睛应该几乎察觉不到排版的变化。 在这里,我们利用了字形形状,但使用颜色来增加或减少对比度只是另一个很好的尝试解决方案。 另一个实验是检测人脸的角度以计算阅读的角度,修改上升、下降和字母的高度:

请参阅 Edoardo Cavazza 的钢笔 [Facemesh 和上升器/下降器](https://codepen.io/smashingmag/pen/oNxrYop)。

请参阅 Edoardo Cavazza 的 Pen Facemesh 和上升器/下降器。

案例#2:当观看人数发生变化时调整布局

在第二种情况下,我们将根据观看屏幕的人数来更改布局。 我们可以想象在高中课堂的背景下,交互式白板上显示的一篇文章。 这种情况与不推荐使用的投影媒体查询检测到的情况完全不同,因为如果观看的学生人数少于或多于 10 人,我们希望调整页面布局。当教室里只有几个学生时,他们可以安全地接近白板,但如果整个教室都在场,可能空间不够,我们需要更改布局以显示更少(和更大)的东西。

我们只需要对前面的脚本进行一些更改,以便正确检测在白板上观看的人脸数量。 首先,我们需要指示 Facemesh 检测多个人脸:

 const model = await facemesh.load({ backend: 'wasm', maxFaces: 30, });

然后,我们必须将该数字传递给样式表:

 async function detectFaces() { const faces = await model.estimateFaces(video); document.documentElement.style.setProperty('--watching', faces.length); // recursively detect faces requestAnimationFrame(detectFace); }

同样,我们可以使用该值来简单地增加字体大小,但我们的目标是提供完全不同的布局。 CSS 网格布局可以帮助我们完成这项任务。 此投影文档是一个长表单,旁边包含相关图像:

 <section> <article> <h1>...</h1> <h2>...</h2> <p>...</p> </article> <aside> <img src="..." alt="..." /> </aside> </section>

这是它的默认布局:

 section { display: grid; grid-template-columns: repeat(12, 1fr); grid-column-gap: 1em; width: 120ch; max-width: 100%; padding: 1em; } section article { grid-column: 1 / -5; } section aside { grid-column: 7 / -1; }
左图:当观看人数超过 10 人时,我们使用所有可用列优先考虑正文,并将图像移到正文之后。右图:使用 12 列的页面布局,带有活动的 Firefox 网格检查器指南。所有 12 列用于正文,4 列用于其后的图像。
:页面的默认布局将 12 列中的 8 列用于长格式文本,其余 4 列用于图像。 :当观看人数超过 10 人时,我们会使用所有可用列优先显示主要文本,并将图像移动到文本之后。 (大预览)

当多人观看时,我们需要对长篇阅读上下文给予特权,给主栏更多的空间,增加它的字体大小,并去除干扰元素。 为此,我们增加了跨栏的数量,将主文本下方的旁边移动。

 :root { --watching: 10; } section { /** The maximum number of people watching for the default layout */ --switch: 10; /** The default number of columns for the text */ --text: 8; /** The default number of columns for the aside */ --aside: 4; grid-template-columns: repeat(calc(var(--text) + var(--aside)), 1fr); } section article { /** * Kinda magic calculation. * When the number of people watching is lower than --switch, it returns -2 * When the number of people watching is greater than --switch, it returns -1 * We are going to use this number for negative span calculation */ --layout: calc(min(2, (max(var(--switch), var(--watching)) - var(--switch) + 1)) - 3); /** * Calculate the position of the end column. * When --layout is -1, the calculation just returns -1 * When --layout is -2, the calculation is lower than -1 */ --layout-span: calc((var(--aside) * var(--layout)) + var(--aside) - 1); /** * Calculate the maximum index of the last column (the one "before" the aside) */ --max-span: calc(-1 * var(--aside) - 1); /** * get the max between --layout-span and the latest column index. * -1 means full width * --max-span means default layout */ --span: max(var(--max-span), var(--span)); grid-column-start: 1; grid-column-end: var(--span); }
  • 你可以在这里看到它 →

反之亦然,当一小群学生在黑板附近体验文字时,我们可以提供更多细节,例如媒体文件和交互动作触发器。

超越人脸识别

我们面临的案例 () 只是我们如何将人脸识别技术用于布局或印刷范围的两个示例。 Tensorflow 提供了其他模型和库,可以将相机流转换为我们页面的变量。 此外,我们不应忘记,在我们的智能手机中,我们可以使用传感器 API 来利用许多其他传感器:GPS、加速度计、环境光等。

由于情绪影响我们阅读、学习和搜索信息的方式,通过机器学习,我们还可以分析用户的表情,根据用户的精神从最小布局切换到详细布局。

多年来,我们一直习惯于使用 CSS 媒体查询进行响应式网页设计。 然而,视口的大小只是用户体验的变量之一。 最近,一种旨在尊重用户偏好的新型媒体查询登陆浏览器,例如prefers-color-schemeprefers-reduced-motion 。 这为设计人员和开发人员提供了一种在网页设计实践中向前迈进的方法,允许网页适应整个环境而不仅仅是用户的设备。 在大数据时代,我们有机会超越响应式和自适应设计。 我们的网页终于可以“离开屏幕”,成为用户全球体验的一部分。 交互设计将涉及所有这些可能性,因此在接下来的几年中不断尝试技术和网页设计之间可能的组合将是至关重要的。