最近,我完成了一個(gè) demo 演示,展示了 OpenVINO 在 Node.js 框架中的強(qiáng)大功能。得益于與 Electron.js 的集成,該演示不僅能夠高效地執(zhí)行神經(jīng)網(wǎng)絡(luò)推理,還提供了交互式的用戶體驗(yàn)。
1
應(yīng)用程序概覽:一種簡單的背景虛化方法
這個(gè)演示展示了如何在 Node.js 環(huán)境中使用 OpenVINO 工具包實(shí)現(xiàn)背景虛化,并通過 Electron.js 創(chuàng)建的直觀桌面界面進(jìn)行呈現(xiàn)。啟動應(yīng)用程序后,您會看到一個(gè)帶有黑色占位符的界面。要激活攝像頭,只需從檢測到的設(shè)備列表中選擇您喜歡的視頻源并點(diǎn)擊“開始”。
完成此步驟后,您應(yīng)該會在界面中的框內(nèi)看到來自攝像頭的實(shí)時(shí)視頻流。
最初,您的畫面將顯示為未經(jīng)過任何虛化處理的原始圖像。要啟用背景虛化功能,請通過指定的切換開關(guān)打開推理模式。該應(yīng)用還允許您選擇推理設(shè)備,可以在操作前或操作過程中進(jìn)行設(shè)置。當(dāng)您在會話中首次選擇設(shè)備時(shí),可能會出現(xiàn)短暫的延遲,因?yàn)槟P托枰M(jìn)行編譯。
在攝像頭畫面上方,您會看到一個(gè)推理時(shí)間顯示器,顯示最近50幀的平均推理時(shí)間。同時(shí),還會顯示每秒處理(或推理)的幀數(shù),讓您了解性能表現(xiàn)。
在應(yīng)用運(yùn)行期間,您可以隨時(shí)停止攝像頭或推理,切換視頻源,或更改推理設(shè)備。
2
項(xiàng)目架構(gòu)
完整代碼可以在 OpenVINO Build & Deploy 倉庫中找到。該 Electron 應(yīng)用項(xiàng)目分為幾個(gè)關(guān)鍵部分:
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 任務(wù)(OpenVINO Jobs):
后端組件,位于 ov-jobs.js 文件中,包含推理、前處理、后處理和背景虛化的相關(guān)函數(shù)。
主程序(Main):
main.js 文件負(fù)責(zé)啟動應(yīng)用程序,設(shè)置必要參數(shù),并處理中斷等操作。
預(yù)加載(Preload):
preload.js 文件作為主程序與界面組件之間的橋梁,啟用后端功能并處理用戶觸發(fā)的中斷。
Node 模塊(Node Modules):
程序所需的外部庫和模塊列在 package.json 文件中,同時(shí)包含其他必要的開發(fā)信息。要安裝這些模塊,只需在主目錄運(yùn)行以下命令:
npm install
保持清晰的項(xiàng)目架構(gòu)至關(guān)重要,因?yàn)?OpenVINO 的 Node.js 庫只能在后端運(yùn)行。如果將其導(dǎo)入到界面層會導(dǎo)致錯(cuò)誤。因此,最佳實(shí)踐是將 OpenVINO 的導(dǎo)入限制在 ov-jobs.js 文件中。
3
它是如何工作的?
背景虛化功能使用了一個(gè)來自 TensorFlow 的分割模型,名為 “TensorFlow_Lite_Frontend_IR”,該模型已被轉(zhuǎn)換為 OpenVINO 的 IR 格式。此模型要求輸入尺寸為 1x3x256x256,因此在預(yù)處理步驟中,需要將圖像調(diào)整為正方形(無需填充)。如果圖像不是 RGB 格式,還需要將其顏色表示轉(zhuǎn)換為 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);
該模型作為分類器工作,將圖像中的每個(gè)像素分配到六個(gè)類別之一,其中類別 0 代表背景。在推理后,輸出會經(jīng)過后處理生成一個(gè)掩碼,其中 0 表示背景(需要進(jìn)行虛化),1 表示其他元素(需要保持清晰)。隨后,該掩碼會被調(diào)整為與原始圖像尺寸匹配。
程序運(yùn)行時(shí)采用兩個(gè)主要線程:一個(gè)線程從攝像頭捕獲幀,根據(jù)掩碼對背景進(jìn)行虛化處理,并將處理后的結(jié)果顯示在屏幕上。另一個(gè)線程“借用”一幀圖像,運(yùn)行推理以更新掩碼,確保攝像頭的畫面保持流暢,即使某些設(shè)備上的推理需要較長時(shí)間。
4
在 JavaScript 中
使用 OpenVINO 的基本功能
發(fā)現(xiàn)可用來進(jìn)行推理的設(shè)備:
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);
運(yùn)行編譯后的模型:
// 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
虛化步驟:
這是最簡單的虛化圖像的方式:
捕獲一幀圖像
從攝像頭捕獲當(dāng)前幀。
復(fù)制并虛化圖像
創(chuàng)建原始圖像的副本,使用 sharp.blur() 對整個(gè)副本進(jìn)行虛化處理(替代方案:OpenCV 或卷積操作)。
獲取分割模型生成的掩碼
在獨(dú)立線程中運(yùn)行分割模型,獲取當(dāng)前幀的掩碼。
應(yīng)用掩碼到虛化圖像
將虛化圖像的所有像素值與掩碼相乘,保留需要虛化的區(qū)域。
生成“反掩碼”
計(jì)算掩碼的反值(即 1 - mask),生成“非虛化區(qū)域掩碼”。
應(yīng)用“反掩碼”到原始圖像
將原始圖像的像素值與“反掩碼”相乘,保留需要保持清晰的區(qū)域。
融合兩張圖像
將虛化后的圖像和清晰區(qū)域的圖像進(jìn)行融合,生成最終圖像。
在最終的應(yīng)用程序代碼中,多個(gè)操作被整合在一起,充分利用了 Sharp 庫的功能。以下是使用 Sharp 實(shí)現(xiàn)的方法:
1.根據(jù)掩碼裁剪原始圖像:
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.對副本進(jìn)行虛化處理,并將其與裁剪后的原始圖像合并(復(fù)合):
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
虛化面臨的挑戰(zhàn)
圖像虛化是一項(xiàng)耗時(shí)的操作,對性能影響較大,尤其是當(dāng)它是圖像顯示線程的一部分時(shí),這直接關(guān)系到用戶體驗(yàn)。沒有人希望視頻流出現(xiàn)卡頓。
一種優(yōu)化方法是在一開始就將圖像調(diào)整為最終顯示的大小。較小的圖像處理速度更快,從而提高性能。
起初,我們嘗試使用 OpenCV 的虛化功能(通過 opencv-wasm 模塊)。然而事實(shí)證明,其速度太慢,甚至比模型推理還慢,這促使我們尋找替代方案。
我們曾設(shè)想創(chuàng)建一個(gè)專門用于虛化的輕量化模型。該模型以圖像幀和掩碼作為輸入,執(zhí)行“虛化過程”部分描述的數(shù)學(xué)操作,并通過深度卷積(Depthwise Convolution)實(shí)現(xiàn)虛化。我們使用 TensorFlow 構(gòu)建了這個(gè)模型,并將其轉(zhuǎn)換為 OpenVINO 格式。
重要說明:OpenVINO 從 2024.4 版本開始度卷積操作。
盡管這種方法具有潛力,但由于深度卷積速度較慢且優(yōu)化不足,最終被我們放棄。
最終,我們發(fā)現(xiàn) Sharp 庫在此操作中表現(xiàn)最佳,因此將其作為項(xiàng)目中的解決方案。
6
總結(jié)
如果遵循以下關(guān)鍵指南,使用 Electron.js 和 OpenVINO 創(chuàng)建桌面應(yīng)用程序是非常簡單的:
1.前后端功能分離:
確保將后端功能與前端功能分開,并記住 OpenVINO 只能在后端使用。
2.JavaScript API 的局限性:
并非所有 Python 中可用的 OpenVINO 方法都已在 JavaScript 中實(shí)現(xiàn)。然而,當(dāng)前的 API 已足以完成大多數(shù)任務(wù)。
3.閱讀文檔:
許多常見問題的答案都可以在文檔中找到。參考 OpenVINO Node.js API 文檔以獲取更多信息。
4.檢查庫兼容性:
某些 JavaScript 庫可能無法很好地與 Electron 配合使用。在這些情況下,您可能需要尋找替代版本或定制分支。
別忘了親自體驗(yàn)一下 "Hide Your Mess Behind" 應(yīng)用程序!您可以從代碼倉庫運(yùn)行該應(yīng)用程序,也可以下載適用于 Windows(exe)或 Linux(deb、rpm)的可執(zhí)行版本。
-
應(yīng)用程序
+關(guān)注
關(guān)注
37文章
3268瀏覽量
57704 -
OpenVINO
+關(guān)注
關(guān)注
0文章
93瀏覽量
201
原文標(biāo)題:開發(fā)者實(shí)戰(zhàn)|如何使用 OpenVINO? 在 ElectronJS 中創(chuàng)建桌面應(yīng)用程序
文章出處:【微信號:英特爾物聯(lián)網(wǎng),微信公眾號:英特爾物聯(lián)網(wǎng)】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。
發(fā)布評論請先 登錄
相關(guān)推薦
評論