0

背景
ウィンドウをポップアップしてウェブカメラを表示する小さなウェブベースのアプリケーションを作成しました。フィードにクロマキー機能を追加したかったのですが、いくつかの異なるアルゴリズムを機能させることに成功しました。しかし、私が見つけた最良のアルゴリズムは、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 値を返します。この LAB 値は、 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
4

2 に答える 2

1

いくつかのタイプミス

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]))
于 2020-09-25T00:52:41.913 に答える
0

まあ、これは私の最初の質問に対する答えではありません。JavaScript でクロマキーを実行しようとして立ち往生している他の人のために、このコードをここに含めます。視覚的に言えば、出力ビデオは、OP のより重い Delta E 76 コードに非常に近いものです。

ステップ 1: RGB をYUV に変換 C で書かれた非常に高速な RGB から YUV への変換関数を含むStackOverflow の回答
を見つけました。その後、RGB を YCbCr に変換する C 関数を備えた Edward Cannon によるGreenscreen Code and Hintsも見つけました。これらの両方を 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: 2 つの YUV カラーがどれだけ近いかを確認するGreenscreen Code と Edward Cannon によるヒント
のおかげで、 2 つの 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 76およびDelta E 101を使用したリアルタイム クロマ キーは、これらのソリューションにたどり着くのに大いに役立ちました。

于 2020-09-25T19:05:20.377 に答える