通过 TensorFlow.js 预训练的机器学习模型,使用 JavaScript 制作智能摄像头

1. 准备工作

机器学习是当今非常热门的词汇。它的应用似乎没有限制,并且在不久的将来有望触及几乎所有行业。如果您是工程师或设计师,无论是前端还是后端,并且熟悉 JavaScript,那么此 Codelab 可帮助您开始学习机器学习,从而提升自己的技能。

前提条件

此 Codelab 专为熟悉 JavaScript 的有经验的工程师编写。

构建内容

在此 Codelab 中,您将

  • 创建一个网页,该网页可直接在网络浏览器中通过 TensorFlow.js 使用机器学习技术对实时网络摄像头视频流中的常见对象(是的,包括一次检测多个对象)进行分类和检测。
  • 增强常规网络摄像头的功能,使其能够识别对象并获取其找到的每个对象的边界框坐标
  • 突出显示视频流中找到的对象,如下所示:

8f9bad6e49e646b.png

想象一下,如果能够检测视频中是否有人,您就可以统计任意给定时间有多少人在场,从而估计特定区域在一天中的繁忙程度;或者,当系统检测到您的狗在您外出时进入了您家中的某个房间(也许它不应该进入该房间)时,您会收到提醒。如果您能做到这一点,那么您将很快就能制作出自己的 Google Nest Cam 版本,该版本可以使用您自己的自定义硬件在发现闯入者(任何类型的闯入者)时向您发出提醒!理论上很简单。实施起来会很难吗?不正确。我们开始行动吧...

学习内容

  • 如何加载预训练的 TensorFlow.js 模型。
  • 如何从实时摄像头画面中抓取数据并将其绘制到画布上。
  • 如何对图像帧进行分类,以找到模型已训练识别的任何对象的边界框。
  • 如何使用从模型传递回的数据来突出显示找到的对象。

本 Codelab 重点介绍如何开始使用 TensorFlow.js 预训练模型。对与 TensorFlow.js 和机器学习无关的概念和代码块不作说明,只提供给您复制和粘贴。

2. 什么是 TensorFlow.js?

1aee0ede85885520.png

TensorFlow.js 是一个开源机器学习库,可在任何能够运行 JavaScript 的环境中运行。它以用 Python 编写的原始 TensorFlow 库为基础,旨在面向 JavaScript 生态系统打造全新的开发者体验以及 API 体验。

它的应用场合如何?

鉴于 JavaScript 的可移植性,您现在可以使用一种语言编写机器学习代码,然后轻松地在下列所有平台上运行此代码:

  • 使用 Vanilla JavaScript 的客户端网络浏览器
  • 使用 Node.js 的服务器端甚至是 Raspberry Pi 等 IoT 设备
  • 使用 Electron 的桌面应用
  • 使用 React Native 的原生移动应用

TensorFlow.js 还支持在这些环境中使用多个后端(这些环境是它可以在其中运行的实际硬件环境,例如 CPU 或 WebGL 等。此处的“后端”并不是指服务器端环境,例如,用于执行的后端可能是 WebGL 中的客户端),以确保兼容性,同时保持快速运行。TensorFlow.js 目前支持:

  • 在设备显卡 (GPU) 上运行 WebGL - 这是使用 GPU 加速运行较大模型(大小超过 3MB)的最快方式。
  • 在 CPU 上执行 Web Assembly (WASM) - 旨在提升包括老款手机在内的设备的 CPU 性能。这种方式更适合较小的模型(大小在 3MB 以下),由于将内容上传到图形处理器存在开销,因此在 CPU 上运行 WASM 可能实际上比运行 WebGL 要快。
  • CPU 执行 - 如果其他环境都不可用,则回退至此方式。这是三种方式中运行最慢的一种,但始终可以使用。

注意:如果您知道要在哪个设备上运行,则可以选择强制调用这些后端之一。或者,如果您没有指定,可以直接让 TensorFlow.js 为您做决定。

客户端的强大功能

在客户端计算机的网络浏览器中运行 TensorFlow.js 具有诸多值得关注的优势。

隐私权

您可以在客户端计算机上对数据进行训练和分类,而不将数据发送到第三方网络服务器。有时候,这可能是一项强制要求,例如 GDPR 等地方法律要求这么做,或者对于您所处理的数据,用户可能想要将其保留在自己的计算机上,而不发送给第三方。

速度

由于您无需向远程服务器发送数据,因此推理(一种对数据进行分类的操作)可能会更快。更棒的是,如果用户向您授予权限,您可以直接访问设备的传感器,比如相机、麦克风、GPS、加速度计等。

覆盖面和规模

只要点击一下您发送的链接,世界上的任何人就可以在他们的浏览器中打开相应网页,并利用您创作的内容。无需使用 CUDA 驱动程序等进行复杂的服务器端 Linux 设置,即可使用该机器学习系统。

费用

没有服务器意味着您只需为托管 HTML、CSS、JS 和模型文件的内容分发网络 (CDN) 付费。CDN 的费用要比全天候运行服务器(可能搭载了显卡)便宜得多。

服务器端功能

利用 TensorFlow.js 的 Node.js 实现可获得以下特性。

全面的 CUDA 支持

在服务器端,要实现显卡加速,必须安装 NVIDIA CUDA 驱动程序,以便 TensorFlow 能够使用显卡(与使用 WebGL 的浏览器不同,这种情况下无需安装该驱动程序)。不过,借助全面的 CUDA 支持,您可以充分利用显卡更精细的功能,从而缩短训练和推理时间。性能与 Python TensorFlow 实现相当,因为它们具有相同的 C++ 后端。

模型大小

对于一些研究中的先进模型,您可能需要使用非常大的模型(大小可能达到 GB 级)。由于每个浏览器标签页的内存用量有限,这些模型目前无法在网络浏览器中运行。如需运行这些较大的模型,您可以使用 Node.js 在自己的服务器上高效运行此类模型,并且服务器的硬件达到自己的需求。

IoT

Raspberry Pi 等热门单板计算机均支持 Node.js,这意味着您也可以在此类设备上执行 TensorFlow.js 模型。

速度

Node.js 采用 JavaScript 语言编写,这意味着您可以从即时编译中受益。也就是说,使用 Node.js 时,您可能会经常发现性能有所提升,因为系统会在运行时对之进行优化,特别是对于您可能正在执行的任何预处理操作。此案例研究中就有一个很好的例子,展示了 Hugging Face 如何使用 Node.js 使其自然语言处理模型的性能提升了两倍。

现在,您已了解 TensorFlow.js 的基础知识,它可以运行的环境,以及它具备的一些优势。接下来,我们来运用一些实用功能。

3. 预训练模型

TensorFlow.js 提供各种预训练的机器学习 (ML) 模型。这些模型由 TensorFlow.js 团队训练,并封装在一个易于使用的类中,是您开始使用机器学习的绝佳方式。您可以导入预训练模型作为起点,而不是构建和训练模型来解决问题。

您可以在 TensorFlow.js 的适用于 JavaScript 的模型页面上找到越来越多的易于使用的预训练模型。您还可以从其他地方获取可在 TensorFlow.js 中使用的转换后的 TensorFlow 模型,包括 TensorFlow Hub

为什么要使用预训练模型?

如果某个热门的预训练模型适合您的预期用例需求,那么从它开始入手就会带来许多好处,例如:

  1. 无需自己收集训练数据。如果要准备正确格式的数据并为其添加标签,以便机器学习系统能够从中学习,可能非常耗时,且成本高昂。
  2. 能够快速对想法进行原型设计,同时缩减成本和时间。
    如果某个预训练模型可能足以满足您的需要,让您能够专注于利用模型所提供的知识来实现创意,您就不会白费力气做重复工作了。
  3. 使用最先进的研究成果。预训练模型通常基于热门的研究,让您能够深入研究这些模型,同时了解它们的实际性能。
  4. 易于使用,且包含详尽的文档。这是因为此类模型很受欢迎。
  5. 迁移学习 功能。一些预训练模型提供迁移学习功能,这本质上是一种将从一个机器学习任务中学习到的信息转移到另一个类似示例的做法。例如,如果您提供了新的训练数据,那么最初被训练用于识别猫的模型也可以被重新训练,用来识别狗。这样速度会更快,因为您不必从零开始。这个模型可以利用它已经学会的识别猫的知识来识别新的对象 - 毕竟狗也有眼睛和耳朵,所以,如果它已经知道如何找到那些特征,我们就成功了一半。您能够以更快的速度使用您自己的数据重新训练模型。

什么是 COCO-SSD?

COCO-SSD 是一种预训练的对象检测机器学习模型,您将在本 Codelab 中使用该模型,该模型旨在定位和识别单个图像中的多个对象。换句话说,它可以告知您它已接受训练要查找的对象的边界框,从而让您了解该对象在您向其提供的任何给定图片中的位置。下图显示了一个示例:

760e5f87c335dd9e.png

如果上图中有 1 只以上的狗,系统会提供 2 个边界框的坐标,用于描述每只狗的位置。COCO-SSD 经过预训练,可以识别90 种常见的日常对象,例如人、汽车、猫等。

这个名称从何而来?

该名称听起来可能很奇怪,但它源自两个缩写:

  • COCO:表示该模型是使用 COCO(上下文中的常见对象)数据集训练的,该数据集可供任何人免费下载和使用,用于训练自己的模型。该数据集包含 20 多万张带标签的图片,可用于学习。
  • SSD(单次检测多框检测器):指模型实现中使用的部分模型架构。您无需了解此内容即可完成本 Codelab,但如果您对此感兴趣,可以点击此处详细了解 SSD

4. 进行设置

所需条件

  • 新式网络浏览器。
  • 具备 HTML、CSS、JavaScript 和 Chrome 开发者工具(查看控制台输出)的基础知识。

开始编码

我们已为 Glitch.comCodepen.io 创建了可供您开始使用的样板模板。您只需点击一下即可克隆任一模板,作为本 Codelab 的基态。

在 Glitch 上,点击 remix this 按钮创建分支,并创建一组可修改的新文件。

或者,在 Codepen 上,点击屏幕右下角的 fork

这个框架非常简单,为您提供了以下文件:

  • HTML 网页 (index.html)
  • 样式表 (style.css)
  • 用于编写 JavaScript 代码的文件 (script.js)

为方便起见,我们还在 HTML 文件中添加了对 TensorFlow.js 库的导入。如下所示:

index.html

<!-- Import TensorFlow.js library -->
<script src="https://cdn.jsdelivr.net/npm/@tensorflow/tfjs/dist/tf.min.js" type="text/javascript"></script>

替代方案:使用您偏好的网页编辑器或在本地工作

如果您想下载代码并在本地或在其他在线编辑器中操作,只需在同一目录中创建上述 3 个文件,然后将 Glitch 样板中的代码复制并粘贴到每个文件中即可。

5. 填充 HTML 框架

所有原型都需要一些基本的 HTML 基架。您将使用此功能来呈现机器学习模型的输出。我们现在就来设置一下:

  • 网页的标题
  • 一些说明性文字
  • 用于启用摄像头的按钮
  • 用于渲染网络摄像头视频流的视频标记

如需设置这些功能,请打开 index.html,并将现有代码替换为以下代码:

index.html

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>Multiple object detection using pre trained model in TensorFlow.js</title>
    <meta charset="utf-8">
    <!-- Import the webpage's stylesheet -->
    <link rel="stylesheet" href="style.css">
  </head>  
  <body>
    <h1>Multiple object detection using pre trained model in TensorFlow.js</h1>

    <p>Wait for the model to load before clicking the button to enable the webcam - at which point it will become visible to use.</p>
    
    <section id="demos" class="invisible">

      <p>Hold some objects up close to your webcam to get a real-time classification! When ready click "enable webcam" below and accept access to the webcam when the browser asks (check the top left of your window)</p>
      
      <div id="liveView" class="camView">
        <button id="webcamButton">Enable Webcam</button>
        <video id="webcam" autoplay muted width="640" height="480"></video>
      </div>
    </section>

    <!-- Import TensorFlow.js library -->
    <script src="https://cdn.jsdelivr.net/npm/@tensorflow/tfjs/dist/tf.min.js" type="text/javascript"></script>
    <!-- Load the coco-ssd model to use to recognize things in images -->
    <script src="https://cdn.jsdelivr.net/npm/@tensorflow-models/coco-ssd"></script>
    
    <!-- Import the page's JavaScript to do some stuff -->
    <script src="script.js" defer></script>
  </body>
</html>

了解代码

请注意您添加的一些关键内容:

  • 您添加了 <h1> 标记和一些用于标题的 <p> 标记,以及一些关于如何使用该网页的信息。这里没有任何特别的地方。

您还添加了一个表示演示空间的 section 标记:

index.html

    <section id="demos" class="invisible">

      <p>Hold some objects up close to your webcam to get a real-time classification! When ready click "enable webcam" below and accept access to the webcam when the browser asks (check the top left of your window)</p>
      
      <div id="liveView" class="webcam">
        <button id="webcamButton">Enable Webcam</button>
        <video id="webcam" autoplay width="640" height="480"></video>
      </div>
    </section>
  • 最初,您将为该 section 提供“invisible”类。这样一来,您就可以直观地向用户展示模型何时准备就绪,以及何时可以安全地点击启用摄像头按钮。
  • 您添加了启用摄像头按钮,您将在 CSS 中设置其样式。
  • 您还添加了一个视频标记,用于将网络摄像头输入内容流式传输到该标记。您很快就会在 JavaScript 代码中设置此参数。

如果您现在预览输出,应如下所示:

b1bfb8c3de68845c.png

6. 添加样式

元素默认值

首先,我们为刚刚添加的 HTML 元素添加样式,以确保它们能够正确呈现:

style.css

body {
  font-family: helvetica, arial, sans-serif;
  margin: 2em;
  color: #3D3D3D;
}

h1 {
  font-style: italic;
  color: #FF6F00;
}

video {
  display: block;
}

section {
  opacity: 1;
  transition: opacity 500ms ease-in-out;
}

接下来,添加一些实用的 CSS 类,以帮助处理界面的各种不同状态,例如当我们想要隐藏按钮时,或者当模型尚未准备就绪时使演示区域显示为不可用。

style.css

.removed {
  display: none;
}

.invisible {
  opacity: 0.2;
}

.camView {
  position: relative;
  float: left;
  width: calc(100% - 20px);
  margin: 10px;
  cursor: pointer;
}

.camView p {
  position: absolute;
  padding: 5px;
  background-color: rgba(255, 111, 0, 0.85);
  color: #FFF;
  border: 1px dashed rgba(255, 255, 255, 0.7);
  z-index: 2;
  font-size: 12px;
}

.highlighter {
  background: rgba(0, 255, 0, 0.25);
  border: 1px dashed #fff;
  z-index: 1;
  position: absolute;
}

太棒了!这就是您所需的一切。如果您成功用上面的两段代码替换了样式,那么实时预览现在应如下所示:

336899a78cf80fcb.png

请注意,演示区域文本和按钮不可用,因为 HTML 默认应用了“invisible”类。您将使用 JavaScript 在模型准备就绪后移除此类。

7. 创建 JavaScript 框架

引用关键 DOM 元素

首先,请确保您可以访问稍后在代码中需要操作或访问的页面的关键部分:

script.js

const video = document.getElementById('webcam');
const liveView = document.getElementById('liveView');
const demosSection = document.getElementById('demos');
const enableWebcamButton = document.getElementById('webcamButton');

检查是否支持摄像头

您现在可以添加一些辅助函数,以检查您使用的浏览器是否支持通过 getUserMedia 访问摄像头视频流:

script.js

// Check if webcam access is supported.
function getUserMediaSupported() {
  return !!(navigator.mediaDevices &&
    navigator.mediaDevices.getUserMedia);
}

// If webcam supported, add event listener to button for when user
// wants to activate it to call enableCam function which we will 
// define in the next step.
if (getUserMediaSupported()) {
  enableWebcamButton.addEventListener('click', enableCam);
} else {
  console.warn('getUserMedia() is not supported by your browser');
}

// Placeholder function for next step. Paste over this in the next step.
function enableCam(event) {
}

获取摄像头直播

接下来,复制并粘贴以下代码,为我们上面定义的之前为空的 enableCam 函数填写代码:

script.js

// Enable the live webcam view and start classification.
function enableCam(event) {
  // Only continue if the COCO-SSD has finished loading.
  if (!model) {
    return;
  }
  
  // Hide the button once clicked.
  event.target.classList.add('removed');  
  
  // getUsermedia parameters to force video but not audio.
  const constraints = {
    video: true
  };

  // Activate the webcam stream.
  navigator.mediaDevices.getUserMedia(constraints).then(function(stream) {
    video.srcObject = stream;
    video.addEventListener('loadeddata', predictWebcam);
  });
}

最后,添加一些临时代码,以便测试摄像头是否正常工作。

以下代码将模拟加载模型并启用相机按钮,以便您可以点击该按钮。您将在下一步中替换所有这些代码,因此请准备好稍后再次删除它们:

script.js

// Placeholder function for next step.
function predictWebcam() {
}

// Pretend model has loaded so we can try out the webcam code.
var model = true;
demosSection.classList.remove('invisible');

太棒了!如果您运行代码并点击当前状态的按钮,应该会看到类似如下的内容:

95442d7227216528.jpeg

8. 机器学习模型使用情况

加载模型

现在,您可以加载 COCO-SSD 模型了。

当它完成初始化后,在网页上启用演示区域和按钮(将此代码粘贴到您在上一步末尾添加的临时代码上):

script.js

// Store the resulting model in the global scope of our app.
var model = undefined;

// Before we can use COCO-SSD class we must wait for it to finish
// loading. Machine Learning models can be large and take a moment 
// to get everything needed to run.
// Note: cocoSsd is an external object loaded from our index.html
// script tag import so ignore any warning in Glitch.
cocoSsd.load().then(function (loadedModel) {
  model = loadedModel;
  // Show demo section now model is ready to use.
  demosSection.classList.remove('invisible');
});

添加上述代码并刷新实时画面后,您会发现,在网页加载完毕后几秒钟内(具体取决于您的网速),当模型准备就绪时,启用摄像头按钮会自动显示。不过,您也粘贴覆盖了 predictWebcam 函数。现在,我们需要全面定义此函数,因为我们的代码目前不会执行任何操作。

进入下一步!

对摄像头拍摄的帧进行分类

运行以下代码,以便在浏览器准备就绪时,应用能够从网络摄像头视频流中持续抓取帧,并将其传递给模型进行分类。

然后,模型会解析结果,并在返回的坐标处绘制 <p> 标记,如果置信度超过一定水平,则将文本设置为对象的标签。

script.js

var children = [];

function predictWebcam() {
  // Now let's start classifying a frame in the stream.
  model.detect(video).then(function (predictions) {
    // Remove any highlighting we did previous frame.
    for (let i = 0; i < children.length; i++) {
      liveView.removeChild(children[i]);
    }
    children.splice(0);
    
    // Now lets loop through predictions and draw them to the live view if
    // they have a high confidence score.
    for (let n = 0; n < predictions.length; n++) {
      // If we are over 66% sure we are sure we classified it right, draw it!
      if (predictions[n].score > 0.66) {
        const p = document.createElement('p');
        p.innerText = predictions[n].class  + ' - with ' 
            + Math.round(parseFloat(predictions[n].score) * 100) 
            + '% confidence.';
        p.style = 'margin-left: ' + predictions[n].bbox[0] + 'px; margin-top: '
            + (predictions[n].bbox[1] - 10) + 'px; width: ' 
            + (predictions[n].bbox[2] - 10) + 'px; top: 0; left: 0;';

        const highlighter = document.createElement('div');
        highlighter.setAttribute('class', 'highlighter');
        highlighter.style = 'left: ' + predictions[n].bbox[0] + 'px; top: '
            + predictions[n].bbox[1] + 'px; width: ' 
            + predictions[n].bbox[2] + 'px; height: '
            + predictions[n].bbox[3] + 'px;';

        liveView.appendChild(highlighter);
        liveView.appendChild(p);
        children.push(highlighter);
        children.push(p);
      }
    }
    
    // Call this function again to keep predicting when the browser is ready.
    window.requestAnimationFrame(predictWebcam);
  });
}

此新代码中真正重要的调用是 model.detect()

所有 TensorFlow.js 预建模型都有一个类似这样的函数(名称可能会因模型而异,因此请查看文档了解详情),用于实际执行机器学习推理。

推理就是获取一些输入内容,通过机器学习模型(本质上是大量数学运算)运行这些内容,然后提供一些结果。借助 TensorFlow.js 预建模型,我们可以以 JSON 对象的形式返回预测结果,因此使用起来非常方便。

您可以在 GitHub 上找到有关 COCO-SSD 模型的文档,其中包含此预测函数的完整详细信息。此函数在幕后做了大量繁重的工作:它可以接受任何“类似图片”的对象作为参数,例如图片、视频、画布等。使用预建模型可以节省大量时间和精力,因为您无需自行编写此代码,即可“开箱即用”。

运行此代码后,您现在应该会获得如下图所示的图片:

8f9bad6e49e646b.png

最后,以下代码示例展示了如何同时检测多个对象:

a2c73a72cf976b22.jpeg

棒极了!现在,您可以想象一下,使用旧手机创建 Nest Cam 等设备有多么简单,当它看到您的狗或猫在沙发上时,就会向您发出提醒。如果您在代码方面遇到问题,请点击此处查看我的最终有效版本,看看您是否复制了错误的内容。

9. 恭喜

恭喜,您已学会初步在网络浏览器中使用 TensorFlow.js 和机器学习!现在,就看您如何利用这些简单的开头,将其转化为富有创意的作品了。您将制作什么内容?

回顾

在此 Codelab 中,我们完成了以下内容:

  • 了解了使用 TensorFlow.js 而不是其他形式的 TensorFlow 的好处。
  • 了解了在哪些情况下您可能需要从预训练的机器学习模型开始。
  • 创建了一个可使用摄像头实时对物体进行分类的完整网页,包括:
  • 为内容创建 HTML 框架
  • 为 HTML 元素和类定义样式
  • 设置 JavaScript 基架以与 HTML 互动并检测网络摄像头的存在
  • 加载预训练的 TensorFlow.js 模型
  • 使用加载的模型对网络摄像头视频流进行持续分类,并在图片中的对象周围绘制边界框。

后续步骤

与我们分享您的作品!您也可以轻松将为此 Codelab 创建的内容扩展到其他创意用例中。我们建议您在完成任务后,继续打破思维定式,不断改进。

(也许您可以添加一个简单的服务器端层,以便在看到您选择的某个对象时,使用 WebSocket 向另一台设备发送通知。这是一种非常棒的旧智能手机升级改造方式,可赋予旧手机新的用途。创意有无限可能!)

  • 在社交媒体上使用 #MadeWithTFJS 标签通知我们,为您的项目争取在我们的 TensorFlow 博客甚至未来的 TensorFlow 活动中特推的机会。

更多可深入学习的 TensorFlow.js Codelab

参考网站