簡體   English   中英

如何使用 GPU.js 將繁重的 JavaScript 數學運算傳遞給 GPU

[英]How to pass off heavy JavaScript math operations to GPU with GPU.js

背景
我已經構建了一個基於 Web 的小應用程序,它會彈出窗口來顯示您的網絡攝像頭。 我想添加對您的提要進行色度鍵控的功能,並且已經成功地讓幾種不同的算法工作。 然而,我發現的最好的算法對於 JavaScript 來說是非常耗費資源的; 單線程應用程序。


有沒有辦法將密集的數學運算卸載到 GPU? 我試過讓 GPU.js 工作,但我不斷收到各種錯誤。 這是我想讓 GPU 運行的功能:

 let dE76 = function(a, b, c, d, e, f) { return Math.sqrt( pow(d - a, 2) + pow(e - b, 2) + pow(f - c, 2) ); }; let rgbToLab = function(r, g, b) { let x, y, z; r = r / 255; g = g / 255; b = b / 255; r = (r > 0.04045) ? Math.pow((r + 0.055) / 1.055, 2.4) : r / 12.92; g = (g > 0.04045) ? Math.pow((g + 0.055) / 1.055, 2.4) : g / 12.92; b = (b > 0.04045) ? Math.pow((b + 0.055) / 1.055, 2.4) : b / 12.92; x = (r * 0.4124 + g * 0.3576 + b * 0.1805) / 0.95047; y = (r * 0.2126 + g * 0.7152 + b * 0.0722) / 1.00000; z = (r * 0.0193 + g * 0.1192 + b * 0.9505) / 1.08883; x = (x > 0.008856) ? Math.pow(x, 1/3) : (7.787 * x) + 16/116; y = (y > 0.008856) ? Math.pow(y, 1/3) : (7.787 * y) + 16/116; z = (z > 0.008856) ? Math.pow(z, 1/3) : (7.787 * z) + 16/116; return [ (116 * y) - 16, 500 * (x - y), 200 * (y - z) ]; };

這里發生的事情是我向rgbToLab發送了一個 RGB 值,它返回的 LAB 值可以與dE76綠屏已存儲的 LAB 值進行dE76 然后在我的應用程序中,我們將dE76值檢查到一個dE76值,比如 25,如果該值小於這個值,我會將視頻源中的像素不透明度設置為 0。

GPU.js 嘗試
這是我最新的 GUI.js 嘗試:

 // Try to combine the 2 functions into a single kernel function for GPU.js let tmp = gpu.createKernel( function( r, g, b, lab ) { let x, y, z; r = r / 255; g = g / 255; b = b / 255; r = (r > 0.04045) ? Math.pow((r + 0.055) / 1.055, 2.4) : r / 12.92; g = (g > 0.04045) ? Math.pow((g + 0.055) / 1.055, 2.4) : g / 12.92; b = (b > 0.04045) ? Math.pow((b + 0.055) / 1.055, 2.4) : b / 12.92; x = (r * 0.4124 + g * 0.3576 + b * 0.1805) / 0.95047; y = (r * 0.2126 + g * 0.7152 + b * 0.0722) / 1.00000; z = (r * 0.0193 + g * 0.1192 + b * 0.9505) / 1.08883; x = (x > 0.008856) ? Math.pow(x, 1/3) : (7.787 * x) + 16/116; y = (y > 0.008856) ? Math.pow(y, 1/3) : (7.787 * y) + 16/116; z = (z > 0.008856) ? Math.pow(z, 1/3) : (7.787 * z) + 16/116; let clab = [ (116 * y) - 16, 500 * (x - y), 200 * (y - z) ]; let d = pow(lab[0] - clab[0], 2) + pow(lab[1] - clab[1], 2) + pow(lab[2] - clab[2], 2); return Math.sqrt( d ); } ).setOutput( [256] ); // ... // Call the function above. let d = tmp( r, g, b, chromaColors[c].lab ); // If the delta (d) is lower than my tolerance level set pixel opacity to 0. if( d < tolerance ){ frame.data[ i * 4 + 3 ] = 0; }

錯誤:
以下是我在調用 tmp 函數時嘗試使用 GPU.js 時遇到的錯誤列表。 1) 用於我上面提供的代碼。 2) 用於擦除 tmp 中的所有代碼並僅添加一個空返回 3) 是如果我嘗試在 tmp 函數中添加函數; 一個有效的 JavaScript 東西,但不是 C 或內核代碼。

  1. 未捕獲的錯誤:未定義標識符
  2. 未捕獲的錯誤:編譯片段着色器時出錯:錯誤:0:463:';' : 語法錯誤
  3. 未捕獲的錯誤:getDependencies 中未處理的類型 FunctionExpression

一些錯別字

pow should be Math.pow()

let x, y, z should be declare on there own

let x = 0
let y = 0
let z = 0

您不能為參數變量賦值。 他們變得統一。

完整的工作腳本

const { GPU } = require('gpu.js')
const gpu = new GPU()

const tmp = gpu.createKernel(function (r, g, b, lab) {
  let x = 0
  let y = 0
  let z = 0

  let r1 = r / 255
  let g1 = g / 255
  let b1 = b / 255

  r1 = (r1 > 0.04045) ? Math.pow((r1 + 0.055) / 1.055, 2.4) : r1 / 12.92
  g1 = (g1 > 0.04045) ? Math.pow((g1 + 0.055) / 1.055, 2.4) : g1 / 12.92
  b1 = (b1 > 0.04045) ? Math.pow((b1 + 0.055) / 1.055, 2.4) : b1 / 12.92

  x = (r1 * 0.4124 + g1 * 0.3576 + b1 * 0.1805) / 0.95047
  y = (r1 * 0.2126 + g1 * 0.7152 + b1 * 0.0722) / 1.00000
  z = (r1 * 0.0193 + g1 * 0.1192 + b1 * 0.9505) / 1.08883

  x = (x > 0.008856) ? Math.pow(x, 1 / 3) : (7.787 * x) + 16 / 116
  y = (y > 0.008856) ? Math.pow(y, 1 / 3) : (7.787 * y) + 16 / 116
  z = (z > 0.008856) ? Math.pow(z, 1 / 3) : (7.787 * z) + 16 / 116

  const clab = [(116 * y) - 16, 500 * (x - y), 200 * (y - z)]
  const d = Math.pow(lab[0] - clab[0], 2) + Math.pow(lab[1] - clab[1], 2) + Math.pow(lab[2] - clab[2], 2)
  return Math.sqrt(d)
}).setOutput([256])

console.log(tmp(128, 139, 117, [40.1332, 10.99816, 5.216413]))

好吧,這不是我最初提出的問題的答案,我確實想出了一個計算速度快的窮人替代方案。 我在此處包含此代碼,供其他人嘗試在 JavaScript 中進行色度鍵控。 從視覺上講,輸出視頻非常接近 OP 中較重的 Delta E 76 代碼。

第 1 步:將 RGB 轉換為 YUV
我找到了一個StackOverflow 答案,它有一個用 C 編寫的非常快速的 RGB 到 YUV 轉換函數。后來我還找到了 Edward Cannon 的Greenscreen Code and Hints ,它有一個 C 函數可以將 RGB 轉換為 YCbCr。 我把這兩個都轉換成 JavaScript,並測試了哪個更適合色度鍵控。 Edward Cannon 的函數很有用,但證明沒有比 Camille Goudeseune 的代碼更好; 上面的SO答案參考。 Edward 的代碼注釋如下:

 let rgbToYuv = function( r, g, b ) { let y = 0.257 * r + 0.504 * g + 0.098 * b + 16; //let y = Math.round( 0.299 * r + 0.587 * g + 0.114 * b ); let u = -0.148 * r - 0.291 * g + 0.439 * b + 128; //let u = Math.round( -0.168736 * r - 0.331264 * g + 0.5 * b + 128 ); let v = 0.439 * r - 0.368 * g - 0.071 * b + 128; //let v = Math.round( 0.5 * r - 0.418688 * g - 0.081312 * b + 128 ); return [ y, u, v ]; }

第 2 步:檢查兩種 YUV 顏色的接近程度
再次感謝 Edward Cannon 的Greenscreen Code 和 Hints比較兩種 YUV 顏色非常簡單。 這里我們可以忽略 Y,只需要 U 和 V 值; 如果您想知道為什么需要研究 YUV (YCbCr) ,尤其是亮度和色度部分。 這是轉換為 JavaScript 的 C 代碼:

 let colorClose = function( u, v, cu, cv ){ return Math.sqrt( ( cu - u ) * ( cu - u ) + ( cv - v ) * ( cv - v ) ); };

如果您閱讀這篇文章,您會注意到這不是完整的功能。 在我的應用程序中,我處理的是視頻而不是靜止圖像,因此提供要包含在計算中的背景和前景色會很困難。 它還會增加計算負載。 在下一步中有一個簡單的解決方法。

第 3 步:檢查公差和清潔邊緣
由於我們在這里處理視頻,因此我們遍歷每一幀的像素數據並檢查colorClose值是否低於某個閾值。 如果我們剛剛檢查的顏色低於容差水平,我們需要將該像素的不透明度設置為 0 使其透明。

由於這是一個非常快的窮人色度鍵,我們往往會在剩余圖像的邊緣出現顏色滲色。 向上或向下調整容差值可以大大減少這種情況,但我們也可以添加簡單的羽化效果。 如果一個像素沒有被標記為透明度但接近容差水平,我們可以部分關閉它。 下面的代碼演示了這一點:

 // ...My app specific code. /* NOTE: chromaColors is an array holding RGB colors the user has selected from the video feed. My app requires the user to select the lightest and darkest part of their green screen. If lighting is bad they can add more colors to this array and we will do our best to chroma key them out. */ // Grab the current frame data from our Canvas. let frame = ctxHolder.getImageData( 0, 0, width, height ); let frames = frame.data.length / 4; let colors = chromaColors.length - 1; // Loop through every pxel of this frame. for ( let i = 0; i < frames; i++ ) { // Each pixel is stored as an rgba value; we don't need a. let r = frame.data[ i * 4 + 0 ]; let g = frame.data[ i * 4 + 1 ]; let b = frame.data[ i * 4 + 2 ]; let yuv = rgbToYuv( r, g, b ); // Check the current pixel against our list of colors to turn transparent. for ( let c = 0; c < colors; c++ ) { // When the user selected a color for chroma keying we wen't ahead // and saved the YUV value to save on resources. Pull it out for use. let cc = chromaColors[c].yuv; // Calc the closeness (distance) of the currnet pixel and chroma color. let d = colorClose( yuv[1], yuv[2], cc[1], cc[2] ); if( d < tolerance ){ // Turn this pixel transparent. frame.data[ i * 4 + 3 ] = 0; break; } else { // Feather edges by lowering the opacity on pixels close to the tolerance level. if ( d - 1 < tolerance ){ frame.data[ i * 4 + 3 ] = 0.1; break; } if ( d - 2 < tolerance ){ frame.data[ i * 4 + 3 ] = 0.2; break; } if ( d - 3 < tolerance ){ frame.data[ i * 4 + 3 ] = 0.3; break; } if ( d - 4 < tolerance ){ frame.data[ i * 4 + 3 ] = 0.4; break; } if ( d - 5 < tolerance ){ frame.data[ i * 4 + 3 ] = 0.5; break; } } } } // ...My app specific code. // Put the altered frame data back into the video feed. ctxMain.putImageData( frame, 0, 0 );

其他資源 我應該提到的是,Zachary Schuessler 的實時色度鍵與 Delta E 76Delta E 101對我獲得這些解決方案有很大幫助。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM