第10回 収縮・膨張

第05回では画像に含まれるノイズを取り除く「平滑化」について紹介したが、元画像が白と黒のいずれかのピクセルからなる二値画像の場合は、周囲のピクセルの明るさの平均を取ったり、中央値を使う方法よりも効率よくノイズを消す方法がある。ここでは収縮・膨張という処理を紹介する。処理は単に以下のことをすべてのピクセルについて繰り返すだけ。

収縮 : 上下左右に隣り合うピクセルのどれか一つでも黒なら自分も黒にする
膨張 : 上下左右に隣り合うピクセルのどれか一つでも白なら自分も白にする

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

プログラムを実行すると実行画面の上半分に元画像が表示される。
さらに実行画面上でクリックすると、dataフォルダに の5つの画像が作られる。
コンソールに「完了」が表示されてからキーボードの0~5のキーを押すとこれらの画像が画面に表示される。
(2と3, 4と5キーで出力される画像は全く同じなので、これらのキーを交互に押しても表示は全く変わらないはず。違いはProcessingのライブラリを使うか自前で処理を行うかだけ)
(クリックだけですべての画像が作成されるので、キーボードを使うのは結果確認のためだけ)
キー画像
0元.jpg
11ノイズ.png
22収縮1.png
32収縮2.png
43膨張1.png
53膨張2.png


収縮処理

膨張処理

ノイズ追加とネガ・ポジ反転

概要

収縮と膨張の処理で「白い背景の中の黒いピクセル」と「黒い背景の中の白いピクセル」がどうなるかを見たいので、二値化された画像に対してランダムに選んだ場所で「白⇔黒」の変更を加える。


また、元画像をネガ・ポジ反転したものにも同様の処理を行うことで、「白い背景の中の黒い文字」「黒い背景の中の白い文字」が収縮・膨張によってどうなるかを調べる。

課題 1

ベースのプログラム (クリックしても元画像を二値化したものとそれをネガ・ポジ反転したものを縦に並べた画像が作られるだけ)
// 出力用画像の変数
PImage[] img = new PImage[6];
// 出力ファイル名
String[] fName = {"1ノイズ", "2収縮1", "2収縮2", "3膨張1", "3膨張2"};
int w, h;

void setup() {
  size(400, 600);
  img[0] = loadImage("元.png");
  w = img[0].width;
  h = img[0].height;
  if (w!= 400 || h!=300) {
    println("サイズが違います");
  }
  noSmooth();
  background(0);
  image(img[0], 0, 0, width, height/2);
}

void draw() {
}

// ノイズ追加 (課題1で変更を加える)
void addNoise() {
  // 元画像を画像1にコピー
  img[1] = createImage(w, h*2, ARGB);
  for (int j=0; j<h; j++) {
    for (int i=0; i<w; i++) {
      // 上半分には元画像のピクセルをそのままコピー
      img[1].pixels[i+j*w] = img[0].pixels[i+j*w];
      // 下半分には元画像のピクセルの明度を反転してコピー
      img[1].pixels[i+(j+h)*w] = color(255-brightness(img[0].pixels[i+j*w]));
    }
  }
  // 画像1を二値化する
  img[1].filter(THRESHOLD);
  // 画像1にノイズを加える
  for (int i=0; i<w*h*2/50; i++) {
    // 画像内の位置をランダムに決める
    // x, yのピクセルの明度を反転させる
  }
  // 収縮・膨張用の画像をこれと同じにする
  for (int i=2; i<=5; i++) {
    img[i] = img[1].get();
  }
}

// filter関数を使った収縮 (課題2で変更を加える)
void filterErode() {
  // img[2]にfilter関数で収縮処理をかける
}

// 自前の収縮処理 (課題2で変更を加える)
void myErode() {
  for (int j=0; j<h*2; j++) {
    for (int i=0; i<w; i++) {
      // img[1]の(i, j)の場所の上下左右のどれか1つでも黒なら
      if (true) {
        // img[3]の(i, j)を黒にする
      }
    }
  }
}

// filter関数を使った膨張 (課題3で変更を加える)
void filterDilate() {
  // img[4]にfilter関数で膨張処理をかける
}

// 自前の膨張処理 (課題3で変更を加える)
void myDilate() {
}

// ノイズ画像の(i, j)の位置の明度を返す
// ただし、(i, j)が範囲外のときは-1を返す
float getBrightness(int i, int j) {
  if (i<0 || i>=w || j<0 || j>=h*2) {
    return -1;
  }
  return brightness(img[1].pixels[i+j*w]);
}

// フィルタ処理を実行
void mousePressed() {
  addNoise();
  filterErode();
  filterDilate();
  myErode();
  myDilate();
  for (int i=1; i<=fName.length; i++) {
    img[i].save("data/" + fName[i-1] + ".png");
  }
  println("完了");
}

// 押したキーに応じて対応する画像を表示
void keyPressed() {
  int k = key-'0';
  if (k>=0 && k<6) {
    background(0);
    if (k==0) {
      image(img[k], 0, 0, width, height/2);
    } else {
      image(img[k], 0, 0, width, height);
    }
  }
}
  1. Processingのエディタに上のサンプルプログラムのコードをコピー&ペーストする。
  2. 「img10」という名前で保存する。
  3. ペイント3Dで以下のようにして文字画像を作る。
    • ペイント3Dを起動する
    • 「新規作成」をクリックする
    • 「テキスト」でサイズ48ポイント、太字設定の適当な文字を入れる
    • 「トリミング」で幅400ピクセル、高さ300ピクセルの部分だけ残して切り抜く
    • 「メニュー」→「保存」で「元.png」という名前で適当なところに保存する


  4. 「元.png」をProcessingのエディタにドラッグ&ドロップする。
  5. addNoise関数の「// 画像内の位置をランダムに決める」の下に以下のコードを追加する。
  6. (元画像の横と縦の幅はw, hで、これを縦に2つ並べてノイズ画像を作るので縦の範囲は 0 ~ h-1 にする)
        int x = (int)random(w);
        int y = (int)random(h*2);

  7. addNoise関数の「// x, yのピクセルの明度を反転させる」の下に以下のコードを追加する。
  8. (「ピクセルの明度を反転させる」とは「そのピクセル元の明度を調べ、255からそれを引いた明るさをもつ色をそこに設定する」ということ)
        img[1].pixels[x+y*w] = color(255-brightness(img[1].pixels[x+y*w]));

  9. 実行して画面をクリックする。
  10. (「完了」が表示されたあとでキーボードの1キーを押すと、元画像を二値化したものとそれをネガ・ポジ反転させた画像を縦に並べ、それにノイズを加えた画像が表示される)
  • この段階でdataフォルダの出力画像がすべてこの状態になっている。本来こうなるべきなのは「1ノイズ.png」のみ。
  • うまくいかない場合は、最終的なaddNoise関数とコードを見比べる。

収縮

概要

収縮処理では、参照ピクセルの上下左右のどれか一つでも黒ならそのピクセルを黒にする (元々参照ピクセルが黒だった場合はなにも変わらない)。


その結果、こういう元画像に収縮処理をかけると


出力画像はこうなる。


Processingでは、これまでに何度か使ったfilter関数の引数を「ERODE」(英語では「浸食」という意味。白い部分が削られるというイメージ) にすることで簡単にこの処理を行うことができる。
これとは別に、自前の処理でも同様のことを行って結果を比較してみる。

課題 2

  1. filterErode()関数の「// img[2]にfilter関数で収縮処理をかける」の下に、その説明に応じたものを追加する。
  2. (概要を参照)

  3. 実行し、画面をクリックしてコンソールに「完了」が表示されたあとでキーボードの1, 2キーを順に押す。
  4. (2キーで表示されるのが、ノイズ画像に収縮処理をかけたもの)
    (白いノイズが消え、黒いノイズが「+」の形に膨らむ)


  5. myErode()関数の「if (true) {」の「true」を、すぐ上のコメント文 (// img[1]の(i, j)の場所の上下左右のどれか1つでも黒なら) に応じたものに書き換える。
  6. (img[1]の(i, j)の場所の色を返す関数は getBrightness関数として実装済み)
    (「img[1]の(i, j)の場所の左隣が黒である」という条件は「getBrightness(i-1, j)==0」。ほかの3方向も同様に書ける)
    (「少なくともこれらのどれか一つを満たす」という条件を、元「true」のところに入れればよい)

  7. myErode()関数の「// img[3]の(i, j)を黒にする」の下に、その説明に応じたものを追加する。
  8. (「黒にする」とは、colorに引数0を入れて作った色を代入するということ)

  9. 実行して画面をクリックし、2, 3キーを交互に押す。
  10. (2キーがfilter関数、3キーが自前の関数の処理の結果なので、正しくできていれば全く同じなので交互にキーを押しても変化はないはず)

  11. この段階でdataフォルダの出力画像のうち「2収縮1.png」「2収縮2.png」も然るべきものになる。
  12. うまくいかない場合は、最終的なfilterErode関数、myErode関数とコードを見比べる。

膨張

概要

膨張処理では、参照ピクセルの上下左右のどれか一つでも白ならそのピクセルを白にする (元々参照ピクセルが白だった場合はなにも変わらない)。


その結果、こういう元画像に膨張処理をかけると


出力画像はこうなる。


Processingでは、filter関数の引数を「DILATE」(英語では「拡張する」という意味。白い部分が膨らむというイメージ) にすることで簡単にこの処理を行うことができる。
これとは別に、自前の処理でも同様のことを行って結果を比較してみる。

課題 3

  1. filterDilate()関数の「// img[4]にfilter関数で膨張処理をかける」の下に、その説明に応じたものを追加する。
  2. (概要を参照)

  3. 実行し、画面をクリックしてコンソールに「完了」が表示されたあとでキーボードの1, 4キーを順に押す。
  4. (2キーで表示されるのが、ノイズ画像に収縮処理をかけたもの)
    (黒いノイズが消え、白いノイズが「+」の形に膨らむ)


  5. myErode()関数の中のコードを、(今は空っぽの) myDilate関数の中にコピーする。
  6. myDilate関数の中の2つのコメント文を、それぞれ「// img[1]の(i, j)の場所の上下左右のどれか1つでも白なら」と「// img[5]の(i, j)を白にする」に変更する。
  7. 変更したコメント文の下のコードを、その説明に応じたものに変更する。
  8. (「白」は色の値でいうと255)

  9. 実行して画面をクリックし、4, 5キーを交互に押す。
  10. (4キーがfilter関数、5キーが自前の関数の処理の結果なので、正しくできていれば全く同じなので交互にキーを押しても変化はないはず)

  11. この段階でdataフォルダの出力画像のうち「3膨張1.png」「3膨張2.png」も然るべきものになる。
  12. うまくいかない場合は、最終的なfilterDilate関数、最終的なmyDilalte関数とコードを見比べる。

提出

小テスト予告

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

戻る

inserted by FC2 system