微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

如何通过GPU.js将大量JavaScript数学运算传递给GPU

如何解决如何通过GPU.js将大量JavaScript数学运算传递给GPU

背景
我已经构建了一个基于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) ];
};

这里发生的是我将RGB值发送到rgbToLab,该值会返回LAB值,该值可以与使用dE76来为我的绿色屏幕存储的LAB值进行比较。然后在我的应用中,将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,lab ) {

  let x,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,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,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 answer具有用C编写的非常快的RGB到YUV转换功能。后来,我还发现了Edward Cannon的Greenscreen Code and Hints具有C函数,可以将RGB转换为YCbCr。我将两者都带了,将它们转换为JavaScript,并测试了实际上对色度键控更好的方法。爱德华·坎农(Edward Cannon)的函数很有用,它的功能没有比卡米尔·古德塞恩(Camille Goudeseune)的代码更好。上面的SO答案参考。爱德华的代码在下面被注释掉了:

let rgbToYuv = function( r,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 and Hints,比较两种YUV颜色非常简单。我们可以在这里忽略Y,只需要U和V值即可;如果您想知道为什么需要study up on 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,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,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 );

其他资源 我应该提到Zachary Schuessler的Real-Time Chroma Key With Delta E 76Delta E 101是使我获得这些解决方案的重要帮助。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。