第04回 スキュー

画像をゴムの膜に印刷し、上下に入れた棒をずらして歪ませるような変換をX方向のスキュー、左右をつかんで上下にずらす変換をY方向のスキューという。

X方向のスキューY方向のスキュー
変換式 X' = X + skewX * Y (skewXは定数)
 Y' = Y
 X' = X
 Y' = Y + skewY * X (skewYは定数)

今回のプログラムの最終的な機能

はじめに、これから作るプログラムが完成した時点の機能をざっと見ておく。


選択範囲の赤枠・青枠を表示する

マウスカーソルの位置に応じて上の図のような赤枠・青枠を表示したい。

  1. 歪みがないときの枠の4隅の座標
  2. このプログラムでは、選択範囲の大きさは画像全体に対してscale倍にする(scaleの値は0.8で固定されている)。
    また、計算を簡単にするために、座標の基準点は画像の中心とする。

    選択範囲の幅と高さをw, hとすると、歪みがなければ画像の4隅の座標は
    になる。現時点のプログラムでは、赤枠・青枠の両方がこの4点をつなぐ四角形になっている。


  3. 歪みの強さratioXを求める
  4. 赤枠がカーソルの横位置に応じて歪むようにするには、X方向のスキューの係数「ratioX」が「画像の中央からのカーソルの横ずれ」に比例するようにすればよい(実装済み)。

  5. X方向のスキューでの枠の4隅の座標
  6. X方向のスキューでは、ずれ幅は「ratioX」×「中心からの縦ずれ」になるので、ratioXが正なら下側は右に、上側は左にずれる。
    つまり のようにすれば赤枠がカーソルの横位置に応じて歪むようになる。


  7. 歪みの強さratioYを求める
  8. 青枠がカーソルの縦位置に応じて歪むようにするには、Y方向のスキューの係数「ratioY」が「画像の中央からのカーソルの縦ずれ」に比例するようにすればよい(実装済み)。

  9. Y方向のスキューでの枠の4隅の座標
  10. Y方向のスキューでは、ずれ幅は「ratioY」×「中心からの横ずれ」になるので、ratioYが正なら右側は下に、左側は上にずれる。
    つまり のようにすれば青枠がカーソルの縦位置に応じて歪むようになる。

課題 1

    ベースのプログラム
    PImage img;  // 元画像の変数
    float scale; // 元画像に対する抽出部分のサイズ比
    float ratioX; // 横スキューの比率
    float ratioY; // 縦スキューの比率
    
    void setup() {
      size(800, 600);
      img = loadImage("1元.jpg");
      imageMode(CENTER); // 画像描画のときに中心で位置指定する設定に変える
      scale = 0.8;
    }
    
    void draw() {
      background(0);
      translate(width/2, height/2); // 基準位置を画面中央にする
      image(img, 0, 0, width, height);// 画像を表示
      noFill(); // 図形の中を塗りつぶさない設定にする
      strokeWeight(2);   // 図形の枠の太さを2ptにする
      float w = width * scale;
      float h = height * scale;
      // 赤枠(X方向のスキューの範囲)の描画
      stroke(255, 0, 0); // 図形の枠の色を赤にする
      ratioX = (mouseX-width/2)*0.001;
      // 赤枠の描画(右下、左下、左上、右上の座標)
      quad(w/2, h/2, -w/2, h/2, -w/2, -h/2, w/2, -h/2);
      // 青枠(Y方向のスキューの範囲)の描画
      stroke(0, 0, 255); // 図形の枠の色を青にする
      ratioY = (mouseY-height/2)*0.001;
      // 青枠の描画(右下、左下、左上、右上の座標)
      quad(w/2, h/2, -w/2, h/2, -w/2, -h/2, w/2, -h/2);
    }
    
    // 画面の状態を画像として出力
    void getScreenShot() {
      save("data/1選択範囲.jpg");
    }
    
    // 双一次補間で、sに応じて横か縦にスキューした画像を保存
    void skew(String s) {
    }
    
    // cを中心として画像を1/scale倍にして、sに応じて横か縦にスキューしたときのベクトルfの移動先のベクトルを返す
    PVector getSkewedPosition(PVector c, PVector f, String s){
      return f;
    }
    
    void mousePressed() {
      getScreenShot(); // 実行画面のスクリーンショットを保存
      skew("横");      // 双一次補間で横スキューした画像を保存
      skew("縦");      // 双一次補間で縦スキューした画像を保存
    }
    

  1. Processingのエディタに上のサンプルプログラムのコードをコピー&ペーストする。
  2. 「img04」という名前で保存する。
  3. 適当に画像検索してサンプル用の画像(例えば建物など、どのように歪んだかがわかりやすいもの)を用意する。
  4. 画像の形式に応じて以下の変更を加える。
  5. 画像の横と縦の比が4:3になるように変更を加える。
  6. 「1元.jpg」をProcessingのウインドウにドラッグ&ドロップする。
  7. draw関数の「// 赤枠の描画(右下、左下、右上、左上の座標)」の下のコードを、ratioXに応じて座標が変わるように書き換える。
  8. 実行してウインドウ上にマウスカーソルを乗せ、カーソルを左右に動かして赤枠が歪むことを確認する。
  9. draw関数の「// 青枠の描画(右下、左下、右上、左上の座標)」の下のコードを、ratioYに応じて座標が変わるように書き換える。
  10. 実行してウインドウ上にマウスカーソルを乗せ、カーソルを左右に動かして青枠が歪むことを確認する。


  11. 画面上でクリックする。
  12. ※ 画像の解像度はProcessingの実行画面のサイズ(800, 600)になる。

    この時点で存在するファイル
    ※ このあとの課題を進めるにつれて「1選択範囲.jpg」は更新される。この段階では提出用のバックアップなどはしないこと。
    ※ うまくいかないときはdraw関数の最終状態とコードを見比べる。

X方向のスキュー

課題 2

  1. 第03回の課題の最終状態のプログラムのbilinear関数の中のコードを、今のプログラムのskew関数の中にコピー&ペーストする。
  2. ※ 今回のプログラムにはgetRotatedPosition関数やグローバル変数「angle」がないので、そのままではエラーになる。

  3. skew関数の中の「PVector ij」を求める部分を以下のように書き換える。
  4.       PVector ij = getRotatedPosition(new PVector(cx, cy), new PVector(i, j), 1/scale, angle);
    
          PVector ij = getSkewedPosition(new PVector(cx, cy), new PVector(i, j), "横");
    
    ※ getSkewedPosition関数は「拡大+スキュー前の対応点」を求める自前の関数。この関数の下に未完成状態で実装されている。


  5. skew関数の最後の行のファイル名のところを「3双一次補間.jpg」から「2スキューX.jpg」に変更する。
  6. getSkewedPosition関数の「return f;」の上に、以下のコメント文とコードを追加する。
  7.   f.mult(scale); // fをscale倍する
      f.x += img.width * (1-scale)/2;  // 画像の中心位置の修正
      f.y += img.height * (1-scale)/2; // 画像の中心位置の修正
      if (s.equals("横")){
        // 中心からの縦ずれに比例した量(係数ratioX)をf.xに加える
      }else{
        // 中心からの横ずれに比例した量(係数ratioY)をf.yに加える
      }
    

  8. skew関数の「// 中心からの縦ずれに比例した量(係数ratioX)をf.xに加える」の左に、対応するコードを追加する。
  9. 実行して適当な場所が選択されている状態でクリックする。


  10. この時点で存在するファイル
    ※ うまくいかないときはこの段階のskew関数この段階のgetSkewedPosition関数とコードを見比べる。

Y方向のスキュー

課題 3

  1. skew関数の中の「PVector ij」を求める部分を以下のように書き換える。
  2.       PVector ij = getSkewedPosition(new PVector(cx, cy), new PVector(i, j), "横");
    
          PVector ij = getSkewedPosition(new PVector(cx, cy), new PVector(i, j), s);
    
    ※ sはskew関数が引数として受け取るもの。この関数は、プログラムの最後のmousePressed関数から呼び出される。
    ※ mousePressed関数では のようにskew関数を2回呼び出しているが、これまでは引数が何であっても「横スキュー画像を作って『2スキューX.jpg』として保存」という処理を行っていた。ここではそれを修正して、引数「"縦"」を受け取ったときに「縦スキュー画像を作って『3スキューY.jpg』として保存」という処理を行うようにしたい。

  3. skew関数の最後の行を以下のように書き換える。
  4.   outputImg.save("data/2スキューX.jpg");
    
      if (s.equals("横")) {
        outputImg.save("data/2スキューX.jpg");
      } else {
        outputImg.save("data/3スキューY.jpg");
      }
    


  5. mousePressed関数の「// 中心からの横ずれに比例した量(係数ratioY)をf.yに加える」の左に、対応するコードを追加する。
  6. ※ すぐ上の「f.x += (f.y -c.y) * ratioX;」が参考になる。これの縦横を逆にした処理になればよい。

  7. 実行して赤枠・青枠とも端が画面からはみ出した状態でクリックする。
  8. ※ 画像を開いてみると、「2スキューX.jpg」「3スキューY.jpg」のいずれも2つの隅に黒い部分(選択範囲が元画像からはみ出した分)があるはず。

    この時点で存在するファイル→提出ファイル
    ※ うまくいかないときは最終状態のskew関数最終状態のgetSkewedPosition関数とコードを見比べる。

提出

小テスト予告



戻る inserted by FC2 system inserted by FC2 system