构建一个 Vanilla JavaScript 应用程序
在本教程中,您将构建一个简单的 Web 应用程序,使用 Transformers.js 检测图像中的物体!要跟随学习,您只需要一个代码编辑器、一个浏览器和一个简单的服务器(例如,VS Code Live Server)。
以下是它的工作原理:用户点击“上传图片”并使用输入对话框选择一张图片。在使用目标检测模型分析图像后,预测的边界框会叠加在图像之上,如下所示
有用链接
步骤 1:HTML 和 CSS 设置
在我们开始使用 Transformers.js 构建之前,我们首先需要用一些标记和样式来打基础。创建一个名为 index.html
的文件,包含一个基本的 HTML 骨架,并将以下 <main>
标签添加到 <body>
中
<main class="container">
<label class="custom-file-upload">
<input id="file-upload" type="file" accept="image/*" />
<img class="upload-icon" src="https://huggingface.co/datasets/Xenova/transformers.js-docs/resolve/main/upload-icon.png" />
Upload image
</label>
<div id="image-container"></div>
<p id="status"></p>
</main>
点击这里查看此标记的细分。
我们添加了一个 <input>
元素,其 type="file"
属性接受图像。这允许用户使用弹出对话框从本地文件系统中选择图像。此元素的默认样式看起来很糟糕,所以让我们添加一些样式。实现这一点最简单的方法是将 <input>
元素包装在一个 <label>
中,隐藏输入,然后将标签样式化为按钮。
我们还添加了一个空的 <div>
容器来显示图像,还有一个空的 <p>
标签,我们将用它来在下载和运行模型时向用户提供状态更新,因为这两个操作都需要一些时间。
接下来,将以下 CSS 规则添加到 style.css
文件中,并将其链接到 HTML
html,
body {
font-family: Arial, Helvetica, sans-serif;
}
.container {
margin: 40px auto;
width: max(50vw, 400px);
display: flex;
flex-direction: column;
align-items: center;
}
.custom-file-upload {
display: flex;
align-items: center;
gap: 10px;
border: 2px solid black;
padding: 8px 16px;
cursor: pointer;
border-radius: 6px;
}
#file-upload {
display: none;
}
.upload-icon {
width: 30px;
}
#image-container {
width: 100%;
margin-top: 20px;
position: relative;
}
#image-container>img {
width: 100%;
}
以下是 UI 在此阶段的样子
步骤 2:JavaScript 设置
完成这些无聊的部分后,让我们开始编写一些 JavaScript 代码!创建一个名为 index.js
的文件,并通过在 <body>
末尾添加以下内容将其链接到 index.html
中
<script src="./index.js" type="module"></script>
type="module"
属性很重要,因为它将我们的文件转换为一个JavaScript 模块,这意味着我们将能够使用导入和导出。
转到 index.js
,让我们通过在文件顶部添加以下行来导入 Transformers.js
import { pipeline, env } from "https://cdn.jsdelivr.net.cn/npm/@huggingface/transformers";
由于我们将从 Hugging Face Hub 下载模型,因此我们可以通过设置以下内容跳过本地模型检查
env.allowLocalModels = false;
接下来,让我们创建对我们将在稍后访问的各种 DOM 元素的引用
const fileUpload = document.getElementById("file-upload");
const imageContainer = document.getElementById("image-container");
const status = document.getElementById("status");
步骤 3:创建目标检测管道
我们终于可以创建目标检测管道了!提醒一下,管道 是库提供的用于执行特定任务的高级接口。在本例中,我们将使用 pipeline()
辅助函数实例化一个目标检测管道。
由于这可能需要一些时间(特别是第一次,因为我们必须下载约 40MB 的模型),所以我们首先更新 status
段落,以便用户知道我们即将加载模型。
status.textContent = "Loading model...";
为了使本教程保持简单,我们将在主(UI)线程中加载和运行模型。不建议在生产应用程序中这样做,因为在执行这些操作时 UI 会冻结。这是因为 JavaScript 是一种单线程语言。要克服这个问题,您可以使用 Web Worker 在后台下载和运行模型。但是,在本教程中我们不会介绍这一点……
我们现在可以调用我们在文件顶部导入的 pipeline()
函数来创建我们的目标检测管道
const detector = await pipeline("object-detection", "Xenova/detr-resnet-50");
我们向 pipeline()
函数传递了两个参数:(1)任务和(2)模型。
第一个告诉 Transformers.js 我们想要执行什么类型的任务。在本例中,是
object-detection
,但库支持许多其他任务,包括text-generation
、sentiment-analysis
、summarization
或automatic-speech-recognition
。有关完整列表,请参见此处。第二个参数指定我们想使用哪个模型来解决给定的任务。我们将使用
Xenova/detr-resnet-50
,因为它是一个相对较小(约 40MB)但功能强大的模型,用于检测图像中的物体。
一旦函数返回,我们将告诉用户应用程序已准备好使用。
status.textContent = "Ready";
步骤 4:创建图像上传器
下一步是支持上传/选择图像。为此,我们将监听 fileUpload
元素的“change”事件。在回调函数中,我们使用 FileReader()
来读取选定图像的内容(如果没有选择则不读取)。
fileUpload.addEventListener("change", function (e) {
const file = e.target.files[0];
if (!file) {
return;
}
const reader = new FileReader();
// Set up a callback when the file is loaded
reader.onload = function (e2) {
imageContainer.innerHTML = "";
const image = document.createElement("img");
image.src = e2.target.result;
imageContainer.appendChild(image);
// detect(image); // Uncomment this line to run the model
};
reader.readAsDataURL(file);
});
图像加载到浏览器后,将调用 reader.onload
回调函数。在其中,我们将新的 <img>
元素追加到 imageContainer
中,以供用户查看。
不用担心 detect(image)
函数调用(已注释掉) - 我们稍后会解释。现在,尝试运行应用程序并上传一个图像到浏览器。您应该会看到您的图像像这样显示在按钮下方。
步骤 5:运行模型
我们终于准备好开始与 Transformers.js 交互了!让我们从上面的代码段中取消 detect(image)
函数调用的注释。然后我们将定义该函数本身。
async function detect(img) {
status.textContent = "Analysing...";
const output = await detector(img.src, {
threshold: 0.5,
percentage: true,
});
status.textContent = "";
console.log("output", output);
// ...
}
注意:detect
函数需要是异步的,因为我们将 await
模型的结果。
将 status
更新为“Analysing”后,我们就可以进行 *推理* 了,这仅仅意味着用一些数据运行模型。这是通过 pipeline()
返回的 detector()
函数完成的。我们传递的第一个参数是图像数据(img.src
)。
第二个参数是选项对象。
- 我们将
threshold
属性设置为0.5
。这意味着我们希望模型至少有 50% 的信心才能声称它在图像中检测到对象。阈值越低,它检测到的对象就越多(但可能会误识别对象);阈值越高,它检测到的对象就越少(但可能会错过场景中的对象)。 - 我们还指定了
percentage: true
,这意味着我们希望将对象的边界框以百分比形式返回(而不是像素)。
如果您现在尝试运行应用程序并上传图像,您应该会看到以下输出记录在控制台中。
在上面的示例中,我们上传了一张两头大象的图像,所以 output
变量包含一个有两个对象的数组,每个对象都包含一个 label
(字符串“elephant”)、一个 score
(表示模型对其预测的信心)和一个 box
对象(表示检测到的实体的边界框)。
步骤 6:渲染方框
最后一步是将 box
坐标显示为围绕每头大象的矩形。
在我们的 detect()
函数的末尾,我们将使用 .forEach()
在 output
数组中的每个对象上运行 renderBox
函数。
output.forEach(renderBox);
以下是 renderBox()
函数的代码,其中包含一些注释以帮助您理解正在发生的事情。
// Render a bounding box and label on the image
function renderBox({ box, label }) {
const { xmax, xmin, ymax, ymin } = box;
// Generate a random color for the box
const color = "#" + Math.floor(Math.random() * 0xffffff).toString(16).padStart(6, 0);
// Draw the box
const boxElement = document.createElement("div");
boxElement.className = "bounding-box";
Object.assign(boxElement.style, {
borderColor: color,
left: 100 * xmin + "%",
top: 100 * ymin + "%",
width: 100 * (xmax - xmin) + "%",
height: 100 * (ymax - ymin) + "%",
});
// Draw the label
const labelElement = document.createElement("span");
labelElement.textContent = label;
labelElement.className = "bounding-box-label";
labelElement.style.backgroundColor = color;
boxElement.appendChild(labelElement);
imageContainer.appendChild(boxElement);
}
边界框和标签跨度也需要一些样式,所以将以下内容添加到 style.css
文件中。
.bounding-box {
position: absolute;
box-sizing: border-box;
border-width: 2px;
border-style: solid;
}
.bounding-box-label {
color: white;
position: absolute;
font-size: 12px;
margin-top: -16px;
margin-left: -2px;
padding: 1px;
}
就这样!
您现在已经构建了自己的功能齐全的 AI 应用程序,它可以检测图像中的对象,并且完全在您的浏览器中运行:没有外部服务器、API 或构建工具。太酷了!🥳
该应用程序的实时网址如下:https://huggingface.co/spaces/Scrimba/vanilla-js-object-detector
< > 在 GitHub 上更新