LoginSignup
1
4

More than 5 years have passed since last update.

webGLでyuv2rgb変換

Last updated at Posted at 2017-02-25

webGLでyuv2rgb変換

久しぶりに投稿します。さて、なぜwebGLなのかといえば、javascriptで1080pの画像を変換したら遅過ぎたからです。見てみるとyuv2rgb変換が足を引っ張っていました。実際、1枚の変換に200ms(うろ覚え)とかかかるのは意味わかりません。

検索すると、webGLtestさんの「webGLでgpuにyuv -> rgb変換させる。」というwebGLサンプルを見つけました。問題なく動きかなりの高速化!。めでたしめでたし。

しかし、初めてのwebGLのため、ソースの意味が分かりません。。yuv.yz = (texture2D(samplerUV, varyUV).ra - vec2(0.5, 0.5));の.raって何?、構造体を定義した覚えはないよ。。とスッキリしない。以下のページで理解しました。

分かったところで、少々、ソースを自分用に変更。元サンプルでは、UVがインタリーブされていましたが、インタリーブさせるコードもオーバーヘッドになっていたので、非インタリーブ画像を表示するようにしました。また、マトリックス演算も不要なようなので省略。

備考:yuv2rgbの変換式が正しいかどうか?、は、確認していません(間違った情報を広めていたらすみません)。。だいたい合っている、では正確な色とはならないので本来は注意が必要。

追記
webGLでyuv2rgb変換(10bit)という続きの記事を書きました。10bitでやりたい方はご参考にしてください。

ソース

99%上記webGLtestさんのソースと同じなので、掲載するか迷いましたが、 YUV独立の方が嬉しい人もいるだろう、ということで書きます。長いですが、全文です。420を想定しています。

index.html
<head>
  <title> YUV2RGB sample </title>
  <script type="x-shader/x-vertex" id="vs">
    uniform vec2 u_resolution;
    attribute vec2 a_position;
    attribute vec2 a_texCoord;
    varying vec2 v_texCoord;
    void main(void) {
      v_texCoord = a_texCoord;
      vec2 pos = a_position / u_resolution;
      gl_Position = vec4(pos, 0, 1);
  }
  </script>
  <script type="x-shader/x-fragment" id="fs">
    precision mediump float; 
    uniform sampler2D samplerY;
    uniform sampler2D samplerU;
    uniform sampler2D samplerV;
    const mat3 kColorConv = mat3( 1.164, 1.164, 1.164, 0.0, -0.213, 2.112, 1.793, -0.533, 0.0 ); 
    varying vec2 v_texCoord;
    void main(void) {
      mediump vec3 yuv;
      lowp vec3 rgb;
      yuv.x = (texture2D(samplerY, v_texCoord).x - (16.0 / 255.0)); 
      yuv.y = (texture2D(samplerU, v_texCoord).x - 0.5);
      yuv.z = (texture2D(samplerV, v_texCoord).x - 0.5);
      rgb = kColorConv * yuv;
      gl_FragColor = vec4(rgb, 1.0);
    }
  </script>
</head>

<body>
  <canvas id="target" />
  <script type="text/javascript" src="./yuv2rgb.js"></script>
</body>
yuv2rgb.js
// canvasエレメントを取得
let c = document.getElementById('target');
c.width = 320;
c.height = 240;
// webglコンテキストを取得
let gl = c.getContext('webgl') || c.getContext('experimental-webgl');
// canvasを初期化する色を設定する
gl.clearColor(0.0, 1.0, 1.0, 1.0);
// canvasを初期化
gl.clear(gl.COLOR_BUFFER_BIT);
// 頂点シェーダとフラグメントシェーダの生成
const v_shader = create_shader('vs');
const f_shader = create_shader('fs');
// プログラムオブジェクトの生成とリンク
let prg = create_program(v_shader, f_shader);

gl.viewport(0, 0, c.width, c.height);
let resolutionUniformLocation = gl.getUniformLocation(prg, "u_resolution");
gl.uniform2f(resolutionUniformLocation, gl.canvas.width, gl.canvas.height);

// y要素のtexture
const w = 320, h = 240;
var dataY = new Uint8Array(w*h);
for (var i = 0; i < w; ++i) {
  for (var j = 0; j < h; ++j) {
    dataY[i + w * j] = 255 - parseInt(i * 255 / w);
  }
}
// cbcr要素のtexture
const w2 = w / 2;
const h2 = h / 2;
dataU = new Uint8Array(w2*h2);
dataV = new Uint8Array(w2*h2);
for (var i = 0; i < w2; ++i) {
  for (var j = 0; j < h2; ++j) {
    dataU[i + w2 * j] = 255 - parseInt(j * 255 / h2);
    dataV[i + w2 * j] = parseInt(j * 255 / h2);
  }
}

// テクスチャの設定
gl.activeTexture(gl.TEXTURE0);
let texture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, texture);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.LUMINANCE, w, h, 0, gl.LUMINANCE, gl.UNSIGNED_BYTE, dataY);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
gl.uniform1i(gl.getUniformLocation(prg, 'samplerY'), 0);
gl.activeTexture(gl.TEXTURE1);
texture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, texture);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.LUMINANCE, w2, h2, 0, gl.LUMINANCE, gl.UNSIGNED_BYTE, dataU);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
gl.uniform1i(gl.getUniformLocation(prg, 'samplerU'), 1);
gl.activeTexture(gl.TEXTURE2);
texture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, texture);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.LUMINANCE, w2, h2, 0, gl.LUMINANCE, gl.UNSIGNED_BYTE, dataV);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
gl.uniform1i(gl.getUniformLocation(prg, 'samplerV'), 2);

// 頂点情報の登録
let attLocation = gl.getAttribLocation(prg, 'a_position');
gl.enableVertexAttribArray(attLocation);
const vertex_position = new Float32Array([
  -w, -h,
  w, -h,
  w, h,
  -w, h,
]);
gl.bindBuffer(gl.ARRAY_BUFFER, gl.createBuffer());
gl.bufferData(gl.ARRAY_BUFFER, vertex_position, gl.STATIC_DRAW);
gl.vertexAttribPointer(attLocation, 2, gl.FLOAT, false, 0, 0);

// テクスチャ一頂点情報の登録
let uvLocation = gl.getAttribLocation(prg, 'a_texCoord');
gl.enableVertexAttribArray(uvLocation);
const uv_position = new Float32Array([
  0.0, 1.0,
  1.0, 1.0,
  1.0, 0.0,
  0.0, 0.0
]);
gl.bindBuffer(gl.ARRAY_BUFFER, gl.createBuffer());
gl.bufferData(gl.ARRAY_BUFFER, uv_position, gl.STATIC_DRAW);
gl.vertexAttribPointer(uvLocation, 2, gl.FLOAT, false, 0, 0);
// 描画
gl.drawArrays(gl.TRIANGLE_FAN, 0, 4);
// 表示更新
gl.flush();

// シェーダを生成する関数
function create_shader(id) {
  // シェーダを格納する変数
  let shader;
  // HTMLからscriptタグへの参照を取得
  let scriptElement = document.getElementById(id);
  // scriptタグが存在しない場合は抜ける
  if (!scriptElement) { return; }
  // scriptタグのtype属性をチェック
  switch (scriptElement.type) {
    // 頂点シェーダの場合
    case 'x-shader/x-vertex':
      shader = gl.createShader(gl.VERTEX_SHADER);
      break;
    // フラグメントシェーダの場合
    case 'x-shader/x-fragment':
      shader = gl.createShader(gl.FRAGMENT_SHADER);
      break;
    default:
      return;
  }
  // 生成されたシェーダにソースを割り当てる
  gl.shaderSource(shader, scriptElement.text);
  // シェーダをコンパイルする
  gl.compileShader(shader);
  // シェーダが正しくコンパイルされたかチェック
  if (gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
    // 成功していたらシェーダを返して終了
    return shader;
  } else {
    // 失敗していたらエラーログをアラートする
    alert(gl.getShaderInfoLog(shader));
  }
}

// プログラムオブジェクトを生成しシェーダをリンクする関数
function create_program(vs, fs) {
  // プログラムオブジェクトの生成
  var program = gl.createProgram();
  // プログラムオブジェクトにシェーダを割り当てる
  gl.attachShader(program, vs);
  gl.attachShader(program, fs);
  // シェーダをリンク
  gl.linkProgram(program);
  // シェーダのリンクが正しく行なわれたかチェック
  if (gl.getProgramParameter(program, gl.LINK_STATUS)) {
    // 成功していたらプログラムオブジェクトを有効にする
    gl.useProgram(program);
    // プログラムオブジェクトを返して終了
    return program;
  } else {
    // 失敗していたらエラーログをアラートする
    alert(gl.getProgramInfoLog(program));
  }
}

 webGLの参考情報

繰り返しになりますが、上記ではwebGLtestさんのソースコードを利用させていただきました。
http://jsdo.it/webGLtest/Opoi

また、次のページ「WebGL Fundamentals」は最初にwebGLを勉強する人にオススメです。
https://webglfundamentals.org/webgl/lessons/webgl-fundamentals.html

特にFundamentals、How It Works、Shaders and GLSL、Image Processingは、同じことを少しづつ表現を変えながら説明してくれるので、1回目で?のところも分かるようになりました。

日本語の解説もありますね。

第十一回 WebGLスクール「キューブ環境マッピング」
http://qiita.com/konweb/items/262b107b2ea9a6113776#_reference-c79295fe4df8c81ed4bf

wgld.org
https://wgld.org/sitemap.html

1
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
4