第02回 回転

Processingの描画機能を使った方法、出力画像のピクセルの色を自前で計算する方法の2通りを使い、元画像の一部を拡大しつつ回転するプログラムを作る。

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

プログラムを実行すると下の図のように元画像が表示され、マウスカーソルの位置に応じて選択範囲が赤枠で表示される。範囲の中心は画像の中心と同じ。


画面上でクリックすると、dataフォルダに の3つの画像が作られる (カーソルのあった位置が、出力画像の右端になる)。

枠の表示

概要

マウスカーソルの位置に応じて上の図のような赤枠を表示したい。
枠を表示するために必要な量、枠の傾きを表す変数 "angle" と、画面に対する枠のサイズ比 "scale" の2つ。

angleの求め方
例えば画面の中心(width/2, height/2)と、マウスカーソルの位置(mouseX, mouseY)が図のような位置関係にあったとすると、


この直角三角形の縦横の辺の長さはそれぞれ「mouseX-width/2」「mouseY-height/2」になる。


Processingのatan2関数を使えば、角度「angle」は
angle = atan2(縦の辺の長さ, 横の辺の長さ);
で求められる。

scaleの求め方
図のAが「画面の中心から右端までの距離」、Bが「選択範囲の中心から右端までの距離」にあたる。そのため、BをAで割れば「画面に対する選択範囲の大きさの比」、つまりscaleが求められる。


Aは単純に
A = width/2;
BはProcessingのdist関数を使って
B = dist(width/2, height/2, mouseX, mouseY);
で求められる。あとはBをAで割るだけ。

課題 1

ベースのプログラム
PImage img; // 元画像の変数
float angle; // 選択範囲の傾き
float scale; // 画面に対する選択範囲のサイズ比

void setup() {
  size(600, 450);
  textAlign(CENTER, CENTER);
  img = loadImage("元.jpg");
  rectMode(CENTER);  // 長方形描画のときに中心で位置指定する設定に変える
  imageMode(CENTER); // 画像描画のときに中心で位置指定する設定に変える
}

void draw() { // (課題1で変更を加える)
  background(0);
  // angleを求める
  // scaleを求める
  noFill(); // 図形の中を塗りつぶさない設定にする
  stroke(255, 0, 0); // 図形の枠の色を赤にする
  strokeWeight(2);   // 図形の枠の太さを2ptにする
  translate(width/2, height/2); // 基準位置を画面中央にする
  image(img, 0, 0, width, height);// 画像を表示
  rotate(angle);                  // 座標系をangleだけ回転
  rect(0, 0, width*scale, height*scale); // 画面サイズのscale倍の長方形を描く
}

// 画面の状態を画像として出力
void getScreenShot(){
  save("data/1選択範囲.jpg");
}

// Processingの描画機能を使って回転 (課題2で変更を加える)
void drawToImage() {
}

// 双一次補間 (課題3で変更を加える)
void bilinear() {
}

// cを中心として画像を1/s倍し、aだけ回転したときのベクトルfの移動先のベクトルを返す
PVector getRotatedPosition(PVector c, PVector f, float s, float a) {
  f.sub(c);
  f.div(s);
  f.rotate(a); // fをaだけ回転させる
  f.add(c);
  return f;
}

void mousePressed() {
  getScreenShot(); // 実行画面のスクリーンショットを保存
  drawToImage();   // 描画機能で回転・拡大した画像を保存
  bilinear();      // 双一次補間で回転・拡大した画像を保存
}
			
  1. Processingのエディタに上のサンプルプログラムのコードをコピー&ペーストする。
  2. 「img02」という名前で保存する。
  3. 適当に画像検索してサンプル用の画像 (横と縦の比が4:3で、建物や樹木などの上下の方向がわかりやすいもの) を用意する。
  4. (画像のファイルにカーソルを当てて、クリックせずにそのまま待つとサイズ情報が表示される。縦の数値を横の数値で割って0.75ならOK)


  5. 画像の形式に応じて以下の変更を加える。
    • JPG形式の場合→名前を元.jpgに変更
    • それ以外の場合→ペイントで開き、形式をJPGに指定して「元」という名前で保存
  6. 「元.jpg」をProcessingのウインドウにドラッグ&ドロップする。
  7. draw関数の「// angleを求める」のところに、対応するコードを入れる。
  8. draw関数の「// scaleを求める」のところに、対応するコードを入れる。
    • いずれも「概要」の説明を参考にする。

  9. 実行してウインドウ上にマウスカーソルを乗せ、最初の図のような位置関係で赤枠が表示されることを確認する。
  10. 画面上でクリックする。
    • dataフォルダに「1選択範囲.jpg」が作られる。
    • 画像の解像度はProcessingの実行画面のサイズ(600, 450)になる。
この時点で存在するファイル
  • 元.jpg
  • 1選択範囲.jpg
  • このあとの課題を進めるにつれて「1選択範囲.jpg」は更新される。この段階では提出用のバックアップなどはしないこと。
  • うまくいかないときはdraw関数の最終状態とコードを見比べる。

Processingの描画機能を使う

概要

赤枠の範囲は、「画面をscale倍してangleだけ回転したもの」なので、その範囲の画像を元画像と同じサイズで作るには、逆に「-angleだけ回転して1/scale倍にしたもの」を作ればよいことになる。
Processingの座標変換の命令には
命令書式
平行移動translate(横移動量, 縦移動量);
回転rotate(回転角);
拡大・縮小scale(倍率);
がある。これらをうまく組み合わせれば上記の変換を実現できる。

課題 2

  1. 前回の課題の最終状態のプログラムのdrawToImage関数の中のコードを、今のプログラムのdrawToImage関数の中にコピー&ペーストする。
    • 前回とは違い、今回のdrawToImage関数には引数「s」がないので、そのままではエラーになる。
    • USBメモリなどで保存していなかった場合は、送信済みメールからコードを回収する。
    • 前回の分ができていない場合は、まずそちらの課題を先に完成させる。

  2. drawToImage関数から以下の行を削除する。
  3.   float cx = mouseX*w/width;  // 画像サイズに換算した拡大基準の横位置
      float cy = mouseY*h/height; // 画像サイズに換算した拡大基準の縦位置
      pg.image(img, -cx*(s-1), -cy*(s-1), w*s, h*s);

  4. drawToImage関数の最後の行のファイル名のところを「1描画機能.jpg」から「2描画機能.jpg」に変更する。
    • この時点でdrawToImage関数は図のようになっているはず。


  5. 「pg.beginDraw();」と「pg.endDraw();」の間に以下のコメント文、コードを追加する。
  6.   pg.background(0);     // (pg)背景を黒で塗りつぶす
      pg.imageMode(CENTER); // (pg)画像描画のときに中心で位置指定する設定に変える
      // (pg)画像の中央の位置まで平行移動
      // (pg)-angleだけ回転
      // (pg)(1/scale)だけ倍率をかける
      pg.image(img, 0, 0);  // (pg)画像imgを元の大きさで表示

  7. コメント文のみになっている3行の左側に、対応するコードを記述する。
    • 画像の横・縦のサイズは上で求めてある (w, h) → 中央は (w/2, h/2)

  8. 実行して適当な場所が選択されている状態でクリックする。
    • dataフォルダに「2描画機能.jpg」が作られる。
    • (「1選択範囲.jpg」も更新される)
    • 「1選択範囲.jpg」の赤枠の部分が「2描画機能.jpg」の中身になるはず。
    • 枠が左に傾いていれば、「2描画機能.jpg」の中のモノは右に傾く。
この時点で存在するファイル
  • 元.jpg
  • 1選択範囲.jpg
  • 2描画機能.jpg

自前の方法 (双一次補間)

概要

今度は自前でピクセルの色を計算する。前回と同じく、出力画像の特定のピクセルに対応する元画像上の座標は整数にならないので、最近傍補間や双一次補間が必要になる。前回の試行の結果、双一次補間の方が性能がいいことは確かめられたので、ここでは最近傍補間は省略し、双一次補間による計算を行う。

課題 3

  1. 前回の課題の最終状態のプログラムのbilinear関数の中のコードを、今のプログラムのbilinear関数の中にコピー&ペーストする。
    • この関数にも引数「s」がないので、そのままではエラーになる。

  2. bilinear関数の中の3, 4行目を以下のように書き換える。
  3.   float cx = mouseX*w/width;  // 画像サイズに換算した拡大基準の横位置
      float cy = mouseY*h/height; // 画像サイズに換算した拡大基準の縦位置
      float cx = w/2; // 画像の中心の横位置
      float cy = h/2; // 画像の中心の縦位置
    • (cx, cy)は回転・拡大の基準点。前回はマウスカーソルの位置によって変わる点だったが、今回はつねに画像の中心。

  4. bilinear関数の中の「PVector ij」を求める部分を以下のように書き換える。
  5.       PVector ij = getScaledPosition(new PVector(cx, cy), new PVector(i, j), s);
          PVector ij = getRotatedPosition(new PVector(cx, cy), new PVector(i, j), 1/scale, angle);
    • 前回はgetScaledPositionで「拡大前の対応点」を求めたが、今回はgetRotatedPositionで「拡大+回転前の対応点」を求める。
      (どちらも自前の関数)

  6. bilinear関数の「int j_ = int(ij.y);」の下に、以下のコードを追加する。
  7.       // (i_, j_)とその右、下、右下のピクセルのいずれかが画像の範囲外なら黒にする
          if (i_<0 || i_>=w-1 || j_<0 || j_>=h-1){
            outputImg.pixels[i+j*w] = color(0);
            continue;
          }
    • わざわざこんなことをするのは、例えば赤枠が図のように元画像からはみ出していると、そこに対応する元画像の座標が許容範囲外になってしまうため。
      (その点の色を取り出そうとしても、「そんなピクセルは存在しない」のでエラーになる。前回は画像内の点を中心とした拡大 (の逆変換) だったので、「枠」がはみ出すことはなかった)



  8. 実行して適当な場所が選択されている状態でクリックする。
    • dataフォルダに「3双一次補間.jpg」が作られる。
    • (「1選択範囲.jpg」と「2描画機能.jpg」も更新される)
    • 「2描画機能.jpg」と「3双一次補間.jpg」はほぼ同じ (わずかに位置がずれる) になるはず。
この時点で存在するファイル → 提出ファイル
  • 元.jpg
  • 1選択範囲.jpg
  • 2描画機能.jpg
  • 3双一次補間.jpg

提出

小テスト予告

次回の授業の初めに今回の内容についての小テストを行う。

戻る

inserted by FC2 system