第07回 平滑化


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

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


移動平均フィルタ


課題 1

    ベースのプログラム (このままではクリックしても画面が黒くなるだけ)
    // ソートのためのライブラリ
    import java.util.ArrayList;
    import java.util.Collections;
    
    PImage img;       // 元画像の変数
    
    // 画素半径1の移動平均フィルタのマスク
    float[] maskA1 = {
      1, 1, 1, 
      1, 1, 1, 
      1, 1, 1
    };
    // 画素半径2の移動平均フィルタのマスク
    float[] maskA2 = {
      1, 1, 1, 1, 1, 
      1, 1, 1, 1, 1, 
      1, 1, 1, 1, 1, 
      1, 1, 1, 1, 1, 
      1, 1, 1, 1, 1
    };
    // 画素半径1のガウシアンフィルタのマスク
    float[] maskG1 = {
    };
    // 画素半径2のガウシアンフィルタのマスク
    float[] maskG2 = {
    };
    
    void setup() {
      size(800, 600);
      // フィルタの値の規格化(全部足して1になるようにする)
      for (int i=0; i<maskA1.length; i++) {
        maskA1[i] /= 9.0;
      }
      for (int i=0; i<maskA2.length; i++) {
        maskA2[i] /= 25.0;
      }
      img = loadImage("1元.jpg");
      // 元画像にランダムノイズを追加する(全画素数の1%)
      for (int i=0; i<img.pixels.length/100; i++) {
        img.pixels[int(random(img.pixels.length))] = color(random(256), random(256), random(256));
      }
      // ノイズを加えた画像を保存
      img.save("data/1ノイズ.jpg");
      background(0);
      image(img, 0, 0, width, height);
    }
    
    void draw() {
    }
    
    // typeに応じた処理を行い、その画像を表示して保存
    void colorFilter(String type) {
      int ra=type.charAt(type.length()-1)-'0'; // 画素半径
      PImage outputImg = createImage(img.width, img.height, ARGB);
      // 外側のra行・列のピクセル以外について繰り返し
      for (int j=ra; j<img.height-ra; j++) {
        for (int i=ra; i<img.width-ra; i++) {
          if (type.charAt(0)!='3') {
            outputImg.pixels[i+j*img.width] = getColorAG(i, j, type); // 移動平均・ガウシアンの色取得
          } else {
            // メディアンの色取得
          }
        }
      }
      background(0);
      outputImg.updatePixels();
      image(outputImg, 0, 0, width, height);
      outputImg.save("data/"+type+".jpg");
    }
    
    // 移動平均、ガウシアンフィルタでの出力画像の(x, y)のピクセルの色を返す
    color getColorAG(int x, int y, String type) {
      int ra=type.charAt(type.length()-1)-'0'; // 画素半径
      float[] mask = new float[0]; // typeに応じたフィルタのマスク
      switch(type) {
      case "1移動平均1":
        mask=maskA1;
        break;
      case "1移動平均2":
        mask=maskA2;
        break;
      }
      float r=0;
      float g=0;
      float b=0;
      int n=0; // 範囲内の位置インデックス
      for (int j=y-ra; j<=y+ra; j++) {
        for (int i=x-ra; i<=x+ra; i++) {
          // 元画像の(i, j)のピクセルの赤成分×マスクのn番目の値をrに加算
          // 元画像の(i, j)のピクセルの緑成分×マスクのn番目の値をgに加算
          // 元画像の(i, j)のピクセルの青成分×マスクのn番目の値をbに加算
          n++;
        }
      }
      return color(r, g, b);
    }
    
    // メディアンフィルタでの出力画像の(x, y)のピクセルの色を返す
    color getColorM(int x, int y, String type) {
      return color(0); // ダミー
    }
    
    // 押したボタンに応じてフィルタ処理を実行
    void mousePressed() {
      if (mouseButton==LEFT) {
        colorFilter("1移動平均1");
        colorFilter("1移動平均2");
      } else if (mouseButton==RIGHT) {
      } else {
      }
    }
    

  1. Processingのエディタに上のサンプルプログラムのコードをコピー&ペーストする。
  2. 「img07」という名前で保存する。
  3. 適当に画像検索してサンプル用の画像を用意する。
  4. 画像の形式に応じて以下の変更を加える。
  5. 画像の解像度を800×600に変更する。
  6. 「1元.jpg」をProcessingのウインドウにドラッグ&ドロップする。
  7. 実行し、ノイズを含んだ画像が表示されることを確認する。

  8. getColorAG関数の「// 元画像の(i, j)のピクセルの赤成分×マスクのn番目の値をrに加算」の左に以下のコードを入れる。
  9. r += red(img.pixels[i+j*img.width])*mask[n];
    

  10. 同様にして緑・青成分についての処理を追加する。
  11. 実行して左クリックする。
  12. ※ 実行画面に表示されるのは「1移動平均2.jpg」の方。

  13. 「1ノイズ.jpg」「1移動平均1.jpg」「1移動平均2.jpg」を開いて比較する。

  14. 同じ部分を拡大して見ると下の図のようになる。
    ノイズ移動平均1移動平均2


    ここで作る提出物
    ※ うまくいかないときはこの時点のgetColorAG関数とコードを見比べる。

ガウシアンフィルタ

課題 2

  1. プログラムの先頭近くの図の部分にコードを追加し、プログラム実行時に配列 maskG1, maskG2 にガウシアンフィルタのマスクの値が入るようにする。

  2. getColorAG関数の図の部分にコードを追加し、変数 type の値が"2ガウシアン1", "2ガウシアン2"だった場合にmaskにmaskG1, maskG2が入るようにする。

  3. mousePressed関数の「} else if (mouseButton==RIGHT) {」の下に"2ガウシアン1"と"2ガウシアン2"を渡してcolorFilter関数を呼び出す命令を追加する。

  4. 実行して右クリックする。
  5. ※ 実行画面に表示されるのは「2ガウシアン2.jpg」の方。

  6. 「1ノイズ.jpg」「1移動平均1.jpg」「1移動平均2.jpg」「2ガウシアン1.jpg」「2ガウシアン2.jpg」を開いて比較する。

  7. 同じ部分を拡大して見ると下の図のようになる。
    ノイズ移動平均1移動平均2

    ガウシアン1ガウシアン2


    ここで作る提出物
    ※ うまくいかないときは最終的なプログラム先頭部分最終的なgetColorAG関数この時点のmousePressed関数とコードを見比べる。

メディアンフィルタ

課題 3

  1. colorFilter関数の「// メディアンの色取得」の左に以下のコードを追加する。
  2. outputImg.pixels[i+j*img.width] = getColorM(i, j, type);
    
    getColorM関数がメディアンフィルタをかけた画像の(i, j)の位置のピクセルの色を返す。いまはダミーの黒を返すだけだが、このあと処理を実装する。

  3. getColorAG関数の中のコードの一部をgetColorM関数にコピーし、下図の状態にする。

  4. もとからある「return color(0); // ダミー」以外のコードはすべてgetColorAG関数にある。手入力せずにコピー&ペーストした方が安全。

  5. 最初の「int ra=type.charAt(type.length()-1)-'0'; // 画素半径」の下に以下のコードを追加する。
  6.   // 色成分格納用のリスト
      ArrayList<Float> ar = new ArrayList<Float>();
      ArrayList<Float> ag = new ArrayList<Float>();
      ArrayList<Float> ab = new ArrayList<Float>();
    
    ArrayListは配列のようなものだが要素数が可変で、あとから要素を追加できる。以下の2重ループで、これらにそれぞれのピクセルの色の赤・緑・青成分の値を追加する。

  7. 「for (int i=x-ra; i<=x+ra; i++) {」の下に以下のコードを追加する。
  8.       // ピクセルの色成分をリストに追加
          ar.add(red(img.pixels[i+j*img.width]));
    
    これが「リストarに元画像の(i, j)の位置のピクセルの色の赤成分の値を追加する」という命令。これを繰り返すことで、最終的にarには9個または25個の要素ができる。

  9. その下に緑・青成分 (ag, ab) についての処理を追加する。

  10. 「return color(0); // ダミー」の上に以下のコードを追加する。
  11.   // 色リストの値を降順にソートする
      Collections.sort(ar);
    
    これはarの要素を昇順に並べ替える命令。こうしておけば、「arの要素の真ん中番目」を取り出すだけで「真ん中番目の赤の強さ」が得られる。

  12. その下に緑・青成分 (ag, ab) についての処理を追加する。

  13. その下に以下のコードを追加する。
  14.   // 中央の値を取り出す
      float r = ar.get((ra*2+1)*(ra*2+1)/2);
    
    raが画素半径。対象範囲の縦横のピクセル数はraが1なら3, 2なら5なので、「ra*2+1」になる。これを2乗すれば範囲のピクセルの数になる。「真ん中」はその半分。

  15. 「return color(0); // ダミー」の「// ダミー」を消し、「return color(r, g, b);」に書き換える。

  16. mousePressed関数の「} else {」の下に"3メディアン1"と"3メディアン2"を渡してcolorFilter関数を呼び出す命令を追加する。

  17. 実行して中央ボタン(ホイール)をクリックする。
  18. ※ 実行画面に表示されるのは「3メディアン2.jpg」の方。

  19. これまでにできた画像を開いて比較する。

  20. 同じ部分を拡大して見ると下の図のようになる。
    ノイズ移動平均1移動平均2

    ガウシアン1ガウシアン2

    メディアン1メディアン2


    ここで作る提出物
    ※ うまくいかないときは最終的なcolorFilter関数最終的なgetColorM関数最終的なmousePressed関数とコードを見比べる。

提出

小テスト予告



戻る inserted by FC2 system