点击蓝字
关注我们,让开发变得更有趣
作者 | Mikołaj Roszczyk 华沙理工大学物联网工程师
翻译 | 武卓 英特尔 AI 软件布道师
排版 | 吴紫琴
OpenVINO™
最近,我完成了一个 demo 演示,展示了 OpenVINO™ 在 Node.js 框架中的强大功能。得益于与 Electron.js 的集成,该演示不仅能够高效地执行神经网络推理,还提供了交互式的用户体验。
OpenVINO™
应用程序概览:一种简单的背景虚化方法
这个演示展示了如何在 Node.js 环境中使用 OpenVINO™ 工具包实现背景虚化,并通过 Electron.js 创建的直观桌面界面进行呈现。启动应用程序后,您会看到一个带有黑色占位符的界面。要激活摄像头,只需从检测到的设备列表中选择您喜欢的视频源并点击"开始"。
完成此步骤后,您应该会在界面中的框内看到来自摄像头的实时视频流。
最初,您的画面将显示为未经过任何虚化处理的原始图像。要启用背景虚化功能,请通过指定的切换开关打开推理模式。该应用还允许您选择推理设备,可以在操作前或操作过程中进行设置。当您在会话中首次选择设备时,可能会出现短暂的延迟,因为模型需要进行编译。
在摄像头画面上方,您会看到一个推理时间显示器,显示最近50帧的平均推理时间。同时,还会显示每秒处理(或推理)的帧数,让您了解性能表现。
在应用运行期间,您可以随时停止摄像头或推理,切换视频源,或更改推理设备。
OpenVINO™
项目架构
完整代码可以在 OpenVINO™ Build & Deploy 仓库中找到。该 Electron 应用项目分为几个关键部分:
GitHub - openvinotoolkit/openvino_build_deploy: Pre-built components and code samples to help you build and deploy production-grade AI applications with the OpenVINO™ Toolkit from Intel
- 界面(Interface):
包括定义界面视觉效果的 HTML 和 CSS 文件,以及控制功能的 renderer.js 文件。
- OpenVINO™ 任务(OpenVINO Jobs):
后端组件,位于 ov-jobs.js 文件中,包含推理、前处理、后处理和背景虚化的相关函数。
- 主程序(Main):
main.js 文件负责启动应用程序,设置必要参数,并处理中断等操作。
- 预加载(Preload):
preload.js 文件作为主程序与界面组件之间的桥梁,启用后端功能并处理用户触发的中断。
- Node 模块(Node Modules):
程序所需的外部库和模块列在 package.json 文件中,同时包含其他必要的开发信息。要安装这些模块,只需在主目录运行以下命令:
npm install
保持清晰的项目架构至关重要,因为 OpenVINO™ 的 Node.js 库只能在后端运行。如果将其导入到界面层会导致错误。因此,最佳实践是将 OpenVINO™ 的导入限制在 ov-jobs.js 文件中。
OpenVINO™
它是如何工作的?
背景虚化功能使用了一个来自 TensorFlow 的分割模型,名为 "TensorFlow_Lite_Frontend_IR",该模型已被转换为 OpenVINO™ 的 IR 格式。此模型要求输入尺寸为 1x3x256x256,因此在预处理步骤中,需要将图像调整为正方形(无需填充)。如果图像不是 RGB 格式,还需要将其颜色表示转换为 RGB 格式。
typescript
const sharp = require('sharp');
// originalImg is a sharp object.
async function preprocess(originalImg) {
const inputSize = { w: 256, h: 256 };
const inputImg = await originalImg
.resize(inputSize.w, inputSize.h, { fit: 'fill' })
.removeAlpha() // converting RGBA to RGB
.raw()
.toBuffer();
const resizedImageData = new Uint8ClampedArray(inputImg.buffer);
const tensorData = Float32Array.from(resizedImageData, x => x / 255);
const shape = [1, inputSize.w, inputSize.h, 3];
return new ov.Tensor(ov.element.f32, shape, tensorData);
该模型作为分类器工作,将图像中的每个像素分配到六个类别之一,其中类别 0 代表背景。在推理后,输出会经过后处理生成一个掩码,其中 0 表示背景(需要进行虚化),1 表示其他元素(需要保持清晰)。随后,该掩码会被调整为与原始图像尺寸匹配。
程序运行时采用两个主要线程:一个线程从摄像头捕获帧,根据掩码对背景进行虚化处理,并将处理后的结果显示在屏幕上。另一个线程"借用"一帧图像,运行推理以更新掩码,确保摄像头的画面保持流畅,即使某些设备上的推理需要较长时间。
OpenVINO™
在 JavaScript 中使用 OpenVINO™ 的基本功能
发现可用来进行推理的设备:
javascript
const { addon: ov } = require('openvino-node');
const core = new ov.Core();
const devices = core.getAvailableDevices();
读入和编译模型:
javascript
const { addon: ov } = require('openvino-node');
const core = new ov.Core();
const modelPath = "path/to/your/model";
const model = await core.readModel(modelPath);
const device = "AUTO"; // or "GPU", "CPU" and others
const compiledModel = await core.compileModel(model, device);
运行编译后的模型:
javascript
// have compiledModel and inputTensor prepared
let inferRequest = compiledModel.createInferRequest();
inferRequest.setInputTensor(inputTensor);
inferRequest.infer() // inference
const outputLayer = compiledModel.outputs[0]; // only one output in this scenario
const resultTensor = inferRequest.getTensor(outputLayer); // your inference result
虚化步骤:
这是最简单的虚化图像的方式:
-
捕获一帧图像
从摄像头捕获当前帧。
-
复制并虚化图像
创建原始图像的副本,使用 sharp.blur() 对整个副本进行虚化处理(替代方案:OpenCV 或卷积操作)。
-
获取分割模型生成的掩码
在独立线程中运行分割模型,获取当前帧的掩码。
-
应用掩码到虚化图像
将虚化图像的所有像素值与掩码相乘,保留需要虚化的区域。
-
生成"反掩码"
计算掩码的反值(即 1 - mask),生成"非虚化区域掩码"。
-
应用"反掩码"到原始图像
将原始图像的像素值与"反掩码"相乘,保留需要保持清晰的区域。
-
融合两张图像
将虚化后的图像和清晰区域的图像进行融合,生成最终图像。
在最终的应用程序代码中,多个操作被整合在一起,充分利用了 Sharp 库的功能。以下是使用 Sharp 实现的方法:
- 根据掩码裁剪原始图像:
properties
const sharp = require('sharp');
const person = await sharp(outputMask, {
raw: {
channels: 3,
width: inputSize.w,
height: inputSize.h,
}
})
.resize(width, height, { fit: 'fill' })
.unflatten()
.composite([{
input: image.data,
raw: {
channels: 4,
width,
height,
},
blend: 'in',
}])
.toBuffer();
- 对副本进行虚化处理,并将其与裁剪后的原始图像合并(复合):
javascript
const blurSize = Math.floor(widthOfImage * 0.01)
const blurredImage = await sharp(imageToBlur.data, {
raw: { channels: 4, width, height }, // back to RGBA
})
.blur(blurSize)
.composite([{
input: person,
raw: { channels: 4, width, height },
blend: 'atop'
}])
.raw()
.toBuffer();
OpenVINO™
虚化面临的挑战
图像虚化是一项耗时的操作,对性能影响较大,尤其是当它是图像显示线程的一部分时,这直接关系到用户体验。没有人希望视频流出现卡顿。
一种优化方法是在一开始就将图像调整为最终显示的大小。较小的图像处理速度更快,从而提高性能。
起初,我们尝试使用 OpenCV 的虚化功能(通过 opencv-wasm 模块)。然而事实证明,其速度太慢,甚至比模型推理还慢,这促使我们寻找替代方案。
我们曾设想创建一个专门用于虚化的轻量化模型。该模型以图像帧和掩码作为输入,执行"虚化过程"部分描述的数学操作,并通过深度卷积(Depthwise Convolution)实现虚化。我们使用 TensorFlow 构建了这个模型,并将其转换为 OpenVINO 格式。
- 重要说明: OpenVINO™ 从 2024.4版本开始支持深度卷积操作。
尽管这种方法具有潜力,但由于深度卷积速度较慢且优化不足,最终被我们放弃。
最终,我们发现 Sharp 库在此操作中表现最佳,因此将其作为项目中的解决方案。
OpenVINO™
总结
如果遵循以下关键指南,使用 Electron.js 和 OpenVINO™ 创建桌面应用程序是非常简单的:
-
前后端功能分离:
确保将后端功能与前端功能分开,并记住 OpenVINO™ 只能在后端使用。
-
JavaScript API 的局限性:
并非所有 Python 中可用的 OpenVINO™ 方法都已在 JavaScript 中实现。然而,当前的 API 已足以完成大多数任务。
-
阅读文档:
许多常见问题的答案都可以在文档中找到。参考 OpenVINO™ Node.js API 文档以获取更多信息。
-
检查库兼容性:
某些 JavaScript 库可能无法很好地与 Electron 配合使用。在这些情况下,您可能需要寻找替代版本或定制分支。
别忘了亲自体验一下 "Hide Your Mess Behind" 应用程序!您可以从代码仓库运行该应用程序,也可以下载适用于 Windows(exe)或 Linux(deb、rpm)的可执行版本。
下载exe文件,一键运行安装的效果如下
OpenVINO™
*OpenVINO and the OpenVINO logo are trademarks of Intel Corporation or its subsidiaries.
OpenVINO 中文社区
微信号 : openvinodev
B站:OpenVINO中文社区
"开放、开源、共创"
致力于通过定期举办线上与线下的沙龙、动手实践及开发者交流大会等活动,促进人工智能开发者之间的交流学习。
○ 点击 " 在看 ",让更多人看见