«GLSL で画像処理 (2) 簡単なフィルタ 最新 GLSL で画像処理 (4) 背景差分»

床井研究室

※このブログは遅くとも 2027 年 3 月に管理者の定年退職により閉鎖します (移転先は管理者本人共々模索中)

■ 2014年08月01日 [OpenGL][GLSL] GLSL で画像処理 (3) ワーピング

2015年02月03日 12:18更新

夏風邪

一般的にバカがひくといわれる夏風邪を引きました.体中+喉が痛いです.インフルエンザに似てますけど,熱はそれほど高くありません.

ワーピング

テクスチャを貼り付ける画像以外の用途で使う例を考えてみます.画像ではないテクスチャとして,もっともよく知られているのは法線マップでしょうか.ここではテクスチャを使って画像のワーピングをやってみます.画像のワーピングはカメラのレンズの歪み補正なんかに使われるようです.また,普通これは画像をマッピングするポリゴンを細かなメッシュに分割して,その格子点に設定するテクスチャ座標を移動させて実現するものだと思いますが,テクスチャを使って実現することもできます.ただし,ポリゴンメッシュを使う方法に比べて,性能面でのアドバンテージはないと思います.

メッシュによるワーピング

テクスチャを作る

サンプルプログラムは例によって GitHubに置いてあります.

とりあえずテクスチャとして使うデータを作成します.テクスチャに転送するデータを格納する配列を用意して,それにデータを書き込みます.まずは配列の要素の位置をそのまま値に用います.

  // ワーピング用データを作成する
  std::vector<GLfloat> map;
  for (int i = 0; i < capture_width * capture_height; ++i)
  {
    const GLfloat x(GLfloat(i % capture_width));
    const GLfloat y(GLfloat(i / capture_width));
    map.push_back(x);
    map.push_back(y);
  }

浮動小数点テクスチャ

このデータをテクスチャとして転送します.このデータは GLfloat 型 (32bit 浮動小数点) であり,各要素は x, y の二つの成分で構成されていますから,テクスチャの internal format は GL_RG32F,format は GL_RG,type は GL_FLOAt を指定します.GL_RG32F はテクスチャの画素ごとに二つの 32bit 浮動小数点データを保持します.

  // ワーピング用データをテクスチャメモリに転送する
  GLuint warp;
  glGenTextures(1, &warp);
  glBindTexture(GL_TEXTURE_RECTANGLE, warp);
  glTexImage2D(GL_TEXTURE_RECTANGLE, 0, GL_RG32F, capture_width, capture_height, 0, GL_RG, GL_FLOAT, &map[0]);
  glTexParameteri(GL_TEXTURE_RECTANGLE, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
  glTexParameteri(GL_TEXTURE_RECTANGLE, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
  glTexParameteri(GL_TEXTURE_RECTANGLE, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);
  glTexParameteri(GL_TEXTURE_RECTANGLE, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);

マルチテクスチャ

テクスチャとしてキャプチャした画像とワーピング用のデータを同時に使いますので,当然ながらマルチテクスチャです.ワーピング用のテクスチャのサンプラは warp という名前にします.

  // プログラムオブジェクトの作成
  const GLuint program(ggLoadShader("simple.vert", "warp.frag"));
  
  // uniform 変数のインデックスの検索(見つからなければ -1)
  const GLuint imageLoc(glGetUniformLocation(program, "image"));
  const GLuint warpLoc(glGetUniformLocation(program, "warp"));
描画の際には,この二つをそれぞれテクスチャユニット 0 とテクスチャユニット 1 に割り当てます.
  // ウィンドウが開いている間繰り返す
  while (window.shouldClose() == GL_FALSE)
  {
    ...
  
    // シェーダプログラムの使用開始
    glUseProgram(program);
    
    // uniform サンプラの指定
    glUniform1i(imageLoc, 0);
    glUniform1i(warpLoc, 1);
    
    // テクスチャユニットとテクスチャの指定
    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_RECTANGLE, image);
    glActiveTexture(GL_TEXTURE1);
    glBindTexture(GL_TEXTURE_RECTANGLE, warp);

フラグメントシェーダ

フラグメントシェーダ warp.frag では,ワーピング用のテクスチャをサンプリングして得た値を使って,キャプチャした画像をサンプリングします.テクスチャを間接参照することになるので,ちょっと性能が気になります.が,ベンチマークしてません.

#version 330
 
uniform sampler2DRect image;
uniform sampler2DRect warp;
 
layout (location = 0) out vec4 fc;
 
void main()
{
  vec4 tc = texture(warp, gl_FragCoord.xy);
  fc = texture(image, tc.st);
}

これで,次のような結果が得られます.サンプリングして得た値は,そのテクスチャ座標と同じ値なので,何にも変わりません.

キャプチャ画像

横に揺らしてみる

このデータの x 座標を,y 軸に応じて変化させてみます.

  // ワーピング用データを準備する
  std::vector<GLfloat> map;
  for (int i = 0; i < capture_width * capture_height; ++i)
  {
    const GLfloat x(GLfloat(i % capture_width));
    const GLfloat y(GLfloat(i / capture_width));
    map.push_back(x + 100.0f * cos(20.0f * y / GLfloat(capture_height)));
    map.push_back(y);
  }

こういう結果が得られます.ワーピング用のテクスチャの解像度をキャプチャ画像と同じにしているので,ゆがみの大きい部分でエリアシングが発生しています.テクスチャのサンプリングはバイリニア補間 (GL_LINEAR) で行われるので,ワーピング用のテクスチャの解像度は,もっと低くても問題ないと思います.

横に揺らす

ただし,このような単純な関数で定義される変形は,テクスチャやメッシュによるワーピングを使うまでもなく,フラグメントシェーダだけで書けてしまいます.

バレル・ピンクッション

バレル歪みやピンクッション歪みの補正に用いられる Brown–Conrady の歪みモデル*1を使ってみます.ただし,めんどくさいので,円周方向の歪みは考えないことにします.

  // ワーピング用データを準備する
  const GLfloat cx(GLfloat(capture_width / 2));
  const GLfloat cy(GLfloat(capture_height / 2));
  static const GLfloat k[] = { 1.0f, 0.18f, 0.115f };
  std::vector<GLfloat> map;
  for (int i = 0; i < capture_width * capture_height; ++i)
  {
    const GLfloat x(GLfloat(i % capture_width) - cx);
    const GLfloat y(GLfloat(i / capture_width) - cy);
    const GLfloat dx(x / cy);
    const GLfloat dy(y / cy);
    const GLfloat d2(dx * dx + dy * dy);
    const GLfloat d4(d2 * d2);
    const GLfloat t(k[0] + k[1] * d2 + k[2] * d4);
    map.push_back(x * t + cx);
    map.push_back(y * t + cy);
  }

これでこういう結果が得られます.

バレル歪み

k[] = { 1.0f, -0.1f, -0.01f } とすれば,次の結果が得られます.

ピンクッション歪み

でも,これらもテクスチャやメッシュによるワーピングを使うまでもなく,フラグメントシェーダだけで書けてしまいます.

3D ワーピング

3D テクスチャを使えば,三次元のワーピングもできます.

*1 Brown, Duane C. "Decentering distortion of lenses." Photogrammetric Engineering. 32 (3): 444–462 (1966)


編集 «GLSL で画像処理 (2) 簡単なフィルタ 最新 GLSL で画像処理 (4) 背景差分»