最近,我完成了一个 demo 演示,展示了 OpenVINO 在 Node.js 框架中的强大功能。得益于与 Electron.js 的集成,该演示不仅能够高效地执行神经网络推理,还提供了交互式的用户体验。
1
应用程序概览:一种简单的背景虚化方法
这个演示展示了如何在 Node.js 环境中使用 OpenVINO 工具包实现背景虚化,并通过 Electron.js 创建的直观桌面界面进行呈现。启动应用程序后,您会看到一个带有黑色占位符的界面。要激活摄像头,只需从检测到的设备列表中选择您喜欢的视频源并点击“开始”。
完成此步骤后,您应该会在界面中的框内看到来自摄像头的实时视频流。
最初,您的画面将显示为未经过任何虚化处理的原始图像。要启用背景虚化功能,请通过指定的切换开关打开推理模式。该应用还允许您选择推理设备,可以在操作前或操作过程中进行设置。当您在会话中首次选择设备时,可能会出现短暂的延迟,因为模型需要进行编译。
在摄像头画面上方,您会看到一个推理时间显示器,显示最近50帧的平均推理时间。同时,还会显示每秒处理(或推理)的帧数,让您了解性能表现。
在应用运行期间,您可以随时停止摄像头或推理,切换视频源,或更改推理设备。
2
项目架构
完整代码可以在 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 文件中。
3
它是如何工作的?
背景虚化功能使用了一个来自 TensorFlow 的分割模型,名为 “TensorFlow_Lite_Frontend_IR”,该模型已被转换为 OpenVINO 的 IR 格式。此模型要求输入尺寸为 1x3x256x256,因此在预处理步骤中,需要将图像调整为正方形(无需填充)。如果图像不是 RGB 格式,还需要将其颜色表示转换为 RGB 格式。
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 表示其他元素(需要保持清晰)。随后,该掩码会被调整为与原始图像尺寸匹配。
程序运行时采用两个主要线程:一个线程从摄像头捕获帧,根据掩码对背景进行虚化处理,并将处理后的结果显示在屏幕上。另一个线程“借用”一帧图像,运行推理以更新掩码,确保摄像头的画面保持流畅,即使某些设备上的推理需要较长时间。
4
在 JavaScript 中
使用 OpenVINO 的基本功能
发现可用来进行推理的设备:
const { addon: ov } = require('openvino-node'); const core = new ov.Core(); const devices = core.getAvailableDevices();
读入和编译模型:
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);
运行编译后的模型:
// 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 实现的方法:
1.根据掩码裁剪原始图像:
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();
2.对副本进行虚化处理,并将其与裁剪后的原始图像合并(复合):
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();
5
虚化面临的挑战
图像虚化是一项耗时的操作,对性能影响较大,尤其是当它是图像显示线程的一部分时,这直接关系到用户体验。没有人希望视频流出现卡顿。
一种优化方法是在一开始就将图像调整为最终显示的大小。较小的图像处理速度更快,从而提高性能。
起初,我们尝试使用 OpenCV 的虚化功能(通过 opencv-wasm 模块)。然而事实证明,其速度太慢,甚至比模型推理还慢,这促使我们寻找替代方案。
我们曾设想创建一个专门用于虚化的轻量化模型。该模型以图像帧和掩码作为输入,执行“虚化过程”部分描述的数学操作,并通过深度卷积(Depthwise Convolution)实现虚化。我们使用 TensorFlow 构建了这个模型,并将其转换为 OpenVINO 格式。
重要说明:OpenVINO 从 2024.4 版本开始度卷积操作。
尽管这种方法具有潜力,但由于深度卷积速度较慢且优化不足,最终被我们放弃。
最终,我们发现 Sharp 库在此操作中表现最佳,因此将其作为项目中的解决方案。
6
总结
如果遵循以下关键指南,使用 Electron.js 和 OpenVINO 创建桌面应用程序是非常简单的:
1.前后端功能分离:
确保将后端功能与前端功能分开,并记住 OpenVINO 只能在后端使用。
2.JavaScript API 的局限性:
并非所有 Python 中可用的 OpenVINO 方法都已在 JavaScript 中实现。然而,当前的 API 已足以完成大多数任务。
3.阅读文档:
许多常见问题的答案都可以在文档中找到。参考 OpenVINO Node.js API 文档以获取更多信息。
4.检查库兼容性:
某些 JavaScript 库可能无法很好地与 Electron 配合使用。在这些情况下,您可能需要寻找替代版本或定制分支。
别忘了亲自体验一下 "Hide Your Mess Behind" 应用程序!您可以从代码仓库运行该应用程序,也可以下载适用于 Windows(exe)或 Linux(deb、rpm)的可执行版本。
全部0条评论
快来发表一下你的评论吧 !